mirror of
https://github.com/slackhq/nebula.git
synced 2026-11-15 20:36:52 +01:00
105 lines
3.2 KiB
Go
105 lines
3.2 KiB
Go
//go:build android
|
|
|
|
package nicfinder
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/netip"
|
|
"os"
|
|
|
|
"github.com/DefinedNet/netlink"
|
|
"github.com/DefinedNet/netlink/nl"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// Android denies bind() on netlink route sockets and RTM_GETLINK, so net.Interfaces fails with EACCES.
|
|
// Enumerate with an RTM_GETADDR dump on an unbound socket instead.
|
|
|
|
// localInterfaces returns every interface that has an address, with its addresses. It lists
|
|
// addresses over a netlink socket that is never bound and names each index with SIOCGIFNAME, the
|
|
// same operations Bionic's getifaddrs uses for app UIDs, which the sandbox permits.
|
|
func localAddrs(ctx context.Context, l *slog.Logger, filter Filter) ([]netip.Addr, error) {
|
|
var errs []error
|
|
addrs, err := netlinkAddrList()
|
|
if err != nil {
|
|
if !errors.Is(err, netlink.ErrDumpInterrupted) {
|
|
return nil, fmt.Errorf("failed to enumerate local interfaces: %w", err)
|
|
}
|
|
// The address table kept changing under the dump and netlink gave up retrying. What it
|
|
// returned is still worth advertising.
|
|
errs = append(errs, fmt.Errorf("local addresses may be incomplete: %w", err))
|
|
}
|
|
|
|
// Any socket will do for SIOCGIFNAME.
|
|
sfd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0)
|
|
if err != nil {
|
|
return nil, os.NewSyscallError("socket", err)
|
|
}
|
|
defer unix.Close(sfd)
|
|
|
|
var out []netip.Addr
|
|
nameAllowed := make(map[int]bool) // by interface index, once named
|
|
for _, a := range addrs {
|
|
allow, named := nameAllowed[a.LinkIndex]
|
|
if (named && !allow) || a.IPNet == nil {
|
|
continue
|
|
}
|
|
addr, ok := allowedAddr(ctx, l, filter, a.IPNet)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if !named {
|
|
name, err := interfaceNameByIndex(sfd, a.LinkIndex)
|
|
// ENODEV means the interface went away after the dump. Any other failure is reported
|
|
// and the interface skipped, so the rest are still returned.
|
|
if err != nil && !errors.Is(err, unix.ENODEV) {
|
|
errs = append(errs, fmt.Errorf("failed to resolve name of interface %d: %w", a.LinkIndex, err))
|
|
}
|
|
allow = err == nil && allowName(ctx, l, filter, name)
|
|
nameAllowed[a.LinkIndex] = allow
|
|
}
|
|
if allow {
|
|
out = append(out, addr)
|
|
}
|
|
}
|
|
return out, errors.Join(errs...)
|
|
}
|
|
|
|
// netlinkAddrList is netlink.AddrList over a socket that is never bound. The kernel autobinds it
|
|
// on the first send, which the sandbox permits.
|
|
func netlinkAddrList() ([]netlink.Addr, error) {
|
|
fd, err := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW|unix.SOCK_CLOEXEC, unix.NETLINK_ROUTE)
|
|
if err != nil {
|
|
return nil, os.NewSyscallError("socket", err)
|
|
}
|
|
s, err := nl.NewNetlinkSocketFromFd(fd)
|
|
if err != nil {
|
|
unix.Close(fd)
|
|
return nil, err
|
|
}
|
|
|
|
h := netlink.NewHandleFromSockets(
|
|
map[int]*nl.SocketHandle{unix.NETLINK_ROUTE: {Socket: s}},
|
|
netlink.HandleOptions{RetryInterrupted: true},
|
|
)
|
|
defer h.Close()
|
|
if err := h.SetSocketTimeout(netlink.GetSocketTimeout()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return h.AddrList(nil, netlink.FAMILY_ALL)
|
|
}
|
|
|
|
// interfaceNameByIndex resolves an interface index with SIOCGIFNAME.
|
|
func interfaceNameByIndex(fd int, index int) (string, error) {
|
|
var ifr unix.Ifreq
|
|
ifr.SetUint32(uint32(index)) // ifr_ifindex is in the ifr_ifru union
|
|
if err := unix.IoctlIfreq(fd, unix.SIOCGIFNAME, &ifr); err != nil {
|
|
return "", err
|
|
}
|
|
return ifr.Name(), nil
|
|
}
|