diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index aa73cff93..f813be528 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -91,7 +91,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/logtail/filch from tailscale.com/logpolicy tailscale.com/metrics from tailscale.com/derp tailscale.com/net/dns from tailscale.com/ipn/ipnlocal+ - tailscale.com/net/dns/resolver from tailscale.com/ipn/ipnlocal+ + tailscale.com/net/dns/resolver from tailscale.com/wgengine tailscale.com/net/dnscache from tailscale.com/control/controlclient+ tailscale.com/net/dnsfallback from tailscale.com/control/controlclient tailscale.com/net/flowtrack from tailscale.com/wgengine/filter+ diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index e04df3f4e..265366a01 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -28,7 +28,6 @@ import ( "tailscale.com/ipn/ipnstate" "tailscale.com/ipn/policy" "tailscale.com/net/dns" - "tailscale.com/net/dns/resolver" "tailscale.com/net/interfaces" "tailscale.com/net/tsaddr" "tailscale.com/paths" @@ -440,9 +439,6 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) { b.updateFilter(st.NetMap, prefs) b.e.SetNetworkMap(st.NetMap) - if !dnsMapsEqual(st.NetMap, netMap) { - b.updateDNSMap(st.NetMap) - } b.e.SetDERPMap(st.NetMap.DERPMap) b.send(ipn.Notify{NetMap: st.NetMap}) @@ -851,32 +847,6 @@ func dnsMapsEqual(new, old *netmap.NetworkMap) bool { return true } -// updateDNSMap updates the domain map in the DNS resolver in wgengine -// based on the given netMap and user preferences. -func (b *LocalBackend) updateDNSMap(netMap *netmap.NetworkMap) { - if netMap == nil { - b.logf("dns map: (not ready)") - return - } - - nameToIP := make(map[string]netaddr.IP) - set := func(name string, addrs []netaddr.IPPrefix) { - if len(addrs) == 0 || name == "" { - return - } - nameToIP[name] = addrs[0].IP - } - - for _, peer := range netMap.Peers { - set(peer.Name, peer.Addresses) - } - set(netMap.Name, netMap.Addresses) - - dnsMap := resolver.NewMap(nameToIP, magicDNSRootDomains(netMap)) - // map diff will be logged in dns.Resolver.SetMap. - b.e.SetDNSMap(dnsMap) -} - // readPoller is a goroutine that receives service lists from // b.portpoll and propagates them into the controlclient's HostInfo. func (b *LocalBackend) readPoller() { @@ -1487,7 +1457,21 @@ func (b *LocalBackend) authReconfig() { } } - err = b.e.Reconfig(cfg, rcfg) + nameToIP := make(map[string][]netaddr.IP) + set := func(name string, addrs []netaddr.IPPrefix) { + if len(addrs) == 0 || name == "" { + return + } + for _, addr := range addrs { + nameToIP[name] = append(nameToIP[name], addr.IP) + } + } + for _, peer := range nm.Peers { + set(peer.Name, peer.Addresses) + } + set(nm.Name, nm.Addresses) + + err = b.e.Reconfig(cfg, rcfg, nameToIP, magicDNSRootDomains(nm)) if err == wgengine.ErrNoChanges { return } @@ -1743,7 +1727,7 @@ func (b *LocalBackend) enterState(newState ipn.State) { b.blockEngineUpdates(true) fallthrough case ipn.Stopped: - err := b.e.Reconfig(&wgcfg.Config{}, &router.Config{}) + err := b.e.Reconfig(&wgcfg.Config{}, &router.Config{}, nil, nil) if err != nil { b.logf("Reconfig(down): %v", err) } @@ -1835,7 +1819,7 @@ func (b *LocalBackend) stateMachine() { // a status update that predates the "I've shut down" update. func (b *LocalBackend) stopEngineAndWait() { b.logf("stopEngineAndWait...") - b.e.Reconfig(&wgcfg.Config{}, &router.Config{}) + b.e.Reconfig(&wgcfg.Config{}, &router.Config{}, nil, nil) b.requestEngineStatusAndWait() b.logf("stopEngineAndWait: done.") } diff --git a/net/dns/resolver/map.go b/net/dns/resolver/map.go deleted file mode 100644 index c73aef9a8..000000000 --- a/net/dns/resolver/map.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package resolver - -import ( - "sort" - "strings" - - "inet.af/netaddr" -) - -// Map is all the data Resolver needs to resolve DNS queries within the Tailscale network. -type Map struct { - // nameToIP is a mapping of Tailscale domain names to their IP addresses. - // For example, monitoring.tailscale.us -> 100.64.0.1. - nameToIP map[string]netaddr.IP - // ipToName is the inverse of nameToIP. - ipToName map[netaddr.IP]string - // names are the keys of nameToIP in sorted order. - names []string - // rootDomains are the domains whose subdomains should always - // be resolved locally to prevent leakage of sensitive names. - rootDomains []string // e.g. "user.provider.beta.tailscale.net." -} - -// NewMap returns a new Map with name to address mapping given by nameToIP. -// -// rootDomains are the domains whose subdomains should always be -// resolved locally to prevent leakage of sensitive names. They should -// end in a period ("user-foo.tailscale.net."). -func NewMap(initNameToIP map[string]netaddr.IP, rootDomains []string) *Map { - // TODO(dmytro): we have to allocate names and ipToName, but nameToIP can be avoided. - // It is here because control sends us names not in canonical form. Change this. - names := make([]string, 0, len(initNameToIP)) - nameToIP := make(map[string]netaddr.IP, len(initNameToIP)) - ipToName := make(map[netaddr.IP]string, len(initNameToIP)) - - for name, ip := range initNameToIP { - if len(name) == 0 { - // Nothing useful can be done with empty names. - continue - } - if name[len(name)-1] != '.' { - name += "." - } - names = append(names, name) - nameToIP[name] = ip - ipToName[ip] = name - } - sort.Strings(names) - - return &Map{ - nameToIP: nameToIP, - ipToName: ipToName, - names: names, - - rootDomains: rootDomains, - } -} - -func printSingleNameIP(buf *strings.Builder, name string, ip netaddr.IP) { - buf.WriteString(name) - buf.WriteByte('\t') - buf.WriteString(ip.String()) - buf.WriteByte('\n') -} - -func (m *Map) Pretty() string { - buf := new(strings.Builder) - for _, name := range m.names { - printSingleNameIP(buf, name, m.nameToIP[name]) - } - return buf.String() -} - -func (m *Map) PrettyDiffFrom(old *Map) string { - var ( - oldNameToIP map[string]netaddr.IP - newNameToIP map[string]netaddr.IP - oldNames []string - newNames []string - ) - if old != nil { - oldNameToIP = old.nameToIP - oldNames = old.names - } - if m != nil { - newNameToIP = m.nameToIP - newNames = m.names - } - - buf := new(strings.Builder) - space := func() bool { - return buf.Len() < (1 << 10) - } - - for len(oldNames) > 0 && len(newNames) > 0 { - var name string - - newName, oldName := newNames[0], oldNames[0] - switch { - case oldName < newName: - name = oldName - oldNames = oldNames[1:] - case oldName > newName: - name = newName - newNames = newNames[1:] - case oldNames[0] == newNames[0]: - name = oldNames[0] - oldNames = oldNames[1:] - newNames = newNames[1:] - } - if !space() { - continue - } - - ipOld, inOld := oldNameToIP[name] - ipNew, inNew := newNameToIP[name] - switch { - case !inOld: - buf.WriteByte('+') - printSingleNameIP(buf, name, ipNew) - case !inNew: - buf.WriteByte('-') - printSingleNameIP(buf, name, ipOld) - case ipOld != ipNew: - buf.WriteByte('-') - printSingleNameIP(buf, name, ipOld) - buf.WriteByte('+') - printSingleNameIP(buf, name, ipNew) - } - } - - for _, name := range oldNames { - if !space() { - break - } - if _, ok := newNameToIP[name]; !ok { - buf.WriteByte('-') - printSingleNameIP(buf, name, oldNameToIP[name]) - } - } - - for _, name := range newNames { - if !space() { - break - } - if _, ok := oldNameToIP[name]; !ok { - buf.WriteByte('+') - printSingleNameIP(buf, name, newNameToIP[name]) - } - } - if !space() { - buf.WriteString("... [truncated]\n") - } - - return buf.String() -} diff --git a/net/dns/resolver/map_test.go b/net/dns/resolver/map_test.go deleted file mode 100644 index 784240ae7..000000000 --- a/net/dns/resolver/map_test.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package resolver - -import ( - "fmt" - "strings" - "testing" - - "inet.af/netaddr" -) - -func TestPretty(t *testing.T) { - tests := []struct { - name string - dmap *Map - want string - }{ - {"empty", NewMap(nil, nil), ""}, - { - "single", - NewMap(map[string]netaddr.IP{ - "hello.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), - }, nil), - "hello.ipn.dev.\t100.101.102.103\n", - }, - { - "multiple", - NewMap(map[string]netaddr.IP{ - "test1.domain.": netaddr.IPv4(100, 101, 102, 103), - "test2.sub.domain.": netaddr.IPv4(100, 99, 9, 1), - }, nil), - "test1.domain.\t100.101.102.103\ntest2.sub.domain.\t100.99.9.1\n", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.dmap.Pretty() - if tt.want != got { - t.Errorf("want %v; got %v", tt.want, got) - } - }) - } -} - -func TestPrettyDiffFrom(t *testing.T) { - tests := []struct { - name string - map1 *Map - map2 *Map - want string - }{ - { - "from_empty", - nil, - NewMap(map[string]netaddr.IP{ - "test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), - "test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), - }, nil), - "+test1.ipn.dev.\t100.101.102.103\n+test2.ipn.dev.\t100.103.102.101\n", - }, - { - "equal", - NewMap(map[string]netaddr.IP{ - "test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), - "test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), - }, nil), - NewMap(map[string]netaddr.IP{ - "test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), - "test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), - }, nil), - "", - }, - { - "changed_ip", - NewMap(map[string]netaddr.IP{ - "test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), - "test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), - }, nil), - NewMap(map[string]netaddr.IP{ - "test2.ipn.dev.": netaddr.IPv4(100, 104, 102, 101), - "test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), - }, nil), - "-test2.ipn.dev.\t100.103.102.101\n+test2.ipn.dev.\t100.104.102.101\n", - }, - { - "new_domain", - NewMap(map[string]netaddr.IP{ - "test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), - "test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), - }, nil), - NewMap(map[string]netaddr.IP{ - "test3.ipn.dev.": netaddr.IPv4(100, 105, 106, 107), - "test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), - "test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), - }, nil), - "+test3.ipn.dev.\t100.105.106.107\n", - }, - { - "gone_domain", - NewMap(map[string]netaddr.IP{ - "test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), - "test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), - }, nil), - NewMap(map[string]netaddr.IP{ - "test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), - }, nil), - "-test2.ipn.dev.\t100.103.102.101\n", - }, - { - "mixed", - NewMap(map[string]netaddr.IP{ - "test1.ipn.dev.": netaddr.IPv4(100, 101, 102, 103), - "test4.ipn.dev.": netaddr.IPv4(100, 107, 106, 105), - "test5.ipn.dev.": netaddr.IPv4(100, 64, 1, 1), - "test2.ipn.dev.": netaddr.IPv4(100, 103, 102, 101), - }, nil), - NewMap(map[string]netaddr.IP{ - "test2.ipn.dev.": netaddr.IPv4(100, 104, 102, 101), - "test1.ipn.dev.": netaddr.IPv4(100, 100, 101, 102), - "test3.ipn.dev.": netaddr.IPv4(100, 64, 1, 1), - }, nil), - "-test1.ipn.dev.\t100.101.102.103\n+test1.ipn.dev.\t100.100.101.102\n" + - "-test2.ipn.dev.\t100.103.102.101\n+test2.ipn.dev.\t100.104.102.101\n" + - "+test3.ipn.dev.\t100.64.1.1\n-test4.ipn.dev.\t100.107.106.105\n-test5.ipn.dev.\t100.64.1.1\n", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.map2.PrettyDiffFrom(tt.map1) - if tt.want != got { - t.Errorf("want %v; got %v", tt.want, got) - } - }) - } - - t.Run("truncated", func(t *testing.T) { - small := NewMap(nil, nil) - m := map[string]netaddr.IP{} - for i := 0; i < 5000; i++ { - m[fmt.Sprintf("host%d.ipn.dev.", i)] = netaddr.IPv4(100, 64, 1, 1) - } - veryBig := NewMap(m, nil) - diff := veryBig.PrettyDiffFrom(small) - if len(diff) > 3<<10 { - t.Errorf("pretty diff too large: %d bytes", len(diff)) - } - if !strings.Contains(diff, "truncated") { - t.Errorf("big diff not truncated") - } - }) -} diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go index 632131f93..d0741c614 100644 --- a/net/dns/resolver/tsdns.go +++ b/net/dns/resolver/tsdns.go @@ -9,7 +9,9 @@ package resolver import ( "encoding/hex" "errors" + "fmt" "net" + "sort" "strings" "sync" "time" @@ -37,11 +39,9 @@ const defaultTTL = 600 * time.Second var ErrClosed = errors.New("closed") var ( - errFullQueue = errors.New("request queue full") - errMapNotSet = errors.New("domain map not set") - errNotImplemented = errors.New("query type not implemented") - errNotQuery = errors.New("not a DNS query") - errNotOurName = errors.New("not a Tailscale DNS name") + errFullQueue = errors.New("request queue full") + errNotQuery = errors.New("not a DNS query") + errNotOurName = errors.New("not a Tailscale DNS name") ) type packet struct { @@ -49,6 +49,30 @@ type packet struct { addr netaddr.IPPort // src for a request, dst for a response } +// Config is a resolver configuration. +// Given a Config, queries are resolved in the following order: +// If the query is an exact match for an entry in LocalHosts, return that. +// Else if the query suffix matches an entry in LocalDomains, return NXDOMAIN. +// Else forward the query to the most specific matching entry in Routes. +// Else return SERVFAIL. +type Config struct { + // Routes is a map of DNS name suffix to the resolvers to use for + // queries within that suffix. + // Queries only match the most specific suffix. + // To register a "default route", add an entry for ".". + Routes map[string][]netaddr.IPPort + // LocalHosts is a map of FQDNs to corresponding IPs. + Hosts map[string][]netaddr.IP + // LocalDomains is a list of DNS name suffixes that should not be + // routed to upstream resolvers. + LocalDomains []string +} + +type route struct { + suffix string + resolvers []netaddr.IPPort +} + // Resolver is a DNS resolver for nodes on the Tailscale network, // associating them with domain names of the form ... // If it is asked to resolve a domain that is not of that form, @@ -72,9 +96,11 @@ type Resolver struct { wg sync.WaitGroup // mu guards the following fields from being updated while used. - mu sync.Mutex - // dnsMap is the map most recently received from the control server. - dnsMap *Map + mu sync.Mutex + localDomains []string + hostToIP map[string][]netaddr.IP + ipToHost map[netaddr.IP]string + routes []route // most specific routes first } // New returns a new resolver. @@ -87,6 +113,8 @@ func New(logf logger.Logf, linkMon *monitor.Mon) (*Resolver, error) { responses: make(chan packet), errors: make(chan error), closed: make(chan struct{}), + hostToIP: map[string][]netaddr.IP{}, + ipToHost: map[netaddr.IP]string{}, } r.forwarder = newForwarder(r.logf, r.responses) if r.linkMon != nil { @@ -103,6 +131,66 @@ func New(logf logger.Logf, linkMon *monitor.Mon) (*Resolver, error) { return r, nil } +func isFQDN(s string) bool { + return strings.HasSuffix(s, ".") +} + +func (r *Resolver) SetConfig(cfg Config) error { + routes := make([]route, 0, len(cfg.Routes)) + reverse := make(map[netaddr.IP]string, len(cfg.Hosts)) + var defaultUpstream []net.Addr + + for host, ips := range cfg.Hosts { + if !isFQDN(host) { + return fmt.Errorf("host entry %q is not a FQDN", host) + } + for _, ip := range ips { + reverse[ip] = host + } + } + for _, domain := range cfg.LocalDomains { + if !isFQDN(domain) { + return fmt.Errorf("local domain %q is not a FQDN", domain) + } + } + + for suffix, ips := range cfg.Routes { + if !strings.HasSuffix(suffix, ".") { + return fmt.Errorf("route suffix %q is not a FQDN", suffix) + } + routes = append(routes, route{ + suffix: suffix, + resolvers: ips, + }) + if suffix == "." { + // TODO: this is a temporary hack to forward upstream + // resolvers to the forwarder, which doesn't yet + // understand per-domain resolvers. Effectively, SetConfig + // currently ignores all routes except for ".", which it + // sets as the only resolver. + for _, ip := range ips { + up := ip.UDPAddr() + defaultUpstream = append(defaultUpstream, up) + } + } + } + // Sort from longest prefix to shortest. + sort.Slice(routes, func(i, j int) bool { + return strings.Count(routes[i].suffix, ".") > strings.Count(routes[j].suffix, ".") + }) + + r.forwarder.setUpstreams(defaultUpstream) + + r.mu.Lock() + defer r.mu.Unlock() + r.localDomains = cfg.LocalDomains + r.hostToIP = cfg.Hosts + r.ipToHost = reverse + r.routes = routes + + return nil +} + // Close shuts down the resolver and ensures poll goroutines have exited. // The Resolver cannot be used again after Close is called. func (r *Resolver) Close() { @@ -129,22 +217,6 @@ func (r *Resolver) onLinkMonitorChange(changed bool, state *interfaces.State) { r.forwarder.rebindFromNetworkChange() } -// SetMap sets the resolver's DNS map, taking ownership of it. -func (r *Resolver) SetMap(m *Map) { - r.mu.Lock() - oldMap := r.dnsMap - r.dnsMap = m - r.mu.Unlock() - r.logf("map diff:\n%s", m.PrettyDiffFrom(oldMap)) -} - -// SetUpstreams sets the addresses of the resolver's -// upstream nameservers, taking ownership of the argument. -func (r *Resolver) SetUpstreams(upstreams []net.Addr) { - r.forwarder.setUpstreams(upstreams) - r.logf("set upstreams: %v", upstreams) -} - // EnqueueRequest places the given DNS request in the resolver's queue. // It takes ownership of the payload and does not block. // If the queue is full, the request will be dropped and an error will be returned. @@ -172,62 +244,70 @@ func (r *Resolver) NextResponse() (packet []byte, to netaddr.IPPort, err error) } } -// resolve maps a given domain name to the IP address of the host that owns it, -// if the IP address conforms to the DNS resource type given by tp (one of A, AAAA, ALL). +// resolveLocal returns an IP for the given domain, if domain is in +// the local hosts map and has an IP corresponding to the requested +// typ (A, AAAA, ALL). // The domain name must be in canonical form (with a trailing period). -func (r *Resolver) resolve(domain string, tp dns.Type) (netaddr.IP, dns.RCode, error) { - r.mu.Lock() - dnsMap := r.dnsMap - r.mu.Unlock() - - if dnsMap == nil { - return netaddr.IP{}, dns.RCodeServerFailure, errMapNotSet - } - +// Returns dns.RCodeRefused to indicate that the local map is not +// authoritative for domain. +func (r *Resolver) resolveLocal(domain string, typ dns.Type) (netaddr.IP, dns.RCode) { // Reject .onion domains per RFC 7686. if dnsname.HasSuffix(domain, ".onion") { - return netaddr.IP{}, dns.RCodeNameError, nil + return netaddr.IP{}, dns.RCodeNameError } - anyHasSuffix := false - for _, suffix := range dnsMap.rootDomains { - if dnsname.HasSuffix(domain, suffix) { - anyHasSuffix = true - break - } - } - addr, found := dnsMap.nameToIP[domain] + r.mu.Lock() + hosts := r.hostToIP + localDomains := r.localDomains + r.mu.Unlock() + + addrs, found := hosts[domain] if !found { - if !anyHasSuffix { - return netaddr.IP{}, dns.RCodeRefused, nil + for _, suffix := range localDomains { + if dnsname.HasSuffix(domain, suffix) { + // We are authoritative for the queried domain. + return netaddr.IP{}, dns.RCodeNameError + } } - return netaddr.IP{}, dns.RCodeNameError, nil + // Not authoritative, signal that forwarding is advisable. + return netaddr.IP{}, dns.RCodeRefused } // Refactoring note: this must happen after we check suffixes, // otherwise we will respond with NOTIMP to requests that should be forwarded. - switch tp { + // + // DNS semantics subtlety: when a DNS name exists, but no records + // are available for the requested record type, we must return + // RCodeSuccess with no data, not NXDOMAIN. + switch typ { case dns.TypeA: - if !addr.Is4() { - return netaddr.IP{}, dns.RCodeSuccess, nil + for _, ip := range addrs { + if ip.Is4() { + return ip, dns.RCodeSuccess + } } - return addr, dns.RCodeSuccess, nil + return netaddr.IP{}, dns.RCodeSuccess case dns.TypeAAAA: - if !addr.Is6() { - return netaddr.IP{}, dns.RCodeSuccess, nil + for _, ip := range addrs { + if ip.Is6() { + return ip, dns.RCodeSuccess + } } - return addr, dns.RCodeSuccess, nil + return netaddr.IP{}, dns.RCodeSuccess case dns.TypeALL: // Answer with whatever we've got. // It could be IPv4, IPv6, or a zero addr. // TODO: Return all available resolutions (A and AAAA, if we have them). - return addr, dns.RCodeSuccess, nil + if len(addrs) == 0 { + return netaddr.IP{}, dns.RCodeSuccess + } + return addrs[0], dns.RCodeSuccess // Leave some some record types explicitly unimplemented. // These types relate to recursive resolution or special - // DNS sematics and might be implemented in the future. + // DNS semantics and might be implemented in the future. case dns.TypeNS, dns.TypeSOA, dns.TypeAXFR, dns.TypeHINFO: - return netaddr.IP{}, dns.RCodeNotImplemented, errNotImplemented + return netaddr.IP{}, dns.RCodeNotImplemented // For everything except for the few types above that are explictly not implemented, return no records. // This is what other DNS systems do: always return NOERROR @@ -236,26 +316,23 @@ func (r *Resolver) resolve(domain string, tp dns.Type) (netaddr.IP, dns.RCode, e // dig -t TYPE9824 example.com // and note that NOERROR is returned, despite that record type being made up. default: - // no records exist of this type - return netaddr.IP{}, dns.RCodeSuccess, nil + // The name exists, but no records exist of the requested type. + return netaddr.IP{}, dns.RCodeSuccess } } // resolveReverse returns the unique domain name that maps to the given address. // The returned domain name is in canonical form (with a trailing period). -func (r *Resolver) resolveReverse(ip netaddr.IP) (string, dns.RCode, error) { +func (r *Resolver) resolveLocalReverse(ip netaddr.IP) (string, dns.RCode) { r.mu.Lock() - dnsMap := r.dnsMap + ips := r.ipToHost r.mu.Unlock() - if dnsMap == nil { - return "", dns.RCodeServerFailure, errMapNotSet - } - name, found := dnsMap.ipToName[ip] + name, found := ips[ip] if !found { - return "", dns.RCodeNameError, nil + return "", dns.RCodeNameError } - return name, dns.RCodeSuccess, nil + return name, dns.RCodeSuccess } func (r *Resolver) poll() { @@ -567,11 +644,7 @@ func (r *Resolver) respondReverse(query []byte, name string, resp *response) ([] return nil, errNotOurName } - var err error - resp.Name, resp.Header.RCode, err = r.resolveReverse(ip) - if err != nil { - r.logf("resolving rdns: %v", ip, err) - } + resp.Name, resp.Header.RCode = r.resolveLocalReverse(ip) if resp.Header.RCode == dns.RCodeNameError { return nil, errNotOurName } @@ -608,16 +681,11 @@ func (r *Resolver) respond(query []byte) ([]byte, error) { return r.respondReverse(query, name, resp) } - resp.IP, resp.Header.RCode, err = r.resolve(name, resp.Question.Type) + resp.IP, resp.Header.RCode = r.resolveLocal(name, resp.Question.Type) // This return code is special: it requests forwarding. if resp.Header.RCode == dns.RCodeRefused { return nil, errNotOurName } - // We will not return this error: it is the sender's fault. - if err != nil { - r.logf("resolving: %v", err) - } - return marshalResponse(resp) } diff --git a/net/dns/resolver/tsdns_test.go b/net/dns/resolver/tsdns_test.go index 5452aab20..19a2561af 100644 --- a/net/dns/resolver/tsdns_test.go +++ b/net/dns/resolver/tsdns_test.go @@ -8,7 +8,6 @@ import ( "bytes" "errors" "net" - "sync" "testing" dns "golang.org/x/net/dns/dnsmessage" @@ -24,13 +23,13 @@ var testipv6 = netaddr.IPv6Raw([16]byte{ 0x0c, 0x0d, 0x0e, 0x0f, }) -var dnsMap = NewMap( - map[string]netaddr.IP{ - "test1.ipn.dev.": testipv4, - "test2.ipn.dev.": testipv6, +var dnsCfg = Config{ + Hosts: map[string][]netaddr.IP{ + "test1.ipn.dev.": []netaddr.IP{testipv4}, + "test2.ipn.dev.": []netaddr.IP{testipv6}, }, - []string{"ipn.dev."}, -) + LocalDomains: []string{"ipn.dev."}, +} func dnspacket(domain string, tp dns.Type) []byte { var dnsHeader dns.Header @@ -192,14 +191,14 @@ func TestRDNSNameToIPv6(t *testing.T) { } } -func TestResolve(t *testing.T) { +func TestResolveLocal(t *testing.T) { r, err := New(t.Logf, nil) if err != nil { t.Fatalf("start: %v", err) } defer r.Close() - r.SetMap(dnsMap) + r.SetConfig(dnsCfg) tests := []struct { name string @@ -223,10 +222,7 @@ func TestResolve(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ip, code, err := r.resolve(tt.qname, tt.qtype) - if err != nil { - t.Errorf("err = %v; want nil", err) - } + ip, code := r.resolveLocal(tt.qname, tt.qtype) if code != tt.code { t.Errorf("code = %v; want %v", code, tt.code) } @@ -238,14 +234,14 @@ func TestResolve(t *testing.T) { } } -func TestResolveReverse(t *testing.T) { +func TestResolveLocalReverse(t *testing.T) { r, err := New(t.Logf, nil) if err != nil { t.Fatalf("start: %v", err) } defer r.Close() - r.SetMap(dnsMap) + r.SetConfig(dnsCfg) tests := []struct { name string @@ -260,10 +256,7 @@ func TestResolveReverse(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - name, code, err := r.resolveReverse(tt.ip) - if err != nil { - t.Errorf("err = %v; want nil", err) - } + name, code := r.resolveLocalReverse(tt.ip) if code != tt.code { t.Errorf("code = %v; want %v", code, tt.code) } @@ -323,11 +316,14 @@ func TestDelegate(t *testing.T) { } defer r.Close() - r.SetMap(dnsMap) - r.SetUpstreams([]net.Addr{ - v4server.PacketConn.LocalAddr(), - v6server.PacketConn.LocalAddr(), - }) + cfg := dnsCfg + cfg.Routes = map[string][]netaddr.IPPort{ + ".": { + netaddr.MustParseIPPort(v4server.PacketConn.LocalAddr().String()), + netaddr.MustParseIPPort(v6server.PacketConn.LocalAddr().String()), + }, + } + r.SetConfig(cfg) tests := []struct { title string @@ -402,8 +398,13 @@ func TestDelegateCollision(t *testing.T) { } defer r.Close() - r.SetMap(dnsMap) - r.SetUpstreams([]net.Addr{server.PacketConn.LocalAddr()}) + cfg := dnsCfg + cfg.Routes = map[string][]netaddr.IPPort{ + ".": { + netaddr.MustParseIPPort(server.PacketConn.LocalAddr().String()), + }, + } + r.SetConfig(cfg) packets := []struct { qname string @@ -460,65 +461,6 @@ func TestDelegateCollision(t *testing.T) { } } -func TestConcurrentSetMap(t *testing.T) { - r, err := New(t.Logf, nil) - if err != nil { - t.Fatalf("start: %v", err) - } - defer r.Close() - - // This is purely to ensure that Resolve does not race with SetMap. - var wg sync.WaitGroup - wg.Add(2) - go func() { - defer wg.Done() - r.SetMap(dnsMap) - }() - go func() { - defer wg.Done() - r.resolve("test1.ipn.dev", dns.TypeA) - }() - wg.Wait() -} - -func TestConcurrentSetUpstreams(t *testing.T) { - dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site.")) - - server, errch := serveDNS(t, "127.0.0.1:0") - defer func() { - if err := <-errch; err != nil { - t.Errorf("server error: %v", err) - } - }() - - if server == nil { - return - } - defer server.Shutdown() - - r, err := New(t.Logf, nil) - if err != nil { - t.Fatalf("start: %v", err) - } - defer r.Close() - - r.SetMap(dnsMap) - - packet := dnspacket("test.site.", dns.TypeA) - // This is purely to ensure that delegation does not race with SetUpstreams. - var wg sync.WaitGroup - wg.Add(2) - go func() { - defer wg.Done() - r.SetUpstreams([]net.Addr{server.PacketConn.LocalAddr()}) - }() - go func() { - defer wg.Done() - syncRespond(r, packet) - }() - wg.Wait() -} - var allResponse = []byte{ 0x00, 0x00, // transaction id: 0 0x84, 0x00, // flags: response, authoritative, no error @@ -673,7 +615,7 @@ func TestFull(t *testing.T) { } defer r.Close() - r.SetMap(dnsMap) + r.SetConfig(dnsCfg) // One full packet and one error packet tests := []struct { @@ -711,7 +653,7 @@ func TestAllocs(t *testing.T) { t.Fatalf("start: %v", err) } defer r.Close() - r.SetMap(dnsMap) + r.SetConfig(dnsCfg) // It is seemingly pointless to test allocs in the delegate path, // as dialer.Dial -> Read -> Write alone comprise 12 allocs. @@ -780,8 +722,12 @@ func BenchmarkFull(b *testing.B) { } defer r.Close() - r.SetMap(dnsMap) - r.SetUpstreams([]net.Addr{server.PacketConn.LocalAddr()}) + cfg := dnsCfg + cfg.Routes = map[string][]netaddr.IPPort{ + ".": { + netaddr.MustParseIPPort(server.PacketConn.LocalAddr().String()), + }, + } tests := []struct { name string diff --git a/wgengine/userspace.go b/wgengine/userspace.go index a8d270c0b..1917d0b45 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -913,7 +913,7 @@ func genLocalAddrFunc(addrs []netaddr.IPPrefix) func(netaddr.IP) bool { return func(t netaddr.IP) bool { return m[t] } } -func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) error { +func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config, hosts map[string][]netaddr.IP, localDomains []string) error { if routerCfg == nil { panic("routerCfg must not be nil") } @@ -933,7 +933,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) e.mu.Unlock() engineChanged := deepprint.UpdateHash(&e.lastEngineSigFull, cfg) - routerChanged := deepprint.UpdateHash(&e.lastRouterSig, routerCfg) + routerChanged := deepprint.UpdateHash(&e.lastRouterSig, routerCfg, hosts, localDomains) if !engineChanged && !routerChanged { return ErrNoChanges } @@ -979,20 +979,24 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) } if routerChanged { + resolverCfg := resolver.Config{ + Hosts: hosts, + LocalDomains: localDomains, + Routes: map[string][]netaddr.IPPort{}, + } if routerCfg.DNS.Proxied { ips := routerCfg.DNS.Nameservers - upstreams := make([]net.Addr, len(ips)) + upstreams := make([]netaddr.IPPort, len(ips)) for i, ip := range ips { - stdIP := ip.IPAddr() - upstreams[i] = &net.UDPAddr{ - IP: stdIP.IP, + upstreams[i] = netaddr.IPPort{ + IP: ip, Port: 53, - Zone: stdIP.Zone, } } - e.resolver.SetUpstreams(upstreams) + resolverCfg.Routes["."] = upstreams routerCfg.DNS.Nameservers = []netaddr.IP{tsaddr.TailscaleServiceIP()} } + e.resolver.SetConfig(resolverCfg) // TODO: check error and propagate to health pkg e.logf("wgengine: Reconfig: configuring router") err := e.router.Set(routerCfg) health.SetRouterHealth(err) @@ -1018,10 +1022,6 @@ func (e *userspaceEngine) SetFilter(filt *filter.Filter) { e.tundev.SetFilter(filt) } -func (e *userspaceEngine) SetDNSMap(dm *resolver.Map) { - e.resolver.SetMap(dm) -} - func (e *userspaceEngine) SetStatusCallback(cb StatusCallback) { e.mu.Lock() defer e.mu.Unlock() diff --git a/wgengine/userspace_test.go b/wgengine/userspace_test.go index 222e183b1..62aaa4433 100644 --- a/wgengine/userspace_test.go +++ b/wgengine/userspace_test.go @@ -108,7 +108,7 @@ func TestUserspaceEngineReconfig(t *testing.T) { }, } - err = e.Reconfig(cfg, routerCfg) + err = e.Reconfig(cfg, routerCfg, nil, nil) if err != nil { t.Fatal(err) } diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index 28b82161c..3f8ea85cd 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -14,7 +14,6 @@ import ( "inet.af/netaddr" "tailscale.com/ipn/ipnstate" - "tailscale.com/net/dns/resolver" "tailscale.com/net/tstun" "tailscale.com/tailcfg" "tailscale.com/types/netmap" @@ -74,8 +73,8 @@ func (e *watchdogEngine) watchdog(name string, fn func()) { }) } -func (e *watchdogEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) error { - return e.watchdogErr("Reconfig", func() error { return e.wrap.Reconfig(cfg, routerCfg) }) +func (e *watchdogEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config, hosts map[string][]netaddr.IP, localDomains []string) error { + return e.watchdogErr("Reconfig", func() error { return e.wrap.Reconfig(cfg, routerCfg, hosts, localDomains) }) } func (e *watchdogEngine) GetLinkMonitor() *monitor.Mon { return e.wrap.GetLinkMonitor() @@ -86,9 +85,6 @@ func (e *watchdogEngine) GetFilter() *filter.Filter { func (e *watchdogEngine) SetFilter(filt *filter.Filter) { e.watchdog("SetFilter", func() { e.wrap.SetFilter(filt) }) } -func (e *watchdogEngine) SetDNSMap(dm *resolver.Map) { - e.watchdog("SetDNSMap", func() { e.wrap.SetDNSMap(dm) }) -} func (e *watchdogEngine) SetStatusCallback(cb StatusCallback) { e.watchdog("SetStatusCallback", func() { e.wrap.SetStatusCallback(cb) }) } diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index 89286ce10..4ec01d609 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -9,7 +9,6 @@ import ( "inet.af/netaddr" "tailscale.com/ipn/ipnstate" - "tailscale.com/net/dns/resolver" "tailscale.com/tailcfg" "tailscale.com/types/netmap" "tailscale.com/wgengine/filter" @@ -57,7 +56,7 @@ type Engine interface { // sends an updated network map. // // The returned error is ErrNoChanges if no changes were made. - Reconfig(*wgcfg.Config, *router.Config) error + Reconfig(*wgcfg.Config, *router.Config, map[string][]netaddr.IP, []string) error // GetFilter returns the current packet filter, if any. GetFilter() *filter.Filter @@ -65,9 +64,6 @@ type Engine interface { // SetFilter updates the packet filter. SetFilter(*filter.Filter) - // SetDNSMap updates the DNS map. - SetDNSMap(*resolver.Map) - // SetStatusCallback sets the function to call when the // WireGuard status changes. SetStatusCallback(StatusCallback)