net/dns: always offer MagicDNS records at 100.100.100.100.

Fixes #1886.

Signed-off-by: David Anderson <danderson@tailscale.com>
pull/1955/head
David Anderson 3 years ago committed by Dave Anderson
parent dd0b690e7b
commit 6690f86ef4

@ -1696,7 +1696,42 @@ func (b *LocalBackend) authReconfig() {
var dcfg dns.Config var dcfg dns.Config
// If CorpDNS is false, dcfg remains the zero value. // Populate MagicDNS records. We do this unconditionally so that
// quad-100 can always respond to MagicDNS queries, even if the OS
// isn't configured to make MagicDNS resolution truly
// magic. Details in
// https://github.com/tailscale/tailscale/issues/1886.
set := func(name string, addrs []netaddr.IPPrefix) {
if len(addrs) == 0 || name == "" {
return
}
fqdn, err := dnsname.ToFQDN(name)
if err != nil {
return // TODO: propagate error?
}
var ips []netaddr.IP
for _, addr := range addrs {
// Remove IPv6 addresses for now, as we don't
// guarantee that the peer node actually can speak
// IPv6 correctly.
//
// https://github.com/tailscale/tailscale/issues/1152
// tracks adding the right capability reporting to
// enable AAAA in MagicDNS.
if addr.IP().Is6() {
continue
}
ips = append(ips, addr.IP())
}
dcfg.Hosts[fqdn] = ips
}
dcfg.AuthoritativeSuffixes = magicDNSRootDomains(nm)
dcfg.Hosts = map[dnsname.FQDN][]netaddr.IP{}
set(nm.Name, nm.Addresses)
for _, peer := range nm.Peers {
set(peer.Name, peer.Addresses)
}
if uc.CorpDNS { if uc.CorpDNS {
addDefault := func(resolvers []tailcfg.DNSResolver) { addDefault := func(resolvers []tailcfg.DNSResolver) {
for _, resolver := range resolvers { for _, resolver := range resolvers {
@ -1734,36 +1769,9 @@ func (b *LocalBackend) authReconfig() {
} }
dcfg.SearchDomains = append(dcfg.SearchDomains, fqdn) dcfg.SearchDomains = append(dcfg.SearchDomains, fqdn)
} }
set := func(name string, addrs []netaddr.IPPrefix) {
if len(addrs) == 0 || name == "" {
return
}
fqdn, err := dnsname.ToFQDN(name)
if err != nil {
return // TODO: propagate error?
}
var ips []netaddr.IP
for _, addr := range addrs {
// Remove IPv6 addresses for now, as we don't
// guarantee that the peer node actually can speak
// IPv6 correctly.
//
// https://github.com/tailscale/tailscale/issues/1152
// tracks adding the right capability reporting to
// enable AAAA in MagicDNS.
if addr.IP().Is6() {
continue
}
ips = append(ips, addr.IP())
}
dcfg.Hosts[fqdn] = ips
}
if nm.DNS.Proxied { // actually means "enable MagicDNS" if nm.DNS.Proxied { // actually means "enable MagicDNS"
dcfg.AuthoritativeSuffixes = magicDNSRootDomains(nm) for _, dom := range dcfg.AuthoritativeSuffixes {
dcfg.Hosts = map[dnsname.FQDN][]netaddr.IP{} dcfg.Routes[dom] = []netaddr.IPPort{netaddr.IPPortFrom(tsaddr.TailscaleServiceIP(), 53)}
set(nm.Name, nm.Addresses)
for _, peer := range nm.Peers {
set(peer.Name, peer.Addresses)
} }
} }

@ -28,8 +28,11 @@ type Config struct {
SearchDomains []dnsname.FQDN SearchDomains []dnsname.FQDN
// Hosts maps DNS FQDNs to their IPs, which can be a mix of IPv4 // Hosts maps DNS FQDNs to their IPs, which can be a mix of IPv4
// and IPv6. // and IPv6.
// Queries matching entries in Hosts are resolved locally without // Queries matching entries in Hosts are resolved locally by
// recursing off-machine. // 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][]netaddr.IP Hosts map[dnsname.FQDN][]netaddr.IP
// AuthoritativeSuffixes is a list of fully-qualified DNS suffixes // AuthoritativeSuffixes is a list of fully-qualified DNS suffixes
// for which the in-process Tailscale resolver is authoritative. // for which the in-process Tailscale resolver is authoritative.
@ -42,7 +45,7 @@ type Config struct {
// needsAnyResolvers reports whether c requires a resolver to be set // needsAnyResolvers reports whether c requires a resolver to be set
// at the OS level. // at the OS level.
func (c Config) needsOSResolver() bool { func (c Config) needsOSResolver() bool {
return c.hasDefaultResolvers() || c.hasRoutes() || c.hasHosts() return c.hasDefaultResolvers() || c.hasRoutes()
} }
func (c Config) hasRoutes() bool { func (c Config) hasRoutes() bool {
@ -52,7 +55,7 @@ func (c Config) hasRoutes() bool {
// hasDefaultResolversOnly reports whether the only resolvers in c are // hasDefaultResolversOnly reports whether the only resolvers in c are
// DefaultResolvers. // DefaultResolvers.
func (c Config) hasDefaultResolversOnly() bool { func (c Config) hasDefaultResolversOnly() bool {
return c.hasDefaultResolvers() && !c.hasRoutes() && !c.hasHosts() return c.hasDefaultResolvers() && !c.hasRoutes()
} }
func (c Config) hasDefaultResolvers() bool { func (c Config) hasDefaultResolvers() bool {
@ -76,31 +79,11 @@ func (c Config) singleResolverSet() []netaddr.IPPort {
return first return first
} }
// hasHosts reports whether c requires resolution of MagicDNS hosts or // matchDomains returns the list of match suffixes needed by Routes.
// domains.
func (c Config) hasHosts() bool {
return len(c.Hosts) > 0 || len(c.AuthoritativeSuffixes) > 0
}
// matchDomains returns the list of match suffixes needed by Routes,
// AuthoritativeSuffixes. Hosts is not considered as we assume that
// they're covered by AuthoritativeSuffixes for now.
func (c Config) matchDomains() []dnsname.FQDN { func (c Config) matchDomains() []dnsname.FQDN {
ret := make([]dnsname.FQDN, 0, len(c.Routes)+len(c.AuthoritativeSuffixes)) ret := make([]dnsname.FQDN, 0, len(c.Routes))
seen := map[dnsname.FQDN]bool{}
for _, suffix := range c.AuthoritativeSuffixes {
if seen[suffix] {
continue
}
ret = append(ret, suffix)
seen[suffix] = true
}
for suffix := range c.Routes { for suffix := range c.Routes {
if seen[suffix] {
continue
}
ret = append(ret, suffix) ret = append(ret, suffix)
seen[suffix] = true
} }
sort.Slice(ret, func(i, j int) bool { sort.Slice(ret, func(i, j int) bool {
return ret[i].WithTrailingDot() < ret[j].WithTrailingDot() return ret[i].WithTrailingDot() < ret[j].WithTrailingDot()

@ -6,7 +6,6 @@ package dns
import ( import (
"runtime" "runtime"
"strings"
"time" "time"
"inet.af/netaddr" "inet.af/netaddr"
@ -75,40 +74,51 @@ func (m *Manager) Set(cfg Config) error {
// compileConfig converts cfg into a quad-100 resolver configuration // compileConfig converts cfg into a quad-100 resolver configuration
// and an OS-level configuration. // and an OS-level configuration.
func (m *Manager) compileConfig(cfg Config) (resolver.Config, OSConfig, error) { func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig, err error) {
authDomains := make(map[dnsname.FQDN]bool, len(cfg.AuthoritativeSuffixes))
for _, dom := range cfg.AuthoritativeSuffixes {
authDomains[dom] = true
}
addRoutes := func() {
for suffix, resolvers := range cfg.Routes {
// Don't add resolver routes for authoritative domains,
// since they're meant to be authoritatively handled
// internally.
if authDomains[suffix] {
continue
}
rcfg.Routes[suffix] = resolvers
}
}
// 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.LocalDomains = cfg.AuthoritativeSuffixes
// Similarly, the OS always gets search paths.
ocfg.SearchDomains = cfg.SearchDomains
// Deal with trivial configs first. // Deal with trivial configs first.
switch { switch {
case !cfg.needsOSResolver(): case !cfg.needsOSResolver():
// Set search domains, but nothing else. This also covers the // Set search domains, but nothing else. This also covers the
// case where cfg is entirely zero, in which case these // case where cfg is entirely zero, in which case these
// configs clear all Tailscale DNS settings. // configs clear all Tailscale DNS settings.
return resolver.Config{}, OSConfig{ return rcfg, ocfg, nil
SearchDomains: cfg.SearchDomains,
}, nil
case cfg.hasDefaultResolversOnly(): case cfg.hasDefaultResolversOnly():
// Trivial CorpDNS configuration, just override the OS // Trivial CorpDNS configuration, just override the OS
// resolver. // resolver.
return resolver.Config{}, OSConfig{ ocfg.Nameservers = toIPsOnly(cfg.DefaultResolvers)
Nameservers: toIPsOnly(cfg.DefaultResolvers), return rcfg, ocfg, nil
SearchDomains: cfg.SearchDomains,
}, nil
case cfg.hasDefaultResolvers(): case cfg.hasDefaultResolvers():
// Default resolvers plus other stuff always ends up proxying // Default resolvers plus other stuff always ends up proxying
// through quad-100. // through quad-100.
rcfg := resolver.Config{ rcfg.Routes = map[dnsname.FQDN][]netaddr.IPPort{
Routes: map[dnsname.FQDN][]netaddr.IPPort{ ".": cfg.DefaultResolvers,
".": cfg.DefaultResolvers,
},
Hosts: cfg.Hosts,
LocalDomains: cfg.AuthoritativeSuffixes,
}
for suffix, resolvers := range cfg.Routes {
rcfg.Routes[suffix] = resolvers
}
ocfg := OSConfig{
Nameservers: []netaddr.IP{tsaddr.TailscaleServiceIP()},
SearchDomains: cfg.SearchDomains,
} }
addRoutes()
ocfg.Nameservers = []netaddr.IP{tsaddr.TailscaleServiceIP()}
return rcfg, ocfg, nil return rcfg, ocfg, nil
} }
@ -116,8 +126,6 @@ func (m *Manager) compileConfig(cfg Config) (resolver.Config, OSConfig, error) {
// configurations. The possible cases don't return directly any // configurations. The possible cases don't return directly any
// more, because as a final step we have to handle the case where // more, because as a final step we have to handle the case where
// the OS can't do split DNS. // the OS can't do split DNS.
var rcfg resolver.Config
var ocfg OSConfig
// Workaround for // Workaround for
// https://github.com/tailscale/corp/issues/1662. Even though // https://github.com/tailscale/corp/issues/1662. Even though
@ -135,35 +143,20 @@ func (m *Manager) compileConfig(cfg Config) (resolver.Config, OSConfig, error) {
// This bool is used in a couple of places below to implement this // This bool is used in a couple of places below to implement this
// workaround. // workaround.
isWindows := runtime.GOOS == "windows" isWindows := runtime.GOOS == "windows"
if cfg.singleResolverSet() != nil && m.os.SupportsSplitDNS() && !isWindows {
// The windows check is for
// . See also below
// for further routing workarounds there.
if !cfg.hasHosts() && cfg.singleResolverSet() != nil && m.os.SupportsSplitDNS() && !isWindows {
// Split DNS configuration requested, where all split domains // Split DNS configuration requested, where all split domains
// go to the same resolvers. We can let the OS do it. // go to the same resolvers. We can let the OS do it.
return resolver.Config{}, OSConfig{ ocfg.Nameservers = toIPsOnly(cfg.singleResolverSet())
Nameservers: toIPsOnly(cfg.singleResolverSet()), ocfg.MatchDomains = cfg.matchDomains()
SearchDomains: cfg.SearchDomains, return rcfg, ocfg, nil
MatchDomains: cfg.matchDomains(),
}, nil
} }
// Split DNS configuration with either multiple upstream routes, // Split DNS configuration with either multiple upstream routes,
// or routes + MagicDNS, or just MagicDNS, or on an OS that cannot // or routes + MagicDNS, or just MagicDNS, or on an OS that cannot
// split-DNS. Install a split config pointing at quad-100. // split-DNS. Install a split config pointing at quad-100.
rcfg = resolver.Config{ rcfg.Routes = map[dnsname.FQDN][]netaddr.IPPort{}
Hosts: cfg.Hosts, addRoutes()
LocalDomains: cfg.AuthoritativeSuffixes, ocfg.Nameservers = []netaddr.IP{tsaddr.TailscaleServiceIP()}
Routes: map[dnsname.FQDN][]netaddr.IPPort{},
}
for suffix, resolvers := range cfg.Routes {
rcfg.Routes[suffix] = resolvers
}
ocfg = OSConfig{
Nameservers: []netaddr.IP{tsaddr.TailscaleServiceIP()},
SearchDomains: cfg.SearchDomains,
}
// If the OS can't do native split-dns, read out the underlying // 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.
@ -173,28 +166,7 @@ func (m *Manager) compileConfig(cfg Config) (resolver.Config, OSConfig, error) {
if !m.os.SupportsSplitDNS() || isWindows { if !m.os.SupportsSplitDNS() || isWindows {
bcfg, err := m.os.GetBaseConfig() bcfg, err := m.os.GetBaseConfig()
if err != nil { if err != nil {
// Temporary hack to make OSes where split-DNS isn't fully return resolver.Config{}, OSConfig{}, err
// implemented yet not completely crap out, but instead
// fall back to quad-9 as a hardcoded "backup resolver".
//
// This codepath currently only triggers when opted into
// the split-DNS feature server side, and when at least
// one search domain is something within tailscale.com, so
// we don't accidentally leak unstable user DNS queries to
// quad-9 if we accidentally go down this codepath.
canUseHack := false
for _, dom := range cfg.SearchDomains {
if strings.HasSuffix(dom.WithoutTrailingDot(), ".tailscale.com") {
canUseHack = true
break
}
}
if !canUseHack {
return resolver.Config{}, OSConfig{}, err
}
bcfg = OSConfig{
Nameservers: []netaddr.IP{netaddr.IPv4(9, 9, 9, 9)},
}
} }
rcfg.Routes["."] = toIPPorts(bcfg.Nameservers) rcfg.Routes["."] = toIPPorts(bcfg.Nameservers)
ocfg.SearchDomains = append(ocfg.SearchDomains, bcfg.SearchDomains...) ocfg.SearchDomains = append(ocfg.SearchDomains, bcfg.SearchDomains...)

@ -76,6 +76,22 @@ func TestManager(t *testing.T) {
SearchDomains: fqdns("tailscale.com", "universe.tf"), SearchDomains: fqdns("tailscale.com", "universe.tf"),
}, },
}, },
{
// Regression test for https://github.com/tailscale/tailscale/issues/1886
name: "hosts-only",
in: Config{
Hosts: hosts(
"dave.ts.com.", "1.2.3.4",
"bradfitz.ts.com.", "2.3.4.5"),
AuthoritativeSuffixes: fqdns("ts.com"),
},
rs: resolver.Config{
Hosts: hosts(
"dave.ts.com.", "1.2.3.4",
"bradfitz.ts.com.", "2.3.4.5"),
LocalDomains: fqdns("ts.com"),
},
},
{ {
name: "corp", name: "corp",
in: Config{ in: Config{
@ -104,6 +120,7 @@ func TestManager(t *testing.T) {
in: Config{ in: Config{
DefaultResolvers: mustIPPs("1.1.1.1:53", "9.9.9.9:53"), DefaultResolvers: mustIPPs("1.1.1.1:53", "9.9.9.9:53"),
SearchDomains: fqdns("tailscale.com", "universe.tf"), SearchDomains: fqdns("tailscale.com", "universe.tf"),
Routes: upstreams("ts.com", "100.100.100.100:53"),
Hosts: hosts( Hosts: hosts(
"dave.ts.com.", "1.2.3.4", "dave.ts.com.", "1.2.3.4",
"bradfitz.ts.com.", "2.3.4.5"), "bradfitz.ts.com.", "2.3.4.5"),
@ -126,6 +143,7 @@ func TestManager(t *testing.T) {
in: Config{ in: Config{
DefaultResolvers: mustIPPs("1.1.1.1:53", "9.9.9.9:53"), DefaultResolvers: mustIPPs("1.1.1.1:53", "9.9.9.9:53"),
SearchDomains: fqdns("tailscale.com", "universe.tf"), SearchDomains: fqdns("tailscale.com", "universe.tf"),
Routes: upstreams("ts.com", "100.100.100.100:53"),
Hosts: hosts( Hosts: hosts(
"dave.ts.com.", "1.2.3.4", "dave.ts.com.", "1.2.3.4",
"bradfitz.ts.com.", "2.3.4.5"), "bradfitz.ts.com.", "2.3.4.5"),
@ -261,6 +279,7 @@ func TestManager(t *testing.T) {
Hosts: hosts( Hosts: hosts(
"dave.ts.com.", "1.2.3.4", "dave.ts.com.", "1.2.3.4",
"bradfitz.ts.com.", "2.3.4.5"), "bradfitz.ts.com.", "2.3.4.5"),
Routes: upstreams("ts.com", "100.100.100.100:53"),
AuthoritativeSuffixes: fqdns("ts.com"), AuthoritativeSuffixes: fqdns("ts.com"),
SearchDomains: fqdns("tailscale.com", "universe.tf"), SearchDomains: fqdns("tailscale.com", "universe.tf"),
}, },
@ -286,6 +305,7 @@ func TestManager(t *testing.T) {
Hosts: hosts( Hosts: hosts(
"dave.ts.com.", "1.2.3.4", "dave.ts.com.", "1.2.3.4",
"bradfitz.ts.com.", "2.3.4.5"), "bradfitz.ts.com.", "2.3.4.5"),
Routes: upstreams("ts.com", "100.100.100.100:53"),
AuthoritativeSuffixes: fqdns("ts.com"), AuthoritativeSuffixes: fqdns("ts.com"),
SearchDomains: fqdns("tailscale.com", "universe.tf"), SearchDomains: fqdns("tailscale.com", "universe.tf"),
}, },
@ -333,7 +353,9 @@ func TestManager(t *testing.T) {
{ {
name: "routes-magic-split", name: "routes-magic-split",
in: Config{ in: Config{
Routes: upstreams("corp.com", "2.2.2.2:53"), Routes: upstreams(
"corp.com", "2.2.2.2:53",
"ts.com", "100.100.100.100:53"),
Hosts: hosts( Hosts: hosts(
"dave.ts.com.", "1.2.3.4", "dave.ts.com.", "1.2.3.4",
"bradfitz.ts.com.", "2.3.4.5"), "bradfitz.ts.com.", "2.3.4.5"),

Loading…
Cancel
Save