Skip to content

Commit 545639e

Browse files
Maisem Alimaisem
authored andcommitted
util/winutil: consolidate interface specific registry keys
Code movement to allow reuse in a follow up PR. Updates tailscale#1659 Signed-off-by: Maisem Ali <maisem@tailscale.com>
1 parent 23f37b0 commit 545639e

File tree

4 files changed

+116
-109
lines changed

4 files changed

+116
-109
lines changed

net/dns/manager_windows.go

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,10 @@ import (
2020
"tailscale.com/envknob"
2121
"tailscale.com/types/logger"
2222
"tailscale.com/util/dnsname"
23+
"tailscale.com/util/winutil"
2324
)
2425

2526
const (
26-
ipv4RegBase = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters`
27-
ipv6RegBase = `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters`
28-
2927
versionKey = `SOFTWARE\Microsoft\Windows NT\CurrentVersion`
3028
)
3129

@@ -59,24 +57,15 @@ func NewOSConfigurator(logf logger.Logf, interfaceName string) (OSConfigurator,
5957
return ret, nil
6058
}
6159

62-
// keyOpenTimeout is how long we wait for a registry key to
63-
// appear. For some reason, registry keys tied to ephemeral interfaces
64-
// can take a long while to appear after interface creation, and we
65-
// can end up racing with that.
66-
const keyOpenTimeout = 20 * time.Second
67-
68-
func (m windowsManager) openKey(path string) (registry.Key, error) {
69-
key, err := openKeyWait(registry.LOCAL_MACHINE, path, registry.SET_VALUE, keyOpenTimeout)
60+
func (m windowsManager) openInterfaceKey(pfx winutil.RegistryPathPrefix) (registry.Key, error) {
61+
path := pfx.WithSuffix(m.guid)
62+
key, err := winutil.OpenKeyWait(registry.LOCAL_MACHINE, path, registry.SET_VALUE)
7063
if err != nil {
7164
return 0, fmt.Errorf("opening %s: %w", path, err)
7265
}
7366
return key, nil
7467
}
7568

76-
func (m windowsManager) ifPath(basePath string) string {
77-
return fmt.Sprintf(`%s\Interfaces\%s`, basePath, m.guid)
78-
}
79-
8069
func delValue(key registry.Key, name string) error {
8170
if err := key.DeleteValue(name); err != nil && err != registry.ErrNotExist {
8271
return err
@@ -134,7 +123,7 @@ func (m windowsManager) setPrimaryDNS(resolvers []netip.Addr, domains []dnsname.
134123
domStrs = append(domStrs, dom.WithoutTrailingDot())
135124
}
136125

137-
key4, err := m.openKey(m.ifPath(ipv4RegBase))
126+
key4, err := m.openInterfaceKey(winutil.IPv4TCPIPInterfacePrefix)
138127
if err != nil {
139128
return err
140129
}
@@ -156,7 +145,7 @@ func (m windowsManager) setPrimaryDNS(resolvers []netip.Addr, domains []dnsname.
156145
return err
157146
}
158147

159-
key6, err := m.openKey(m.ifPath(ipv6RegBase))
148+
key6, err := m.openInterfaceKey(winutil.IPv6TCPIPInterfacePrefix)
160149
if err != nil {
161150
return err
162151
}
@@ -308,25 +297,26 @@ func (m windowsManager) Close() error {
308297
// Windows DHCP client from sending dynamic DNS updates for our interface to
309298
// AD domain controllers.
310299
func (m windowsManager) disableDynamicUpdates() error {
311-
setRegValue := func(regBase string) error {
312-
key, err := m.openKey(m.ifPath(regBase))
313-
if err != nil {
314-
return err
315-
}
316-
defer key.Close()
317-
318-
return key.SetDWordValue("DisableDynamicUpdate", 1)
300+
if err := m.setSingleDWORD(winutil.IPv4TCPIPInterfacePrefix, "EnableDNSUpdate", 0); err != nil {
301+
return err
319302
}
320-
321-
for _, regBase := range []string{ipv4RegBase, ipv6RegBase} {
322-
if err := setRegValue(regBase); err != nil {
323-
return err
324-
}
303+
if err := m.setSingleDWORD(winutil.IPv6TCPIPInterfacePrefix, "EnableDNSUpdate", 0); err != nil {
304+
return err
325305
}
326-
327306
return nil
328307
}
329308

309+
// setSingleDWORD opens the Registry Key in HKLM for the interface associated
310+
// with the windowsManager and sets the "keyPrefix\value" to data.
311+
func (m windowsManager) setSingleDWORD(prefix winutil.RegistryPathPrefix, value string, data uint32) error {
312+
k, err := m.openInterfaceKey(prefix)
313+
if err != nil {
314+
return err
315+
}
316+
defer k.Close()
317+
return k.SetDWordValue(value, data)
318+
}
319+
330320
func (m windowsManager) GetBaseConfig() (OSConfig, error) {
331321
resolvers, err := m.getBasePrimaryResolver()
332322
if err != nil {

net/dns/manager_windows_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,11 +339,12 @@ func deleteFakeGPKey(t *testing.T) {
339339
}
340340

341341
func createFakeInterfaceKey(t *testing.T, guid windows.GUID) (func(), error) {
342-
basePaths := []string{ipv4RegBase, ipv6RegBase}
342+
basePaths := []winutil.RegistryPathPrefix{winutil.IPv4TCPIPInterfacePrefix, winutil.IPv6TCPIPInterfacePrefix}
343343
keyPaths := make([]string, 0, len(basePaths))
344344

345+
guidStr := guid.String()
345346
for _, basePath := range basePaths {
346-
keyPath := fmt.Sprintf(`%s\Interfaces\%s`, basePath, guid)
347+
keyPath := string(basePath.WithSuffix(guidStr))
347348
key, _, err := registry.CreateKey(registry.LOCAL_MACHINE, keyPath, registry.SET_VALUE)
348349
if err != nil {
349350
return nil, err

net/dns/registry_windows.go

Lines changed: 0 additions & 76 deletions
This file was deleted.

util/winutil/winutil_windows.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import (
1010
"log"
1111
"os/exec"
1212
"runtime"
13+
"strings"
1314
"syscall"
15+
"time"
1416
"unsafe"
1517

1618
"golang.org/x/sys/windows"
@@ -391,3 +393,93 @@ func IsCurrentProcessElevated() bool {
391393

392394
return token.IsElevated()
393395
}
396+
397+
// keyOpenTimeout is how long we wait for a registry key to appear. For some
398+
// reason, registry keys tied to ephemeral interfaces can take a long while to
399+
// appear after interface creation, and we can end up racing with that.
400+
const keyOpenTimeout = 20 * time.Second
401+
402+
// RegistryPath represents a path inside a root registry.Key.
403+
type RegistryPath string
404+
405+
// RegistryPathPrefix specifies a RegistryPath prefix that must be suffixed with
406+
// another RegistryPath to make a valid RegistryPath.
407+
type RegistryPathPrefix string
408+
409+
// WithSuffix returns a RegistryPath with the given suffix appended.
410+
func (p RegistryPathPrefix) WithSuffix(suf string) RegistryPath {
411+
return RegistryPath(string(p) + suf)
412+
}
413+
414+
const (
415+
IPv4TCPIPBase RegistryPath = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters`
416+
IPv6TCPIPBase RegistryPath = `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters`
417+
NetBTBase RegistryPath = `SYSTEM\CurrentControlSet\Services\NetBT\Parameters`
418+
419+
IPv4TCPIPInterfacePrefix RegistryPathPrefix = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\`
420+
IPv6TCPIPInterfacePrefix RegistryPathPrefix = `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces\`
421+
NetBTInterfacePrefix RegistryPathPrefix = `SYSTEM\CurrentControlSet\Services\NetBT\Parameters\Interfaces\Tcpip_`
422+
)
423+
424+
// ErrKeyWaitTimeout is returned by OpenKeyWait when calls timeout.
425+
var ErrKeyWaitTimeout = errors.New("timeout waiting for registry key")
426+
427+
// OpenKeyWait opens a registry key, waiting for it to appear if necessary. It
428+
// returns the opened key, or ErrKeyWaitTimeout if the key does not appear
429+
// within 20s. The caller must call Close on the returned key.
430+
func OpenKeyWait(k registry.Key, path RegistryPath, access uint32) (registry.Key, error) {
431+
runtime.LockOSThread()
432+
defer runtime.UnlockOSThread()
433+
434+
deadline := time.Now().Add(keyOpenTimeout)
435+
pathSpl := strings.Split(string(path), "\\")
436+
for i := 0; ; i++ {
437+
keyName := pathSpl[i]
438+
isLast := i+1 == len(pathSpl)
439+
440+
event, err := windows.CreateEvent(nil, 0, 0, nil)
441+
if err != nil {
442+
return 0, fmt.Errorf("windows.CreateEvent: %w", err)
443+
}
444+
defer windows.CloseHandle(event)
445+
446+
var key registry.Key
447+
for {
448+
err = windows.RegNotifyChangeKeyValue(windows.Handle(k), false, windows.REG_NOTIFY_CHANGE_NAME, event, true)
449+
if err != nil {
450+
return 0, fmt.Errorf("windows.RegNotifyChangeKeyValue: %w", err)
451+
}
452+
453+
var accessFlags uint32
454+
if isLast {
455+
accessFlags = access
456+
} else {
457+
accessFlags = registry.NOTIFY
458+
}
459+
key, err = registry.OpenKey(k, keyName, accessFlags)
460+
if err == windows.ERROR_FILE_NOT_FOUND || err == windows.ERROR_PATH_NOT_FOUND {
461+
timeout := time.Until(deadline) / time.Millisecond
462+
if timeout < 0 {
463+
timeout = 0
464+
}
465+
s, err := windows.WaitForSingleObject(event, uint32(timeout))
466+
if err != nil {
467+
return 0, fmt.Errorf("windows.WaitForSingleObject: %w", err)
468+
}
469+
if s == uint32(windows.WAIT_TIMEOUT) { // windows.WAIT_TIMEOUT status const is misclassified as error in golang.org/x/sys/windows
470+
return 0, ErrKeyWaitTimeout
471+
}
472+
} else if err != nil {
473+
return 0, fmt.Errorf("registry.OpenKey(%v): %w", path, err)
474+
} else {
475+
if isLast {
476+
return key, nil
477+
}
478+
defer key.Close()
479+
break
480+
}
481+
}
482+
483+
k = key
484+
}
485+
}

0 commit comments

Comments
 (0)