Update dependencies
This commit is contained in:
9
vendor/tailscale.com/net/netns/mksyscall.go
generated
vendored
Normal file
9
vendor/tailscale.com/net/netns/mksyscall.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package netns
|
||||
|
||||
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go mksyscall.go
|
||||
//go:generate go run golang.org/x/tools/cmd/goimports -w zsyscall_windows.go
|
||||
|
||||
//sys getBestInterfaceEx(sockaddr *winipcfg.RawSockaddrInet, bestIfaceIndex *uint32) (ret error) = iphlpapi.GetBestInterfaceEx
|
||||
135
vendor/tailscale.com/net/netns/netns.go
generated
vendored
Normal file
135
vendor/tailscale.com/net/netns/netns.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
// 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/net/netmon"
|
||||
"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 and Windows.
|
||||
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, netMon *netmon.Monitor) *net.ListenConfig {
|
||||
if netMon == nil {
|
||||
panic("netns.Listener called with nil netMon")
|
||||
}
|
||||
if disabled.Load() {
|
||||
return new(net.ListenConfig)
|
||||
}
|
||||
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, netMon *netmon.Monitor) Dialer {
|
||||
if netMon == nil {
|
||||
panic("netns.NewDialer called with nil netMon")
|
||||
}
|
||||
return FromDialer(logf, netMon, &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, netMon *netmon.Monitor, d *net.Dialer) Dialer {
|
||||
if netMon == nil {
|
||||
panic("netns.FromDialer called with nil netMon")
|
||||
}
|
||||
if disabled.Load() {
|
||||
return d
|
||||
}
|
||||
d.Control = control(logf, netMon)
|
||||
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()
|
||||
}
|
||||
75
vendor/tailscale.com/net/netns/netns_android.go
generated
vendored
Normal file
75
vendor/tailscale.com/net/netns/netns_android.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build android
|
||||
|
||||
package netns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
androidProtectFuncMu sync.Mutex
|
||||
androidProtectFunc func(fd int) error
|
||||
)
|
||||
|
||||
// UseSocketMark reports whether SO_MARK is in use. Android does not use SO_MARK.
|
||||
func UseSocketMark() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAndroidProtectFunc register a func that Android provides that JNI calls into
|
||||
// https://developer.android.com/reference/android/net/VpnService#protect(int)
|
||||
// which is documented as:
|
||||
//
|
||||
// "Protect a socket from VPN connections. After protecting, data sent
|
||||
// through this socket will go directly to the underlying network, so
|
||||
// its traffic will not be forwarded through the VPN. This method is
|
||||
// useful if some connections need to be kept outside of VPN. For
|
||||
// example, a VPN tunnel should protect itself if its destination is
|
||||
// covered by VPN routes. Otherwise its outgoing packets will be sent
|
||||
// back to the VPN interface and cause an infinite loop. This method
|
||||
// will fail if the application is not prepared or is revoked."
|
||||
//
|
||||
// A nil func disables the use the hook.
|
||||
//
|
||||
// This indirection is necessary because this is the supported, stable
|
||||
// interface to use on Android, and doing the sockopts to set the
|
||||
// fwmark return errors on Android. The actual implementation of
|
||||
// VpnService.protect ends up doing an IPC to another process on
|
||||
// Android, asking for the fwmark to be set.
|
||||
func SetAndroidProtectFunc(f func(fd int) error) {
|
||||
androidProtectFuncMu.Lock()
|
||||
defer androidProtectFuncMu.Unlock()
|
||||
androidProtectFunc = f
|
||||
}
|
||||
|
||||
func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
|
||||
return controlC
|
||||
}
|
||||
|
||||
// controlC 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 controlC(network, address string, c syscall.RawConn) error {
|
||||
var sockErr error
|
||||
err := c.Control(func(fd uintptr) {
|
||||
androidProtectFuncMu.Lock()
|
||||
f := androidProtectFunc
|
||||
androidProtectFuncMu.Unlock()
|
||||
if f != nil {
|
||||
sockErr = f(int(fd))
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("RawConn.Control on %T: %w", c, err)
|
||||
}
|
||||
return sockErr
|
||||
}
|
||||
287
vendor/tailscale.com/net/netns/netns_darwin.go
generated
vendored
Normal file
287
vendor/tailscale.com/net/netns/netns_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build darwin
|
||||
|
||||
package netns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
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, netMon, network, address, c)
|
||||
}
|
||||
}
|
||||
|
||||
var bindToInterfaceByRouteEnv = envknob.RegisterBool("TS_BIND_TO_INTERFACE_BY_ROUTE")
|
||||
|
||||
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, netMon *netmon.Monitor, network, address string, c syscall.RawConn) error {
|
||||
if isLocalhost(address) {
|
||||
// Don't bind to an interface for localhost connections.
|
||||
return nil
|
||||
}
|
||||
|
||||
if disableBindConnToInterface.Load() {
|
||||
logf("netns_darwin: binding connection to interfaces disabled")
|
||||
return nil
|
||||
}
|
||||
|
||||
idx, err := getInterfaceIndex(logf, netMon, address)
|
||||
if err != nil {
|
||||
// callee logged
|
||||
return nil
|
||||
}
|
||||
|
||||
return bindConnToInterface(c, network, address, idx, logf)
|
||||
}
|
||||
|
||||
func getInterfaceIndex(logf logger.Logf, netMon *netmon.Monitor, address string) (int, error) {
|
||||
// Helper so we can log errors.
|
||||
defaultIdx := func() (int, error) {
|
||||
if netMon == nil {
|
||||
idx, err := netmon.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, netmon.ErrNoGatewayIndexFound) {
|
||||
logf("[unexpected] netns: DefaultRouteInterfaceIndex: %v", err)
|
||||
}
|
||||
return -1, err
|
||||
}
|
||||
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()
|
||||
if !useRoute {
|
||||
return defaultIdx()
|
||||
}
|
||||
|
||||
// If the address doesn't parse, use the default index.
|
||||
addr, err := parseAddress(address)
|
||||
if err != nil {
|
||||
if err != errUnspecifiedHost {
|
||||
logf("[unexpected] netns: error parsing address %q: %v", address, err)
|
||||
}
|
||||
return defaultIdx()
|
||||
}
|
||||
|
||||
idx, err := interfaceIndexFor(addr, true /* canRecurse */)
|
||||
if err != nil {
|
||||
logf("netns: error in interfaceIndexFor: %v", err)
|
||||
return defaultIdx()
|
||||
}
|
||||
|
||||
// Verify that we didn't just choose the Tailscale interface;
|
||||
// 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")
|
||||
return defaultIdx()
|
||||
}
|
||||
|
||||
return idx, err
|
||||
}
|
||||
|
||||
// tailscaleInterface returns the current machine's Tailscale interface, if any.
|
||||
// If none is found, (nil, nil) is returned.
|
||||
// A non-nil error is only returned on a problem listing the system interfaces.
|
||||
func tailscaleInterface() (*net.Interface, error) {
|
||||
ifs, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, iface := range ifs {
|
||||
if !strings.HasPrefix(iface.Name, "utun") {
|
||||
continue
|
||||
}
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, a := range addrs {
|
||||
if ipnet, ok := a.(*net.IPNet); ok {
|
||||
nip, ok := netip.AddrFromSlice(ipnet.IP)
|
||||
if ok && tsaddr.IsTailscaleIP(nip.Unmap()) {
|
||||
return &iface, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// interfaceIndexFor returns the interface index that we should bind to in
|
||||
// order to send traffic to the provided address.
|
||||
func interfaceIndexFor(addr netip.Addr, canRecurse bool) (int, error) {
|
||||
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("creating AF_ROUTE socket: %w", err)
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
|
||||
var routeAddr route.Addr
|
||||
if addr.Is4() {
|
||||
routeAddr = &route.Inet4Addr{IP: addr.As4()}
|
||||
} else {
|
||||
routeAddr = &route.Inet6Addr{IP: addr.As16()}
|
||||
}
|
||||
|
||||
rm := route.RouteMessage{
|
||||
// NOTE: This is unix.RTM_VERSION, but we want to pin this to a
|
||||
// particular constant so that it doesn't change under us if
|
||||
// the x/sys/unix package changes down the road. Currently this
|
||||
// is 0x5 on both Darwin x86 and ARM64.
|
||||
Version: 0x5,
|
||||
Type: unix.RTM_GET,
|
||||
Flags: unix.RTF_UP,
|
||||
ID: uintptr(os.Getpid()),
|
||||
Seq: 1,
|
||||
Addrs: []route.Addr{
|
||||
unix.RTAX_DST: routeAddr,
|
||||
},
|
||||
}
|
||||
b, err := rm.Marshal()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("marshaling RouteMessage: %w", err)
|
||||
}
|
||||
_, err = unix.Write(fd, b)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("writing message: %w", err)
|
||||
}
|
||||
|
||||
// On macOS, the RTM_GET call should return exactly one route message.
|
||||
// Given the following sizes and constants:
|
||||
// - sizeof(struct rt_msghdr) = 92
|
||||
// - RTAX_MAX = 8
|
||||
// - sizeof(struct sockaddr_in6) = 28
|
||||
// - sizeof(struct sockaddr_in) = 16
|
||||
// - sizeof(struct sockaddr_dl) = 20
|
||||
//
|
||||
// The maximum buffer size should be:
|
||||
// sizeof(struct rt_msghdr) + RTAX_MAX*sizeof(struct sockaddr_in6)
|
||||
// = 92 + 8*28
|
||||
// = 316
|
||||
//
|
||||
// During my testing, responses are typically ~120 bytes.
|
||||
//
|
||||
// We provide a much larger buffer just in case we're off by a bit, or
|
||||
// the kernel decides to return more than one message; 2048 bytes
|
||||
// should be plenty here. This also means we can do a single Read.
|
||||
var buf [2048]byte
|
||||
n, err := unix.Read(fd, buf[:])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("reading message: %w", err)
|
||||
}
|
||||
msgs, err := route.ParseRIB(route.RIBTypeRoute, buf[:n])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("route.ParseRIB: %w", err)
|
||||
}
|
||||
if len(msgs) == 0 {
|
||||
return 0, fmt.Errorf("no messages")
|
||||
}
|
||||
|
||||
for _, msg := range msgs {
|
||||
rm, ok := msg.(*route.RouteMessage)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if rm.Version < 3 || rm.Version > 5 || rm.Type != unix.RTM_GET {
|
||||
continue
|
||||
}
|
||||
if len(rm.Addrs) < unix.RTAX_GATEWAY {
|
||||
continue
|
||||
}
|
||||
|
||||
switch addr := rm.Addrs[unix.RTAX_GATEWAY].(type) {
|
||||
case *route.LinkAddr:
|
||||
return addr.Index, nil
|
||||
case *route.Inet4Addr:
|
||||
// We can get a gateway IP; recursively call ourselves
|
||||
// (exactly once) to get the link (and thus index) for
|
||||
// the gateway IP.
|
||||
if canRecurse {
|
||||
return interfaceIndexFor(netip.AddrFrom4(addr.IP), false)
|
||||
}
|
||||
case *route.Inet6Addr:
|
||||
// As above.
|
||||
if canRecurse {
|
||||
return interfaceIndexFor(netip.AddrFrom16(addr.IP), false)
|
||||
}
|
||||
default:
|
||||
// Unknown type; skip it
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("no valid address found")
|
||||
}
|
||||
|
||||
// SetListenConfigInterfaceIndex sets lc.Control such that sockets are bound
|
||||
// to the provided interface index.
|
||||
func SetListenConfigInterfaceIndex(lc *net.ListenConfig, ifIndex int) 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 {
|
||||
return bindConnToInterface(c, network, address, ifIndex, log.Printf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindConnToInterface(c syscall.RawConn, network, address string, ifIndex int, logf logger.Logf) error {
|
||||
v6 := strings.Contains(address, "]:") || strings.HasSuffix(network, "6") // hacky test for v6
|
||||
proto := unix.IPPROTO_IP
|
||||
opt := unix.IP_BOUND_IF
|
||||
if v6 {
|
||||
proto = unix.IPPROTO_IPV6
|
||||
opt = unix.IPV6_BOUND_IF
|
||||
}
|
||||
|
||||
var sockErr error
|
||||
err := c.Control(func(fd uintptr) {
|
||||
sockErr = unix.SetsockoptInt(int(fd), proto, opt, ifIndex)
|
||||
})
|
||||
if sockErr != nil {
|
||||
logf("[unexpected] netns: bindConnToInterface(%q, %q), v6=%v, index=%v: %v", network, address, v6, ifIndex, sockErr)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("RawConn.Control on %T: %w", c, err)
|
||||
}
|
||||
return sockErr
|
||||
}
|
||||
22
vendor/tailscale.com/net/netns/netns_default.go
generated
vendored
Normal file
22
vendor/tailscale.com/net/netns/netns_default.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !linux && !windows && !darwin
|
||||
|
||||
package netns
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
|
||||
return controlC
|
||||
}
|
||||
|
||||
// controlC does nothing to c.
|
||||
func controlC(network, address string, c syscall.RawConn) error {
|
||||
return nil
|
||||
}
|
||||
27
vendor/tailscale.com/net/netns/netns_dw.go
generated
vendored
Normal file
27
vendor/tailscale.com/net/netns/netns_dw.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build darwin || windows
|
||||
|
||||
package netns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
var errUnspecifiedHost = errors.New("unspecified host")
|
||||
|
||||
func parseAddress(address string) (addr netip.Addr, err error) {
|
||||
host, _, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
// error means the string didn't contain a port number, so use the string directly
|
||||
host = address
|
||||
}
|
||||
if host == "" {
|
||||
return addr, errUnspecifiedHost
|
||||
}
|
||||
|
||||
return netip.ParseAddr(host)
|
||||
}
|
||||
133
vendor/tailscale.com/net/netns/netns_linux.go
generated
vendored
Normal file
133
vendor/tailscale.com/net/netns/netns_linux.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && !android
|
||||
|
||||
package netns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/linuxfw"
|
||||
)
|
||||
|
||||
// socketMarkWorksOnce is the sync.Once & cached value for useSocketMark.
|
||||
var socketMarkWorksOnce struct {
|
||||
sync.Once
|
||||
v bool
|
||||
}
|
||||
|
||||
// socketMarkWorks returns whether SO_MARK works.
|
||||
func socketMarkWorks() bool {
|
||||
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:1")
|
||||
if err != nil {
|
||||
return true // unsure, returning true does the least harm.
|
||||
}
|
||||
|
||||
sConn, err := net.DialUDP("udp", nil, addr)
|
||||
if err != nil {
|
||||
return true // unsure, return true
|
||||
}
|
||||
defer sConn.Close()
|
||||
|
||||
rConn, err := sConn.SyscallConn()
|
||||
if err != nil {
|
||||
return true // unsure, return true
|
||||
}
|
||||
|
||||
var sockErr error
|
||||
err = rConn.Control(func(fd uintptr) {
|
||||
sockErr = setBypassMark(fd)
|
||||
})
|
||||
if err != nil || sockErr != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
var forceBindToDevice = envknob.RegisterBool("TS_FORCE_LINUX_BIND_TO_DEVICE")
|
||||
|
||||
// UseSocketMark reports whether SO_MARK is in use.
|
||||
// If it doesn't, we have to use SO_BINDTODEVICE on our sockets instead.
|
||||
func UseSocketMark() bool {
|
||||
if forceBindToDevice() {
|
||||
return false
|
||||
}
|
||||
socketMarkWorksOnce.Do(func() {
|
||||
socketMarkWorksOnce.v = socketMarkWorks()
|
||||
})
|
||||
return socketMarkWorksOnce.v
|
||||
}
|
||||
|
||||
// ignoreErrors returns true if we should ignore setsocketopt errors in
|
||||
// this instance.
|
||||
func ignoreErrors() bool {
|
||||
if os.Getuid() != 0 {
|
||||
// only root can manipulate these socket flags
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
|
||||
return controlC
|
||||
}
|
||||
|
||||
// controlC 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 controlC(network, address string, c syscall.RawConn) error {
|
||||
if isLocalhost(address) {
|
||||
// Don't bind to an interface for localhost connections.
|
||||
return nil
|
||||
}
|
||||
|
||||
var sockErr error
|
||||
err := c.Control(func(fd uintptr) {
|
||||
if UseSocketMark() {
|
||||
sockErr = setBypassMark(fd)
|
||||
} else {
|
||||
sockErr = bindToDevice(fd)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("RawConn.Control on %T: %w", c, err)
|
||||
}
|
||||
if sockErr != nil && ignoreErrors() {
|
||||
// TODO(bradfitz): maybe log once? probably too spammy for e.g. CLI tools like tailscale netcheck.
|
||||
return nil
|
||||
}
|
||||
return sockErr
|
||||
}
|
||||
|
||||
func setBypassMark(fd uintptr) error {
|
||||
if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, linuxfw.TailscaleBypassMarkNum); err != nil {
|
||||
return fmt.Errorf("setting SO_MARK bypass: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindToDevice(fd uintptr) error {
|
||||
ifc, err := netmon.DefaultRouteInterface()
|
||||
if err != nil {
|
||||
// Make sure we bind to *some* interface,
|
||||
// or we could get a routing loop.
|
||||
// "lo" is always wrong, but if we don't have
|
||||
// a default route anyway, it doesn't matter.
|
||||
ifc = "lo"
|
||||
}
|
||||
if err := unix.SetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_BINDTODEVICE, ifc); err != nil {
|
||||
return fmt.Errorf("setting SO_BINDTODEVICE: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
224
vendor/tailscale.com/net/netns/netns_windows.go
generated
vendored
Normal file
224
vendor/tailscale.com/net/netns/netns_windows.go
generated
vendored
Normal file
@@ -0,0 +1,224 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package netns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/cpu"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/tsconst"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
func interfaceIndex(iface *winipcfg.IPAdapterAddresses) uint32 {
|
||||
if iface == nil {
|
||||
// The zero ifidx means "unspecified". If we end up passing zero
|
||||
// to bindSocket*(), it unsets the binding and lets the socket
|
||||
// behave as normal again, which is what we want if there's no
|
||||
// default route we can use.
|
||||
return 0
|
||||
}
|
||||
return iface.IfIndex
|
||||
}
|
||||
|
||||
func defaultInterfaceIndex(family winipcfg.AddressFamily) (uint32, error) {
|
||||
iface, err := netmon.GetWindowsDefault(family)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return interfaceIndex(iface), nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
var bindToInterfaceByRouteEnv = envknob.RegisterBool("TS_BIND_TO_INTERFACE_BY_ROUTE")
|
||||
|
||||
// controlC binds c to the Windows interface that holds a default
|
||||
// route, and is not the Tailscale WinTun interface.
|
||||
func controlC(logf logger.Logf, network, address string, c syscall.RawConn) (err error) {
|
||||
if isLocalhost(address) {
|
||||
// Don't bind to an interface for localhost connections,
|
||||
// otherwise we get:
|
||||
// connectex: The requested address is not valid in its context
|
||||
// (The derphttp tests were failing)
|
||||
return nil
|
||||
}
|
||||
|
||||
canV4, canV6 := false, false
|
||||
switch network {
|
||||
case "tcp", "udp":
|
||||
canV4, canV6 = true, true
|
||||
case "tcp4", "udp4":
|
||||
canV4 = true
|
||||
case "tcp6", "udp6":
|
||||
canV6 = true
|
||||
}
|
||||
|
||||
var defIfaceIdxV4, defIfaceIdxV6 uint32
|
||||
if canV4 {
|
||||
defIfaceIdxV4, err = defaultInterfaceIndex(windows.AF_INET)
|
||||
if err != nil {
|
||||
return fmt.Errorf("defaultInterfaceIndex(AF_INET): %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if canV6 {
|
||||
defIfaceIdxV6, err = defaultInterfaceIndex(windows.AF_INET6)
|
||||
if err != nil {
|
||||
return fmt.Errorf("defaultInterfaceIndex(AF_INET6): %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var ifaceIdxV4, ifaceIdxV6 uint32
|
||||
if useRoute := bindToInterfaceByRoute.Load() || bindToInterfaceByRouteEnv(); useRoute {
|
||||
addr, err := parseAddress(address)
|
||||
if err == nil {
|
||||
if canV4 && (addr.Is4() || addr.Is4In6()) {
|
||||
addrV4 := addr.Unmap()
|
||||
ifaceIdxV4, err = getInterfaceIndex(logf, addrV4, defIfaceIdxV4)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getInterfaceIndex(%v): %w", addrV4, err)
|
||||
}
|
||||
}
|
||||
|
||||
if canV6 && addr.Is6() {
|
||||
ifaceIdxV6, err = getInterfaceIndex(logf, addr, defIfaceIdxV6)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getInterfaceIndex(%v): %w", addr, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err != errUnspecifiedHost {
|
||||
logf("[unexpected] netns: error parsing address %q: %v", address, err)
|
||||
}
|
||||
ifaceIdxV4, ifaceIdxV6 = defIfaceIdxV4, defIfaceIdxV6
|
||||
}
|
||||
} else {
|
||||
ifaceIdxV4, ifaceIdxV6 = defIfaceIdxV4, defIfaceIdxV6
|
||||
}
|
||||
|
||||
if canV4 {
|
||||
if err := bindSocket4(c, ifaceIdxV4); err != nil {
|
||||
return fmt.Errorf("bindSocket4(%d): %w", ifaceIdxV4, err)
|
||||
}
|
||||
}
|
||||
|
||||
if canV6 {
|
||||
if err := bindSocket6(c, ifaceIdxV6); err != nil {
|
||||
return fmt.Errorf("bindSocket6(%d): %w", ifaceIdxV6, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getInterfaceIndex(logf logger.Logf, addr netip.Addr, defaultIdx uint32) (idx uint32, err error) {
|
||||
idx, err = interfaceIndexFor(addr)
|
||||
if err != nil {
|
||||
return defaultIdx, fmt.Errorf("interfaceIndexFor: %w", err)
|
||||
}
|
||||
|
||||
isTS, err := isTailscaleInterface(idx)
|
||||
if err != nil {
|
||||
return defaultIdx, fmt.Errorf("isTailscaleInterface: %w", err)
|
||||
}
|
||||
if isTS {
|
||||
return defaultIdx, nil
|
||||
}
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
func isTailscaleInterface(ifaceIdx uint32) (bool, error) {
|
||||
ifaceLUID, err := winipcfg.LUIDFromIndex(ifaceIdx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
iface, err := ifaceLUID.Interface()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
result := iface.Type == winipcfg.IfTypePropVirtual &&
|
||||
strings.Contains(iface.Description(), tsconst.WintunInterfaceDesc)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func interfaceIndexFor(addr netip.Addr) (uint32, error) {
|
||||
var sockaddr winipcfg.RawSockaddrInet
|
||||
if err := sockaddr.SetAddr(addr); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var idx uint32
|
||||
if err := getBestInterfaceEx(&sockaddr, &idx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
// sockoptBoundInterface is the value of IP_UNICAST_IF and IPV6_UNICAST_IF.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options
|
||||
// and https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ipv6-socket-options
|
||||
const sockoptBoundInterface = 31
|
||||
|
||||
// bindSocket4 binds the given RawConn to the network interface with
|
||||
// index ifidx, for IPv4 traffic only.
|
||||
func bindSocket4(c syscall.RawConn, ifidx uint32) error {
|
||||
// For IPv4 (but NOT IPv6) the interface index must be passed
|
||||
// as a big-endian integer (regardless of platform endianness)
|
||||
// because the underlying sockopt takes either an IPv4 address
|
||||
// or an index shoved into IPv4 address representation (an IP
|
||||
// in 0.0.0.0/8 means it's actually an index).
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options
|
||||
// and IP_UNICAST_IF.
|
||||
indexAsAddr := nativeToBigEndian(ifidx)
|
||||
var controlErr error
|
||||
err := c.Control(func(fd uintptr) {
|
||||
controlErr = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, sockoptBoundInterface, int(indexAsAddr))
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return controlErr
|
||||
}
|
||||
|
||||
// bindSocket6 binds the given RawConn to the network interface with
|
||||
// index ifidx, for IPv6 traffic only.
|
||||
func bindSocket6(c syscall.RawConn, ifidx uint32) error {
|
||||
var controlErr error
|
||||
err := c.Control(func(fd uintptr) {
|
||||
controlErr = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, sockoptBoundInterface, int(ifidx))
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return controlErr
|
||||
}
|
||||
|
||||
// nativeToBigEndian returns i converted into big-endian
|
||||
// representation, suitable for passing to Windows APIs that require a
|
||||
// mangled uint32.
|
||||
func nativeToBigEndian(i uint32) uint32 {
|
||||
if cpu.IsBigEndian {
|
||||
return i
|
||||
}
|
||||
return bits.ReverseBytes32(i)
|
||||
}
|
||||
19
vendor/tailscale.com/net/netns/socks.go
generated
vendored
Normal file
19
vendor/tailscale.com/net/netns/socks.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ios && !js
|
||||
|
||||
package netns
|
||||
|
||||
import "golang.org/x/net/proxy"
|
||||
|
||||
func init() {
|
||||
wrapDialer = wrapSocks
|
||||
}
|
||||
|
||||
func wrapSocks(d Dialer) Dialer {
|
||||
if cd, ok := proxy.FromEnvironmentUsing(d).(Dialer); ok {
|
||||
return cd
|
||||
}
|
||||
return d
|
||||
}
|
||||
53
vendor/tailscale.com/net/netns/zsyscall_windows.go
generated
vendored
Normal file
53
vendor/tailscale.com/net/netns/zsyscall_windows.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
// Code generated by 'go generate'; DO NOT EDIT.
|
||||
|
||||
package netns
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
|
||||
|
||||
procGetBestInterfaceEx = modiphlpapi.NewProc("GetBestInterfaceEx")
|
||||
)
|
||||
|
||||
func getBestInterfaceEx(sockaddr *winipcfg.RawSockaddrInet, bestIfaceIndex *uint32) (ret error) {
|
||||
r0, _, _ := syscall.Syscall(procGetBestInterfaceEx.Addr(), 2, uintptr(unsafe.Pointer(sockaddr)), uintptr(unsafe.Pointer(bestIfaceIndex)), 0)
|
||||
if r0 != 0 {
|
||||
ret = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user