diff options
Diffstat (limited to 'net')
| -rw-r--r-- | net/netmon/interfaces_windows.go | 109 | ||||
| -rw-r--r-- | net/netmon/mksyscall.go | 17 | ||||
| -rw-r--r-- | net/netmon/state.go | 34 | ||||
| -rw-r--r-- | net/netmon/zsyscall_windows.go | 62 |
4 files changed, 216 insertions, 6 deletions
diff --git a/net/netmon/interfaces_windows.go b/net/netmon/interfaces_windows.go index d6625ead3..d6403e8ad 100644 --- a/net/netmon/interfaces_windows.go +++ b/net/netmon/interfaces_windows.go @@ -4,7 +4,9 @@ package netmon import ( + "cmp" "log" + "net" "net/netip" "net/url" "strings" @@ -15,6 +17,7 @@ import ( "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "tailscale.com/feature/buildfeatures" "tailscale.com/tsconst" + "tailscale.com/util/winutil/winnet" ) const ( @@ -22,12 +25,118 @@ const ( ) func init() { + altNetInterfaces = altNetInterfacesWindows likelyHomeRouterIP = likelyHomeRouterIPWindows if buildfeatures.HasUseProxy { getPAC = getPACWindows } } +func altNetInterfacesWindows() ([]Interface, error) { + adapterAddrs, err := getInterfaces(windows.AF_UNSPEC, winipcfg.GAAFlagIncludePrefix|winipcfg.GAAFlagIncludeGateways, notTailscaleInterface) + if err != nil { + return nil, err + } + + result := make([]Interface, 0, len(adapterAddrs)) + + for _, aa := range adapterAddrs { + curIface := net.Interface{ + Index: int(cmp.Or(aa.IfIndex, aa.IPv6IfIndex)), + Name: aa.FriendlyName(), + } + + if aa.OperStatus == windows.IfOperStatusUp { + curIface.Flags |= net.FlagUp + curIface.Flags |= net.FlagRunning + } + + platFlags := connectivityFlags(aa.NetworkGUID) + + entry, err := ifEntry(aa.LUID) + if err != nil { + return nil, err + } + + eat := entry.AccessType + if eat&winipcfg.NetIfAccessLoopback != 0 { + curIface.Flags |= net.FlagLoopback + } + if eat&winipcfg.NetIfAccessBroadcast != 0 { + curIface.Flags |= net.FlagBroadcast + } + if eat&winipcfg.NetIfAccessPointToPoint != 0 { + curIface.Flags |= net.FlagPointToPoint + } + if eat&winipcfg.NetIfAccessPointToMultiPoint != 0 { + curIface.Flags |= net.FlagMulticast + } + + if aa.MTU == 0xffffffff { + curIface.MTU = -1 + } else { + curIface.MTU = int(aa.MTU) + } + + if physAddr := aa.PhysicalAddress(); len(physAddr) > 0 { + curIface.HardwareAddr = make([]byte, len(physAddr)) + copy(curIface.HardwareAddr, physAddr) + } + + result = append(result, Interface{ + Interface: &curIface, + Desc: aa.Description(), + PlatFlags: platFlags, + }) + } + + return result, nil +} + +func connectivityFlags(ifGUID windows.GUID) (flags PlatFlags) { + nlm, err := winnet.GetNetworkListManager() + if err != nil { + return 0 + } + + network, err := nlm.GetNetwork(ifGUID) + if err != nil { + return 0 + } + defer network.Release() + + connectivity, err := network.GetConnectivity() + if err != nil { + return 0 + } + + if connectivity&winnet.NLM_CONNECTIVITY_IPV4_INTERNET == 0 { + flags |= PlatFlagNoIPv4InternetConnectivity + } + + if connectivity&winnet.NLM_CONNECTIVITY_IPV6_INTERNET == 0 { + flags |= PlatFlagNoIPv6InternetConnectivity + } + + return flags +} + +func ifEntry(ifLUID winipcfg.LUID) (*winipcfg.MibIfRow2, error) { + row := &winipcfg.MibIfRow2{ + InterfaceLUID: ifLUID, + } + if procGetIfEntry2Ex.Find() == nil { + if err := getIfEntry2Ex(_MibIfEntryNormalWithoutStatistics, row); err != nil { + return nil, err + } + } else { + if err := getIfEntry2(row); err != nil { + return nil, err + } + } + return row, nil +} + func likelyHomeRouterIPWindows() (ret netip.Addr, _ netip.Addr, ok bool) { rs, err := winipcfg.GetIPForwardTable2(windows.AF_INET) if err != nil { diff --git a/net/netmon/mksyscall.go b/net/netmon/mksyscall.go new file mode 100644 index 000000000..89f7820b1 --- /dev/null +++ b/net/netmon/mksyscall.go @@ -0,0 +1,17 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package netmon + +//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 + +type _MIB_IF_ENTRY_LEVEL int32 + +const ( + _MibIfEntryNormal _MIB_IF_ENTRY_LEVEL = 0 + _MibIfEntryNormalWithoutStatistics _MIB_IF_ENTRY_LEVEL = 2 +) + +//sys getIfEntry2(row *winipcfg.MibIfRow2) (ret error) = iphlpapi.GetIfEntry2 +//sys getIfEntry2Ex(level _MIB_IF_ENTRY_LEVEL, row *winipcfg.MibIfRow2) (ret error) = iphlpapi.GetIfEntry2Ex diff --git a/net/netmon/state.go b/net/netmon/state.go index 27e3524e8..bd8daf4fd 100644 --- a/net/netmon/state.go +++ b/net/netmon/state.go @@ -142,15 +142,29 @@ func sortIPs(s []netip.Addr) { sort.Slice(s, func(i, j int) bool { return s[i].Less(s[j]) }) } +type PlatFlags uint + +const ( + PlatFlagNoIPv4InternetConnectivity PlatFlags = 1 << iota + PlatFlagNoIPv6InternetConnectivity +) + // Interface is a wrapper around Go's net.Interface with some extra methods. type Interface struct { *net.Interface - AltAddrs []net.Addr // if non-nil, returned by Addrs - Desc string // extra description (used on Windows) + AltAddrs []net.Addr // if non-nil, returned by Addrs + Desc string // extra description (used on Windows) + PlatFlags PlatFlags // flags with additional connectivity information +} + +func (i Interface) IsLoopback() bool { + return isLoopback(i.Interface) +} + +func (i Interface) IsUp() bool { + return isUp(i.Interface) } -func (i Interface) IsLoopback() bool { return isLoopback(i.Interface) } -func (i Interface) IsUp() bool { return isUp(i.Interface) } func (i Interface) Addrs() ([]net.Addr, error) { if i.AltAddrs != nil { return i.AltAddrs, nil @@ -158,6 +172,14 @@ func (i Interface) Addrs() ([]net.Addr, error) { return i.Interface.Addrs() } +func (i Interface) isNoIPv6InternetConnectivity() bool { + return i.PlatFlags&PlatFlagNoIPv6InternetConnectivity != 0 +} + +func (i Interface) isNoIPv4InternetConnectivity() bool { + return i.PlatFlags&PlatFlagNoIPv6InternetConnectivity != 0 +} + // ForeachInterfaceAddress is a wrapper for GetList, then // List.ForeachInterfaceAddress. func ForeachInterfaceAddress(fn func(Interface, netip.Prefix)) error { @@ -492,8 +514,8 @@ func getState(optTSInterfaceName string) (*State, error) { if pfx.Addr().IsLoopback() { continue } - s.HaveV6 = s.HaveV6 || isUsableV6(pfx.Addr()) - s.HaveV4 = s.HaveV4 || isUsableV4(pfx.Addr()) + s.HaveV6 = s.HaveV6 || (isUsableV6(pfx.Addr()) && !ni.isNoIPv6InternetConnectivity()) + s.HaveV4 = s.HaveV4 || (isUsableV4(pfx.Addr()) && !ni.isNoIPv4InternetConnectivity()) } }); err != nil { return nil, err diff --git a/net/netmon/zsyscall_windows.go b/net/netmon/zsyscall_windows.go new file mode 100644 index 000000000..68d3230a9 --- /dev/null +++ b/net/netmon/zsyscall_windows.go @@ -0,0 +1,62 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package netmon + +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") + + procGetIfEntry2 = modiphlpapi.NewProc("GetIfEntry2") + procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex") +) + +func getIfEntry2(row *winipcfg.MibIfRow2) (ret error) { + r0, _, _ := syscall.Syscall(procGetIfEntry2.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + +func getIfEntry2Ex(level _MIB_IF_ENTRY_LEVEL, row *winipcfg.MibIfRow2) (ret error) { + r0, _, _ := syscall.Syscall(procGetIfEntry2Ex.Addr(), 2, uintptr(level), uintptr(unsafe.Pointer(row)), 0) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} |
