From 7e962a9932667f4a161b20aba5ff1c75ab8e578a Mon Sep 17 00:00:00 2001 From: Simon Rozman Date: Thu, 9 May 2019 10:11:15 +0200 Subject: wintun: wait for interface registry key on device creation By using RegNotifyChangeKeyValue(). Also disable dead gateway detection. Signed-off-by: Simon Rozman --- tun/tun_windows.go | 40 ++--- tun/wintun/registry/mksyscall.go | 8 + tun/wintun/registry/registry_windows.go | 240 +++++++++++++++++++++++++++ tun/wintun/registry/registry_windows_test.go | 103 ++++++++++++ tun/wintun/registry/zregistry_windows.go | 63 +++++++ tun/wintun/registryhacks_windows.go | 42 ----- tun/wintun/wintun_windows.go | 171 +++++++++++-------- 7 files changed, 534 insertions(+), 133 deletions(-) create mode 100644 tun/wintun/registry/mksyscall.go create mode 100644 tun/wintun/registry/registry_windows.go create mode 100644 tun/wintun/registry/registry_windows_test.go create mode 100644 tun/wintun/registry/zregistry_windows.go delete mode 100644 tun/wintun/registryhacks_windows.go diff --git a/tun/tun_windows.go b/tun/tun_windows.go index b319c27..8218fc3 100644 --- a/tun/tun_windows.go +++ b/tun/tun_windows.go @@ -62,35 +62,27 @@ func packetAlign(size uint32) uint32 { func CreateTUN(ifname string) (TUNDevice, error) { var err error var wt *wintun.Wintun - for i := 0; i < 3; i++ { - // Does an interface with this name already exist? - wt, err = wintun.GetInterface(ifname, 0) - if wt == nil { - // Interface does not exist or an error occured. Create one. - wt, _, err = wintun.CreateInterface("WireGuard Tunnel Adapter", 0) - if err != nil { - err = fmt.Errorf("wintun.CreateInterface: %v", err) - continue - } - } else if err != nil { - // Foreign interface with the same name found. - // We could create a Wintun interface under a temporary name. But, should our - // process die without deleting this interface first, the interface would remain - // orphaned. - err = fmt.Errorf("wintun.GetInterface: %v", err) - continue - } - err = wt.SetInterfaceName(ifname) //TODO: This is the function that most often fails + // Does an interface with this name already exist? + wt, err = wintun.GetInterface(ifname, 0) + if wt == nil { + // Interface does not exist or an error occurred. Create one. + wt, _, err = wintun.CreateInterface("WireGuard Tunnel Adapter", 0) if err != nil { - wt.DeleteInterface(0) - wt = nil - err = fmt.Errorf("wintun.SetInterfaceName: %v", err) - continue + return nil, fmt.Errorf("wintun.CreateInterface: %v", err) } + } else if err != nil { + // Foreign interface with the same name found. + // We could create a Wintun interface under a temporary name. But, should our + // process die without deleting this interface first, the interface would remain + // orphaned. + return nil, fmt.Errorf("wintun.GetInterface: %v", err) } + + err = wt.SetInterfaceName(ifname) if err != nil { - return nil, err + wt.DeleteInterface(0) + return nil, fmt.Errorf("wintun.SetInterfaceName: %v", err) } err = wt.FlushInterface() diff --git a/tun/wintun/registry/mksyscall.go b/tun/wintun/registry/mksyscall.go new file mode 100644 index 0000000..d0cac6c --- /dev/null +++ b/tun/wintun/registry/mksyscall.go @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package registry + +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zregistry_windows.go registry_windows.go diff --git a/tun/wintun/registry/registry_windows.go b/tun/wintun/registry/registry_windows.go new file mode 100644 index 0000000..65da6bf --- /dev/null +++ b/tun/wintun/registry/registry_windows.go @@ -0,0 +1,240 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package registry + +import ( + "errors" + "fmt" + "runtime" + "strings" + "time" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +const ( + KEY_NOTIFY uint32 = 0x0010 // should be defined upstream as registry.KEY_NOTIFY +) + +const ( + // REG_NOTIFY_CHANGE_NAME notifies the caller if a subkey is added or deleted. + REG_NOTIFY_CHANGE_NAME uint32 = 0x00000001 + + // REG_NOTIFY_CHANGE_ATTRIBUTES notifies the caller of changes to the attributes of the key, such as the security descriptor information. + REG_NOTIFY_CHANGE_ATTRIBUTES uint32 = 0x00000002 + + // REG_NOTIFY_CHANGE_LAST_SET notifies the caller of changes to a value of the key. This can include adding or deleting a value, or changing an existing value. + REG_NOTIFY_CHANGE_LAST_SET uint32 = 0x00000004 + + // REG_NOTIFY_CHANGE_SECURITY notifies the caller of changes to the security descriptor of the key. + REG_NOTIFY_CHANGE_SECURITY uint32 = 0x00000008 + + // REG_NOTIFY_THREAD_AGNOSTIC indicates that the lifetime of the registration must not be tied to the lifetime of the thread issuing the RegNotifyChangeKeyValue call. Note: This flag value is only supported in Windows 8 and later. + REG_NOTIFY_THREAD_AGNOSTIC uint32 = 0x10000000 +) + +//sys regNotifyChangeKeyValue(key windows.Handle, watchSubtree bool, notifyFilter uint32, event windows.Handle, asynchronous bool) (regerrno error) = advapi32.RegNotifyChangeKeyValue + +func OpenKeyWait(k registry.Key, path string, access uint32, timeout time.Duration) (registry.Key, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + deadline := time.Now().Add(timeout) + pathSpl := strings.Split(path, "\\") + for i := 0; ; i++ { + keyName := pathSpl[i] + isLast := i+1 == len(pathSpl) + + event, err := windows.CreateEvent(nil, 0, 0, nil) + if err != nil { + return 0, fmt.Errorf("Error creating event: %v", err) + } + defer windows.CloseHandle(event) + + var key registry.Key + for { + err = regNotifyChangeKeyValue(windows.Handle(k), false, REG_NOTIFY_CHANGE_NAME, windows.Handle(event), true) + if err != nil { + return 0, fmt.Errorf("Setting up change notification on registry key failed: %v", err) + } + + var accessFlags uint32 + if isLast { + accessFlags = access + } else { + accessFlags = KEY_NOTIFY + } + key, err = registry.OpenKey(k, keyName, accessFlags) + if err == windows.ERROR_FILE_NOT_FOUND || err == windows.ERROR_PATH_NOT_FOUND { + timeout := time.Until(deadline) / time.Millisecond + if timeout < 0 { + timeout = 0 + } + s, err := windows.WaitForSingleObject(event, uint32(timeout)) + if err != nil { + return 0, fmt.Errorf("Unable to wait on registry key: %v", err) + } + if s == uint32(windows.WAIT_TIMEOUT) { // windows.WAIT_TIMEOUT status const is misclassified as error in golang.org/x/sys/windows + return 0, errors.New("Timeout waiting for registry key") + } + } else if err != nil { + return 0, fmt.Errorf("Error opening registry key %v: %v", path, err) + } else { + if isLast { + return key, nil + } + defer key.Close() + break + } + } + + k = key + } +} + +func WaitForKey(k registry.Key, path string, timeout time.Duration) error { + key, err := OpenKeyWait(k, path, KEY_NOTIFY, timeout) + if err != nil { + return err + } + key.Close() + return nil +} + +// +// getStringValueRetry function reads a string value from registry. It waits for +// the registry value to become available or returns error on timeout. +// +// Key must be opened with at least QUERY_VALUE|KEY_NOTIFY access. +// +func getStringValueRetry(key registry.Key, name string, timeout time.Duration) (string, uint32, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + event, err := windows.CreateEvent(nil, 0, 0, nil) + if err != nil { + return "", 0, fmt.Errorf("Error creating event: %v", err) + } + defer windows.CloseHandle(event) + + deadline := time.Now().Add(timeout) + for { + err := regNotifyChangeKeyValue(windows.Handle(key), false, REG_NOTIFY_CHANGE_LAST_SET, windows.Handle(event), true) + if err != nil { + return "", 0, fmt.Errorf("Setting up change notification on registry value failed: %v", err) + } + + value, valueType, err := key.GetStringValue(name) + if err == windows.ERROR_FILE_NOT_FOUND || err == windows.ERROR_PATH_NOT_FOUND { + timeout := time.Until(deadline) / time.Millisecond + if timeout < 0 { + timeout = 0 + } + s, err := windows.WaitForSingleObject(event, uint32(timeout)) + if err != nil { + return "", 0, fmt.Errorf("Unable to wait on registry value: %v", err) + } + if s == uint32(windows.WAIT_TIMEOUT) { // windows.WAIT_TIMEOUT status const is misclassified as error in golang.org/x/sys/windows + return "", 0, errors.New("Timeout waiting for registry value") + } + } else if err != nil { + return "", 0, fmt.Errorf("Error reading registry value %v: %v", name, err) + } else { + return value, valueType, nil + } + } +} + +func expandString(value string, valueType uint32, err error) (string, error) { + if err != nil { + return "", err + } + + if valueType != registry.EXPAND_SZ { + // Value does not require expansion. + return value, nil + } + + valueExp, err := registry.ExpandString(value) + if err != nil { + // Expanding failed: return original sting value. + return value, nil + } + + // Return expanded value. + return valueExp, nil +} + +// +// GetStringValueWait function reads a string value from registry. It waits +// for the registry value to become available or returns error on timeout. +// +// Key must be opened with at least QUERY_VALUE|KEY_NOTIFY access. +// +// If the value type is REG_EXPAND_SZ the environment variables are expanded. +// Should expanding fail, original string value and nil error are returned. +// +func GetStringValueWait(key registry.Key, name string, timeout time.Duration) (string, error) { + return expandString(getStringValueRetry(key, name, timeout)) +} + +// +// GetStringValue function reads a string value from registry. +// +// Key must be opened with at least QUERY_VALUE access. +// +// If the value type is REG_EXPAND_SZ the environment variables are expanded. +// Should expanding fail, original string value and nil error are returned. +// +func GetStringValue(key registry.Key, name string) (string, error) { + return expandString(key.GetStringValue(name)) +} + +// +// GetIntegerValueWait function reads a DWORD32 or QWORD value from registry. +// It waits for the registry value to become available or returns error on +// timeout. +// +// Key must be opened with at least QUERY_VALUE|KEY_NOTIFY access. +// +func GetIntegerValueWait(key registry.Key, name string, timeout time.Duration) (uint64, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + event, err := windows.CreateEvent(nil, 0, 0, nil) + if err != nil { + return 0, fmt.Errorf("Error creating event: %v", err) + } + defer windows.CloseHandle(event) + + deadline := time.Now().Add(timeout) + for { + err := regNotifyChangeKeyValue(windows.Handle(key), false, REG_NOTIFY_CHANGE_LAST_SET, windows.Handle(event), true) + if err != nil { + return 0, fmt.Errorf("Setting up change notification on registry value failed: %v", err) + } + + value, _, err := key.GetIntegerValue(name) + if err == windows.ERROR_FILE_NOT_FOUND || err == windows.ERROR_PATH_NOT_FOUND { + timeout := time.Until(deadline) / time.Millisecond + if timeout < 0 { + timeout = 0 + } + s, err := windows.WaitForSingleObject(event, uint32(timeout)) + if err != nil { + return 0, fmt.Errorf("Unable to wait on registry value: %v", err) + } + if s == uint32(windows.WAIT_TIMEOUT) { // windows.WAIT_TIMEOUT status const is misclassified as error in golang.org/x/sys/windows + return 0, errors.New("Timeout waiting for registry value") + } + } else if err != nil { + return 0, fmt.Errorf("Error reading registry value %v: %v", name, err) + } else { + return value, nil + } + } +} diff --git a/tun/wintun/registry/registry_windows_test.go b/tun/wintun/registry/registry_windows_test.go new file mode 100644 index 0000000..c5a6e28 --- /dev/null +++ b/tun/wintun/registry/registry_windows_test.go @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package registry + +import ( + "testing" + "time" + + "golang.org/x/sys/windows/registry" +) + +const keyRoot = registry.CURRENT_USER +const pathRoot = "Software\\WireGuardRegistryTest" +const path = pathRoot + "\\foobar" +const pathFake = pathRoot + "\\raboof" + +func Test_WaitForKey(t *testing.T) { + registry.DeleteKey(keyRoot, path) + registry.DeleteKey(keyRoot, pathRoot) + go func() { + time.Sleep(time.Second * 1) + key, _, err := registry.CreateKey(keyRoot, pathFake, registry.QUERY_VALUE) + if err != nil { + t.Errorf("Error creating registry key: %v", err) + } + key.Close() + registry.DeleteKey(keyRoot, pathFake) + + key, _, err = registry.CreateKey(keyRoot, path, registry.QUERY_VALUE) + if err != nil { + t.Errorf("Error creating registry key: %v", err) + } + key.Close() + }() + err := WaitForKey(keyRoot, path, time.Second*2) + if err != nil { + t.Errorf("Error waiting for registry key: %v", err) + } + registry.DeleteKey(keyRoot, path) + registry.DeleteKey(keyRoot, pathRoot) + + err = WaitForKey(keyRoot, path, time.Second*1) + if err == nil { + t.Error("Registry key notification expected to timeout but it succeeded.") + } +} + +func Test_GetValueWait(t *testing.T) { + registry.DeleteKey(keyRoot, path) + registry.DeleteKey(keyRoot, pathRoot) + go func() { + time.Sleep(time.Second * 1) + key, _, err := registry.CreateKey(keyRoot, path, registry.SET_VALUE) + if err != nil { + t.Errorf("Error creating registry key: %v", err) + } + time.Sleep(time.Second * 1) + key.SetStringValue("name1", "eulav") + key.SetExpandStringValue("name2", "value") + time.Sleep(time.Second * 1) + key.SetDWordValue("name3", ^uint32(123)) + key.SetDWordValue("name4", 123) + key.Close() + }() + + key, err := OpenKeyWait(keyRoot, path, registry.QUERY_VALUE|KEY_NOTIFY, time.Second*2) + if err != nil { + t.Errorf("Error waiting for registry key: %v", err) + } + + valueStr, err := GetStringValueWait(key, "name2", time.Second*2) + if err != nil { + t.Errorf("Error waiting for registry value: %v", err) + } + if valueStr != "value" { + t.Errorf("Wrong value read: %v", valueStr) + } + + _, err = GetStringValueWait(key, "nonexisting", time.Second*1) + if err == nil { + t.Error("Registry value notification expected to timeout but it succeeded.") + } + + valueInt, err := GetIntegerValueWait(key, "name4", time.Second*2) + if err != nil { + t.Errorf("Error waiting for registry value: %v", err) + } + if valueInt != 123 { + t.Errorf("Wrong value read: %v", valueInt) + } + + _, err = GetIntegerValueWait(key, "nonexisting", time.Second*1) + if err == nil { + t.Error("Registry value notification expected to timeout but it succeeded.") + } + + key.Close() + registry.DeleteKey(keyRoot, path) + registry.DeleteKey(keyRoot, pathRoot) +} diff --git a/tun/wintun/registry/zregistry_windows.go b/tun/wintun/registry/zregistry_windows.go new file mode 100644 index 0000000..f7ac33b --- /dev/null +++ b/tun/wintun/registry/zregistry_windows.go @@ -0,0 +1,63 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package registry + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +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) +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return nil + 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 ( + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") + + procRegNotifyChangeKeyValue = modadvapi32.NewProc("RegNotifyChangeKeyValue") +) + +func regNotifyChangeKeyValue(key windows.Handle, watchSubtree bool, notifyFilter uint32, event windows.Handle, asynchronous bool) (regerrno error) { + var _p0 uint32 + if watchSubtree { + _p0 = 1 + } else { + _p0 = 0 + } + var _p1 uint32 + if asynchronous { + _p1 = 1 + } else { + _p1 = 0 + } + r0, _, _ := syscall.Syscall6(procRegNotifyChangeKeyValue.Addr(), 5, uintptr(key), uintptr(_p0), uintptr(notifyFilter), uintptr(event), uintptr(_p1), 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} diff --git a/tun/wintun/registryhacks_windows.go b/tun/wintun/registryhacks_windows.go deleted file mode 100644 index bf72f92..0000000 --- a/tun/wintun/registryhacks_windows.go +++ /dev/null @@ -1,42 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. - */ - -package wintun - -import ( - "golang.org/x/sys/windows/registry" - "time" -) - -const ( - numRetries = 50 - retryTimeout = 100 * time.Millisecond -) - -func registryOpenKeyRetry(k registry.Key, path string, access uint32) (key registry.Key, err error) { - for i := 0; i < numRetries; i++ { - key, err = registry.OpenKey(k, path, access) - if err == nil { - break - } - if i != numRetries-1 { - time.Sleep(retryTimeout) - } - } - return -} - -func keyGetStringValueRetry(k registry.Key, name string) (val string, valtype uint32, err error) { - for i := 0; i < numRetries; i++ { - val, valtype, err = k.GetStringValue(name) - if err == nil { - break - } - if i != numRetries-1 { - time.Sleep(retryTimeout) - } - } - return -} diff --git a/tun/wintun/wintun_windows.go b/tun/wintun/wintun_windows.go index 69f6eb6..fb2cc22 100644 --- a/tun/wintun/wintun_windows.go +++ b/tun/wintun/wintun_windows.go @@ -8,7 +8,6 @@ package wintun import ( "errors" "fmt" - "golang.zx2c4.com/wireguard/tun/wintun/netshell" "strings" "syscall" "time" @@ -17,6 +16,8 @@ import ( "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" "golang.zx2c4.com/wireguard/tun/wintun/guid" + "golang.zx2c4.com/wireguard/tun/wintun/netshell" + registryEx "golang.zx2c4.com/wireguard/tun/wintun/registry" "golang.zx2c4.com/wireguard/tun/wintun/setupapi" ) @@ -34,33 +35,24 @@ var deviceClassNetGUID = windows.GUID{Data1: 0x4d36e972, Data2: 0xe325, Data3: 0 const hardwareID = "Wintun" const enumerator = "" const machineName = "" +const waitForRegistryTimeout = time.Second * 5 // // MakeWintun creates interface handle and populates it from device registry key // -func makeWintun(deviceInfoSet setupapi.DevInfo, deviceInfoData *setupapi.DevInfoData, wait bool) (*Wintun, error) { +func makeWintun(deviceInfoSet setupapi.DevInfo, deviceInfoData *setupapi.DevInfoData) (*Wintun, error) { // Open HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\\ registry key. - key, err := deviceInfoSet.OpenDevRegKey(deviceInfoData, setupapi.DICS_FLAG_GLOBAL, 0, setupapi.DIREG_DRV, registry.READ) + key, err := deviceInfoSet.OpenDevRegKey(deviceInfoData, setupapi.DICS_FLAG_GLOBAL, 0, setupapi.DIREG_DRV, registry.QUERY_VALUE) if err != nil { return nil, errors.New("Device-specific registry key open failed: " + err.Error()) } defer key.Close() - var valueStr string - var valueType uint32 - // Read the NetCfgInstanceId value. - if wait { - valueStr, valueType, err = keyGetStringValueRetry(key, "NetCfgInstanceId") - } else { - valueStr, valueType, err = key.GetStringValue("NetCfgInstanceId") - } + valueStr, err := registryEx.GetStringValue(key, "NetCfgInstanceId") if err != nil { return nil, errors.New("RegQueryStringValue(\"NetCfgInstanceId\") failed: " + err.Error()) } - if valueType != registry.SZ { - return nil, fmt.Errorf("NetCfgInstanceId registry value is not REG_SZ (expected: %v, provided: %v)", registry.SZ, valueType) - } // Convert to windows.GUID. ifid, err := guid.FromString(valueStr) @@ -69,13 +61,13 @@ func makeWintun(deviceInfoSet setupapi.DevInfo, deviceInfoData *setupapi.DevInfo } // Read the NetLuidIndex value. - luidIdx, valueType, err := key.GetIntegerValue("NetLuidIndex") + luidIdx, _, err := key.GetIntegerValue("NetLuidIndex") if err != nil { return nil, errors.New("RegQueryValue(\"NetLuidIndex\") failed: " + err.Error()) } // Read the NetLuidIndex value. - ifType, valueType, err := key.GetIntegerValue("*IfType") + ifType, _, err := key.GetIntegerValue("*IfType") if err != nil { return nil, errors.New("RegQueryValue(\"*IfType\") failed: " + err.Error()) } @@ -125,14 +117,14 @@ func GetInterface(ifname string, hwndParent uintptr) (*Wintun, error) { } // Get interface ID. - wintun, err := makeWintun(devInfoList, deviceData, false) + wintun, err := makeWintun(devInfoList, deviceData) if err != nil { continue } //TODO: is there a better way than comparing ifnames? // Get interface name. - ifname2, err := wintun.getInterfaceNameNoRetry() + ifname2, err := wintun.GetInterfaceName() if err != nil { continue } @@ -298,24 +290,85 @@ func CreateInterface(description string, hwndParent uintptr) (*Wintun, bool, err rebootRequired = true } - // Get network interface. DIF_INSTALLDEVICE returns almost immediately, while the device - // installation continues in the background. It might take a while, before all registry + // DIF_INSTALLDEVICE returns almost immediately, while the device installation + // continues in the background. It might take a while, before all registry // keys and values are populated. - for numAttempts := 0; numAttempts < 30; numAttempts++ { - wintun, err = makeWintun(devInfoList, deviceData, true) - if err != nil { - if errWin, ok := err.(syscall.Errno); ok && errWin == windows.ERROR_FILE_NOT_FOUND { - // Wait and retry. TODO: Wait for a cancellable event instead. - err = errors.New("Time-out waiting for adapter to get ready") - time.Sleep(time.Second / 4) - continue - } + + // Wait for device registry key to emerge and populate. + key, err := registryEx.OpenKeyWait( + registry.LOCAL_MACHINE, + fmt.Sprintf("SYSTEM\\CurrentControlSet\\Control\\Class\\%v\\%04d", guid.ToString(&deviceClassNetGUID), deviceData.DevInst), + registry.QUERY_VALUE|registryEx.KEY_NOTIFY, + waitForRegistryTimeout) + if err == nil { + _, err = registryEx.GetStringValueWait(key, "NetCfgInstanceId", waitForRegistryTimeout) + if err == nil { + _, err = registryEx.GetIntegerValueWait(key, "NetLuidIndex", waitForRegistryTimeout) } + if err == nil { + _, err = registryEx.GetIntegerValueWait(key, "*IfType", waitForRegistryTimeout) + } + key.Close() + } + // Clear error and let makeWintun() open the key using SetupAPI's devInfoList.OpenDevRegKey(). + err = nil + } - break + if err == nil { + // Get network interface. + wintun, err = makeWintun(devInfoList, deviceData) + } + + if err == nil { + // Wait for network registry key to emerge and populate. + key, err := registryEx.OpenKeyWait( + registry.LOCAL_MACHINE, + wintun.GetNetRegKeyName(), + registry.QUERY_VALUE|registryEx.KEY_NOTIFY, + waitForRegistryTimeout) + if err == nil { + _, err = registryEx.GetStringValueWait(key, "Name", waitForRegistryTimeout) + key.Close() } } + if err == nil { + // Wait for TCP/IP adapter registry key to emerge and populate. + key, err := registryEx.OpenKeyWait( + registry.LOCAL_MACHINE, + wintun.GetTcpipAdapterRegKeyName(), registry.QUERY_VALUE|registryEx.KEY_NOTIFY, + waitForRegistryTimeout) + if err == nil { + _, err = registryEx.GetStringValueWait(key, "IpConfig", waitForRegistryTimeout) + key.Close() + } + } + + if err == nil { + // Wait for TCP/IP interface registry key to emerge. + key, err := registryEx.OpenKeyWait( + registry.LOCAL_MACHINE, + wintun.GetTcpipInterfaceRegKeyName(), registry.QUERY_VALUE, + waitForRegistryTimeout) + if err == nil { + key.Close() + } + } + + // + // All the registry keys and values we're relying on are present now. + // + + if err == nil { + // Disable dead gateway detection on our interface. + key, err := registry.OpenKey(registry.LOCAL_MACHINE, wintun.GetTcpipInterfaceRegKeyName(), registry.SET_VALUE) + if err != nil { + err = errors.New("Error opening interface-specific TCP/IP network registry key: " + err.Error()) + } + key.SetDWordValue("EnableDeadGWDetect", 0) + key.Close() + } + if err == nil { return wintun, rebootRequired, nil } @@ -373,7 +426,7 @@ func (wintun *Wintun) DeleteInterface(hwndParent uintptr) (bool, bool, error) { // Get interface ID. //TODO: Store some ID in the Wintun object such that this call isn't required. - wintun2, err := makeWintun(devInfoList, deviceData, false) + wintun2, err := makeWintun(devInfoList, deviceData) if err != nil { continue } @@ -438,17 +491,6 @@ func checkReboot(deviceInfoSet setupapi.DevInfo, deviceInfoData *setupapi.DevInf // GetInterfaceName returns network interface name. // func (wintun *Wintun) GetInterfaceName() (string, error) { - key, err := registryOpenKeyRetry(registry.LOCAL_MACHINE, wintun.GetNetRegKeyName(), registry.QUERY_VALUE) - if err != nil { - return "", errors.New("Network-specific registry key open failed: " + err.Error()) - } - defer key.Close() - - // Get the interface name. - return getRegStringValue(key, "Name") -} - -func (wintun *Wintun) getInterfaceNameNoRetry() (string, error) { key, err := registry.OpenKey(registry.LOCAL_MACHINE, wintun.GetNetRegKeyName(), registry.QUERY_VALUE) if err != nil { return "", errors.New("Network-specific registry key open failed: " + err.Error()) @@ -456,20 +498,13 @@ func (wintun *Wintun) getInterfaceNameNoRetry() (string, error) { defer key.Close() // Get the interface name. - return getRegStringValue(key, "Name") + return registryEx.GetStringValue(key, "Name") } // // SetInterfaceName sets network interface name. // func (wintun *Wintun) SetInterfaceName(ifname string) error { - // We open the registry key before calling HrRename, because the registry open will wait until the key exists. - key, err := registryOpenKeyRetry(registry.LOCAL_MACHINE, wintun.GetNetRegKeyName(), registry.SET_VALUE) - if err != nil { - return errors.New("Network-specific registry key open failed: " + err.Error()) - } - defer key.Close() - // We have to tell the various runtime COM services about the new name too. We ignore the // error because netshell isn't available on servercore. // TODO: netsh.exe falls back to NciSetConnection in this case. If somebody complains, maybe @@ -477,6 +512,11 @@ func (wintun *Wintun) SetInterfaceName(ifname string) error { _ = netshell.HrRenameConnection(&wintun.CfgInstanceID, windows.StringToUTF16Ptr(ifname)) // Set the interface name. The above line should have done this too, but in case it failed, we force it. + key, err := registry.OpenKey(registry.LOCAL_MACHINE, wintun.GetNetRegKeyName(), registry.SET_VALUE) + if err != nil { + return errors.New("Network-specific registry key open failed: " + err.Error()) + } + defer key.Close() return key.SetStringValue("Name", ifname) } @@ -488,31 +528,28 @@ func (wintun *Wintun) GetNetRegKeyName() string { } // -// getRegStringValue function reads a string value from registry. +// GetTcpipAdapterRegKeyName returns adapter-specific TCP/IP network registry key name. // -// If the value type is REG_EXPAND_SZ the environment variables are expanded. -// Should expanding fail, original string value and nil error are returned. +func (wintun *Wintun) GetTcpipAdapterRegKeyName() string { + return fmt.Sprintf("SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Adapters\\%v", guid.ToString(&wintun.CfgInstanceID)) +} + +// +// GetTcpipInterfaceRegKeyName returns interface-specific TCP/IP network registry key name. // -func getRegStringValue(key registry.Key, name string) (string, error) { - // Read string value. - value, valueType, err := keyGetStringValueRetry(key, name) +func (wintun *Wintun) GetTcpipInterfaceRegKeyName() string { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, wintun.GetTcpipAdapterRegKeyName(), registry.QUERY_VALUE) if err != nil { - return "", err - } - - if valueType != registry.EXPAND_SZ { - // Value does not require expansion. - return value, nil + err = errors.New("Error opening adapter-specific TCP/IP network registry key: " + err.Error()) } + defer key.Close() - valueExp, err := registry.ExpandString(value) + path, err := registryEx.GetStringValue(key, "IpConfig") if err != nil { - // Expanding failed: return original sting value. - return value, nil + err = errors.New("Error reading IpConfig: " + err.Error()) } - // Return expanded value. - return valueExp, nil + return "SYSTEM\\CurrentControlSet\\Services\\" + path } // -- cgit v1.2.3-59-g8ed1b