diff --git a/cmd/incus-agent/state.go b/cmd/incus-agent/state.go index 9b0218e38ee..189d6274a10 100644 --- a/cmd/incus-agent/state.go +++ b/cmd/incus-agent/state.go @@ -123,7 +123,7 @@ func memoryState() api.InstanceStateMemory { func networkState() map[string]api.InstanceStateNetwork { result := map[string]api.InstanceStateNetwork{} - ifs, err := net.Interfaces() + ifs, err := linux.NetlinkInterfaces() if err != nil { logger.Errorf("Failed to retrieve network interfaces: %v", err) return result @@ -180,9 +180,7 @@ func networkState() map[string]api.InstanceStateNetwork { } // Addresses - addrs, _ := iface.Addrs() - - for _, addr := range addrs { + for _, addr := range iface.Addresses { addressFields := strings.Split(addr.String(), "/") networkAddress := api.InstanceStateNetworkAddress{ diff --git a/internal/linux/netlink.go b/internal/linux/netlink.go new file mode 100644 index 00000000000..0c79defe599 --- /dev/null +++ b/internal/linux/netlink.go @@ -0,0 +1,99 @@ +//go:build linux + +package linux + +import ( + "fmt" + "net" + "syscall" + "unsafe" +) + +// NetlinkInterface returns a net.Interface extended to also contain its addresses. +type NetlinkInterface struct { + net.Interface + + Addresses []net.Addr +} + +// NetlinkInterfaces performs a RTM_GETADDR call to get both. +func NetlinkInterfaces() ([]NetlinkInterface, error) { + // Grab the interface list. + ifaces, err := net.Interfaces() + if err != nil { + return nil, err + } + + // Initialize result slice. + netlinkIfaces := make([]NetlinkInterface, 0, len(ifaces)) + for _, iface := range ifaces { + netlinkIfaces = append(netlinkIfaces, NetlinkInterface{iface, make([]net.Addr, 0)}) + } + + // Turn it into a map. + ifaceMap := make(map[int]*NetlinkInterface, len(ifaces)) + for k, v := range netlinkIfaces { + ifaceMap[v.Index] = &netlinkIfaces[k] //nolint:typecheck + } + + // Make the netlink call. + rib, err := syscall.NetlinkRIB(syscall.RTM_GETADDR, syscall.AF_UNSPEC) + if err != nil { + return nil, fmt.Errorf("Failed to query RTM_GETADDR: %v", err) + } + + messages, err := syscall.ParseNetlinkMessage(rib) + if err != nil { + return nil, fmt.Errorf("Failed to parse RTM_GETADDR: %v", err) + } + + for _, m := range messages { + if m.Header.Type == syscall.RTM_NEWADDR { + addrMessage := (*syscall.IfAddrmsg)(unsafe.Pointer(&m.Data[0])) + + addrAttrs, err := syscall.ParseNetlinkRouteAttr(&m) + if err != nil { + return nil, fmt.Errorf("Failed to parse route attribute: %v", err) + } + + ifi, ok := ifaceMap[int(addrMessage.Index)] + if ok { + ifi.Addresses = append(ifi.Addresses, newAddr(addrMessage, addrAttrs)) + } + } + } + + return netlinkIfaces, nil +} + +// Variation of function of the same name from within Go source. +func newAddr(ifam *syscall.IfAddrmsg, attrs []syscall.NetlinkRouteAttr) net.Addr { + var ipPointToPoint bool + + // Seems like we need to make sure whether the IP interface + // stack consists of IP point-to-point numbered or unnumbered + // addressing. + for _, a := range attrs { + if a.Attr.Type == syscall.IFA_LOCAL { + ipPointToPoint = true + break + } + } + + for _, a := range attrs { + if ipPointToPoint && a.Attr.Type == syscall.IFA_ADDRESS { + continue + } + + switch ifam.Family { + case syscall.AF_INET: + return &net.IPNet{IP: net.IPv4(a.Value[0], a.Value[1], a.Value[2], a.Value[3]), Mask: net.CIDRMask(int(ifam.Prefixlen), 8*net.IPv4len)} + case syscall.AF_INET6: + ifa := &net.IPNet{IP: make(net.IP, net.IPv6len), Mask: net.CIDRMask(int(ifam.Prefixlen), 8*net.IPv6len)} + copy(ifa.IP, a.Value[:]) + return ifa + } + } + + return nil +}