all: avoid repeated default interface lookups
On some platforms (notably macOS and iOS) we look up the default interface to bind outgoing connections to. This is both duplicated work and results in logspam when the default interface is not available (i.e. when a phone has no connectivity, we log an error and thus cause more things that we will try to upload and fail). Fixed by passing around a netmon.Monitor to more places, so that we can use its cached interface state. Fixes #7850 Updates #7621 Signed-off-by: Mihai Parparita <mihai@tailscale.com>
This commit is contained in:
committed by
Mihai Parparita
parent
7f17e04a5a
commit
7330aa593e
+10
-6
@@ -20,6 +20,7 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
"tailscale.com/net/netknob"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
@@ -55,19 +56,21 @@ func SetDisableBindConnToInterface(v bool) {
|
||||
// Listener returns a new net.Listener with its Control hook func
|
||||
// initialized as necessary to run in logical network namespace that
|
||||
// doesn't route back into Tailscale.
|
||||
func Listener(logf logger.Logf) *net.ListenConfig {
|
||||
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
|
||||
func Listener(logf logger.Logf, netMon *netmon.Monitor) *net.ListenConfig {
|
||||
if disabled.Load() {
|
||||
return new(net.ListenConfig)
|
||||
}
|
||||
return &net.ListenConfig{Control: control(logf)}
|
||||
return &net.ListenConfig{Control: control(logf, netMon)}
|
||||
}
|
||||
|
||||
// NewDialer returns a new Dialer using a net.Dialer with its Control
|
||||
// hook func initialized as necessary to run in a logical network
|
||||
// namespace that doesn't route back into Tailscale. It also handles
|
||||
// using a SOCKS if configured in the environment with ALL_PROXY.
|
||||
func NewDialer(logf logger.Logf) Dialer {
|
||||
return FromDialer(logf, &net.Dialer{
|
||||
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
|
||||
func NewDialer(logf logger.Logf, netMon *netmon.Monitor) Dialer {
|
||||
return FromDialer(logf, netMon, &net.Dialer{
|
||||
KeepAlive: netknob.PlatformTCPKeepAlive(),
|
||||
})
|
||||
}
|
||||
@@ -76,11 +79,12 @@ func NewDialer(logf logger.Logf) Dialer {
|
||||
// network namespace that doesn't route back into Tailscale. It also
|
||||
// handles using a SOCKS if configured in the environment with
|
||||
// ALL_PROXY.
|
||||
func FromDialer(logf logger.Logf, d *net.Dialer) Dialer {
|
||||
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
|
||||
func FromDialer(logf logger.Logf, netMon *netmon.Monitor, d *net.Dialer) Dialer {
|
||||
if disabled.Load() {
|
||||
return d
|
||||
}
|
||||
d.Control = control(logf)
|
||||
d.Control = control(logf, netMon)
|
||||
if wrapDialer != nil {
|
||||
return wrapDialer(d)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
@@ -49,7 +50,7 @@ func SetAndroidProtectFunc(f func(fd int) error) {
|
||||
androidProtectFunc = f
|
||||
}
|
||||
|
||||
func control(logger.Logf) func(network, address string, c syscall.RawConn) error {
|
||||
func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
|
||||
return controlC
|
||||
}
|
||||
|
||||
|
||||
+27
-15
@@ -19,24 +19,25 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
func control(logf logger.Logf) func(network, address string, c syscall.RawConn) error {
|
||||
func control(logf logger.Logf, netMon *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
|
||||
return func(network, address string, c syscall.RawConn) error {
|
||||
return controlLogf(logf, network, address, c)
|
||||
return controlLogf(logf, netMon, network, address, c)
|
||||
}
|
||||
}
|
||||
|
||||
var bindToInterfaceByRouteEnv = envknob.RegisterBool("TS_BIND_TO_INTERFACE_BY_ROUTE")
|
||||
|
||||
var errInterfaceIndexInvalid = errors.New("interface index invalid")
|
||||
var errInterfaceStateInvalid = errors.New("interface state invalid")
|
||||
|
||||
// controlLogf marks c as necessary to dial in a separate network namespace.
|
||||
//
|
||||
// It's intentionally the same signature as net.Dialer.Control
|
||||
// and net.ListenConfig.Control.
|
||||
func controlLogf(logf logger.Logf, network, address string, c syscall.RawConn) error {
|
||||
func controlLogf(logf logger.Logf, netMon *netmon.Monitor, network, address string, c syscall.RawConn) error {
|
||||
if isLocalhost(address) {
|
||||
// Don't bind to an interface for localhost connections.
|
||||
return nil
|
||||
@@ -47,7 +48,7 @@ func controlLogf(logf logger.Logf, network, address string, c syscall.RawConn) e
|
||||
return nil
|
||||
}
|
||||
|
||||
idx, err := getInterfaceIndex(logf, address)
|
||||
idx, err := getInterfaceIndex(logf, netMon, address)
|
||||
if err != nil {
|
||||
// callee logged
|
||||
return nil
|
||||
@@ -56,20 +57,31 @@ func controlLogf(logf logger.Logf, network, address string, c syscall.RawConn) e
|
||||
return bindConnToInterface(c, network, address, idx, logf)
|
||||
}
|
||||
|
||||
func getInterfaceIndex(logf logger.Logf, address string) (int, error) {
|
||||
func getInterfaceIndex(logf logger.Logf, netMon *netmon.Monitor, address string) (int, error) {
|
||||
// Helper so we can log errors.
|
||||
defaultIdx := func() (int, error) {
|
||||
idx, err := interfaces.DefaultRouteInterfaceIndex()
|
||||
if err != nil {
|
||||
// It's somewhat common for there to be no default gateway route
|
||||
// (e.g. on a phone with no connectivity), don't log those errors
|
||||
// since they are expected.
|
||||
if !errors.Is(err, interfaces.ErrNoGatewayIndexFound) {
|
||||
logf("[unexpected] netns: DefaultRouteInterfaceIndex: %v", err)
|
||||
if netMon == nil {
|
||||
idx, err := interfaces.DefaultRouteInterfaceIndex()
|
||||
if err != nil {
|
||||
// It's somewhat common for there to be no default gateway route
|
||||
// (e.g. on a phone with no connectivity), don't log those errors
|
||||
// since they are expected.
|
||||
if !errors.Is(err, interfaces.ErrNoGatewayIndexFound) {
|
||||
logf("[unexpected] netns: DefaultRouteInterfaceIndex: %v", err)
|
||||
}
|
||||
return -1, err
|
||||
}
|
||||
return -1, err
|
||||
return idx, nil
|
||||
}
|
||||
return idx, nil
|
||||
state := netMon.InterfaceState()
|
||||
if state == nil {
|
||||
return -1, errInterfaceStateInvalid
|
||||
}
|
||||
|
||||
if iface, ok := state.Interface[state.DefaultRouteInterface]; ok {
|
||||
return iface.Index, nil
|
||||
}
|
||||
return -1, errInterfaceStateInvalid
|
||||
}
|
||||
|
||||
useRoute := bindToInterfaceByRoute.Load() || bindToInterfaceByRouteEnv()
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestGetInterfaceIndex(t *testing.T) {
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
idx, err := getInterfaceIndex(t.Logf, tc.addr)
|
||||
idx, err := getInterfaceIndex(t.Logf, nil, tc.addr)
|
||||
if err != nil {
|
||||
if tc.err == "" {
|
||||
t.Fatalf("got unexpected error: %v", err)
|
||||
@@ -68,7 +68,7 @@ func TestGetInterfaceIndex(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
idx, err := getInterfaceIndex(t.Logf, "100.100.100.100:53")
|
||||
idx, err := getInterfaceIndex(t.Logf, nil, "100.100.100.100:53")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ package netns
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
func control(logger.Logf) func(network, address string, c syscall.RawConn) error {
|
||||
func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
|
||||
return controlC
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
@@ -85,7 +86,7 @@ func ignoreErrors() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func control(logger.Logf) func(network, address string, c syscall.RawConn) error {
|
||||
func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
|
||||
return controlC
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestDial(t *testing.T) {
|
||||
if !*extNetwork {
|
||||
t.Skip("skipping test without --use-external-network")
|
||||
}
|
||||
d := NewDialer(t.Logf)
|
||||
d := NewDialer(t.Logf, nil)
|
||||
c, err := d.Dial("tcp", "google.com:80")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
@@ -26,7 +27,7 @@ func interfaceIndex(iface *winipcfg.IPAdapterAddresses) uint32 {
|
||||
return iface.IfIndex
|
||||
}
|
||||
|
||||
func control(logger.Logf) func(network, address string, c syscall.RawConn) error {
|
||||
func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
|
||||
return controlC
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user