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:
Mihai Parparita
2023-04-17 16:01:41 -07:00
committed by Mihai Parparita
parent 7f17e04a5a
commit 7330aa593e
50 changed files with 242 additions and 126 deletions
+10 -6
View File
@@ -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)
}
+2 -1
View File
@@ -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
View File
@@ -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()
+2 -2
View File
@@ -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)
}
+2 -1
View File
@@ -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
}
+2 -1
View File
@@ -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
}
+1 -1
View File
@@ -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)
+2 -1
View File
@@ -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
}