|
|
@@ -4,14 +4,18 @@
|
|
|
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"
|
|
|
)
|
|
|
|
|
|
@@ -26,20 +30,34 @@ func interfaceIndex(iface *winipcfg.IPAdapterAddresses) uint32 {
|
|
|
return iface.IfIndex
|
|
|
}
|
|
|
|
|
|
-func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
|
|
|
- return controlC
|
|
|
+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(network, address string, c syscall.RawConn) error {
|
|
|
- if strings.HasPrefix(address, "127.") {
|
|
|
+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":
|
|
|
@@ -50,29 +68,107 @@ func controlC(network, address string, c syscall.RawConn) error {
|
|
|
canV6 = true
|
|
|
}
|
|
|
|
|
|
+ var defIfaceIdxV4, defIfaceIdxV6 uint32
|
|
|
if canV4 {
|
|
|
- iface, err := netmon.GetWindowsDefault(windows.AF_INET)
|
|
|
+ defIfaceIdxV4, err = defaultInterfaceIndex(windows.AF_INET)
|
|
|
if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- if err := bindSocket4(c, interfaceIndex(iface)); err != nil {
|
|
|
- return err
|
|
|
+ return fmt.Errorf("defaultInterfaceIndex(AF_INET): %w", err)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if canV6 {
|
|
|
- iface, err := netmon.GetWindowsDefault(windows.AF_INET6)
|
|
|
+ defIfaceIdxV6, err = defaultInterfaceIndex(windows.AF_INET6)
|
|
|
if err != nil {
|
|
|
- return err
|
|
|
+ return fmt.Errorf("defaultInterfaceIndex(AF_INET6): %w", err)
|
|
|
}
|
|
|
- if err := bindSocket6(c, interfaceIndex(iface)); err != nil {
|
|
|
- return err
|
|
|
+ }
|
|
|
+
|
|
|
+ var ifaceIdxV4, ifaceIdxV6 uint32
|
|
|
+ if useRoute := bindToInterfaceByRoute.Load() || bindToInterfaceByRouteEnv(); useRoute {
|
|
|
+ addr, err := parseAddress(address)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("parseAddress: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 {
|
|
|
+ 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
|