tailscale/wgengine/router/dns/manager_windows.go
David Anderson 68ddf134d7 wgengine/router/dns: issue ipconfig /registerdns when applying DNS settings.
Amazingly, there doesn't seem to be a documented way of updating network
configuration programmatically in a way that Windows takes notice of.
The naturopathic remedy for this is to invoke ipconfig /registerdns, which
does a variety of harmless things and also invokes the private API that
tells windows to notice new adapter settings. This makes our DNS config
changes stick within a few seconds of us setting them.

If we're invoking a shell command anyway, why futz with the registry at
all? Because netsh has no command for changing the DNS suffix list, and
its commands for setting resolvers requires parsing its output and
keeping track of which server is in what index. Amazingly, twiddling
the registry directly is the less painful option.

Fixes #853.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-10-29 20:05:38 -07:00

112 lines
2.9 KiB
Go

// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package dns
import (
"fmt"
"os/exec"
"strings"
"syscall"
"github.com/tailscale/wireguard-go/tun"
"golang.org/x/sys/windows/registry"
"tailscale.com/types/logger"
)
const (
ipv4RegBase = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters`
ipv6RegBase = `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters`
tsRegBase = `SOFTWARE\Tailscale IPN`
)
type windowsManager struct {
logf logger.Logf
guid string
}
func newManager(mconfig ManagerConfig) managerImpl {
return windowsManager{
logf: mconfig.Logf,
guid: tun.WintunGUID,
}
}
func setRegistryString(path, name, value string) error {
key, err := registry.OpenKey(registry.LOCAL_MACHINE, path, registry.SET_VALUE)
if err != nil {
return fmt.Errorf("opening %s: %w", path, err)
}
defer key.Close()
err = key.SetStringValue(name, value)
if err != nil {
return fmt.Errorf("setting %s[%s]: %w", path, name, err)
}
return nil
}
func (m windowsManager) setNameservers(basePath string, nameservers []string) error {
path := fmt.Sprintf(`%s\Interfaces\%s`, basePath, m.guid)
value := strings.Join(nameservers, ",")
return setRegistryString(path, "NameServer", value)
}
func (m windowsManager) setDomains(basePath string, domains []string) error {
path := fmt.Sprintf(`%s\Interfaces\%s`, basePath, m.guid)
value := strings.Join(domains, ",")
return setRegistryString(path, "SearchList", value)
}
func (m windowsManager) Up(config Config) error {
var ipsv4 []string
var ipsv6 []string
for _, ip := range config.Nameservers {
if ip.Is4() {
ipsv4 = append(ipsv4, ip.String())
} else {
ipsv6 = append(ipsv6, ip.String())
}
}
if err := m.setNameservers(ipv4RegBase, ipsv4); err != nil {
return err
}
if err := m.setDomains(ipv4RegBase, config.Domains); err != nil {
return err
}
if err := m.setNameservers(ipv6RegBase, ipsv6); err != nil {
return err
}
if err := m.setDomains(ipv6RegBase, config.Domains); err != nil {
return err
}
newSearchList := strings.Join(config.Domains, ",")
if err := setRegistryString(tsRegBase, "SearchList", newSearchList); err != nil {
return err
}
// Force DNS re-registration in Active Directory. What we actually
// care about is that this command invokes the undocumented hidden
// function that forces Windows to notice that adapter settings
// have changed, which makes the DNS settings actually take
// effect.
//
// This command can take a few seconds to run.
cmd := exec.Command("ipconfig", "/registerdns")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
if err := cmd.Run(); err != nil {
return fmt.Errorf("running ipconfig /registerdns: %w", err)
}
return nil
}
func (m windowsManager) Down() error {
return m.Up(Config{Nameservers: nil, Domains: nil})
}