| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- //go:build !e2e_testing
- package overlay
- import (
- "errors"
- "fmt"
- "log/slog"
- "runtime"
- "strings"
- "syscall"
- "time"
- "unsafe"
- "golang.org/x/sys/windows"
- )
- // networkCategory mirrors NLM_NETWORK_CATEGORY from netlistmgr.h.
- type networkCategory int32
- const (
- networkCategoryPublic networkCategory = 0
- networkCategoryPrivate networkCategory = 1
- networkCategoryDomainAuthenticated networkCategory = 2
- )
- func (c networkCategory) String() string {
- switch c {
- case networkCategoryPublic:
- return "public"
- case networkCategoryPrivate:
- return "private"
- case networkCategoryDomainAuthenticated:
- return "domain"
- }
- return fmt.Sprintf("unknown(%d)", c)
- }
- // parseNetworkCategory accepts the user-supplied tun.network_category. A
- // second return of false means "leave the category alone".
- func parseNetworkCategory(s string) (networkCategory, bool, error) {
- switch strings.ToLower(strings.TrimSpace(s)) {
- case "", "unset":
- return 0, false, nil
- case "public":
- return networkCategoryPublic, true, nil
- case "private":
- return networkCategoryPrivate, true, nil
- case "domain", "domainauthenticated":
- return networkCategoryDomainAuthenticated, true, nil
- }
- return 0, false, fmt.Errorf("unknown tun.network_category %q (expected public, private, domain, or unset)", s)
- }
- // CLSID_NetworkListManager {DCB00C01-570F-4A9B-8D69-199FDBA5723B}
- var clsidNetworkListManager = windows.GUID{
- Data1: 0xDCB00C01, Data2: 0x570F, Data3: 0x4A9B,
- Data4: [8]byte{0x8D, 0x69, 0x19, 0x9F, 0xDB, 0xA5, 0x72, 0x3B},
- }
- // IID_INetworkListManager {DCB00000-570F-4A9B-8D69-199FDBA5723B}
- var iidINetworkListManager = windows.GUID{
- Data1: 0xDCB00000, Data2: 0x570F, Data3: 0x4A9B,
- Data4: [8]byte{0x8D, 0x69, 0x19, 0x9F, 0xDB, 0xA5, 0x72, 0x3B},
- }
- // x/sys/windows doesn't expose CoCreateInstance, so we bind it ourselves.
- var procCoCreateInstance = windows.NewLazySystemDLL("ole32.dll").NewProc("CoCreateInstance")
- const clsCtxAll = windows.CLSCTX_INPROC_SERVER | windows.CLSCTX_INPROC_HANDLER |
- windows.CLSCTX_LOCAL_SERVER | windows.CLSCTX_REMOTE_SERVER
- const (
- hrSFALSE = 0x00000001
- hrRPCEChangedMode = 0x80010106
- )
- type hresult uint32
- func (h hresult) failed() bool { return int32(h) < 0 }
- func (h hresult) String() string {
- return fmt.Sprintf("HRESULT 0x%08x", uint32(h))
- }
- var errAdapterNotFound = errors.New("adapter not present in network connections enumeration")
- // Vtable layouts. Slot order must match the declaration order in netlistmgr.h.
- // All NLM interfaces here derive from IDispatch, which derives from IUnknown.
- type iUnknownVtbl struct {
- QueryInterface uintptr
- AddRef uintptr
- Release uintptr
- }
- type iDispatchVtbl struct {
- iUnknownVtbl
- GetTypeInfoCount uintptr
- GetTypeInfo uintptr
- GetIDsOfNames uintptr
- Invoke uintptr
- }
- type iNetworkListManagerVtbl struct {
- iDispatchVtbl
- GetNetworks uintptr
- GetNetwork uintptr
- GetNetworkConnections uintptr
- GetNetworkConnection uintptr
- IsConnectedToInternet uintptr
- IsConnected uintptr
- GetConnectivity uintptr
- }
- type iNetworkListManager struct{ Vtbl *iNetworkListManagerVtbl }
- func (n *iNetworkListManager) Release() {
- syscall.SyscallN(n.Vtbl.Release, uintptr(unsafe.Pointer(n)))
- }
- func (n *iNetworkListManager) GetNetworkConnections() (*iEnumNetworkConnections, error) {
- var enum *iEnumNetworkConnections
- r1, _, _ := syscall.SyscallN(n.Vtbl.GetNetworkConnections,
- uintptr(unsafe.Pointer(n)), uintptr(unsafe.Pointer(&enum)),
- )
- if hr := hresult(r1); hr.failed() {
- return nil, fmt.Errorf("INetworkListManager.GetNetworkConnections: %s", hr)
- }
- return enum, nil
- }
- type iEnumNetworkConnectionsVtbl struct {
- iDispatchVtbl
- NewEnum uintptr
- Next uintptr
- Skip uintptr
- Reset uintptr
- Clone uintptr
- }
- type iEnumNetworkConnections struct{ Vtbl *iEnumNetworkConnectionsVtbl }
- func (e *iEnumNetworkConnections) Release() {
- syscall.SyscallN(e.Vtbl.Release, uintptr(unsafe.Pointer(e)))
- }
- // Next returns the next connection, or (nil, nil) at the end of the enumeration.
- func (e *iEnumNetworkConnections) Next() (*iNetworkConnection, error) {
- var conn *iNetworkConnection
- var fetched uint32
- r1, _, _ := syscall.SyscallN(e.Vtbl.Next,
- uintptr(unsafe.Pointer(e)), 1,
- uintptr(unsafe.Pointer(&conn)), uintptr(unsafe.Pointer(&fetched)),
- )
- if hr := hresult(r1); hr.failed() {
- return nil, fmt.Errorf("IEnumNetworkConnections.Next: %s", hr)
- }
- if fetched == 0 {
- return nil, nil
- }
- return conn, nil
- }
- type iNetworkConnectionVtbl struct {
- iDispatchVtbl
- GetNetwork uintptr
- IsConnectedToInternet uintptr
- IsConnected uintptr
- GetConnectivity uintptr
- GetConnectionId uintptr
- GetAdapterId uintptr
- GetDomainType uintptr
- }
- type iNetworkConnection struct{ Vtbl *iNetworkConnectionVtbl }
- func (c *iNetworkConnection) Release() {
- syscall.SyscallN(c.Vtbl.Release, uintptr(unsafe.Pointer(c)))
- }
- func (c *iNetworkConnection) GetAdapterId() (windows.GUID, error) {
- var g windows.GUID
- r1, _, _ := syscall.SyscallN(c.Vtbl.GetAdapterId,
- uintptr(unsafe.Pointer(c)), uintptr(unsafe.Pointer(&g)),
- )
- if hr := hresult(r1); hr.failed() {
- return windows.GUID{}, fmt.Errorf("INetworkConnection.GetAdapterId: %s", hr)
- }
- return g, nil
- }
- func (c *iNetworkConnection) GetNetwork() (*iNetwork, error) {
- var net *iNetwork
- r1, _, _ := syscall.SyscallN(c.Vtbl.GetNetwork,
- uintptr(unsafe.Pointer(c)), uintptr(unsafe.Pointer(&net)),
- )
- if hr := hresult(r1); hr.failed() {
- return nil, fmt.Errorf("INetworkConnection.GetNetwork: %s", hr)
- }
- return net, nil
- }
- type iNetworkVtbl struct {
- iDispatchVtbl
- GetName uintptr
- SetName uintptr
- GetDescription uintptr
- SetDescription uintptr
- GetNetworkId uintptr
- GetDomainType uintptr
- GetNetworkConnections uintptr
- GetTimeCreatedAndConnected uintptr
- IsConnectedToInternet uintptr
- IsConnected uintptr
- GetConnectivity uintptr
- GetCategory uintptr
- SetCategory uintptr
- }
- type iNetwork struct{ Vtbl *iNetworkVtbl }
- func (n *iNetwork) Release() {
- syscall.SyscallN(n.Vtbl.Release, uintptr(unsafe.Pointer(n)))
- }
- func (n *iNetwork) GetCategory() (networkCategory, error) {
- var c networkCategory
- r1, _, _ := syscall.SyscallN(n.Vtbl.GetCategory,
- uintptr(unsafe.Pointer(n)), uintptr(unsafe.Pointer(&c)),
- )
- if hr := hresult(r1); hr.failed() {
- return 0, fmt.Errorf("INetwork.GetCategory: %s", hr)
- }
- return c, nil
- }
- func (n *iNetwork) SetCategory(c networkCategory) error {
- r1, _, _ := syscall.SyscallN(n.Vtbl.SetCategory,
- uintptr(unsafe.Pointer(n)), uintptr(int32(c)),
- )
- if hr := hresult(r1); hr.failed() {
- return fmt.Errorf("INetwork.SetCategory: %s", hr)
- }
- return nil
- }
- // coInit initializes COM for the current OS thread. The returned function must
- // be deferred to balance a successful init. RPC_E_CHANGED_MODE means COM is
- // already initialized in a different mode on this thread, which is still fine
- // for our calls but we must not Uninitialize in that case.
- func coInit() (func(), error) {
- err := windows.CoInitializeEx(0, windows.COINIT_MULTITHREADED)
- if err == nil {
- return windows.CoUninitialize, nil
- }
- if e, ok := err.(syscall.Errno); ok {
- switch uint32(e) {
- case hrSFALSE:
- return windows.CoUninitialize, nil
- case hrRPCEChangedMode:
- return func() {}, nil
- }
- }
- return nil, fmt.Errorf("CoInitializeEx: %w", err)
- }
- func createNetworkListManager() (*iNetworkListManager, error) {
- var nlm *iNetworkListManager
- r1, _, _ := procCoCreateInstance.Call(
- uintptr(unsafe.Pointer(&clsidNetworkListManager)),
- 0,
- uintptr(clsCtxAll),
- uintptr(unsafe.Pointer(&iidINetworkListManager)),
- uintptr(unsafe.Pointer(&nlm)),
- )
- if hr := hresult(r1); hr.failed() {
- return nil, fmt.Errorf("CoCreateInstance(NetworkListManager): %s", hr)
- }
- return nlm, nil
- }
- // setNetworkCategory locates the network connection bound to adapterGUID and
- // sets the category of its parent network. Returns errAdapterNotFound if the
- // adapter is not yet visible in the NLM enumeration.
- func setNetworkCategory(adapterGUID windows.GUID, cat networkCategory) error {
- deinit, err := coInit()
- if err != nil {
- return err
- }
- defer deinit()
- nlm, err := createNetworkListManager()
- if err != nil {
- return err
- }
- defer nlm.Release()
- enum, err := nlm.GetNetworkConnections()
- if err != nil {
- return err
- }
- defer enum.Release()
- for {
- conn, err := enum.Next()
- if err != nil {
- return err
- }
- if conn == nil {
- return errAdapterNotFound
- }
- guid, err := conn.GetAdapterId()
- if err != nil || guid != adapterGUID {
- conn.Release()
- continue
- }
- net, err := conn.GetNetwork()
- conn.Release()
- if err != nil {
- return err
- }
- err = net.SetCategory(cat)
- net.Release()
- return err
- }
- }
- // applyNetworkCategory polls until the wintun adapter shows up in the NLM
- // enumeration, then sets the category. Intended to run in its own goroutine.
- func applyNetworkCategory(l *slog.Logger, adapterGUID windows.GUID, cat networkCategory) {
- // COM Init/Uninit must be paired on the same OS thread.
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- const (
- attempts = 30
- interval = 500 * time.Millisecond
- )
- for i := 0; i < attempts; i++ {
- err := setNetworkCategory(adapterGUID, cat)
- if err == nil {
- l.Info("Set Windows network category", "category", cat.String())
- return
- }
- if !errors.Is(err, errAdapterNotFound) {
- l.Warn("Failed to set Windows network category", "error", err, "category", cat.String())
- return
- }
- time.Sleep(interval)
- }
- l.Warn("Gave up waiting for adapter to appear in NLM enumeration; network category not set",
- "category", cat.String(),
- "waited", time.Duration(attempts)*interval,
- )
- }
|