tailscale/net/netmon/interfaces_darwin.go
Brad Fitzpatrick b9adbe2002 net/{interfaces,netmon}, all: merge net/interfaces package into net/netmon
In prep for most of the package funcs in net/interfaces to become
methods in a long-lived netmon.Monitor that can cache things.  (Many
of the funcs are very heavy to call regularly, whereas the long-lived
netmon.Monitor can subscribe to things from the OS and remember
answers to questions it's asked regularly later)

Updates tailscale/corp#10910
Updates tailscale/corp#18960
Updates #7967
Updates #3299

Change-Id: Ie4e8dedb70136af2d611b990b865a822cd1797e5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-28 07:34:52 -07:00

112 lines
3.1 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package netmon
import (
"fmt"
"net"
"strings"
"sync"
"syscall"
"unsafe"
"golang.org/x/net/route"
"golang.org/x/sys/unix"
"tailscale.com/util/mak"
)
// fetchRoutingTable calls route.FetchRIB, fetching NET_RT_DUMP2.
func fetchRoutingTable() (rib []byte, err error) {
return route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0)
}
func parseRoutingTable(rib []byte) ([]route.Message, error) {
return route.ParseRIB(syscall.NET_RT_IFLIST2, rib)
}
var ifNames struct {
sync.Mutex
m map[int]string // ifindex => name
}
func init() {
interfaceDebugExtras = interfaceDebugExtrasDarwin
}
// getDelegatedInterface returns the interface index of the underlying interface
// for the given interface index. 0 is returned if the interface does not
// delegate.
func getDelegatedInterface(ifIndex int) (int, error) {
ifNames.Lock()
defer ifNames.Unlock()
// To get the delegated interface, we do what ifconfig does and use the
// SIOCGIFDELEGATE ioctl. It operates in term of a ifreq struct, which
// has to be populated with a interface name. To avoid having to do a
// interface index -> name lookup every time, we cache interface names
// (since indexes and names are stable after boot).
ifName, ok := ifNames.m[ifIndex]
if !ok {
iface, err := net.InterfaceByIndex(ifIndex)
if err != nil {
return 0, err
}
ifName = iface.Name
mak.Set(&ifNames.m, ifIndex, ifName)
}
// Only tunnels (like Tailscale itself) have a delegated interface, avoid
// the ioctl if we can.
if !strings.HasPrefix(ifName, "utun") {
return 0, nil
}
// We don't cache the result of the ioctl, since the delegated interface can
// change, e.g. if the user changes the preferred service order in the
// network preference pane.
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
if err != nil {
return 0, err
}
defer unix.Close(fd)
// Match the ifreq struct/union from the bsd/net/if.h header in the Darwin
// open source release.
var ifr struct {
ifr_name [unix.IFNAMSIZ]byte
ifr_delegated uint32
}
copy(ifr.ifr_name[:], ifName)
// SIOCGIFDELEGATE is not in the Go x/sys package or in the public macOS
// <sys/sockio.h> headers. However, it is in the Darwin/xnu open source
// release (and is used by ifconfig, see
// https://github.com/apple-oss-distributions/network_cmds/blob/6ccdc225ad5aa0d23ea5e7d374956245d2462427/ifconfig.tproj/ifconfig.c#L2183-L2187).
// We generate its value by evaluating the `_IOWR('i', 157, struct ifreq)`
// macro, which is how it's defined in
// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/sockio.h#L264
const SIOCGIFDELEGATE = 0xc020699d
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
uintptr(fd),
uintptr(SIOCGIFDELEGATE),
uintptr(unsafe.Pointer(&ifr)))
if errno != 0 {
return 0, errno
}
return int(ifr.ifr_delegated), nil
}
func interfaceDebugExtrasDarwin(ifIndex int) (string, error) {
delegated, err := getDelegatedInterface(ifIndex)
if err != nil {
return "", err
}
if delegated == 0 {
return "", nil
}
return fmt.Sprintf("delegated=%d", delegated), nil
}