mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 13:05:46 +00:00
62f4df3257
With #6566 we started to more aggressively bind to the default interface on Darwin. We are seeing some reports of the wrong cellular interface being chosen on iOS. To help with the investigation, this adds to knobs to control the behavior changes: - CapabilityDebugDisableAlternateDefaultRouteInterface disables the alternate function that we use to get the default interface on macOS and iOS (implemented in tailscale/corp#8201). We still log what it would have returned so we can see if it gets things wrong. - CapabilityDebugDisableBindConnToInterface is a bigger hammer that disables binding of connections to the default interface altogether. Updates #7184 Updates #7188 Signed-off-by: Mihai Parparita <mihai@tailscale.com>
179 lines
4.8 KiB
Go
179 lines
4.8 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Common code for FreeBSD and Darwin. This might also work on other
|
|
// BSD systems (e.g. OpenBSD) but has not been tested.
|
|
|
|
//go:build darwin || freebsd
|
|
|
|
package interfaces
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/netip"
|
|
"syscall"
|
|
|
|
"golang.org/x/net/route"
|
|
"golang.org/x/sys/unix"
|
|
"tailscale.com/net/netaddr"
|
|
"tailscale.com/syncs"
|
|
)
|
|
|
|
func defaultRoute() (d DefaultRouteDetails, err error) {
|
|
idx, err := DefaultRouteInterfaceIndex()
|
|
if err != nil {
|
|
return d, err
|
|
}
|
|
iface, err := net.InterfaceByIndex(idx)
|
|
if err != nil {
|
|
return d, err
|
|
}
|
|
d.InterfaceName = iface.Name
|
|
d.InterfaceIndex = idx
|
|
return d, nil
|
|
}
|
|
|
|
// DefaultRouteInterfaceIndex returns the index of the network interface that
|
|
// owns the default route. It returns the first IPv4 or IPv6 default route it
|
|
// finds (it does not prefer one or the other).
|
|
func DefaultRouteInterfaceIndex() (int, error) {
|
|
disabledAlternateDefaultRouteInterface := false
|
|
if f := defaultRouteInterfaceIndexFunc.Load(); f != nil {
|
|
if ifIndex := f(); ifIndex != 0 {
|
|
if !disableAlternateDefaultRouteInterface.Load() {
|
|
return ifIndex, nil
|
|
} else {
|
|
disabledAlternateDefaultRouteInterface = true
|
|
log.Printf("interfaces_bsd: alternate default route interface function disabled, would have returned interface %d", ifIndex)
|
|
}
|
|
}
|
|
// Fallthrough if we can't use the alternate implementation.
|
|
}
|
|
|
|
// $ netstat -nr
|
|
// Routing tables
|
|
// Internet:
|
|
// Destination Gateway Flags Netif Expire
|
|
// default 10.0.0.1 UGSc en0 <-- want this one
|
|
// default 10.0.0.1 UGScI en1
|
|
|
|
// From man netstat:
|
|
// U RTF_UP Route usable
|
|
// G RTF_GATEWAY Destination requires forwarding by intermediary
|
|
// S RTF_STATIC Manually added
|
|
// c RTF_PRCLONING Protocol-specified generate new routes on use
|
|
// I RTF_IFSCOPE Route is associated with an interface scope
|
|
|
|
rib, err := fetchRoutingTable()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("route.FetchRIB: %w", err)
|
|
}
|
|
msgs, err := parseRoutingTable(rib)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("route.ParseRIB: %w", err)
|
|
}
|
|
for _, m := range msgs {
|
|
rm, ok := m.(*route.RouteMessage)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if isDefaultGateway(rm) {
|
|
if disabledAlternateDefaultRouteInterface {
|
|
log.Printf("interfaces_bsd: alternate default route interface function disabled, default implementation returned %d", rm.Index)
|
|
}
|
|
return rm.Index, nil
|
|
}
|
|
}
|
|
return 0, errors.New("no gateway index found")
|
|
}
|
|
|
|
var defaultRouteInterfaceIndexFunc syncs.AtomicValue[func() int]
|
|
|
|
// SetDefaultRouteInterfaceIndexFunc allows an alternate implementation of
|
|
// DefaultRouteInterfaceIndex to be provided. If none is set, or if f() returns a 0
|
|
// (indicating an unknown interface index), then the default implementation (that parses
|
|
// the routing table) will be used.
|
|
func SetDefaultRouteInterfaceIndexFunc(f func() int) {
|
|
defaultRouteInterfaceIndexFunc.Store(f)
|
|
}
|
|
|
|
func init() {
|
|
likelyHomeRouterIP = likelyHomeRouterIPBSDFetchRIB
|
|
}
|
|
|
|
func likelyHomeRouterIPBSDFetchRIB() (ret netip.Addr, ok bool) {
|
|
rib, err := fetchRoutingTable()
|
|
if err != nil {
|
|
log.Printf("routerIP/FetchRIB: %v", err)
|
|
return ret, false
|
|
}
|
|
msgs, err := parseRoutingTable(rib)
|
|
if err != nil {
|
|
log.Printf("routerIP/ParseRIB: %v", err)
|
|
return ret, false
|
|
}
|
|
for _, m := range msgs {
|
|
rm, ok := m.(*route.RouteMessage)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if !isDefaultGateway(rm) {
|
|
continue
|
|
}
|
|
|
|
gw, ok := rm.Addrs[unix.RTAX_GATEWAY].(*route.Inet4Addr)
|
|
if !ok {
|
|
continue
|
|
}
|
|
return netaddr.IPv4(gw.IP[0], gw.IP[1], gw.IP[2], gw.IP[3]), true
|
|
}
|
|
|
|
return ret, false
|
|
}
|
|
|
|
var v4default = [4]byte{0, 0, 0, 0}
|
|
var v6default = [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
|
|
|
func isDefaultGateway(rm *route.RouteMessage) bool {
|
|
if rm.Flags&unix.RTF_GATEWAY == 0 {
|
|
return false
|
|
}
|
|
// Defined locally because FreeBSD does not have unix.RTF_IFSCOPE.
|
|
const RTF_IFSCOPE = 0x1000000
|
|
if rm.Flags&RTF_IFSCOPE != 0 {
|
|
return false
|
|
}
|
|
|
|
// Addrs is [RTAX_DST, RTAX_GATEWAY, RTAX_NETMASK, ...]
|
|
if len(rm.Addrs) <= unix.RTAX_NETMASK {
|
|
return false
|
|
}
|
|
|
|
dst := rm.Addrs[unix.RTAX_DST]
|
|
netmask := rm.Addrs[unix.RTAX_NETMASK]
|
|
if dst == nil || netmask == nil {
|
|
return false
|
|
}
|
|
|
|
if dst.Family() == syscall.AF_INET && netmask.Family() == syscall.AF_INET {
|
|
dstAddr, dstOk := dst.(*route.Inet4Addr)
|
|
nmAddr, nmOk := netmask.(*route.Inet4Addr)
|
|
if dstOk && nmOk && dstAddr.IP == v4default && nmAddr.IP == v4default {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if dst.Family() == syscall.AF_INET6 && netmask.Family() == syscall.AF_INET6 {
|
|
dstAddr, dstOk := dst.(*route.Inet6Addr)
|
|
nmAddr, nmOk := netmask.(*route.Inet6Addr)
|
|
if dstOk && nmOk && dstAddr.IP == v6default && nmAddr.IP == v6default {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|