From d9369f5df426a6d8c5c45912879bd4949b818daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Mon, 11 Nov 2024 15:08:36 -0500 Subject: [PATCH 1/2] internal/linux: Add NetlinkInterfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber Sponsored-by: https://webdock.io --- internal/linux/netlink.go | 99 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 internal/linux/netlink.go 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 +} From 94cc8d6d3d6f0edbb09b8adeadafefaceee32f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Mon, 11 Nov 2024 15:18:33 -0500 Subject: [PATCH 2/2] incus-agent: Use NetlinkInterfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fetching addresses for a single interface in Go is terribly slow, use a bulk query method. Signed-off-by: Stéphane Graber Sponsored-by: https://webdock.io --- cmd/incus-agent/state.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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{