summaryrefslogtreecommitdiffhomepage
path: root/net
diff options
context:
space:
mode:
Diffstat (limited to 'net')
-rw-r--r--net/netmon/interfaces_windows.go109
-rw-r--r--net/netmon/mksyscall.go17
-rw-r--r--net/netmon/state.go34
-rw-r--r--net/netmon/zsyscall_windows.go62
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
+}