This is implemented via GetBestInterfaceEx. Should we encounter errors or fail to resolve a valid, non-Tailscale interface, we fall back to returning the index for the default interface instead. Fixes #12551 Signed-off-by: Aaron Klotz <aaron@tailscale.com>main
parent
591979b95f
commit
7dd76c3411
@ -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
|
||||
@ -0,0 +1,21 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build darwin || windows
|
||||
|
||||
package netns |
||||
|
||||
import ( |
||||
"net" |
||||
"net/netip" |
||||
) |
||||
|
||||
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 |
||||
} |
||||
|
||||
return netip.ParseAddr(host) |
||||
} |
||||
@ -0,0 +1,112 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package netns |
||||
|
||||
import ( |
||||
"strings" |
||||
"testing" |
||||
|
||||
"golang.org/x/sys/windows" |
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" |
||||
"tailscale.com/tsconst" |
||||
) |
||||
|
||||
func TestGetInterfaceIndex(t *testing.T) { |
||||
oldVal := bindToInterfaceByRoute.Load() |
||||
t.Cleanup(func() { bindToInterfaceByRoute.Store(oldVal) }) |
||||
bindToInterfaceByRoute.Store(true) |
||||
|
||||
defIfaceIdxV4, err := defaultInterfaceIndex(windows.AF_INET) |
||||
if err != nil { |
||||
t.Fatalf("defaultInterfaceIndex(AF_INET) failed: %v", err) |
||||
} |
||||
|
||||
tests := []struct { |
||||
name string |
||||
addr string |
||||
err string |
||||
}{ |
||||
{ |
||||
name: "IP_and_port", |
||||
addr: "8.8.8.8:53", |
||||
}, |
||||
{ |
||||
name: "bare_ip", |
||||
addr: "8.8.8.8", |
||||
}, |
||||
} |
||||
for _, tc := range tests { |
||||
t.Run(tc.name, func(t *testing.T) { |
||||
addr, err := parseAddress(tc.addr) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
idx, err := getInterfaceIndex(t.Logf, addr, defIfaceIdxV4) |
||||
if err != nil { |
||||
if tc.err == "" { |
||||
t.Fatalf("got unexpected error: %v", err) |
||||
} |
||||
if errstr := err.Error(); errstr != tc.err { |
||||
t.Errorf("expected error %q, got %q", errstr, tc.err) |
||||
} |
||||
} else { |
||||
t.Logf("getInterfaceIndex(%q) = %d", tc.addr, idx) |
||||
if tc.err != "" { |
||||
t.Fatalf("wanted error %q", tc.err) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
t.Run("NoTailscale", func(t *testing.T) { |
||||
tsIdx, ok, err := tailscaleInterfaceIndex() |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if !ok { |
||||
t.Skip("no tailscale interface on this machine") |
||||
} |
||||
|
||||
defaultIdx, err := defaultInterfaceIndex(windows.AF_INET) |
||||
if err != nil { |
||||
t.Fatalf("defaultInterfaceIndex(AF_INET) failed: %v", err) |
||||
} |
||||
|
||||
addr, err := parseAddress("100.100.100.100:53") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
idx, err := getInterfaceIndex(t.Logf, addr, defaultIdx) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
t.Logf("tailscaleIdx=%d defaultIdx=%d idx=%d", tsIdx, defaultIdx, idx) |
||||
|
||||
if idx == tsIdx { |
||||
t.Fatalf("got idx=%d; wanted not Tailscale interface", idx) |
||||
} else if idx != defaultIdx { |
||||
t.Fatalf("got idx=%d, want %d", idx, defaultIdx) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
func tailscaleInterfaceIndex() (idx uint32, found bool, err error) { |
||||
ifs, err := winipcfg.GetAdaptersAddresses(windows.AF_INET, winipcfg.GAAFlagIncludeAllInterfaces) |
||||
if err != nil { |
||||
return idx, false, err |
||||
} |
||||
|
||||
for _, iface := range ifs { |
||||
if iface.IfType != winipcfg.IfTypePropVirtual { |
||||
continue |
||||
} |
||||
if strings.Contains(iface.Description(), tsconst.WintunInterfaceDesc) { |
||||
return iface.IfIndex, true, nil |
||||
} |
||||
} |
||||
return idx, false, nil |
||||
} |
||||
@ -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 |
||||
} |
||||
Loading…
Reference in new issue