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" }