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
|
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package dns contains code to configure and manage DNS settings.
package dns
import (
"bufio"
"fmt"
"net/netip"
"slices"
"sort"
"tailscale.com/control/controlknobs"
"tailscale.com/envknob"
"tailscale.com/net/dns/publicdns"
"tailscale.com/net/dns/resolver"
"tailscale.com/net/tsaddr"
"tailscale.com/types/dnstype"
"tailscale.com/util/dnsname"
)
// Config is a DNS configuration.
type Config struct {
// DefaultResolvers are the DNS resolvers to use for DNS names
// which aren't covered by more specific per-domain routes below.
// If empty, the OS's default resolvers (the ones that predate
// Tailscale altering the configuration) are used.
DefaultResolvers []*dnstype.Resolver
// Routes maps a DNS suffix to the resolvers that should be used
// for queries that fall within that suffix.
// If a query doesn't match any entry in Routes, the
// DefaultResolvers are used.
// A Routes entry with no resolvers means the route should be
// authoritatively answered using the contents of Hosts.
Routes map[dnsname.FQDN][]*dnstype.Resolver
// SearchDomains are DNS suffixes to try when expanding
// single-label queries.
SearchDomains []dnsname.FQDN
// Hosts maps DNS FQDNs to their IPs, which can be a mix of IPv4
// and IPv6.
// Queries matching entries in Hosts are resolved locally by
// 100.100.100.100 without leaving the machine.
// Adding an entry to Hosts merely creates the record. If you want
// it to resolve, you also need to add appropriate routes to
// Routes.
Hosts map[dnsname.FQDN][]netip.Addr
// OnlyIPv6, if true, uses the IPv6 service IP (for MagicDNS)
// instead of the IPv4 version (100.100.100.100).
OnlyIPv6 bool
}
var magicDNSDualStack = envknob.RegisterBool("TS_DEBUG_MAGIC_DNS_DUAL_STACK")
// serviceIPs returns the list of service IPs where MagicDNS is reachable.
//
// The provided knobs may be nil.
func (c *Config) serviceIPs(knobs *controlknobs.Knobs) []netip.Addr {
if c.OnlyIPv6 {
return []netip.Addr{tsaddr.TailscaleServiceIPv6()}
}
// TODO(bradfitz,mikeodr,raggi): include IPv6 here too; tailscale/tailscale#15404
// And add a controlknobs knob to disable dual stack.
//
// For now, opt-in for testing.
if magicDNSDualStack() {
return []netip.Addr{
tsaddr.TailscaleServiceIP(),
tsaddr.TailscaleServiceIPv6(),
}
}
return []netip.Addr{tsaddr.TailscaleServiceIP()}
}
// WriteToBufioWriter write a debug version of c for logs to w, omitting
// spammy stuff like *.arpa entries and replacing it with a total count.
func (c *Config) WriteToBufioWriter(w *bufio.Writer) {
w.WriteString("{DefaultResolvers:")
resolver.WriteDNSResolvers(w, c.DefaultResolvers)
w.WriteString(" Routes:")
resolver.WriteRoutes(w, c.Routes)
fmt.Fprintf(w, " SearchDomains:%v", c.SearchDomains)
fmt.Fprintf(w, " Hosts:%v", len(c.Hosts))
w.WriteString("}")
}
// needsAnyResolvers reports whether c requires a resolver to be set
// at the OS level.
func (c Config) needsOSResolver() bool {
return c.hasDefaultResolvers() || c.hasRoutes()
}
func (c Config) hasRoutes() bool {
return len(c.Routes) > 0
}
// hasDefaultIPResolversOnly reports whether the only resolvers in c are
// DefaultResolvers, and that those resolvers are simple IP addresses
// that speak regular port 53 DNS.
func (c Config) hasDefaultIPResolversOnly() bool {
if !c.hasDefaultResolvers() || c.hasRoutes() {
return false
}
for _, r := range c.DefaultResolvers {
if ipp, ok := r.IPPort(); !ok || ipp.Port() != 53 || publicdns.IPIsDoHOnlyServer(ipp.Addr()) {
return false
}
}
return true
}
// hasHostsWithoutSplitDNSRoutes reports whether c contains any Host entries
// that aren't covered by a SplitDNS route suffix.
func (c Config) hasHostsWithoutSplitDNSRoutes() bool {
// TODO(bradfitz): this could be more efficient, but we imagine
// the number of SplitDNS routes and/or hosts will be small.
for host := range c.Hosts {
if !c.hasSplitDNSRouteForHost(host) {
return true
}
}
return false
}
// hasSplitDNSRouteForHost reports whether c contains a SplitDNS route
// that contains hosts.
func (c Config) hasSplitDNSRouteForHost(host dnsname.FQDN) bool {
for route := range c.Routes {
if route.Contains(host) {
return true
}
}
return false
}
func (c Config) hasDefaultResolvers() bool {
return len(c.DefaultResolvers) > 0
}
// singleResolverSet returns the resolvers used by c.Routes if all
// routes use the same resolvers, or nil if multiple sets of resolvers
// are specified.
func (c Config) singleResolverSet() []*dnstype.Resolver {
var (
prev []*dnstype.Resolver
prevInitialized bool
)
for _, resolvers := range c.Routes {
if !prevInitialized {
prev = resolvers
prevInitialized = true
continue
}
if !sameResolverNames(prev, resolvers) {
return nil
}
}
return prev
}
// matchDomains returns the list of match suffixes needed by Routes.
func (c Config) matchDomains() []dnsname.FQDN {
ret := make([]dnsname.FQDN, 0, len(c.Routes))
for suffix := range c.Routes {
ret = append(ret, suffix)
}
sort.Slice(ret, func(i, j int) bool {
return ret[i].WithTrailingDot() < ret[j].WithTrailingDot()
})
return ret
}
func sameResolverNames(a, b []*dnstype.Resolver) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i].Addr != b[i].Addr {
return false
}
if !slices.Equal(a[i].BootstrapResolution, b[i].BootstrapResolution) {
return false
}
}
return true
}
|