Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package netns
|
||||
|
||||
+13
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package netns contains the common code for using the Go net package
|
||||
@@ -46,6 +46,18 @@ func SetBindToInterfaceByRoute(logf logger.Logf, v bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// When true, disableAndroidBindToActiveNetwork skips binding sockets to the currently
|
||||
// active network on Android.
|
||||
var disableAndroidBindToActiveNetwork atomic.Bool
|
||||
|
||||
// SetDisableAndroidBindToActiveNetwork disables the default behavior of binding
|
||||
// sockets to the currently active network on Android.
|
||||
func SetDisableAndroidBindToActiveNetwork(logf logger.Logf, v bool) {
|
||||
if runtime.GOOS == "android" && disableAndroidBindToActiveNetwork.Swap(v) != v {
|
||||
logf("netns: disableAndroidBindToActiveNetwork changed to %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
var disableBindConnToInterface atomic.Bool
|
||||
|
||||
// SetDisableBindConnToInterface disables the (normal) behavior of binding
|
||||
|
||||
+37
-4
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build android
|
||||
@@ -17,6 +17,9 @@ import (
|
||||
var (
|
||||
androidProtectFuncMu sync.Mutex
|
||||
androidProtectFunc func(fd int) error
|
||||
|
||||
androidBindToNetworkFuncMu sync.Mutex
|
||||
androidBindToNetworkFunc func(fd int) error
|
||||
)
|
||||
|
||||
// UseSocketMark reports whether SO_MARK is in use. Android does not use SO_MARK.
|
||||
@@ -50,6 +53,14 @@ func SetAndroidProtectFunc(f func(fd int) error) {
|
||||
androidProtectFunc = f
|
||||
}
|
||||
|
||||
// SetAndroidBindToNetworkFunc registers a func provided by Android that binds
|
||||
// the socket FD to the currently selected underlying network.
|
||||
func SetAndroidBindToNetworkFunc(f func(fd int) error) {
|
||||
androidBindToNetworkFuncMu.Lock()
|
||||
defer androidBindToNetworkFuncMu.Unlock()
|
||||
androidBindToNetworkFunc = f
|
||||
}
|
||||
|
||||
func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
|
||||
return controlC
|
||||
}
|
||||
@@ -60,14 +71,36 @@ func control(logger.Logf, *netmon.Monitor) func(network, address string, c sysca
|
||||
// and net.ListenConfig.Control.
|
||||
func controlC(network, address string, c syscall.RawConn) error {
|
||||
var sockErr error
|
||||
|
||||
err := c.Control(func(fd uintptr) {
|
||||
fdInt := int(fd)
|
||||
|
||||
// Protect from VPN loops
|
||||
androidProtectFuncMu.Lock()
|
||||
f := androidProtectFunc
|
||||
pf := androidProtectFunc
|
||||
androidProtectFuncMu.Unlock()
|
||||
if f != nil {
|
||||
sockErr = f(int(fd))
|
||||
if pf != nil {
|
||||
if err := pf(fdInt); err != nil {
|
||||
sockErr = err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if disableAndroidBindToActiveNetwork.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
androidBindToNetworkFuncMu.Lock()
|
||||
bf := androidBindToNetworkFunc
|
||||
androidBindToNetworkFuncMu.Unlock()
|
||||
if bf != nil {
|
||||
if err := bf(fdInt); err != nil {
|
||||
sockErr = err
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("RawConn.Control on %T: %w", c, err)
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build darwin
|
||||
@@ -85,7 +85,7 @@ func getInterfaceIndex(logf logger.Logf, netMon *netmon.Monitor, address string)
|
||||
// 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.
|
||||
// the oses view of the defaultIf yet.
|
||||
if osIferr == nil {
|
||||
idx = osIf.InterfaceIndex
|
||||
errOut = nil
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !linux && !windows && !darwin
|
||||
//go:build !linux && !windows && !darwin && !openbsd
|
||||
|
||||
package netns
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build darwin || windows
|
||||
|
||||
+35
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && !android
|
||||
@@ -6,7 +6,9 @@
|
||||
package netns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
@@ -131,3 +133,35 @@ func bindToDevice(fd uintptr) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetListenConfigInterfaceName sets lc.Control so that sockets created
|
||||
// with it are bound to the named interface using SO_BINDTODEVICE, and
|
||||
// thus only receive packets that arrived on that interface. It is the
|
||||
// Linux counterpart of darwin's SetListenConfigInterfaceIndex.
|
||||
//
|
||||
// Failures to set the socket option are logged and otherwise ignored:
|
||||
// the socket then simply listens as it would have without this call.
|
||||
// Setting SO_BINDTODEVICE requires CAP_NET_RAW, and hardening must not
|
||||
// take out the caller's listener on systems where that is unavailable.
|
||||
func SetListenConfigInterfaceName(lc *net.ListenConfig, ifName string) error {
|
||||
if lc == nil {
|
||||
return errors.New("nil ListenConfig")
|
||||
}
|
||||
if lc.Control != nil {
|
||||
return errors.New("ListenConfig.Control already set")
|
||||
}
|
||||
lc.Control = func(network, address string, c syscall.RawConn) error {
|
||||
var sockErr error
|
||||
err := c.Control(func(fd uintptr) {
|
||||
sockErr = unix.SetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_BINDTODEVICE, ifName)
|
||||
})
|
||||
if sockErr != nil {
|
||||
log.Printf("[unexpected] netns: SO_BINDTODEVICE(%q) on %v %v: %v", ifName, network, address, sockErr)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("RawConn.Control on %T: %w", c, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build openbsd
|
||||
|
||||
package netns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
bypassMu sync.Mutex
|
||||
bypassRtable int
|
||||
)
|
||||
|
||||
// Called by the router when exit node routes are configured.
|
||||
func SetBypassRtable(rtable int) {
|
||||
bypassMu.Lock()
|
||||
defer bypassMu.Unlock()
|
||||
bypassRtable = rtable
|
||||
}
|
||||
|
||||
func GetBypassRtable() int {
|
||||
bypassMu.Lock()
|
||||
defer bypassMu.Unlock()
|
||||
return bypassRtable
|
||||
}
|
||||
|
||||
func control(logf logger.Logf, _ *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
|
||||
return func(network, address string, c syscall.RawConn) error {
|
||||
return controlC(logf, network, address, c)
|
||||
}
|
||||
}
|
||||
|
||||
func controlC(logf logger.Logf, _, address string, c syscall.RawConn) error {
|
||||
if isLocalhost(address) {
|
||||
return nil
|
||||
}
|
||||
|
||||
rtable := GetBypassRtable()
|
||||
if rtable == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return bindToRtable(c, rtable, logf)
|
||||
}
|
||||
|
||||
func bindToRtable(c syscall.RawConn, rtable int, logf logger.Logf) error {
|
||||
var sockErr error
|
||||
err := c.Control(func(fd uintptr) {
|
||||
sockErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RTABLE, rtable)
|
||||
})
|
||||
if sockErr != nil {
|
||||
logf("netns: SO_RTABLE(%d): %v", rtable, sockErr)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("RawConn.Control: %w", err)
|
||||
}
|
||||
return sockErr
|
||||
}
|
||||
|
||||
// SetupBypassRtable creates a bypass rtable with the existing default route
|
||||
// in it routing through its existing physical interface. It should be called
|
||||
// by the router when exit node routes are being added.
|
||||
// Returns the rtable number.
|
||||
func SetupBypassRtable(logf logger.Logf) (int, error) {
|
||||
bypassMu.Lock()
|
||||
defer bypassMu.Unlock()
|
||||
|
||||
if bypassRtable != 0 {
|
||||
return bypassRtable, nil
|
||||
}
|
||||
|
||||
gw, err := getPhysicalGateway()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("getPhysicalGateway: %w", err)
|
||||
}
|
||||
|
||||
rtable, err := findAvailableRtable()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("findAvailableRtable: %w", err)
|
||||
}
|
||||
|
||||
// Add the existing default route interface to the new bypass rtable
|
||||
out, err := exec.Command("route", "-T", strconv.Itoa(rtable), "-qn", "add", "default", gw).CombinedOutput()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("route -T%d add default %s: %w\n%s", rtable, gw, err, out)
|
||||
}
|
||||
|
||||
bypassRtable = rtable
|
||||
logf("netns: created bypass rtable %d with default route via %s", rtable, gw)
|
||||
return rtable, nil
|
||||
}
|
||||
|
||||
func CleanupBypassRtable(logf logger.Logf) {
|
||||
bypassMu.Lock()
|
||||
defer bypassMu.Unlock()
|
||||
|
||||
if bypassRtable == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Delete the default route from the bypass rtable which should clear it
|
||||
out, err := exec.Command("route", "-T", strconv.Itoa(bypassRtable), "-qn", "delete", "default").CombinedOutput()
|
||||
if err != nil {
|
||||
logf("netns: failed to clear bypass route: %v\n%s", err, out)
|
||||
} else {
|
||||
logf("netns: cleared bypass rtable %d", bypassRtable)
|
||||
}
|
||||
|
||||
bypassRtable = 0
|
||||
}
|
||||
|
||||
// getPhysicalGateway returns the default gateway IP that goes through a
|
||||
// physical interface (not tun).
|
||||
func getPhysicalGateway() (string, error) {
|
||||
out, err := exec.Command("route", "-n", "show", "-inet").CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("route show: %w", err)
|
||||
}
|
||||
|
||||
// Parse the routing table looking for default routes not via tun
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 8 {
|
||||
continue
|
||||
}
|
||||
// Format: Destination Gateway Flags Refs Use Mtu Prio Iface
|
||||
dest := fields[0]
|
||||
gateway := fields[1]
|
||||
iface := fields[7]
|
||||
|
||||
if dest == "default" && !strings.HasPrefix(iface, "tun") {
|
||||
return gateway, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no physical default gateway found")
|
||||
}
|
||||
|
||||
func findAvailableRtable() (int, error) {
|
||||
for i := 1; i <= 255; i++ {
|
||||
out, err := exec.Command("route", "-T", strconv.Itoa(i), "-n", "show", "-inet").CombinedOutput()
|
||||
if err != nil {
|
||||
// rtable doesn't exist, consider it available
|
||||
return i, nil
|
||||
}
|
||||
// Check if the output only contains the header (no actual routes)
|
||||
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
|
||||
hasRoutes := false
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "Routing") || strings.HasPrefix(line, "Destination") {
|
||||
continue
|
||||
}
|
||||
hasRoutes = true
|
||||
break
|
||||
}
|
||||
if !hasRoutes {
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("no available rtable")
|
||||
}
|
||||
|
||||
func UseSocketMark() bool {
|
||||
return false
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package netns
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ios && !js && !android && !ts_omit_useproxy
|
||||
|
||||
Reference in new issue
Block a user