summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-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
-rw-r--r--util/winutil/winnet/winnet_windows.go (renamed from wgengine/winnet/winnet.go)107
-rw-r--r--util/winutil/winnet/winnet_windows_386.go32
-rw-r--r--util/winutil/winnet/winnet_windows_not386.go30
-rw-r--r--wgengine/router/osrouter/ifconfig_windows.go10
-rw-r--r--wgengine/winnet/winnet_windows.go26
9 files changed, 371 insertions, 56 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
+}
diff --git a/wgengine/winnet/winnet.go b/util/winutil/winnet/winnet_windows.go
index e04e6f5c5..f59cc7a9b 100644
--- a/wgengine/winnet/winnet.go
+++ b/util/winutil/winnet/winnet_windows.go
@@ -1,27 +1,67 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
-//go:build windows
-
// Package winnet contains Windows-specific networking code.
package winnet
import (
"fmt"
+ "sync"
"syscall"
"unsafe"
"github.com/go-ole/go-ole"
"github.com/go-ole/go-ole/oleutil"
+ "golang.org/x/sys/windows"
)
-const CLSID_NetworkListManager = "{DCB00C01-570F-4A9B-8D69-199FDBA5723B}"
+type NLM_CONNECTIVITY int32
+
+const (
+ NLM_CONNECTIVITY_DISCONNECTED NLM_CONNECTIVITY = 0
+ NLM_CONNECTIVITY_IPV4_NOTRAFFIC NLM_CONNECTIVITY = 0x1
+ NLM_CONNECTIVITY_IPV6_NOTRAFFIC NLM_CONNECTIVITY = 0x2
+ NLM_CONNECTIVITY_IPV4_SUBNET NLM_CONNECTIVITY = 0x10
+ NLM_CONNECTIVITY_IPV4_LOCALNETWORK NLM_CONNECTIVITY = 0x20
+ NLM_CONNECTIVITY_IPV4_INTERNET NLM_CONNECTIVITY = 0x40
+ NLM_CONNECTIVITY_IPV6_SUBNET NLM_CONNECTIVITY = 0x100
+ NLM_CONNECTIVITY_IPV6_LOCALNETWORK NLM_CONNECTIVITY = 0x200
+ NLM_CONNECTIVITY_IPV6_INTERNET NLM_CONNECTIVITY = 0x400
+)
+var CLSID_NetworkListManager = ole.NewGUID("{DCB00C01-570F-4A9B-8D69-199FDBA5723B}")
+
+var IID_INetworkListManager = ole.NewGUID("{DCB00000-570F-4A9B-8D69-199FDBA5723B}")
var IID_INetwork = ole.NewGUID("{8A40A45D-055C-4B62-ABD7-6D613E2CEAEC}")
var IID_INetworkConnection = ole.NewGUID("{DCB00005-570F-4A9B-8D69-199FDBA5723B}")
type NetworkListManager struct {
- d *ole.Dispatch
+ i *INetworkListManager
+}
+
+func (m *NetworkListManager) GetNetwork(networkID windows.GUID) (*INetwork, error) {
+ return m.i.GetNetwork(networkID)
+}
+
+type INetworkListManager struct {
+ ole.IUnknown
+}
+
+func (i *INetworkListManager) VTable() *INetworkListManagerVtbl {
+ return (*INetworkListManagerVtbl)(unsafe.Pointer(i.RawVTable))
+}
+
+type INetworkListManagerVtbl struct {
+ ole.IDispatchVtbl
+ GetNetworks uintptr
+ GetNetwork uintptr
+ GetNetworkConnections uintptr
+ GetNetworkConnection uintptr
+ Get_IsConnectedToInternet uintptr
+ Get_IsConnected uintptr
+ GetConnectivity uintptr
+ SetSimulatedProfileInfo uintptr
+ ClearSimulatedProfileInfo uintptr
}
type INetworkConnection struct {
@@ -62,25 +102,29 @@ type INetworkVtbl struct {
SetCategory uintptr
}
-func NewNetworkListManager(c *ole.Connection) (*NetworkListManager, error) {
- err := c.Create(CLSID_NetworkListManager)
- if err != nil {
- return nil, err
- }
- defer c.Release()
-
- d, err := c.Dispatch()
+func newNetworkListManager() (*NetworkListManager, error) {
+ unk, err := ole.CreateInstance(CLSID_NetworkListManager, IID_INetworkListManager)
if err != nil {
return nil, err
}
+ nlm := (*INetworkListManager)(unsafe.Pointer(unk))
return &NetworkListManager{
- d: d,
+ i: nlm,
}, nil
}
-func (m *NetworkListManager) Release() {
- m.d.Release()
+var (
+ once sync.Once
+ nlm *NetworkListManager
+ nlmErr error
+)
+
+func GetNetworkListManager() (*NetworkListManager, error) {
+ once.Do(func() {
+ nlm, nlmErr = newNetworkListManager()
+ })
+ return nlm, nlmErr
}
func (cl ConnectionList) Release() {
@@ -103,7 +147,10 @@ func asIID(u ole.UnknownLike, iid *ole.GUID) (*ole.IDispatch, error) {
}
func (m *NetworkListManager) GetNetworkConnections() (ConnectionList, error) {
- ncraw, err := m.d.Call("GetNetworkConnections")
+ d := ole.Dispatch{
+ Object: (*ole.IDispatch)(unsafe.Pointer(m.i)),
+ }
+ ncraw, err := d.Call("GetNetworkConnections")
if err != nil {
return nil, err
}
@@ -168,6 +215,20 @@ func (n *INetwork) SetCategory(v int32) error {
return nil
}
+func (n *INetwork) GetConnectivity() (c NLM_CONNECTIVITY, _ error) {
+ r, _, _ := syscall.SyscallN(
+ n.VTable().GetConnectivity,
+ uintptr(unsafe.Pointer(n)),
+ uintptr(unsafe.Pointer(&c)),
+ )
+
+ if int32(r) < 0 {
+ return 0, ole.NewError(r)
+ }
+
+ return c, nil
+}
+
func (n *INetwork) VTable() *INetworkVtbl {
return (*INetworkVtbl)(unsafe.Pointer(n.RawVTable))
}
@@ -190,3 +251,17 @@ func (v *INetworkConnection) GetNetwork() (*INetwork, error) {
return result, nil
}
+
+func (v *INetworkConnection) GetAdapterId() (string, error) {
+ buf := ole.GUID{}
+ hr, _, _ := syscall.Syscall(
+ v.VTable().GetAdapterId,
+ 2,
+ uintptr(unsafe.Pointer(v)),
+ uintptr(unsafe.Pointer(&buf)),
+ 0)
+ if hr != 0 {
+ return "", fmt.Errorf("GetAdapterId failed: %08x", hr)
+ }
+ return buf.String(), nil
+}
diff --git a/util/winutil/winnet/winnet_windows_386.go b/util/winutil/winnet/winnet_windows_386.go
new file mode 100644
index 000000000..88e5583f2
--- /dev/null
+++ b/util/winutil/winnet/winnet_windows_386.go
@@ -0,0 +1,32 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package winnet
+
+import (
+ "syscall"
+ "unsafe"
+
+ ole "github.com/go-ole/go-ole"
+ "golang.org/x/sys/windows"
+)
+
+func (i *INetworkListManager) GetNetwork(networkID windows.GUID) (*INetwork, error) {
+ words := (*[4]uintptr)(unsafe.Pointer(&networkID))
+ var result *INetwork
+ r, _, _ := syscall.SyscallN(
+ i.VTable().GetNetwork,
+ uintptr(unsafe.Pointer(i)),
+ words[0],
+ words[1],
+ words[2],
+ words[3],
+ uintptr(unsafe.Pointer(&result)),
+ )
+
+ if int32(r) < 0 {
+ return nil, ole.NewError(r)
+ }
+
+ return result, nil
+}
diff --git a/util/winutil/winnet/winnet_windows_not386.go b/util/winutil/winnet/winnet_windows_not386.go
new file mode 100644
index 000000000..762eb72d9
--- /dev/null
+++ b/util/winutil/winnet/winnet_windows_not386.go
@@ -0,0 +1,30 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build windows && !386
+
+package winnet
+
+import (
+ "syscall"
+ "unsafe"
+
+ ole "github.com/go-ole/go-ole"
+ "golang.org/x/sys/windows"
+)
+
+func (i *INetworkListManager) GetNetwork(networkID windows.GUID) (*INetwork, error) {
+ var result *INetwork
+ r, _, _ := syscall.SyscallN(
+ i.VTable().GetNetwork,
+ uintptr(unsafe.Pointer(i)),
+ uintptr(unsafe.Pointer(&networkID)),
+ uintptr(unsafe.Pointer(&result)),
+ )
+
+ if int32(r) < 0 {
+ return nil, ole.NewError(r)
+ }
+
+ return result, nil
+}
diff --git a/wgengine/router/osrouter/ifconfig_windows.go b/wgengine/router/osrouter/ifconfig_windows.go
index cb87ad5f2..4c7586c52 100644
--- a/wgengine/router/osrouter/ifconfig_windows.go
+++ b/wgengine/router/osrouter/ifconfig_windows.go
@@ -18,10 +18,9 @@ import (
"tailscale.com/net/netmon"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tstun"
+ "tailscale.com/util/winutil/winnet"
"tailscale.com/wgengine/router"
- "tailscale.com/wgengine/winnet"
- ole "github.com/go-ole/go-ole"
"github.com/tailscale/wireguard-go/tun"
"go4.org/netipx"
"golang.org/x/sys/windows"
@@ -175,15 +174,10 @@ func setPrivateNetwork(ifcLUID winipcfg.LUID) (bool, error) {
return false, fmt.Errorf("ifcLUID.GUID: %v", err)
}
- // aaron: DO NOT call Initialize() or Uninitialize() on c!
- // We've already handled that process-wide.
- var c ole.Connection
-
- m, err := winnet.NewNetworkListManager(&c)
+ m, err := winnet.GetNetworkListManager()
if err != nil {
return false, fmt.Errorf("winnet.NewNetworkListManager: %v", err)
}
- defer m.Release()
cl, err := m.GetNetworkConnections()
if err != nil {
diff --git a/wgengine/winnet/winnet_windows.go b/wgengine/winnet/winnet_windows.go
deleted file mode 100644
index 283ce5ad1..000000000
--- a/wgengine/winnet/winnet_windows.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package winnet
-
-import (
- "fmt"
- "syscall"
- "unsafe"
-
- "github.com/go-ole/go-ole"
-)
-
-func (v *INetworkConnection) GetAdapterId() (string, error) {
- buf := ole.GUID{}
- hr, _, _ := syscall.Syscall(
- v.VTable().GetAdapterId,
- 2,
- uintptr(unsafe.Pointer(v)),
- uintptr(unsafe.Pointer(&buf)),
- 0)
- if hr != 0 {
- return "", fmt.Errorf("GetAdapterId failed: %08x", hr)
- }
- return buf.String(), nil
-}