Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Linux performance of GetAllInterfaces #47

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions ifaddrs.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ func IfAttrs(selectorName string, ifAddrs IfAddrs) (string, error) {
// available IP addresses on each interface and converts them to
// sockaddr.IPAddrs, and returning the result as an array of IfAddr.
func GetAllInterfaces() (IfAddrs, error) {
ifs, err := net.Interfaces()
ifs, err := NetInterfaces()
if err != nil {
return nil, err
}
Expand All @@ -259,10 +259,7 @@ func GetAllInterfaces() (IfAddrs, error) {
return IfAddrs{}, fmt.Errorf("unable to create an IP address from %q", addr.String())
}

ifAddr := IfAddr{
SockAddr: ipAddr,
Interface: intf,
}
ifAddr := NewIfAddr(ipAddr, intf)
ifAddrs = append(ifAddrs, ifAddr)
}
}
Expand Down
16 changes: 16 additions & 0 deletions net_default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:build !linux
// +build !linux

package sockaddr

import (
"net"
)

func NetInterfaces() ([]net.Interface, error) {
return net.Interfaces()
}

func NewIfAddr(addr IPAddr, intf net.Interface) IfAddr {
return IfAddr{addr, intf}
}
91 changes: 91 additions & 0 deletions net_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package sockaddr

import (
"net"
"os"
"syscall"
"unsafe"
)

// LinuxNetInterface contains a net.Interface along with its associated []net.Addr.
type LinuxNetInterface struct {
net.Interface
addrs []net.Addr
}

func (ifi *LinuxNetInterface) Addrs() ([]net.Addr, error) {
return ifi.addrs, nil
}

// NetInterfaces returns all net.Interfaces along with their associated []net.Addr.
// This Linux optimization avoids a separate Netlink dump of addresses for each individual interface,
// which is prohibitively slow on servers with large numbers of interfaces:
// https://github.com/golang/go/issues/53660
func NetInterfaces() ([]LinuxNetInterface, error) {
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
netInterfaces := make([]LinuxNetInterface, 0, len(interfaces))
for _, ifi := range interfaces {
netInterfaces = append(netInterfaces, LinuxNetInterface{ifi, make([]net.Addr, 0)})
}
ifMap := make(map[int]*LinuxNetInterface, len(netInterfaces))
for i, ifi := range netInterfaces {
ifMap[ifi.Index] = &netInterfaces[i]
}

tab, err := syscall.NetlinkRIB(syscall.RTM_GETADDR, syscall.AF_UNSPEC)
if err != nil {
return nil, os.NewSyscallError("NetlinkRIB", err)
}
msgs, err := syscall.ParseNetlinkMessage(tab)
if err != nil {
return nil, os.NewSyscallError("ParseNetLinkMessage", err)
}

for _, m := range msgs {
if m.Header.Type == syscall.RTM_NEWADDR {
ifam := (*syscall.IfAddrmsg)(unsafe.Pointer(&m.Data[0]))
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
if err != nil {
return nil, os.NewSyscallError("ParseNetLinkRouteAttr", err)
}
if ifi, ok := ifMap[int(ifam.Index)]; ok {
ifi.addrs = append(ifi.addrs, newAddr(ifam, attrs))
}
}
}

return netInterfaces, err
}

// Vendored unexported function:
// https://github.com/golang/go/blob/8bcc490667d4dd44c633c536dd463bbec0a3838f/src/net/interface_linux.go#L178-L203
func newAddr(ifam *syscall.IfAddrmsg, attrs []syscall.NetlinkRouteAttr) net.Addr {
var ipPointToPoint bool
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
}

func NewIfAddr(addr IPAddr, intf LinuxNetInterface) IfAddr {
return IfAddr{addr, intf.Interface}
}