This commit is contained in:
2026-02-19 10:07:43 +00:00
parent 007438e372
commit 6e637ecf77
1763 changed files with 60820 additions and 279516 deletions

View File

@@ -20,17 +20,19 @@ import (
"time"
"tailscale.com/control/controlknobs"
"tailscale.com/feature/buildfeatures"
"tailscale.com/health"
"tailscale.com/net/dns/resolver"
"tailscale.com/net/netmon"
"tailscale.com/net/tsdial"
"tailscale.com/syncs"
"tailscale.com/tstime/rate"
"tailscale.com/types/dnstype"
"tailscale.com/types/logger"
"tailscale.com/util/clientmetric"
"tailscale.com/util/dnsname"
"tailscale.com/util/eventbus"
"tailscale.com/util/slicesx"
"tailscale.com/util/syspolicy/policyclient"
)
var (
@@ -53,6 +55,8 @@ type Manager struct {
logf logger.Logf
health *health.Tracker
eventClient *eventbus.Client
activeQueriesAtomic int32
ctx context.Context // good until Down
@@ -63,16 +67,17 @@ type Manager struct {
knobs *controlknobs.Knobs // or nil
goos string // if empty, gets set to runtime.GOOS
mu sync.Mutex // guards following
// config is the last configuration we successfully compiled or nil if there
// was any failure applying the last configuration.
config *Config
mu sync.Mutex // guards following
config *Config // Tracks the last viable DNS configuration set by Set. nil on failures other than compilation failures or if set has never been called.
}
// NewManagers created a new manager from the given config.
// NewManager created a new manager from the given config.
//
// knobs may be nil.
func NewManager(logf logger.Logf, oscfg OSConfigurator, health *health.Tracker, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector, knobs *controlknobs.Knobs, goos string) *Manager {
func NewManager(logf logger.Logf, oscfg OSConfigurator, health *health.Tracker, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector, knobs *controlknobs.Knobs, goos string, bus *eventbus.Bus) *Manager {
if !buildfeatures.HasDNS {
return nil
}
if dialer == nil {
panic("nil Dialer")
}
@@ -93,19 +98,17 @@ func NewManager(logf logger.Logf, oscfg OSConfigurator, health *health.Tracker,
goos: goos,
}
// Rate limit our attempts to correct our DNS configuration.
// This is done on incoming queries, we don't want to spam it.
limiter := rate.NewLimiter(1.0/5.0, 1)
// This will recompile the DNS config, which in turn will requery the system
// DNS settings. The recovery func should triggered only when we are missing
// upstream nameservers and require them to forward a query.
m.resolver.SetMissingUpstreamRecovery(func() {
if limiter.Allow() {
m.logf("resolution failed due to missing upstream nameservers. Recompiling DNS configuration.")
if err := m.RecompileDNSConfig(); err != nil {
m.logf("config recompilation failed: %v", err)
}
m.eventClient = bus.Client("dns.Manager")
eventbus.SubscribeFunc(m.eventClient, func(trample TrampleDNS) {
m.mu.Lock()
defer m.mu.Unlock()
if m.config == nil {
m.logf("resolve.conf was trampled, but there is no DNS config")
return
}
m.logf("resolve.conf was trampled, setting existing config again")
if err := m.setLocked(*m.config); err != nil {
m.logf("error setting DNS config: %s", err)
}
})
@@ -115,9 +118,14 @@ func NewManager(logf logger.Logf, oscfg OSConfigurator, health *health.Tracker,
}
// Resolver returns the Manager's DNS Resolver.
func (m *Manager) Resolver() *resolver.Resolver { return m.resolver }
func (m *Manager) Resolver() *resolver.Resolver {
if !buildfeatures.HasDNS {
return nil
}
return m.resolver
}
// RecompileDNSConfig sets the DNS config to the current value, which has
// RecompileDNSConfig recompiles the last attempted DNS configuration, which has
// the side effect of re-querying the OS's interface nameservers. This should be used
// on platforms where the interface nameservers can change. Darwin, for example,
// where the nameservers aren't always available when we process a major interface
@@ -127,17 +135,23 @@ func (m *Manager) Resolver() *resolver.Resolver { return m.resolver }
// give a better or different result than when [Manager.Set] was last called. The
// logic for making that determination is up to the caller.
//
// It returns [ErrNoDNSConfig] if the [Manager] has no existing DNS configuration.
// It returns [ErrNoDNSConfig] if [Manager.Set] has never been called.
func (m *Manager) RecompileDNSConfig() error {
if !buildfeatures.HasDNS {
return nil
}
m.mu.Lock()
defer m.mu.Unlock()
if m.config == nil {
return ErrNoDNSConfig
if m.config != nil {
return m.setLocked(*m.config)
}
return m.setLocked(*m.config)
return ErrNoDNSConfig
}
func (m *Manager) Set(cfg Config) error {
if !buildfeatures.HasDNS {
return nil
}
m.mu.Lock()
defer m.mu.Unlock()
return m.setLocked(cfg)
@@ -145,6 +159,9 @@ func (m *Manager) Set(cfg Config) error {
// GetBaseConfig returns the current base OS DNS configuration as provided by the OSConfigurator.
func (m *Manager) GetBaseConfig() (OSConfig, error) {
if !buildfeatures.HasDNS {
panic("unreachable")
}
return m.os.GetBaseConfig()
}
@@ -154,15 +171,15 @@ func (m *Manager) GetBaseConfig() (OSConfig, error) {
func (m *Manager) setLocked(cfg Config) error {
syncs.AssertLocked(&m.mu)
// On errors, the 'set' config is cleared.
m.config = nil
m.logf("Set: %v", logger.ArgWriter(func(w *bufio.Writer) {
cfg.WriteToBufioWriter(w)
}))
rcfg, ocfg, err := m.compileConfig(cfg)
if err != nil {
// On a compilation failure, set m.config set for later reuse by
// [Manager.RecompileDNSConfig] and return the error.
m.config = &cfg
return err
}
@@ -174,10 +191,10 @@ func (m *Manager) setLocked(cfg Config) error {
}))
if err := m.resolver.SetConfig(rcfg); err != nil {
m.config = nil
return err
}
if err := m.os.SetDNS(ocfg); err != nil {
m.health.SetUnhealthy(osConfigurationSetWarnable, health.Args{health.ArgError: err.Error()})
if err := m.setDNSLocked(ocfg); err != nil {
return err
}
@@ -187,6 +204,15 @@ func (m *Manager) setLocked(cfg Config) error {
return nil
}
func (m *Manager) setDNSLocked(ocfg OSConfig) error {
if err := m.os.SetDNS(ocfg); err != nil {
m.config = nil
m.health.SetUnhealthy(osConfigurationSetWarnable, health.Args{health.ArgError: err.Error()})
return err
}
return nil
}
// compileHostEntries creates a list of single-label resolutions possible
// from the configured hosts and search domains.
// The entries are compiled in the order of the search domains, then the hosts.
@@ -284,7 +310,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
// Deal with trivial configs first.
switch {
case !cfg.needsOSResolver():
case !cfg.needsOSResolver() || runtime.GOOS == "plan9":
// Set search domains, but nothing else. This also covers the
// case where cfg is entirely zero, in which case these
// configs clear all Tailscale DNS settings.
@@ -307,7 +333,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
// through quad-100.
rcfg.Routes = routes
rcfg.Routes["."] = cfg.DefaultResolvers
ocfg.Nameservers = []netip.Addr{cfg.serviceIP()}
ocfg.Nameservers = cfg.serviceIPs(m.knobs)
return rcfg, ocfg, nil
}
@@ -345,7 +371,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
// or routes + MagicDNS, or just MagicDNS, or on an OS that cannot
// split-DNS. Install a split config pointing at quad-100.
rcfg.Routes = routes
ocfg.Nameservers = []netip.Addr{cfg.serviceIP()}
ocfg.Nameservers = cfg.serviceIPs(m.knobs)
var baseCfg *OSConfig // base config; non-nil if/when known
@@ -355,7 +381,10 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
// that as the forwarder for all DNS traffic that quad-100 doesn't handle.
if isApple || !m.os.SupportsSplitDNS() {
// If the OS can't do native split-dns, read out the underlying
// resolver config and blend it into our config.
// resolver config and blend it into our config. On apple platforms, [OSConfigurator.GetBaseConfig]
// has a tendency to temporarily fail if called immediately following
// an interface change. These failures should be retried if/when the OS
// indicates that the DNS configuration has changed via [RecompileDNSConfig].
cfg, err := m.os.GetBaseConfig()
if err == nil {
baseCfg = &cfg
@@ -451,6 +480,13 @@ const (
maxReqSizeTCP = 4096
)
// TrampleDNS is an an event indicating we detected that DNS config was
// overwritten by another process.
type TrampleDNS struct {
LastTrample time.Time
TramplesInTimeout int64
}
// dnsTCPSession services DNS requests sent over TCP.
type dnsTCPSession struct {
m *Manager
@@ -572,15 +608,22 @@ func (m *Manager) HandleTCPConn(conn net.Conn, srcAddr netip.AddrPort) {
}
func (m *Manager) Down() error {
if !buildfeatures.HasDNS {
return nil
}
m.ctxCancel()
if err := m.os.Close(); err != nil {
return err
}
m.eventClient.Close()
m.resolver.Close()
return nil
}
func (m *Manager) FlushCaches() error {
if !buildfeatures.HasDNS {
return nil
}
return flushCaches()
}
@@ -589,20 +632,22 @@ func (m *Manager) FlushCaches() error {
// No other state needs to be instantiated before this runs.
//
// health must not be nil
func CleanUp(logf logger.Logf, netMon *netmon.Monitor, health *health.Tracker, interfaceName string) {
oscfg, err := NewOSConfigurator(logf, nil, nil, interfaceName)
func CleanUp(logf logger.Logf, netMon *netmon.Monitor, bus *eventbus.Bus, health *health.Tracker, interfaceName string) {
if !buildfeatures.HasDNS {
return
}
oscfg, err := NewOSConfigurator(logf, health, bus, policyclient.Get(), nil, interfaceName)
if err != nil {
logf("creating dns cleanup: %v", err)
return
}
d := &tsdial.Dialer{Logf: logf}
d.SetNetMon(netMon)
dns := NewManager(logf, oscfg, health, d, nil, nil, runtime.GOOS)
d.SetBus(bus)
dns := NewManager(logf, oscfg, health, d, nil, nil, runtime.GOOS, bus)
if err := dns.Down(); err != nil {
logf("dns down: %v", err)
}
}
var (
metricDNSQueryErrorQueue = clientmetric.NewCounter("dns_query_local_error_queue")
)
var metricDNSQueryErrorQueue = clientmetric.NewCounter("dns_query_local_error_queue")