mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 21:15:39 +00:00
fa932fefe7
With #6566 we added an external mechanism for getting the default interface, and used it on macOS and iOS (see tailscale/corp#8201). The goal was to be able to get the default physical interface even when using an exit node (in which case the routing table would say that the Tailscale utun* interface is the default). However, the external mechanism turns out to be unreliable in some cases, e.g. when multiple cellular interfaces are present/toggled (I have occasionally gotten my phone into a state where it reports the pdp_ip1 interface as the default, even though it can't actually route traffic). It was observed that `ifconfig -v` on macOS reports an "effective interface" for the Tailscale utn* interface, which seems promising. By examining the ifconfig source code, it turns out that this is done via a SIOCGIFDELEGATE ioctl syscall. Though this is a private API, it appears to have been around for a long time (e.g. it's in the 10.13 xnu release at https://opensource.apple.com/source/xnu/xnu-4570.41.2/bsd/net/if_types.h.auto.html) and thus is unlikely to go away. We can thus use this ioctl if the routing table says that a utun* interface is the default, and go back to the simpler mechanism that we had before #6566. Updates #7184 Updates #7188 Signed-off-by: Mihai Parparita <mihai@tailscale.com>
96 lines
2.8 KiB
Go
96 lines
2.8 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package interfaces
|
|
|
|
import (
|
|
"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
|
|
}
|
|
|
|
// 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
|
|
}
|