Skip to content

Commit

Permalink
Merge pull request #1367 from stgraber/main
Browse files Browse the repository at this point in the history
Improve agent interface listing performance
  • Loading branch information
stgraber authored Nov 12, 2024
2 parents a27705e + 94cc8d6 commit 0013b13
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 4 deletions.
6 changes: 2 additions & 4 deletions cmd/incus-agent/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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{
Expand Down
99 changes: 99 additions & 0 deletions internal/linux/netlink.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 0013b13

Please sign in to comment.