This commit is contained in:
2026-02-19 10:07:43 +00:00
parent 007438e372
commit 6e637ecf77
1763 changed files with 60820 additions and 279516 deletions

View File

@@ -17,6 +17,7 @@ import (
"context"
"net"
"net/netip"
"runtime"
"sync/atomic"
"tailscale.com/net/netknob"
@@ -39,18 +40,36 @@ var bindToInterfaceByRoute atomic.Bool
// setting the TS_BIND_TO_INTERFACE_BY_ROUTE.
//
// Currently, this only changes the behaviour on macOS and Windows.
func SetBindToInterfaceByRoute(v bool) {
bindToInterfaceByRoute.Store(v)
func SetBindToInterfaceByRoute(logf logger.Logf, v bool) {
if bindToInterfaceByRoute.Swap(v) != v {
logf("netns: bindToInterfaceByRoute changed to %v", v)
}
}
var disableBindConnToInterface atomic.Bool
// SetDisableBindConnToInterface disables the (normal) behavior of binding
// connections to the default network interface.
// connections to the default network interface on Darwin nodes.
//
// Currently, this only has an effect on Darwin.
func SetDisableBindConnToInterface(v bool) {
disableBindConnToInterface.Store(v)
// Unless you intended to disable this for tailscaled on macos (which is likely
// to break things), you probably wanted to set
// SetDisableBindConnToInterfaceAppleExt which will disable explicit interface
// binding only when tailscaled is running inside a network extension process.
func SetDisableBindConnToInterface(logf logger.Logf, v bool) {
if disableBindConnToInterface.Swap(v) != v {
logf("netns: disableBindConnToInterface changed to %v", v)
}
}
var disableBindConnToInterfaceAppleExt atomic.Bool
// SetDisableBindConnToInterfaceAppleExt disables the (normal) behavior of binding
// connections to the default network interface but only on Apple clients where
// tailscaled is running inside a network extension.
func SetDisableBindConnToInterfaceAppleExt(logf logger.Logf, v bool) {
if runtime.GOOS == "darwin" && disableBindConnToInterfaceAppleExt.Swap(v) != v {
logf("netns: disableBindConnToInterfaceAppleExt changed to %v", v)
}
}
// Listener returns a new net.Listener with its Control hook func

View File

@@ -21,6 +21,7 @@ import (
"tailscale.com/net/netmon"
"tailscale.com/net/tsaddr"
"tailscale.com/types/logger"
"tailscale.com/version"
)
func control(logf logger.Logf, netMon *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
@@ -33,18 +34,14 @@ var bindToInterfaceByRouteEnv = envknob.RegisterBool("TS_BIND_TO_INTERFACE_BY_RO
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.
// controlLogf binds c to a particular interface as necessary to dial the
// provided (network, address).
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.
if disableBindConnToInterface.Load() || (version.IsMacGUIVariant() && disableBindConnToInterfaceAppleExt.Load()) {
return nil
}
if disableBindConnToInterface.Load() {
logf("netns_darwin: binding connection to interfaces disabled")
if isLocalhost(address) {
return nil
}
@@ -78,10 +75,38 @@ func getInterfaceIndex(logf logger.Logf, netMon *netmon.Monitor, address string)
return -1, errInterfaceStateInvalid
}
if iface, ok := state.Interface[state.DefaultRouteInterface]; ok {
return iface.Index, nil
// Netmon's cached view of the default inteface
cachedIdx, ok := state.Interface[state.DefaultRouteInterface]
// OSes view (if available) of the default interface
osIf, osIferr := netmon.OSDefaultRoute()
idx := -1
errOut := errInterfaceStateInvalid
// Preferentially choose the OS's view of the default if index. Due to the way darwin sets the delegated
// interface on tunnel creation only, it is possible for netmon to have a stale view of the default and
// netmon's view is often temporarily wrong during network transitions, or for us to not have the
// the the oses view of the defaultIf yet.
if osIferr == nil {
idx = osIf.InterfaceIndex
errOut = nil
} else if ok {
idx = cachedIdx.Index
errOut = nil
}
return -1, errInterfaceStateInvalid
if osIferr == nil && ok && (osIf.InterfaceIndex != cachedIdx.Index) {
logf("netns: [unexpected] os default if %q (%d) != netmon cached if %q (%d)", osIf.InterfaceName, osIf.InterfaceIndex, cachedIdx.Name, cachedIdx.Index)
}
// Sanity check to make sure we didn't pick the tailscale interface
if tsif, err2 := tailscaleInterface(); tsif != nil && err2 == nil && errOut == nil {
if tsif.Index == idx {
idx = -1
errOut = errInterfaceStateInvalid
}
}
return idx, errOut
}
useRoute := bindToInterfaceByRoute.Load() || bindToInterfaceByRouteEnv()
@@ -100,7 +125,7 @@ func getInterfaceIndex(logf logger.Logf, netMon *netmon.Monitor, address string)
idx, err := interfaceIndexFor(addr, true /* canRecurse */)
if err != nil {
logf("netns: error in interfaceIndexFor: %v", err)
logf("netns: error getting interface index for %q: %v", address, err)
return defaultIdx()
}
@@ -108,10 +133,13 @@ func getInterfaceIndex(logf logger.Logf, netMon *netmon.Monitor, address string)
// if so, we fall back to binding from the default.
tsif, err2 := tailscaleInterface()
if err2 == nil && tsif != nil && tsif.Index == idx {
logf("[unexpected] netns: interfaceIndexFor returned Tailscale interface")
// note: with an exit node enabled, this is almost always true. defaultIdx() is the
// right thing to do here.
return defaultIdx()
}
logf("netns: completed success interfaceIndexFor(%s) = %d", address, idx)
return idx, err
}

View File

@@ -20,3 +20,7 @@ func control(logger.Logf, *netmon.Monitor) func(network, address string, c sysca
func controlC(network, address string, c syscall.RawConn) error {
return nil
}
func UseSocketMark() bool {
return false
}

View File

@@ -25,3 +25,7 @@ func parseAddress(address string) (addr netip.Addr, err error) {
return netip.ParseAddr(host)
}
func UseSocketMark() bool {
return false
}

View File

@@ -15,8 +15,8 @@ import (
"golang.org/x/sys/unix"
"tailscale.com/envknob"
"tailscale.com/net/netmon"
"tailscale.com/tsconst"
"tailscale.com/types/logger"
"tailscale.com/util/linuxfw"
)
// socketMarkWorksOnce is the sync.Once & cached value for useSocketMark.
@@ -111,7 +111,7 @@ func controlC(network, address string, c syscall.RawConn) error {
}
func setBypassMark(fd uintptr) error {
if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, linuxfw.TailscaleBypassMarkNum); err != nil {
if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, tsconst.LinuxBypassMarkNum); err != nil {
return fmt.Errorf("setting SO_MARK bypass: %w", err)
}
return nil

View File

@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ios && !js
//go:build !ios && !js && !android && !ts_omit_useproxy
package netns

View File

@@ -45,7 +45,7 @@ var (
)
func getBestInterfaceEx(sockaddr *winipcfg.RawSockaddrInet, bestIfaceIndex *uint32) (ret error) {
r0, _, _ := syscall.Syscall(procGetBestInterfaceEx.Addr(), 2, uintptr(unsafe.Pointer(sockaddr)), uintptr(unsafe.Pointer(bestIfaceIndex)), 0)
r0, _, _ := syscall.SyscallN(procGetBestInterfaceEx.Addr(), uintptr(unsafe.Pointer(sockaddr)), uintptr(unsafe.Pointer(bestIfaceIndex)))
if r0 != 0 {
ret = syscall.Errno(r0)
}