-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Expand file tree
/
Copy pathmanager.go
More file actions
683 lines (614 loc) · 20.9 KB
/
manager.go
File metadata and controls
683 lines (614 loc) · 20.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package dns
import (
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"net/netip"
"runtime"
"slices"
"strings"
"sync"
"sync/atomic"
"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/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 (
errFullQueue = errors.New("request queue full")
// ErrNoDNSConfig is returned by RecompileDNSConfig when the Manager
// has no existing DNS configuration.
ErrNoDNSConfig = errors.New("no DNS configuration")
)
// maxActiveQueries returns the maximal number of DNS requests that can
// be running.
const maxActiveQueries = 256
// ResponseMapper is a function that accepts the bytes representing
// a DNS response and returns bytes representing a DNS response.
// Used to observe and/or mutate DNS responses managed by this manager.
type ResponseMapper func([]byte) []byte
// We use file-ignore below instead of ignore because on some platforms,
// the lint exception is necessary and on others it is not,
// and plain ignore complains if the exception is unnecessary.
// Manager manages system DNS settings.
type Manager struct {
logf logger.Logf
health *health.Tracker
eventClient *eventbus.Client
activeQueriesAtomic int32
ctx context.Context // good until Down
ctxCancel context.CancelFunc // closes ctx
resolver *resolver.Resolver
os OSConfigurator
knobs *controlknobs.Knobs // or nil
goos string // if empty, gets set to runtime.GOOS
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.
queryResponseMapper ResponseMapper
}
// 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, bus *eventbus.Bus) *Manager {
if !buildfeatures.HasDNS {
return nil
}
if dialer == nil {
panic("nil Dialer")
}
if dialer.NetMon() == nil {
panic("Dialer has nil NetMon")
}
logf = logger.WithPrefix(logf, "dns: ")
if goos == "" {
goos = runtime.GOOS
}
m := &Manager{
logf: logf,
resolver: resolver.New(logf, linkSel, dialer, health, knobs),
os: oscfg,
health: health,
knobs: knobs,
goos: goos,
}
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)
}
})
m.ctx, m.ctxCancel = context.WithCancel(context.Background())
m.logf("using %T", m.os)
return m
}
// Resolver returns the Manager's DNS Resolver.
func (m *Manager) Resolver() *resolver.Resolver {
if !buildfeatures.HasDNS {
return nil
}
return m.resolver
}
// 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
// change event, or platforms where the nameservers may change while tunnel is up.
//
// This should be called if it is determined that [OSConfigurator.GetBaseConfig] may
// 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 [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 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)
}
// 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()
}
// setLocked sets the DNS configuration.
//
// m.mu must be held.
func (m *Manager) setLocked(cfg Config) error {
syncs.AssertLocked(&m.mu)
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
}
m.logf("Resolvercfg: %v", logger.ArgWriter(func(w *bufio.Writer) {
rcfg.WriteToBufioWriter(w)
}))
m.logf("OScfg: %v", logger.ArgWriter(func(w *bufio.Writer) {
ocfg.WriteToBufioWriter(w)
}))
if err := m.resolver.SetConfig(rcfg); err != nil {
m.config = nil
return err
}
if err := m.setDNSLocked(ocfg); err != nil {
return err
}
m.health.SetHealthy(osConfigurationSetWarnable)
m.config = &cfg
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.
// The returned list is sorted by the first hostname in each entry.
func compileHostEntries(cfg Config) (hosts []*HostEntry) {
didLabel := make(map[string]bool, len(cfg.Hosts))
hostsMap := make(map[netip.Addr]*HostEntry, len(cfg.Hosts))
for _, sd := range cfg.SearchDomains {
for h, ips := range cfg.Hosts {
if !sd.Contains(h) || h.NumLabels() != (sd.NumLabels()+1) {
continue
}
ipHosts := []string{string(h.WithTrailingDot())}
if label := dnsname.FirstLabel(string(h)); !didLabel[label] {
didLabel[label] = true
ipHosts = append(ipHosts, label)
}
for _, ip := range ips {
if cfg.OnlyIPv6 && ip.Is4() {
continue
}
if e := hostsMap[ip]; e != nil {
e.Hosts = append(e.Hosts, ipHosts...)
} else {
hostsMap[ip] = &HostEntry{
Addr: ip,
Hosts: ipHosts,
}
}
// Only add IPv4 or IPv6 per host, like we do in the resolver.
break
}
}
}
if len(hostsMap) == 0 {
return nil
}
hosts = slicesx.MapValues(hostsMap)
slices.SortFunc(hosts, func(a, b *HostEntry) int {
if len(a.Hosts) == 0 && len(b.Hosts) == 0 {
return 0
} else if len(a.Hosts) == 0 {
return -1
} else if len(b.Hosts) == 0 {
return 1
}
return strings.Compare(a.Hosts[0], b.Hosts[0])
})
return hosts
}
var osConfigurationReadWarnable = health.Register(&health.Warnable{
Code: "dns-read-os-config-failed",
Title: "Failed to read system DNS configuration",
Text: func(args health.Args) string {
return fmt.Sprintf("Tailscale failed to fetch the DNS configuration of your device: %v", args[health.ArgError])
},
Severity: health.SeverityLow,
DependsOn: []*health.Warnable{health.NetworkStatusWarnable},
})
var osConfigurationSetWarnable = health.Register(&health.Warnable{
Code: "dns-set-os-config-failed",
Title: "Failed to set system DNS configuration",
Text: func(args health.Args) string {
return fmt.Sprintf("Tailscale failed to set the DNS configuration of your device: %v", args[health.ArgError])
},
Severity: health.SeverityMedium,
DependsOn: []*health.Warnable{health.NetworkStatusWarnable},
})
// compileConfig converts cfg into a quad-100 resolver configuration
// and an OS-level configuration.
func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig, err error) {
// The internal resolver always gets MagicDNS hosts and
// authoritative suffixes, even if we don't propagate MagicDNS to
// the OS.
rcfg.Hosts = cfg.Hosts
rcfg.SubdomainHosts = cfg.SubdomainHosts
rcfg.AcceptDNS = cfg.AcceptDNS
routes := map[dnsname.FQDN][]*dnstype.Resolver{} // assigned conditionally to rcfg.Routes below.
var propagateHostsToOS bool
for suffix, resolvers := range cfg.Routes {
if len(resolvers) == 0 {
propagateHostsToOS = true
rcfg.LocalDomains = append(rcfg.LocalDomains, suffix)
} else {
routes[suffix] = resolvers
}
}
// Similarly, the OS always gets search paths.
ocfg.SearchDomains = cfg.SearchDomains
if propagateHostsToOS && m.goos == "windows" {
ocfg.Hosts = compileHostEntries(cfg)
}
// Deal with trivial configs first.
switch {
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.
return rcfg, ocfg, nil
case cfg.hasDefaultIPResolversOnly() && !cfg.hasHostsWithoutSplitDNSRoutes():
// Trivial CorpDNS configuration, just override the OS resolver.
//
// If there are hosts (ExtraRecords) that are not covered by an existing
// SplitDNS route, then we don't go into this path so that we fall into
// the next case and send the extra record hosts queries through
// 100.100.100.100 instead where we can answer them.
//
// TODO: for OSes that support it, pass IP:port and DoH
// addresses directly to OS.
// https://github.com/tailscale/tailscale/issues/1666
ocfg.Nameservers = toIPsOnly(cfg.DefaultResolvers)
return rcfg, ocfg, nil
case cfg.hasDefaultResolvers():
// Default resolvers plus other stuff always ends up proxying
// through quad-100.
rcfg.Routes = routes
rcfg.Routes["."] = cfg.DefaultResolvers
ocfg.Nameservers = cfg.serviceIPs(m.knobs)
return rcfg, ocfg, nil
}
// From this point on, we're figuring out split DNS
// configurations. The possible cases don't return directly any
// more, because as a final step we have to handle the case where
// the OS can't do split DNS.
// Workaround for
// https://github.com/tailscale/corp/issues/1662. Even though
// Windows natively supports split DNS, it only configures linux
// containers using whatever the primary is, and doesn't apply
// NRPT rules to DNS traffic coming from WSL.
//
// In order to make WSL work okay when the host Windows is using
// Tailscale, we need to set up quad-100 as a "full proxy"
// resolver, regardless of whether Windows itself can do split
// DNS. We still make Windows do split DNS itself when it can, but
// quad-100 will still have the full split configuration as well,
// and so can service WSL requests correctly.
//
// This bool is used in a couple of places below to implement this
// workaround.
isWindows := m.goos == "windows"
isApple := (m.goos == "darwin" || m.goos == "ios")
if len(cfg.singleResolverSet()) > 0 && m.os.SupportsSplitDNS() && !isWindows && !isApple {
// Split DNS configuration requested, where all split domains
// go to the same resolvers. We can let the OS do it.
ocfg.Nameservers = toIPsOnly(cfg.singleResolverSet())
ocfg.MatchDomains = cfg.matchDomains()
return rcfg, ocfg, nil
}
// Split DNS configuration with either multiple upstream routes,
// 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 = cfg.serviceIPs(m.knobs)
var baseCfg *OSConfig // base config; non-nil if/when known
// Even though Apple devices can do split DNS, they don't provide a way to
// selectively answer ExtraRecords, and ignore other DNS traffic. As a
// workaround, we read the existing default resolver configuration and use
// 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. 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
} else if (isApple || isNoopManager(m.os)) && err == ErrGetBaseConfigNotSupported {
// Expected when using noopManager (userspace networking) or on
// certain iOS/macOS builds. Continue without base config.
} else {
m.health.SetUnhealthy(osConfigurationReadWarnable, health.Args{health.ArgError: err.Error()})
return resolver.Config{}, OSConfig{}, err
}
m.health.SetHealthy(osConfigurationReadWarnable)
}
if baseCfg == nil {
// If there was no base config, then we need to fallback to SplitDNS mode.
ocfg.MatchDomains = cfg.matchDomains()
} else {
// On iOS only (for now), check if all route names point to resources inside the tailnet.
// If so, we can set those names as MatchDomains to enable a split DNS configuration
// which will help preserve battery life.
// Because on iOS MatchDomains must equal SearchDomains, we cannot do this when
// we have any Routes outside the tailnet. Otherwise when app connectors are enabled,
// a query for 'work-laptop' might lead to search domain expansion, resolving
// as 'work-laptop.aws.com' for example.
if m.goos == "ios" && rcfg.RoutesRequireNoCustomResolvers() {
if !m.disableSplitDNSOptimization() {
for r := range rcfg.Routes {
ocfg.MatchDomains = append(ocfg.MatchDomains, r)
}
} else {
m.logf("iOS split DNS is disabled by nodeattr")
}
}
var defaultRoutes []*dnstype.Resolver
for _, ip := range baseCfg.Nameservers {
defaultRoutes = append(defaultRoutes, &dnstype.Resolver{Addr: ip.String()})
}
rcfg.Routes["."] = defaultRoutes
// Append base config search domains, but only if not already present.
// This prevents duplicates when GetBaseConfig() reads back domains that
// Tailscale itself previously wrote to resolv.conf.
for _, domain := range baseCfg.SearchDomains {
if !slices.Contains(ocfg.SearchDomains, domain) {
ocfg.SearchDomains = append(ocfg.SearchDomains, domain)
}
}
}
return rcfg, ocfg, nil
}
func (m *Manager) disableSplitDNSOptimization() bool {
return m.knobs != nil && m.knobs.DisableSplitDNSWhenNoCustomResolvers.Load()
}
// toIPsOnly returns only the IP portion of dnstype.Resolver.
// Only safe to use if the resolvers slice has been cleared of
// DoH or custom-port entries with something like hasDefaultIPResolversOnly.
func toIPsOnly(resolvers []*dnstype.Resolver) (ret []netip.Addr) {
for _, r := range resolvers {
if ipp, ok := r.IPPort(); ok && ipp.Port() == 53 {
ret = append(ret, ipp.Addr())
}
}
return ret
}
// Query executes a DNS query received from the given address. The query is
// provided in bs as a wire-encoded DNS query without any transport header.
// This method is called for requests arriving over UDP and TCP.
//
// The "family" parameter should indicate what type of DNS query this is:
// either "tcp" or "udp".
func (m *Manager) Query(ctx context.Context, bs []byte, family string, from netip.AddrPort) ([]byte, error) {
select {
case <-m.ctx.Done():
return nil, net.ErrClosed
default:
// continue
}
if n := atomic.AddInt32(&m.activeQueriesAtomic, 1); n > maxActiveQueries {
atomic.AddInt32(&m.activeQueriesAtomic, -1)
metricDNSQueryErrorQueue.Add(1)
return nil, errFullQueue
}
defer atomic.AddInt32(&m.activeQueriesAtomic, -1)
outbs, err := m.resolver.Query(ctx, bs, family, from)
if err != nil {
return outbs, err
}
m.mu.Lock()
defer m.mu.Unlock()
if m.queryResponseMapper != nil {
outbs = m.queryResponseMapper(outbs)
}
return outbs, err
}
const (
// RFC 7766 6.2 recommends connection reuse & request pipelining
// be undertaken, and the connection be closed by the server
// using an idle timeout on the order of seconds.
idleTimeoutTCP = 45 * time.Second
// The RFCs don't specify the max size of a TCP-based DNS query,
// but we want to keep this reasonable. Given payloads are typically
// much larger and all known client send a single query, I've arbitrarily
// chosen 4k.
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
conn net.Conn
srcAddr netip.AddrPort
readClosing chan struct{}
responses chan []byte // DNS replies pending writing
ctx context.Context
closeCtx context.CancelFunc
}
func (s *dnsTCPSession) handleWrites() {
defer s.conn.Close()
defer s.closeCtx()
// NOTE(andrew): we explicitly do not close the 'responses' channel
// when this function exits. If we hit an error and return, we could
// still have outstanding 'handleQuery' goroutines running, and if we
// closed this channel they'd end up trying to send on a closed channel
// when they finish.
//
// Because we call closeCtx, those goroutines will not hang since they
// select on <-s.ctx.Done() as well as s.responses.
for {
select {
case <-s.readClosing:
return // connection closed or timeout, teardown time
case resp := <-s.responses:
s.conn.SetWriteDeadline(time.Now().Add(idleTimeoutTCP))
if err := binary.Write(s.conn, binary.BigEndian, uint16(len(resp))); err != nil {
s.m.logf("tcp write (len): %v", err)
return
}
if _, err := s.conn.Write(resp); err != nil {
s.m.logf("tcp write (response): %v", err)
return
}
}
}
}
func (s *dnsTCPSession) handleQuery(q []byte) {
resp, err := s.m.Query(s.ctx, q, "tcp", s.srcAddr)
if err != nil {
s.m.logf("tcp query: %v", err)
return
}
// See note in handleWrites (above) regarding this select{}
select {
case <-s.ctx.Done():
case s.responses <- resp:
}
}
func (s *dnsTCPSession) handleReads() {
defer s.conn.Close()
defer close(s.readClosing)
for {
select {
case <-s.ctx.Done():
return
default:
s.conn.SetReadDeadline(time.Now().Add(idleTimeoutTCP))
var reqLen uint16
if err := binary.Read(s.conn, binary.BigEndian, &reqLen); err != nil {
if err == io.EOF || err == io.ErrClosedPipe {
return // connection closed nominally, we gucci
}
s.m.logf("tcp read (len): %v", err)
return
}
if int(reqLen) > maxReqSizeTCP {
s.m.logf("tcp request too large (%d > %d)", reqLen, maxReqSizeTCP)
return
}
buf := make([]byte, int(reqLen))
if _, err := io.ReadFull(s.conn, buf); err != nil {
s.m.logf("tcp read (payload): %v", err)
return
}
select {
case <-s.ctx.Done():
return
default:
// NOTE: by kicking off the query handling in a
// new goroutine, it is possible that we'll
// deliver responses out-of-order. This is
// explicitly allowed by RFC7766, Section
// 6.2.1.1 ("Query Pipelining").
go s.handleQuery(buf)
}
}
}
}
// HandleTCPConn implements magicDNS over TCP, taking a connection and
// servicing DNS requests sent down it.
func (m *Manager) HandleTCPConn(conn net.Conn, srcAddr netip.AddrPort) {
s := dnsTCPSession{
m: m,
conn: conn,
srcAddr: srcAddr,
responses: make(chan []byte),
readClosing: make(chan struct{}),
}
s.ctx, s.closeCtx = context.WithCancel(m.ctx)
go s.handleReads()
s.handleWrites()
}
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()
}
// CleanUp restores the system DNS configuration to its original state
// in case the Tailscale daemon terminated without closing the router.
// No other state needs to be instantiated before this runs.
//
// health must not be nil
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)
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")
func (m *Manager) SetQueryResponseMapper(fx ResponseMapper) {
m.mu.Lock()
defer m.mu.Unlock()
m.queryResponseMapper = fx
}