mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-30 05:25:35 +00:00
b9adbe2002
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>
112 lines
3.1 KiB
Go
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
|
|
}
|