| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- //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
- }
|