Files
mesh-host/internal/outward/networks.go
T
jochen cb7b71dcc5
mesh/merge-gate head cb7b71dcc568 merged into trunk 12bc6a56a38a — pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes wi…
mesh/repo-check head cb7b71dcc568 merged into trunk 12bc6a56a38a — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group fix/154-local-network delivered: every member is delivered
Report the networks the outward links are on, for the local network (issue 154, ADR 0340)
A port reached from local-network is admitted from the private networks the machine is attached to, and only the machine can say which those are. Every network on a link facing outside is said among its facts, and a change is said at the next reconcile.
2026-10-13 11:20:21 +02:00

93 lines
2.8 KiB
Go

package outward
import (
"errors"
"fmt"
"net"
"net/netip"
"sort"
)
// Network is one network this machine is directly attached to, and the link it is on (novox/hq ADR
// 0340 §7): the address and prefix of the network, not of the machine's own address on it.
//
// **Every one is said, and the machine judges none of them.** Which count as the machine's local
// network — private ranges only, never a public subnet, never a link-local one — is the controller's
// judgement (ADR 0340 §2), made where the filter is composed and shown with why; a machine that left a
// network out would make that judgement silently.
type Network struct {
Link string `json:"link"`
Network string `json:"network"`
}
// AddressesOf is the addresses on one link, as address and prefix. A parameter so a test can hold a
// machine's links without one.
type AddressesOf func(link string) ([]netip.Prefix, error)
// RealAddresses reads one link's addresses from the kernel.
func RealAddresses(link string) ([]netip.Prefix, error) {
iface, err := net.InterfaceByName(link)
if err != nil {
return nil, err
}
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
var out []netip.Prefix
for _, a := range addrs {
ipnet, ok := a.(*net.IPNet)
if !ok {
continue
}
addr, ok := netip.AddrFromSlice(ipnet.IP)
if !ok {
continue
}
bits, _ := ipnet.Mask.Size()
out = append(out, netip.PrefixFrom(addr.Unmap(), bits))
}
return out, nil
}
// Networks is the networks on each of these links — the links that face outside (Links) — sorted by link
// and network, without repeats. A link that has gone since Links read it has no networks rather than
// failing the rest; a link whose addresses cannot be read fails, so the machine says the fact is unknown
// rather than reporting a partial account as the whole.
func Networks(links []string, addressesOf AddressesOf) ([]Network, error) {
if addressesOf == nil {
addressesOf = RealAddresses
}
seen := map[Network]bool{}
var out []Network
for _, link := range links {
prefixes, err := addressesOf(link)
if err != nil {
if isNoSuchInterface(err) {
continue
}
return nil, fmt.Errorf("cannot read the addresses on %s: %w", link, err)
}
for _, p := range prefixes {
n := Network{Link: link, Network: p.Masked().String()}
if !seen[n] {
seen[n] = true
out = append(out, n)
}
}
}
sort.Slice(out, func(i, j int) bool {
if out[i].Link != out[j].Link {
return out[i].Link < out[j].Link
}
return out[i].Network < out[j].Network
})
return out, nil
}
// isNoSuchInterface is the error net.InterfaceByName gives for a link that is not there.
func isNoSuchInterface(err error) bool {
var op *net.OpError
return errors.As(err, &op) && op.Err != nil && op.Err.Error() == "no such network interface"
}