mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-26 11:35:35 +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>
126 lines
3.7 KiB
Go
126 lines
3.7 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Package netns contains the common code for using the Go net package
|
|
// in a logical "network namespace" to avoid routing loops where
|
|
// Tailscale-created packets would otherwise loop back through
|
|
// Tailscale routes.
|
|
//
|
|
// Despite the name netns, the exact mechanism used differs by
|
|
// operating system, and perhaps even by version of the OS.
|
|
//
|
|
// The netns package also handles connecting via SOCKS proxies when
|
|
// configured by the environment.
|
|
package netns
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/netip"
|
|
"sync/atomic"
|
|
|
|
"tailscale.com/net/netknob"
|
|
"tailscale.com/types/logger"
|
|
)
|
|
|
|
var disabled atomic.Bool
|
|
|
|
// SetEnabled enables or disables netns for the process.
|
|
// It defaults to being enabled.
|
|
func SetEnabled(on bool) {
|
|
disabled.Store(!on)
|
|
}
|
|
|
|
var bindToInterfaceByRoute atomic.Bool
|
|
|
|
// SetBindToInterfaceByRoute enables or disables whether we use the system's
|
|
// route information to bind to a particular interface. It is the same as
|
|
// setting the TS_BIND_TO_INTERFACE_BY_ROUTE.
|
|
//
|
|
// Currently, this only changes the behaviour on macOS.
|
|
func SetBindToInterfaceByRoute(v bool) {
|
|
bindToInterfaceByRoute.Store(v)
|
|
}
|
|
|
|
var disableBindConnToInterface atomic.Bool
|
|
|
|
// SetDisableBindConnToInterface disables the (normal) behavior of binding
|
|
// connections to the default network interface.
|
|
//
|
|
// Currently, this only has an effect on Darwin.
|
|
func SetDisableBindConnToInterface(v bool) {
|
|
disableBindConnToInterface.Store(v)
|
|
}
|
|
|
|
// 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 {
|
|
if disabled.Load() {
|
|
return new(net.ListenConfig)
|
|
}
|
|
return &net.ListenConfig{Control: control(logf)}
|
|
}
|
|
|
|
// 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{
|
|
KeepAlive: netknob.PlatformTCPKeepAlive(),
|
|
})
|
|
}
|
|
|
|
// FromDialer returns sets d.Control 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 FromDialer(logf logger.Logf, d *net.Dialer) Dialer {
|
|
if disabled.Load() {
|
|
return d
|
|
}
|
|
d.Control = control(logf)
|
|
if wrapDialer != nil {
|
|
return wrapDialer(d)
|
|
}
|
|
return d
|
|
}
|
|
|
|
// IsSOCKSDialer reports whether d is SOCKS-proxying dialer as returned by
|
|
// NewDialer or FromDialer.
|
|
func IsSOCKSDialer(d Dialer) bool {
|
|
if d == nil {
|
|
return false
|
|
}
|
|
_, ok := d.(*net.Dialer)
|
|
return !ok
|
|
}
|
|
|
|
// wrapDialer, if non-nil, specifies a function to wrap a dialer in a
|
|
// SOCKS-using dialer. It's set conditionally by socks.go.
|
|
var wrapDialer func(Dialer) Dialer
|
|
|
|
// Dialer is the interface for a dialer that can dial with or without a context.
|
|
// It's the type implemented both by net.Dialer and the Go SOCKS dialer.
|
|
type Dialer interface {
|
|
Dial(network, address string) (net.Conn, error)
|
|
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
|
}
|
|
|
|
func isLocalhost(addr string) bool {
|
|
host, _, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
// error means the string didn't contain a port number, so use the string directly
|
|
host = addr
|
|
}
|
|
|
|
// localhost6 == RedHat /etc/hosts for ::1, ip6-loopback & ip6-localhost == Debian /etc/hosts for ::1
|
|
if host == "localhost" || host == "localhost6" || host == "ip6-loopback" || host == "ip6-localhost" {
|
|
return true
|
|
}
|
|
|
|
ip, _ := netip.ParseAddr(host)
|
|
return ip.IsLoopback()
|
|
}
|