mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 13:05:46 +00:00
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>
This commit is contained in:
parent
7e1a146e6c
commit
68ddf134d7
@ -5,9 +5,10 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
@ -46,68 +47,16 @@ func setRegistryString(path, name, value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRegistryString(path, name string) (string, error) {
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, path, registry.READ)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("opening %s: %w", path, err)
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
value, _, err := key.GetStringValue(name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getting %s[%s]: %w", path, name, err)
|
||||
}
|
||||
return value, 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(path string, oldDomains, newDomains []string) error {
|
||||
// We reimplement setRegistryString to ensure that we hold the key for the whole operation.
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, path, registry.READ|registry.SET_VALUE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening %s: %w", path, err)
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
searchList, _, err := key.GetStringValue("SearchList")
|
||||
if err != nil && err != registry.ErrNotExist {
|
||||
return fmt.Errorf("getting %s[SearchList]: %w", path, err)
|
||||
}
|
||||
currentDomains := strings.Split(searchList, ",")
|
||||
|
||||
var domainsToSet []string
|
||||
for _, domain := range currentDomains {
|
||||
inOld, inNew := false, false
|
||||
|
||||
// The number of domains should be small,
|
||||
// so this is probaly faster than constructing a map.
|
||||
for _, oldDomain := range oldDomains {
|
||||
if domain == oldDomain {
|
||||
inOld = true
|
||||
}
|
||||
}
|
||||
for _, newDomain := range newDomains {
|
||||
if domain == newDomain {
|
||||
inNew = true
|
||||
}
|
||||
}
|
||||
|
||||
if !inNew && !inOld {
|
||||
domainsToSet = append(domainsToSet, domain)
|
||||
}
|
||||
}
|
||||
domainsToSet = append(domainsToSet, newDomains...)
|
||||
|
||||
searchList = strings.Join(domainsToSet, ",")
|
||||
if err := key.SetStringValue("SearchList", searchList); err != nil {
|
||||
return fmt.Errorf("setting %s[SearchList]: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
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 {
|
||||
@ -122,23 +71,17 @@ func (m windowsManager) Up(config Config) error {
|
||||
}
|
||||
}
|
||||
|
||||
lastSearchList, err := getRegistryString(tsRegBase, "SearchList")
|
||||
if err != nil && !errors.Is(err, registry.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
lastDomains := strings.Split(lastSearchList, ",")
|
||||
|
||||
if err := m.setNameservers(ipv4RegBase, ipsv4); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.setDomains(ipv4RegBase, lastDomains, config.Domains); err != nil {
|
||||
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, lastDomains, config.Domains); err != nil {
|
||||
if err := m.setDomains(ipv6RegBase, config.Domains); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -147,6 +90,19 @@ func (m windowsManager) Up(config Config) error {
|
||||
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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user