Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+137
-100
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package dns
|
||||
@@ -8,15 +8,14 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"maps"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
@@ -34,6 +33,7 @@ import (
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/util/syspolicy/ptype"
|
||||
"tailscale.com/util/winutil"
|
||||
"tailscale.com/util/winutil/winenv"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -49,6 +49,9 @@ type windowsManager struct {
|
||||
nrptDB *nrptRuleDatabase
|
||||
wslManager *wslManager
|
||||
polc policyclient.Client
|
||||
doV4 bool
|
||||
doV6 bool
|
||||
doNetBIOS bool
|
||||
|
||||
unregisterPolicyChangeCb func() // called when the manager is closing
|
||||
|
||||
@@ -71,12 +74,16 @@ func NewOSConfigurator(logf logger.Logf, health *health.Tracker, bus *eventbus.B
|
||||
wslManager: newWSLManager(logf, health),
|
||||
}
|
||||
|
||||
if err := ret.checkInterfaces(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isWindows10OrBetter() {
|
||||
ret.nrptDB = newNRPTRuleDatabase(logf)
|
||||
}
|
||||
|
||||
var err error
|
||||
if ret.unregisterPolicyChangeCb, err = polc.RegisterChangeCallback(ret.sysPolicyChanged); err != nil {
|
||||
if ret.unregisterPolicyChangeCb, err = polc.RegisterChangeCallback("", ret.sysPolicyChanged); err != nil {
|
||||
logf("error registering policy change callback: %v", err) // non-fatal
|
||||
}
|
||||
|
||||
@@ -92,22 +99,42 @@ func NewOSConfigurator(logf logger.Logf, health *health.Tracker, bus *eventbus.B
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (m *windowsManager) openInterfaceKey(pfx winutil.RegistryPathPrefix) (registry.Key, error) {
|
||||
var key registry.Key
|
||||
var err error
|
||||
path := pfx.WithSuffix(m.guid)
|
||||
|
||||
m.mu.Lock()
|
||||
closing := m.closing
|
||||
m.mu.Unlock()
|
||||
if closing {
|
||||
// Do not wait for the interface key to appear if the manager is being closed.
|
||||
// If it's being closed due to the removal of the wintun adapter,
|
||||
// the key would already be gone by now and will not reappear until tailscaled is restarted.
|
||||
key, err = registry.OpenKey(registry.LOCAL_MACHINE, string(path), registry.SET_VALUE)
|
||||
} else {
|
||||
key, err = winutil.OpenKeyWait(registry.LOCAL_MACHINE, path, registry.SET_VALUE)
|
||||
func (m *windowsManager) checkInterfaces() error {
|
||||
guid, err := windows.GUIDFromString(m.guid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
luid, err := winipcfg.LUIDFromGUID(&guid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = luid.IPInterface(windows.AF_INET)
|
||||
if err != nil && !errors.Is(err, windows.ERROR_NOT_FOUND) {
|
||||
return fmt.Errorf("getting AF_INET interface: %w", err)
|
||||
}
|
||||
m.doV4 = err == nil
|
||||
|
||||
_, err = luid.IPInterface(windows.AF_INET6)
|
||||
if err != nil && !errors.Is(err, windows.ERROR_NOT_FOUND) {
|
||||
return fmt.Errorf("getting AF_INET6 interface: %w", err)
|
||||
}
|
||||
m.doV6 = err == nil
|
||||
|
||||
if !m.doV4 && !m.doV6 {
|
||||
return errors.New("no IP interfaces available for dns configuration")
|
||||
}
|
||||
|
||||
// NetBIOS is non-fatal
|
||||
_, err = luid.IPInterface(windows.AF_NETBIOS)
|
||||
m.doNetBIOS = err == nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *windowsManager) openInterfaceKey(pfx winutil.RegistryPathPrefix) (registry.Key, error) {
|
||||
path := pfx.WithSuffix(m.guid)
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, string(path), registry.SET_VALUE)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("opening %s: %w", path, err)
|
||||
}
|
||||
@@ -145,7 +172,7 @@ func (m *windowsManager) setSplitDNS(resolvers []netip.Addr, domains []dnsname.F
|
||||
return fmt.Errorf("Split DNS unsupported on this Windows version")
|
||||
}
|
||||
|
||||
defer m.nrptDB.Refresh()
|
||||
defer m.nrptDB.NotifyPolicyChanged()
|
||||
if len(resolvers) == 0 {
|
||||
return m.nrptDB.DelAllRuleKeys()
|
||||
}
|
||||
@@ -245,7 +272,13 @@ func (m *windowsManager) setHosts(hosts []*HostEntry) error {
|
||||
}
|
||||
hostsFile := filepath.Join(systemDir, "drivers", "etc", "hosts")
|
||||
b, err := os.ReadFile(hostsFile)
|
||||
if err != nil {
|
||||
switch {
|
||||
case err == nil:
|
||||
// Continue.
|
||||
case errors.Is(err, fs.ErrNotExist):
|
||||
// Non-fatal, we'll just create a new hosts file.
|
||||
m.logf("failed to read the hosts file: %v", err)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
outB, err := setTailscaleHosts(m.logf, b, hosts)
|
||||
@@ -293,58 +326,66 @@ func (m *windowsManager) setPrimaryDNS(resolvers []netip.Addr, domains []dnsname
|
||||
domStrs = append(domStrs, dom.WithoutTrailingDot())
|
||||
}
|
||||
|
||||
key4, err := m.openInterfaceKey(winutil.IPv4TCPIPInterfacePrefix)
|
||||
if err != nil {
|
||||
return m.muteKeyNotFoundIfClosing(err)
|
||||
}
|
||||
defer key4.Close()
|
||||
if m.doV4 {
|
||||
key4, err := m.openInterfaceKey(winutil.IPv4TCPIPInterfacePrefix)
|
||||
if err != nil {
|
||||
return m.muteKeyNotFoundIfClosing(err)
|
||||
}
|
||||
defer key4.Close()
|
||||
|
||||
if len(ipsv4) == 0 {
|
||||
if err := delValue(key4, "NameServer"); err != nil {
|
||||
if len(ipsv4) == 0 {
|
||||
if err := delValue(key4, "NameServer"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := key4.SetStringValue("NameServer", strings.Join(ipsv4, ",")); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := key4.SetStringValue("NameServer", strings.Join(ipsv4, ",")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(domains) == 0 {
|
||||
if err := delValue(key4, "SearchList"); err != nil {
|
||||
if len(domains) == 0 {
|
||||
if err := delValue(key4, "SearchList"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := key4.SetStringValue("SearchList", strings.Join(domStrs, ",")); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := key4.SetStringValue("SearchList", strings.Join(domStrs, ",")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key6, err := m.openInterfaceKey(winutil.IPv6TCPIPInterfacePrefix)
|
||||
if err != nil {
|
||||
return m.muteKeyNotFoundIfClosing(err)
|
||||
}
|
||||
defer key6.Close()
|
||||
|
||||
if len(ipsv6) == 0 {
|
||||
if err := delValue(key6, "NameServer"); err != nil {
|
||||
// Disable LLMNR on the Tailscale interface. We don't do multicast, and we
|
||||
// certainly don't do LLMNR, so it's pointless to make Windows try it. It is
|
||||
// being deprecated.
|
||||
if err := key4.SetDWordValue("EnableMulticast", 0); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := key6.SetStringValue("NameServer", strings.Join(ipsv6, ",")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(domains) == 0 {
|
||||
if err := delValue(key6, "SearchList"); err != nil {
|
||||
if m.doV6 {
|
||||
key6, err := m.openInterfaceKey(winutil.IPv6TCPIPInterfacePrefix)
|
||||
if err != nil {
|
||||
return m.muteKeyNotFoundIfClosing(err)
|
||||
}
|
||||
defer key6.Close()
|
||||
|
||||
if len(ipsv6) == 0 {
|
||||
if err := delValue(key6, "NameServer"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := key6.SetStringValue("NameServer", strings.Join(ipsv6, ",")); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := key6.SetStringValue("SearchList", strings.Join(domStrs, ",")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Disable LLMNR on the Tailscale interface. We don't do multicast, and we
|
||||
// certainly don't do LLMNR, so it's pointless to make Windows try it. It is
|
||||
// being deprecated.
|
||||
if err := key4.SetDWordValue("EnableMulticast", 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := key6.SetDWordValue("EnableMulticast", 0); err != nil {
|
||||
return err
|
||||
if len(domains) == 0 {
|
||||
if err := delValue(key6, "SearchList"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := key6.SetStringValue("SearchList", strings.Join(domStrs, ",")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Disable LLMNR on the Tailscale interface. We don't do multicast, and we
|
||||
// certainly don't do LLMNR, so it's pointless to make Windows try it. It is
|
||||
// being deprecated.
|
||||
if err := key6.SetDWordValue("EnableMulticast", 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -354,6 +395,10 @@ func (m *windowsManager) disableLocalDNSOverrideViaNRPT() bool {
|
||||
return m.knobs != nil && m.knobs.DisableLocalDNSOverrideViaNRPT.Load()
|
||||
}
|
||||
|
||||
func (m *windowsManager) disableHostsFileUpdates() bool {
|
||||
return m.knobs != nil && m.knobs.DisableHostsFileUpdates.Load()
|
||||
}
|
||||
|
||||
func (m *windowsManager) SetDNS(cfg OSConfig) error {
|
||||
// We can configure Windows DNS in one of two ways:
|
||||
//
|
||||
@@ -399,7 +444,15 @@ func (m *windowsManager) SetDNS(cfg OSConfig) error {
|
||||
if err := m.setSplitDNS(resolvers, domains); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.setHosts(nil); err != nil {
|
||||
var hosts []*HostEntry
|
||||
if !m.disableHostsFileUpdates() && winenv.IsDomainJoined() {
|
||||
// On domain-joined Windows devices the primary search domain (the one the device is joined to)
|
||||
// always takes precedence over other search domains. This breaks MagicDNS when we are the primary
|
||||
// resolver on the device (see #18712). To work around this Windows behavior, we should write MagicDNS
|
||||
// host names the hosts file just as we do when we're not the primary resolver.
|
||||
hosts = cfg.Hosts
|
||||
}
|
||||
if err := m.setHosts(hosts); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.setPrimaryDNS(cfg.Nameservers, cfg.SearchDomains); err != nil {
|
||||
@@ -421,12 +474,14 @@ func (m *windowsManager) SetDNS(cfg OSConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// As we are not the primary resolver in this setup, we need to
|
||||
// explicitly set some single name hosts to ensure that we can resolve
|
||||
// them quickly and get around the 2.3s delay that otherwise occurs due
|
||||
// to multicast timeouts.
|
||||
if err := m.setHosts(cfg.Hosts); err != nil {
|
||||
return err
|
||||
if !m.disableHostsFileUpdates() {
|
||||
// As we are not the primary resolver in this setup, we need to
|
||||
// explicitly set some single name hosts to ensure that we can resolve
|
||||
// them quickly and get around the 2.3s delay that otherwise occurs due
|
||||
// to multicast timeouts.
|
||||
if err := m.setHosts(cfg.Hosts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,40 +491,15 @@ func (m *windowsManager) SetDNS(cfg OSConfig) error {
|
||||
// have changed, which makes the DNS settings actually take
|
||||
// effect.
|
||||
//
|
||||
// This command can take a few seconds to run, so run it async, best effort.
|
||||
//
|
||||
// After re-registering DNS, also flush the DNS cache to clear out
|
||||
// any cached split-horizon queries that are no longer the correct
|
||||
// answer.
|
||||
go func() {
|
||||
t0 := time.Now()
|
||||
m.logf("running ipconfig /registerdns ...")
|
||||
cmd := exec.Command("ipconfig", "/registerdns")
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
CreationFlags: windows.DETACHED_PROCESS,
|
||||
}
|
||||
err := cmd.Run()
|
||||
d := time.Since(t0).Round(time.Millisecond)
|
||||
if err != nil {
|
||||
m.logf("error running ipconfig /registerdns after %v: %v", d, err)
|
||||
} else {
|
||||
m.logf("ran ipconfig /registerdns in %v", d)
|
||||
}
|
||||
|
||||
t0 = time.Now()
|
||||
m.logf("running ipconfig /flushdns ...")
|
||||
cmd = exec.Command("ipconfig", "/flushdns")
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
CreationFlags: windows.DETACHED_PROCESS,
|
||||
}
|
||||
err = cmd.Run()
|
||||
d = time.Since(t0).Round(time.Millisecond)
|
||||
if err != nil {
|
||||
m.logf("error running ipconfig /flushdns after %v: %v", d, err)
|
||||
} else {
|
||||
m.logf("ran ipconfig /flushdns in %v", d)
|
||||
}
|
||||
}()
|
||||
//
|
||||
// Both operations are non-blocking and coalesced: at most one of
|
||||
// each is in flight at a time, with at most one more queued.
|
||||
m.logf("Re-registering DNS and flushing cache")
|
||||
registerDNS()
|
||||
Flush()
|
||||
|
||||
// On initial setup of WSL, the restart caused by --shutdown is slow,
|
||||
// so we do it out-of-line.
|
||||
@@ -551,9 +581,12 @@ func (m *windowsManager) reconfigureDNSRegistration() {
|
||||
// the Windows DHCP client from registering Tailscale IP addresses with DNS
|
||||
// and sending dynamic updates for our interface to AD domain controllers.
|
||||
func (m *windowsManager) configureDNSRegistration(enabled bool) error {
|
||||
prefixen := []winutil.RegistryPathPrefix{
|
||||
winutil.IPv4TCPIPInterfacePrefix,
|
||||
winutil.IPv6TCPIPInterfacePrefix,
|
||||
prefixen := make([]winutil.RegistryPathPrefix, 0, 2)
|
||||
if m.doV4 {
|
||||
prefixen = append(prefixen, winutil.IPv4TCPIPInterfacePrefix)
|
||||
}
|
||||
if m.doV6 {
|
||||
prefixen = append(prefixen, winutil.IPv6TCPIPInterfacePrefix)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -607,6 +640,10 @@ func (m *windowsManager) setSingleDWORD(prefix winutil.RegistryPathPrefix, value
|
||||
// Further, LLMNR and NetBIOS are being deprecated anyway in favor of MDNS.
|
||||
// https://techcommunity.microsoft.com/t5/networking-blog/aligning-on-mdns-ramping-down-netbios-name-resolution-and-llmnr/ba-p/3290816
|
||||
func (m *windowsManager) disableNetBIOS() error {
|
||||
if !m.doNetBIOS {
|
||||
return nil
|
||||
}
|
||||
|
||||
return m.setSingleDWORD(winutil.NetBTInterfacePrefix, "NetbiosOptions", 2)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user