mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-11-04 10:11:18 +01:00 
			
		
		
		
	And enable U1000 check in staticcheck. Updates #cleanup Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
		
			
				
	
	
		
			1349 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1349 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) Tailscale Inc & AUTHORS
 | 
						|
// SPDX-License-Identifier: BSD-3-Clause
 | 
						|
 | 
						|
// Package resolver implements a stub DNS resolver that can also serve
 | 
						|
// records out of an internal local zone.
 | 
						|
package resolver
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"context"
 | 
						|
	"encoding/hex"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"net"
 | 
						|
	"net/netip"
 | 
						|
	"os"
 | 
						|
	"runtime"
 | 
						|
	"sort"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
 | 
						|
	dns "golang.org/x/net/dns/dnsmessage"
 | 
						|
	"tailscale.com/control/controlknobs"
 | 
						|
	"tailscale.com/envknob"
 | 
						|
	"tailscale.com/net/dns/resolvconffile"
 | 
						|
	"tailscale.com/net/netaddr"
 | 
						|
	"tailscale.com/net/netmon"
 | 
						|
	"tailscale.com/net/tsaddr"
 | 
						|
	"tailscale.com/net/tsdial"
 | 
						|
	"tailscale.com/syncs"
 | 
						|
	"tailscale.com/types/dnstype"
 | 
						|
	"tailscale.com/types/logger"
 | 
						|
	"tailscale.com/util/clientmetric"
 | 
						|
	"tailscale.com/util/cloudenv"
 | 
						|
	"tailscale.com/util/dnsname"
 | 
						|
)
 | 
						|
 | 
						|
const dnsSymbolicFQDN = "magicdns.localhost-tailscale-daemon."
 | 
						|
 | 
						|
// maxResponseBytes is the maximum size of a response from a Resolver. The
 | 
						|
// actual buffer size will be one larger than this so that we can detect
 | 
						|
// truncation in a platform-agnostic way.
 | 
						|
const maxResponseBytes = 4095
 | 
						|
 | 
						|
// defaultTTL is the TTL of all responses from Resolver.
 | 
						|
const defaultTTL = 600 * time.Second
 | 
						|
 | 
						|
var (
 | 
						|
	errNotQuery   = errors.New("not a DNS query")
 | 
						|
	errNotOurName = errors.New("not a Tailscale DNS name")
 | 
						|
)
 | 
						|
 | 
						|
type packet struct {
 | 
						|
	bs     []byte
 | 
						|
	family string         // either "tcp" or "udp"
 | 
						|
	addr   netip.AddrPort // 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[dnsname.FQDN][]*dnstype.Resolver
 | 
						|
	// LocalHosts is a map of FQDNs to corresponding IPs.
 | 
						|
	Hosts map[dnsname.FQDN][]netip.Addr
 | 
						|
	// LocalDomains is a list of DNS name suffixes that should not be
 | 
						|
	// routed to upstream resolvers.
 | 
						|
	LocalDomains []dnsname.FQDN
 | 
						|
}
 | 
						|
 | 
						|
// 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("{Routes:")
 | 
						|
	WriteRoutes(w, c.Routes)
 | 
						|
	fmt.Fprintf(w, " Hosts:%v LocalDomains:[", len(c.Hosts))
 | 
						|
	space := false
 | 
						|
	arpa := 0
 | 
						|
	for _, d := range c.LocalDomains {
 | 
						|
		if strings.HasSuffix(string(d), ".arpa.") {
 | 
						|
			arpa++
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if space {
 | 
						|
			w.WriteByte(' ')
 | 
						|
		}
 | 
						|
		w.WriteString(string(d))
 | 
						|
		space = true
 | 
						|
	}
 | 
						|
	w.WriteString("]")
 | 
						|
	if arpa > 0 {
 | 
						|
		fmt.Fprintf(w, "+%darpa", arpa)
 | 
						|
	}
 | 
						|
	if c := cloudenv.Get(); c != "" {
 | 
						|
		fmt.Fprintf(w, ", cloud=%q", string(c))
 | 
						|
	}
 | 
						|
	w.WriteString("}")
 | 
						|
}
 | 
						|
 | 
						|
// WriteIPPorts writes vv to w.
 | 
						|
func WriteIPPorts(w *bufio.Writer, vv []netip.AddrPort) {
 | 
						|
	w.WriteByte('[')
 | 
						|
	var b []byte
 | 
						|
	for i, v := range vv {
 | 
						|
		if i > 0 {
 | 
						|
			w.WriteByte(' ')
 | 
						|
		}
 | 
						|
		b = v.AppendTo(b[:0])
 | 
						|
		w.Write(b)
 | 
						|
	}
 | 
						|
	w.WriteByte(']')
 | 
						|
}
 | 
						|
 | 
						|
// WriteDNSResolver writes r to w.
 | 
						|
func WriteDNSResolver(w *bufio.Writer, r *dnstype.Resolver) {
 | 
						|
	io.WriteString(w, r.Addr)
 | 
						|
	if len(r.BootstrapResolution) > 0 {
 | 
						|
		w.WriteByte('(')
 | 
						|
		var b []byte
 | 
						|
		for _, ip := range r.BootstrapResolution {
 | 
						|
			ip.AppendTo(b[:0])
 | 
						|
			w.Write(b)
 | 
						|
		}
 | 
						|
		w.WriteByte(')')
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WriteDNSResolvers writes resolvers to w.
 | 
						|
func WriteDNSResolvers(w *bufio.Writer, resolvers []*dnstype.Resolver) {
 | 
						|
	w.WriteByte('[')
 | 
						|
	for i, r := range resolvers {
 | 
						|
		if i > 0 {
 | 
						|
			w.WriteByte(' ')
 | 
						|
		}
 | 
						|
		WriteDNSResolver(w, r)
 | 
						|
	}
 | 
						|
	w.WriteByte(']')
 | 
						|
}
 | 
						|
 | 
						|
// WriteRoutes writes routes to w, omitting *.arpa routes and instead
 | 
						|
// summarizing how many of them there were.
 | 
						|
func WriteRoutes(w *bufio.Writer, routes map[dnsname.FQDN][]*dnstype.Resolver) {
 | 
						|
	var kk []dnsname.FQDN
 | 
						|
	arpa := 0
 | 
						|
	for k := range routes {
 | 
						|
		if strings.HasSuffix(string(k), ".arpa.") {
 | 
						|
			arpa++
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		kk = append(kk, k)
 | 
						|
	}
 | 
						|
	sort.Slice(kk, func(i, j int) bool { return kk[i] < kk[j] })
 | 
						|
	w.WriteByte('{')
 | 
						|
	for i, k := range kk {
 | 
						|
		if i > 0 {
 | 
						|
			w.WriteByte(' ')
 | 
						|
		}
 | 
						|
		w.WriteString(string(k))
 | 
						|
		w.WriteByte(':')
 | 
						|
		WriteDNSResolvers(w, routes[k])
 | 
						|
	}
 | 
						|
	w.WriteByte('}')
 | 
						|
	if arpa > 0 {
 | 
						|
		fmt.Fprintf(w, "+%darpa", arpa)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Resolver is a DNS resolver for nodes on the Tailscale network,
 | 
						|
// associating them with domain names of the form <mynode>.<mydomain>.<root>.
 | 
						|
// If it is asked to resolve a domain that is not of that form,
 | 
						|
// it delegates to upstream nameservers if any are set.
 | 
						|
type Resolver struct {
 | 
						|
	logf               logger.Logf
 | 
						|
	netMon             *netmon.Monitor  // or nil
 | 
						|
	dialer             *tsdial.Dialer   // non-nil
 | 
						|
	saveConfigForTests func(cfg Config) // used in tests to capture resolver config
 | 
						|
	// forwarder forwards requests to upstream nameservers.
 | 
						|
	forwarder *forwarder
 | 
						|
 | 
						|
	// closed signals all goroutines to stop.
 | 
						|
	closed chan struct{}
 | 
						|
 | 
						|
	// mu guards the following fields from being updated while used.
 | 
						|
	mu           sync.Mutex
 | 
						|
	localDomains []dnsname.FQDN
 | 
						|
	hostToIP     map[dnsname.FQDN][]netip.Addr
 | 
						|
	ipToHost     map[netip.Addr]dnsname.FQDN
 | 
						|
}
 | 
						|
 | 
						|
type ForwardLinkSelector interface {
 | 
						|
	// PickLink returns which network device should be used to query
 | 
						|
	// the DNS server at the given IP.
 | 
						|
	// The empty string means to use an unspecified default.
 | 
						|
	PickLink(netip.Addr) (linkName string)
 | 
						|
}
 | 
						|
 | 
						|
// New returns a new resolver.
 | 
						|
// netMon optionally specifies a network monitor to use for socket rebinding.
 | 
						|
func New(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkSelector, dialer *tsdial.Dialer, knobs *controlknobs.Knobs) *Resolver {
 | 
						|
	if dialer == nil {
 | 
						|
		panic("nil Dialer")
 | 
						|
	}
 | 
						|
	r := &Resolver{
 | 
						|
		logf:     logger.WithPrefix(logf, "resolver: "),
 | 
						|
		netMon:   netMon,
 | 
						|
		closed:   make(chan struct{}),
 | 
						|
		hostToIP: map[dnsname.FQDN][]netip.Addr{},
 | 
						|
		ipToHost: map[netip.Addr]dnsname.FQDN{},
 | 
						|
		dialer:   dialer,
 | 
						|
	}
 | 
						|
	r.forwarder = newForwarder(r.logf, netMon, linkSel, dialer, knobs)
 | 
						|
	return r
 | 
						|
}
 | 
						|
 | 
						|
func (r *Resolver) TestOnlySetHook(hook func(Config)) { r.saveConfigForTests = hook }
 | 
						|
 | 
						|
func (r *Resolver) SetConfig(cfg Config) error {
 | 
						|
	if r.saveConfigForTests != nil {
 | 
						|
		r.saveConfigForTests(cfg)
 | 
						|
	}
 | 
						|
 | 
						|
	reverse := make(map[netip.Addr]dnsname.FQDN, len(cfg.Hosts))
 | 
						|
 | 
						|
	for host, ips := range cfg.Hosts {
 | 
						|
		for _, ip := range ips {
 | 
						|
			reverse[ip] = host
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	r.forwarder.setRoutes(cfg.Routes)
 | 
						|
 | 
						|
	r.mu.Lock()
 | 
						|
	defer r.mu.Unlock()
 | 
						|
	r.localDomains = cfg.LocalDomains
 | 
						|
	r.hostToIP = cfg.Hosts
 | 
						|
	r.ipToHost = reverse
 | 
						|
	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() {
 | 
						|
	select {
 | 
						|
	case <-r.closed:
 | 
						|
		return
 | 
						|
	default:
 | 
						|
		// continue
 | 
						|
	}
 | 
						|
	close(r.closed)
 | 
						|
 | 
						|
	r.forwarder.Close()
 | 
						|
}
 | 
						|
 | 
						|
// dnsQueryTimeout is not intended to be user-visible (the users
 | 
						|
// DNS resolver will retry well before that), just put an upper
 | 
						|
// bound on per-query resource usage.
 | 
						|
const dnsQueryTimeout = 10 * time.Second
 | 
						|
 | 
						|
func (r *Resolver) Query(ctx context.Context, bs []byte, family string, from netip.AddrPort) ([]byte, error) {
 | 
						|
	metricDNSQueryLocal.Add(1)
 | 
						|
	select {
 | 
						|
	case <-r.closed:
 | 
						|
		metricDNSQueryErrorClosed.Add(1)
 | 
						|
		return nil, net.ErrClosed
 | 
						|
	default:
 | 
						|
	}
 | 
						|
 | 
						|
	out, err := r.respond(bs)
 | 
						|
	if err == errNotOurName {
 | 
						|
		responses := make(chan packet, 1)
 | 
						|
		ctx, cancel := context.WithTimeout(ctx, dnsQueryTimeout)
 | 
						|
		defer close(responses)
 | 
						|
		defer cancel()
 | 
						|
		err = r.forwarder.forwardWithDestChan(ctx, packet{bs, family, from}, responses)
 | 
						|
		if err != nil {
 | 
						|
			select {
 | 
						|
			// Best effort: use any error response sent by forwardWithDestChan.
 | 
						|
			// This is present in some errors paths, such as when all upstream
 | 
						|
			// DNS servers replied with an error.
 | 
						|
			case resp := <-responses:
 | 
						|
				return resp.bs, err
 | 
						|
			default:
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return (<-responses).bs, nil
 | 
						|
	}
 | 
						|
 | 
						|
	return out, err
 | 
						|
}
 | 
						|
 | 
						|
// parseExitNodeQuery parses a DNS request packet.
 | 
						|
// It returns nil if it's malformed or lacking a question.
 | 
						|
func parseExitNodeQuery(q []byte) *response {
 | 
						|
	p := dnsParserPool.Get().(*dnsParser)
 | 
						|
	defer dnsParserPool.Put(p)
 | 
						|
	p.zeroParser()
 | 
						|
	defer p.zeroParser()
 | 
						|
	if err := p.parseQuery(q); err != nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	return p.response()
 | 
						|
}
 | 
						|
 | 
						|
// HandlePeerDNSQuery handles a DNS query that arrived from a peer
 | 
						|
// via the peerapi's DoH server. This is used when the local
 | 
						|
// node is being an exit node or an app connector.
 | 
						|
//
 | 
						|
// The provided allowName callback is whether a DNS query for a name
 | 
						|
// (as found by parsing q) is allowed.
 | 
						|
//
 | 
						|
// In most (all?) cases, err will be nil. A bogus DNS query q will
 | 
						|
// still result in a response DNS packet (saying there's a failure)
 | 
						|
// and a nil error.
 | 
						|
// TODO: figure out if we even need an error result.
 | 
						|
func (r *Resolver) HandlePeerDNSQuery(ctx context.Context, q []byte, from netip.AddrPort, allowName func(name string) bool) (res []byte, err error) {
 | 
						|
	metricDNSExitProxyQuery.Add(1)
 | 
						|
	ch := make(chan packet, 1)
 | 
						|
 | 
						|
	resp := parseExitNodeQuery(q)
 | 
						|
	if resp == nil {
 | 
						|
		return nil, errors.New("bad query")
 | 
						|
	}
 | 
						|
	name := resp.Question.Name.String()
 | 
						|
	if !allowName(name) {
 | 
						|
		metricDNSExitProxyErrorName.Add(1)
 | 
						|
		resp.Header.RCode = dns.RCodeRefused
 | 
						|
		return marshalResponse(resp)
 | 
						|
	}
 | 
						|
 | 
						|
	switch runtime.GOOS {
 | 
						|
	default:
 | 
						|
		return nil, errors.New("unsupported exit node OS")
 | 
						|
	case "windows", "android":
 | 
						|
		return handleExitNodeDNSQueryWithNetPkg(ctx, r.logf, nil, resp)
 | 
						|
	case "darwin":
 | 
						|
		// /etc/resolv.conf is a lie and only says one upstream DNS
 | 
						|
		// but for now that's probably good enough. Later we'll
 | 
						|
		// want to blend in everything from scutil --dns.
 | 
						|
		fallthrough
 | 
						|
	case "linux", "freebsd", "openbsd", "illumos", "ios":
 | 
						|
		nameserver, err := stubResolverForOS()
 | 
						|
		if err != nil {
 | 
						|
			r.logf("stubResolverForOS: %v", err)
 | 
						|
			metricDNSExitProxyErrorResolvConf.Add(1)
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		// TODO: more than 1 resolver from /etc/resolv.conf?
 | 
						|
 | 
						|
		var resolvers []resolverAndDelay
 | 
						|
		switch nameserver {
 | 
						|
		case tsaddr.TailscaleServiceIP(), tsaddr.TailscaleServiceIPv6():
 | 
						|
			// If resolv.conf says 100.100.100.100, it's coming right back to us anyway
 | 
						|
			// so avoid the loop through the kernel and just do what we
 | 
						|
			// would've done anyway. By not passing any resolvers, the forwarder
 | 
						|
			// will use its default ones from our DNS config.
 | 
						|
		case netip.Addr{}:
 | 
						|
			// Likewise, if the platform has no resolv.conf, just use our defaults.
 | 
						|
		default:
 | 
						|
			resolvers = []resolverAndDelay{{
 | 
						|
				name: &dnstype.Resolver{Addr: net.JoinHostPort(nameserver.String(), "53")},
 | 
						|
			}}
 | 
						|
		}
 | 
						|
 | 
						|
		err = r.forwarder.forwardWithDestChan(ctx, packet{q, "tcp", from}, ch, resolvers...)
 | 
						|
		if err != nil {
 | 
						|
			metricDNSExitProxyErrorForward.Add(1)
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	select {
 | 
						|
	case p, ok := <-ch:
 | 
						|
		if ok {
 | 
						|
			return p.bs, nil
 | 
						|
		}
 | 
						|
		panic("unexpected close chan")
 | 
						|
	default:
 | 
						|
		panic("unexpected unreadable chan")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
var debugExitNodeDNSNetPkg = envknob.RegisterBool("TS_DEBUG_EXIT_NODE_DNS_NET_PKG")
 | 
						|
 | 
						|
// handleExitNodeDNSQueryWithNetPkg takes a DNS query message in q and
 | 
						|
// return a reply (for the ExitDNS DoH service) using the net package's
 | 
						|
// native APIs.
 | 
						|
//
 | 
						|
// If resolver is nil, the net.Resolver zero value is used.
 | 
						|
//
 | 
						|
// response contains the pre-serialized response, which notably
 | 
						|
// includes the original question and its header.
 | 
						|
func handleExitNodeDNSQueryWithNetPkg(ctx context.Context, logf logger.Logf, resolver *net.Resolver, resp *response) (res []byte, err error) {
 | 
						|
	logf = logger.WithPrefix(logf, "exitNodeDNSQueryWithNetPkg: ")
 | 
						|
	if resp.Question.Class != dns.ClassINET {
 | 
						|
		return nil, errors.New("unsupported class")
 | 
						|
	}
 | 
						|
 | 
						|
	r := resolver
 | 
						|
	if r == nil {
 | 
						|
		r = new(net.Resolver)
 | 
						|
	}
 | 
						|
	name := resp.Question.Name.String()
 | 
						|
 | 
						|
	handleError := func(err error) (res []byte, _ error) {
 | 
						|
		if isGoNoSuchHostError(err) {
 | 
						|
			if debugExitNodeDNSNetPkg() {
 | 
						|
				logf(`converting Go "no such host" error to a NXDOMAIN: %v`, err)
 | 
						|
			}
 | 
						|
			resp.Header.RCode = dns.RCodeNameError
 | 
						|
			return marshalResponse(resp)
 | 
						|
		}
 | 
						|
 | 
						|
		if debugExitNodeDNSNetPkg() {
 | 
						|
			logf("returning error: %v", err)
 | 
						|
		}
 | 
						|
		// TODO: map other errors to RCodeServerFailure?
 | 
						|
		// Or I guess our caller should do that?
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	resp.Header.RCode = dns.RCodeSuccess // unless changed below
 | 
						|
 | 
						|
	switch resp.Question.Type {
 | 
						|
	case dns.TypeA, dns.TypeAAAA:
 | 
						|
		network := "ip4"
 | 
						|
		if resp.Question.Type == dns.TypeAAAA {
 | 
						|
			network = "ip6"
 | 
						|
		}
 | 
						|
		if debugExitNodeDNSNetPkg() {
 | 
						|
			logf("resolving %s %q", network, name)
 | 
						|
		}
 | 
						|
		ips, err := r.LookupIP(ctx, network, name)
 | 
						|
		if err != nil {
 | 
						|
			return handleError(err)
 | 
						|
		}
 | 
						|
		for _, stdIP := range ips {
 | 
						|
			if ip, ok := netip.AddrFromSlice(stdIP); ok {
 | 
						|
				resp.IPs = append(resp.IPs, ip.Unmap())
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case dns.TypeTXT:
 | 
						|
		if debugExitNodeDNSNetPkg() {
 | 
						|
			logf("resolving TXT %q", name)
 | 
						|
		}
 | 
						|
		strs, err := r.LookupTXT(ctx, name)
 | 
						|
		if err != nil {
 | 
						|
			return handleError(err)
 | 
						|
		}
 | 
						|
		resp.TXT = strs
 | 
						|
	case dns.TypePTR:
 | 
						|
		ipStr, ok := unARPA(name)
 | 
						|
		if !ok {
 | 
						|
			// TODO: is this RCodeFormatError?
 | 
						|
			return nil, errors.New("bogus PTR name")
 | 
						|
		}
 | 
						|
		if debugExitNodeDNSNetPkg() {
 | 
						|
			logf("resolving PTR %q", ipStr)
 | 
						|
		}
 | 
						|
		addrs, err := r.LookupAddr(ctx, ipStr)
 | 
						|
		if err != nil {
 | 
						|
			return handleError(err)
 | 
						|
		}
 | 
						|
		if len(addrs) > 0 {
 | 
						|
			resp.Name, _ = dnsname.ToFQDN(addrs[0])
 | 
						|
		}
 | 
						|
	case dns.TypeCNAME:
 | 
						|
		if debugExitNodeDNSNetPkg() {
 | 
						|
			logf("resolving CNAME %q", name)
 | 
						|
		}
 | 
						|
		cname, err := r.LookupCNAME(ctx, name)
 | 
						|
		if err != nil {
 | 
						|
			return handleError(err)
 | 
						|
		}
 | 
						|
		resp.CNAME = cname
 | 
						|
	case dns.TypeSRV:
 | 
						|
		if debugExitNodeDNSNetPkg() {
 | 
						|
			logf("resolving SRV %q", name)
 | 
						|
		}
 | 
						|
		// Thanks, Go: "To accommodate services publishing SRV
 | 
						|
		// records under non-standard names, if both service
 | 
						|
		// and proto are empty strings, LookupSRV looks up
 | 
						|
		// name directly."
 | 
						|
		_, srvs, err := r.LookupSRV(ctx, "", "", name)
 | 
						|
		if err != nil {
 | 
						|
			return handleError(err)
 | 
						|
		}
 | 
						|
		resp.SRVs = srvs
 | 
						|
	case dns.TypeNS:
 | 
						|
		if debugExitNodeDNSNetPkg() {
 | 
						|
			logf("resolving NS %q", name)
 | 
						|
		}
 | 
						|
		nss, err := r.LookupNS(ctx, name)
 | 
						|
		if err != nil {
 | 
						|
			return handleError(err)
 | 
						|
		}
 | 
						|
		resp.NSs = nss
 | 
						|
	default:
 | 
						|
		return nil, fmt.Errorf("unsupported record type %v", resp.Question.Type)
 | 
						|
	}
 | 
						|
	return marshalResponse(resp)
 | 
						|
}
 | 
						|
 | 
						|
func isGoNoSuchHostError(err error) bool {
 | 
						|
	if de, ok := err.(*net.DNSError); ok {
 | 
						|
		return de.IsNotFound
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
type resolvConfCache struct {
 | 
						|
	mod  time.Time
 | 
						|
	size int64
 | 
						|
	ip   netip.Addr
 | 
						|
	// TODO: inode/dev?
 | 
						|
}
 | 
						|
 | 
						|
// resolvConfCacheValue contains the most recent stat metadata and parsed
 | 
						|
// version of /etc/resolv.conf.
 | 
						|
var resolvConfCacheValue syncs.AtomicValue[resolvConfCache]
 | 
						|
 | 
						|
var errEmptyResolvConf = errors.New("resolv.conf has no nameservers")
 | 
						|
 | 
						|
// stubResolverForOS returns the IP address of the first nameserver in
 | 
						|
// /etc/resolv.conf.
 | 
						|
//
 | 
						|
// It may also return the netip.Addr zero value and a nil error to mean
 | 
						|
// that the platform has no resolv.conf.
 | 
						|
func stubResolverForOS() (ip netip.Addr, err error) {
 | 
						|
	if runtime.GOOS == "ios" {
 | 
						|
		return netip.Addr{}, nil // no resolv.conf on iOS
 | 
						|
	}
 | 
						|
	fi, err := os.Stat(resolvconffile.Path)
 | 
						|
	if err != nil {
 | 
						|
		return netip.Addr{}, err
 | 
						|
	}
 | 
						|
	cur := resolvConfCache{
 | 
						|
		mod:  fi.ModTime(),
 | 
						|
		size: fi.Size(),
 | 
						|
	}
 | 
						|
	if c, ok := resolvConfCacheValue.LoadOk(); ok && c.mod == cur.mod && c.size == cur.size {
 | 
						|
		return c.ip, nil
 | 
						|
	}
 | 
						|
	conf, err := resolvconffile.ParseFile(resolvconffile.Path)
 | 
						|
	if err != nil {
 | 
						|
		return netip.Addr{}, err
 | 
						|
	}
 | 
						|
	if len(conf.Nameservers) == 0 {
 | 
						|
		return netip.Addr{}, errEmptyResolvConf
 | 
						|
	}
 | 
						|
	ip = conf.Nameservers[0]
 | 
						|
	cur.ip = ip
 | 
						|
	resolvConfCacheValue.Store(cur)
 | 
						|
	return ip, nil
 | 
						|
}
 | 
						|
 | 
						|
// 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).
 | 
						|
// Returns dns.RCodeRefused to indicate that the local map is not
 | 
						|
// authoritative for domain.
 | 
						|
func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr, dns.RCode) {
 | 
						|
	metricDNSResolveLocal.Add(1)
 | 
						|
	// Reject .onion domains per RFC 7686.
 | 
						|
	if dnsname.HasSuffix(domain.WithoutTrailingDot(), ".onion") {
 | 
						|
		metricDNSResolveLocalErrorOnion.Add(1)
 | 
						|
		return netip.Addr{}, dns.RCodeNameError
 | 
						|
	}
 | 
						|
 | 
						|
	// We return a symbolic domain if someone does a reverse lookup on the
 | 
						|
	// DNS endpoint. To round out this special case, we also do the inverse
 | 
						|
	// (returning the endpoint IP if someone looks up the symbolic domain).
 | 
						|
	if domain == dnsSymbolicFQDN {
 | 
						|
		switch typ {
 | 
						|
		case dns.TypeA:
 | 
						|
			return tsaddr.TailscaleServiceIP(), dns.RCodeSuccess
 | 
						|
		case dns.TypeAAAA:
 | 
						|
			return tsaddr.TailscaleServiceIPv6(), dns.RCodeSuccess
 | 
						|
		}
 | 
						|
	}
 | 
						|
	// Special-case: 4via6 DNS names.
 | 
						|
	if ip, ok := r.parseViaDomain(domain, typ); ok {
 | 
						|
		return ip, dns.RCodeSuccess
 | 
						|
	}
 | 
						|
 | 
						|
	r.mu.Lock()
 | 
						|
	hosts := r.hostToIP
 | 
						|
	localDomains := r.localDomains
 | 
						|
	r.mu.Unlock()
 | 
						|
 | 
						|
	addrs, found := hosts[domain]
 | 
						|
	if !found {
 | 
						|
		for _, suffix := range localDomains {
 | 
						|
			if suffix.Contains(domain) {
 | 
						|
				// We are authoritative for the queried domain.
 | 
						|
				metricDNSResolveLocalErrorMissing.Add(1)
 | 
						|
				return netip.Addr{}, dns.RCodeNameError
 | 
						|
			}
 | 
						|
		}
 | 
						|
		// Not authoritative, signal that forwarding is advisable.
 | 
						|
		metricDNSResolveLocalErrorRefused.Add(1)
 | 
						|
		return netip.Addr{}, dns.RCodeRefused
 | 
						|
	}
 | 
						|
 | 
						|
	// Refactoring note: this must happen after we check suffixes,
 | 
						|
	// otherwise we will respond with NOTIMP to requests that should be forwarded.
 | 
						|
	//
 | 
						|
	// 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:
 | 
						|
		for _, ip := range addrs {
 | 
						|
			if ip.Is4() {
 | 
						|
				metricDNSResolveLocalOKA.Add(1)
 | 
						|
				return ip, dns.RCodeSuccess
 | 
						|
			}
 | 
						|
		}
 | 
						|
		metricDNSResolveLocalNoA.Add(1)
 | 
						|
		return netip.Addr{}, dns.RCodeSuccess
 | 
						|
	case dns.TypeAAAA:
 | 
						|
		for _, ip := range addrs {
 | 
						|
			if ip.Is6() {
 | 
						|
				metricDNSResolveLocalOKAAAA.Add(1)
 | 
						|
				return ip, dns.RCodeSuccess
 | 
						|
			}
 | 
						|
		}
 | 
						|
		metricDNSResolveLocalNoAAAA.Add(1)
 | 
						|
		return netip.Addr{}, 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).
 | 
						|
		if len(addrs) == 0 {
 | 
						|
			metricDNSResolveLocalNoAll.Add(1)
 | 
						|
			return netip.Addr{}, dns.RCodeSuccess
 | 
						|
		}
 | 
						|
		metricDNSResolveLocalOKAll.Add(1)
 | 
						|
		return addrs[0], dns.RCodeSuccess
 | 
						|
 | 
						|
	// Leave some record types explicitly unimplemented.
 | 
						|
	// These types relate to recursive resolution or special
 | 
						|
	// DNS semantics and might be implemented in the future.
 | 
						|
	case dns.TypeNS, dns.TypeSOA, dns.TypeAXFR, dns.TypeHINFO:
 | 
						|
		metricDNSResolveNotImplType.Add(1)
 | 
						|
		return netip.Addr{}, dns.RCodeNotImplemented
 | 
						|
 | 
						|
	// For everything except for the few types above that are explicitly not implemented, return no records.
 | 
						|
	// This is what other DNS systems do: always return NOERROR
 | 
						|
	// without any records whenever the requested record type is unknown.
 | 
						|
	// You can try this with:
 | 
						|
	//   dig -t TYPE9824 example.com
 | 
						|
	// and note that NOERROR is returned, despite that record type being made up.
 | 
						|
	default:
 | 
						|
		metricDNSResolveNoRecordType.Add(1)
 | 
						|
		// The name exists, but no records exist of the requested type.
 | 
						|
		return netip.Addr{}, dns.RCodeSuccess
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// parseViaDomain synthesizes an IP address for quad-A DNS requests of the form
 | 
						|
// `<IPv4-address-with-hypens-instead-of-dots>-via-<siteid>[.*]`. Two prior formats that
 | 
						|
// didn't pan out (due to a Chrome issue and DNS search ndots issues) were
 | 
						|
// `<IPv4-address>.via-<X>` and the older `via-<X>.<IPv4-address>`,
 | 
						|
// where X is a decimal, or hex-encoded number with a '0x' prefix.
 | 
						|
//
 | 
						|
// This exists as a convenient mapping into Tailscales 'Via Range'.
 | 
						|
//
 | 
						|
// TODO(maisem/bradfitz/tom): `<IPv4-address>.via-<X>` was introduced
 | 
						|
// (2022-06-02) to work around an issue in Chrome where it would treat
 | 
						|
// "http://via-1.1.2.3.4" as a search string instead of a URL. We should rip out
 | 
						|
// the old format in early 2023.
 | 
						|
func (r *Resolver) parseViaDomain(domain dnsname.FQDN, typ dns.Type) (netip.Addr, bool) {
 | 
						|
	fqdn := string(domain.WithoutTrailingDot())
 | 
						|
	if typ != dns.TypeAAAA {
 | 
						|
		return netip.Addr{}, false
 | 
						|
	}
 | 
						|
	if len(fqdn) < len("via-X.0.0.0.0") {
 | 
						|
		return netip.Addr{}, false // too short to be valid
 | 
						|
	}
 | 
						|
 | 
						|
	var siteID string
 | 
						|
	var ip4Str string
 | 
						|
	switch {
 | 
						|
	case strings.Contains(fqdn, "-via-"):
 | 
						|
		// Format number 3: "192-168-1-2-via-7" or "192-168-1-2-via-7.foo.ts.net."
 | 
						|
		// Third time's a charm. The earlier two formats follow after this block.
 | 
						|
		firstLabel, domain, _ := strings.Cut(fqdn, ".") // "192-168-1-2-via-7"
 | 
						|
		if !(domain == "" || dnsname.HasSuffix(domain, "ts.net") || dnsname.HasSuffix(domain, "tailscale.net")) {
 | 
						|
			return netip.Addr{}, false
 | 
						|
		}
 | 
						|
		v4hyphens, suffix, ok := strings.Cut(firstLabel, "-via-")
 | 
						|
		if !ok {
 | 
						|
			return netip.Addr{}, false
 | 
						|
		}
 | 
						|
		siteID = suffix
 | 
						|
		ip4Str = strings.ReplaceAll(v4hyphens, "-", ".")
 | 
						|
	case strings.HasPrefix(fqdn, "via-"):
 | 
						|
		firstDot := strings.Index(fqdn, ".")
 | 
						|
		if firstDot < 0 {
 | 
						|
			return netip.Addr{}, false // missing dot delimiters
 | 
						|
		}
 | 
						|
		siteID = fqdn[len("via-"):firstDot]
 | 
						|
		ip4Str = fqdn[firstDot+1:]
 | 
						|
	default:
 | 
						|
		lastDot := strings.LastIndex(fqdn, ".")
 | 
						|
		if lastDot < 0 {
 | 
						|
			return netip.Addr{}, false // missing dot delimiters
 | 
						|
		}
 | 
						|
		suffix := fqdn[lastDot+1:]
 | 
						|
		if !strings.HasPrefix(suffix, "via-") {
 | 
						|
			return netip.Addr{}, false
 | 
						|
		}
 | 
						|
		siteID = suffix[len("via-"):]
 | 
						|
		ip4Str = fqdn[:lastDot]
 | 
						|
	}
 | 
						|
 | 
						|
	ip4, err := netip.ParseAddr(ip4Str)
 | 
						|
	if err != nil {
 | 
						|
		return netip.Addr{}, false // badly formed, don't respond
 | 
						|
	}
 | 
						|
 | 
						|
	prefix, err := strconv.ParseUint(siteID, 0, 32)
 | 
						|
	if err != nil {
 | 
						|
		return netip.Addr{}, false // badly formed, don't respond
 | 
						|
	}
 | 
						|
 | 
						|
	// MapVia will never error when given an IPv4 netip.Prefix.
 | 
						|
	out, _ := tsaddr.MapVia(uint32(prefix), netip.PrefixFrom(ip4, ip4.BitLen()))
 | 
						|
	return out.Addr(), true
 | 
						|
}
 | 
						|
 | 
						|
// resolveReverse returns the unique domain name that maps to the given address.
 | 
						|
func (r *Resolver) resolveLocalReverse(name dnsname.FQDN) (dnsname.FQDN, dns.RCode) {
 | 
						|
	var ip netip.Addr
 | 
						|
	var ok bool
 | 
						|
	switch {
 | 
						|
	case strings.HasSuffix(name.WithTrailingDot(), rdnsv4Suffix):
 | 
						|
		ip, ok = rdnsNameToIPv4(name)
 | 
						|
	case strings.HasSuffix(name.WithTrailingDot(), rdnsv6Suffix):
 | 
						|
		ip, ok = rdnsNameToIPv6(name)
 | 
						|
	}
 | 
						|
	if !ok {
 | 
						|
		// This isn't a well-formed in-addr.arpa or ip6.arpa name, but
 | 
						|
		// who knows what upstreams might do, try kicking it up to
 | 
						|
		// them. We definitely won't handle it.
 | 
						|
		return "", dns.RCodeRefused
 | 
						|
	}
 | 
						|
 | 
						|
	r.mu.Lock()
 | 
						|
	defer r.mu.Unlock()
 | 
						|
 | 
						|
	// If the requested IP is part of the IPv6 4-to-6 range, it might
 | 
						|
	// correspond to an IPv4 address (assuming IPv4 is enabled).
 | 
						|
	if ip4, ok := tsaddr.Tailscale6to4(ip); ok {
 | 
						|
		fqdn, code := r.fqdnForIPLocked(ip4, name)
 | 
						|
		if code == dns.RCodeSuccess {
 | 
						|
			return fqdn, code
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return r.fqdnForIPLocked(ip, name)
 | 
						|
}
 | 
						|
 | 
						|
// r.mu must be held.
 | 
						|
func (r *Resolver) fqdnForIPLocked(ip netip.Addr, name dnsname.FQDN) (dnsname.FQDN, dns.RCode) {
 | 
						|
	// If someone curiously does a reverse lookup on the DNS IP, we
 | 
						|
	// return a domain that helps indicate that Tailscale is using
 | 
						|
	// this IP for a special purpose and it is not a node on their
 | 
						|
	// tailnet.
 | 
						|
	if ip == tsaddr.TailscaleServiceIP() || ip == tsaddr.TailscaleServiceIPv6() {
 | 
						|
		return dnsSymbolicFQDN, dns.RCodeSuccess
 | 
						|
	}
 | 
						|
 | 
						|
	ret, ok := r.ipToHost[ip]
 | 
						|
	if !ok {
 | 
						|
		for _, suffix := range r.localDomains {
 | 
						|
			if suffix.Contains(name) {
 | 
						|
				// We are authoritative for this chunk of IP space.
 | 
						|
				return "", dns.RCodeNameError
 | 
						|
			}
 | 
						|
		}
 | 
						|
		// Not authoritative, signal that forwarding is advisable.
 | 
						|
		return "", dns.RCodeRefused
 | 
						|
	}
 | 
						|
	return ret, dns.RCodeSuccess
 | 
						|
}
 | 
						|
 | 
						|
type response struct {
 | 
						|
	Header   dns.Header
 | 
						|
	Question dns.Question
 | 
						|
 | 
						|
	// Name is the response to a PTR query.
 | 
						|
	Name dnsname.FQDN
 | 
						|
 | 
						|
	// IP and IPs are the responses to an A, AAAA, or ALL query.
 | 
						|
	// Either/both/neither can be populated.
 | 
						|
	IP  netip.Addr
 | 
						|
	IPs []netip.Addr
 | 
						|
 | 
						|
	// TXT is the response to a TXT query.
 | 
						|
	// Each one is its own RR with one string.
 | 
						|
	TXT []string
 | 
						|
 | 
						|
	// CNAME is the response to a CNAME query.
 | 
						|
	CNAME string
 | 
						|
 | 
						|
	// SRVs are the responses to a SRV query.
 | 
						|
	SRVs []*net.SRV
 | 
						|
 | 
						|
	// NSs are the responses to an NS query.
 | 
						|
	NSs []*net.NS
 | 
						|
}
 | 
						|
 | 
						|
var dnsParserPool = &sync.Pool{
 | 
						|
	New: func() any {
 | 
						|
		return new(dnsParser)
 | 
						|
	},
 | 
						|
}
 | 
						|
 | 
						|
// dnsParser parses DNS queries using x/net/dns/dnsmessage.
 | 
						|
// These structs are pooled with dnsParserPool.
 | 
						|
type dnsParser struct {
 | 
						|
	Header   dns.Header
 | 
						|
	Question dns.Question
 | 
						|
 | 
						|
	parser dns.Parser
 | 
						|
}
 | 
						|
 | 
						|
func (p *dnsParser) response() *response {
 | 
						|
	return &response{Header: p.Header, Question: p.Question}
 | 
						|
}
 | 
						|
 | 
						|
// zeroParser clears parser so it doesn't retain its most recently
 | 
						|
// parsed DNS query's []byte while it's sitting in a sync.Pool.
 | 
						|
// It's not useful to keep anyway: the next Start will do the same.
 | 
						|
func (p *dnsParser) zeroParser() { p.parser = dns.Parser{} }
 | 
						|
 | 
						|
// parseQuery parses the query in given packet into p.Header and
 | 
						|
// p.Question.
 | 
						|
func (p *dnsParser) parseQuery(query []byte) error {
 | 
						|
	defer p.zeroParser()
 | 
						|
	p.zeroParser()
 | 
						|
	var err error
 | 
						|
	p.Header, err = p.parser.Start(query)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if p.Header.Response {
 | 
						|
		return errNotQuery
 | 
						|
	}
 | 
						|
	p.Question, err = p.parser.Question()
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// marshalARecord serializes an A record into an active builder.
 | 
						|
// The caller may continue using the builder following the call.
 | 
						|
func marshalARecord(name dns.Name, ip netip.Addr, builder *dns.Builder) error {
 | 
						|
	var answer dns.AResource
 | 
						|
 | 
						|
	answerHeader := dns.ResourceHeader{
 | 
						|
		Name:  name,
 | 
						|
		Type:  dns.TypeA,
 | 
						|
		Class: dns.ClassINET,
 | 
						|
		TTL:   uint32(defaultTTL / time.Second),
 | 
						|
	}
 | 
						|
	ipbytes := ip.As4()
 | 
						|
	copy(answer.A[:], ipbytes[:])
 | 
						|
	return builder.AResource(answerHeader, answer)
 | 
						|
}
 | 
						|
 | 
						|
// marshalAAAARecord serializes an AAAA record into an active builder.
 | 
						|
// The caller may continue using the builder following the call.
 | 
						|
func marshalAAAARecord(name dns.Name, ip netip.Addr, builder *dns.Builder) error {
 | 
						|
	var answer dns.AAAAResource
 | 
						|
 | 
						|
	answerHeader := dns.ResourceHeader{
 | 
						|
		Name:  name,
 | 
						|
		Type:  dns.TypeAAAA,
 | 
						|
		Class: dns.ClassINET,
 | 
						|
		TTL:   uint32(defaultTTL / time.Second),
 | 
						|
	}
 | 
						|
	ipbytes := ip.As16()
 | 
						|
	copy(answer.AAAA[:], ipbytes[:])
 | 
						|
	return builder.AAAAResource(answerHeader, answer)
 | 
						|
}
 | 
						|
 | 
						|
func marshalIP(name dns.Name, ip netip.Addr, builder *dns.Builder) error {
 | 
						|
	if ip.Is4() {
 | 
						|
		return marshalARecord(name, ip, builder)
 | 
						|
	}
 | 
						|
	if ip.Is6() {
 | 
						|
		return marshalAAAARecord(name, ip, builder)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// marshalPTRRecord serializes a PTR record into an active builder.
 | 
						|
// The caller may continue using the builder following the call.
 | 
						|
func marshalPTRRecord(queryName dns.Name, name dnsname.FQDN, builder *dns.Builder) error {
 | 
						|
	var answer dns.PTRResource
 | 
						|
	var err error
 | 
						|
 | 
						|
	answerHeader := dns.ResourceHeader{
 | 
						|
		Name:  queryName,
 | 
						|
		Type:  dns.TypePTR,
 | 
						|
		Class: dns.ClassINET,
 | 
						|
		TTL:   uint32(defaultTTL / time.Second),
 | 
						|
	}
 | 
						|
	answer.PTR, err = dns.NewName(name.WithTrailingDot())
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return builder.PTRResource(answerHeader, answer)
 | 
						|
}
 | 
						|
 | 
						|
func marshalTXT(queryName dns.Name, txts []string, builder *dns.Builder) error {
 | 
						|
	for _, txt := range txts {
 | 
						|
		if err := builder.TXTResource(dns.ResourceHeader{
 | 
						|
			Name:  queryName,
 | 
						|
			Type:  dns.TypeTXT,
 | 
						|
			Class: dns.ClassINET,
 | 
						|
			TTL:   uint32(defaultTTL / time.Second),
 | 
						|
		}, dns.TXTResource{
 | 
						|
			TXT: []string{txt},
 | 
						|
		}); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func marshalCNAME(queryName dns.Name, cname string, builder *dns.Builder) error {
 | 
						|
	if cname == "" {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	name, err := dns.NewName(cname)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return builder.CNAMEResource(dns.ResourceHeader{
 | 
						|
		Name:  queryName,
 | 
						|
		Type:  dns.TypeCNAME,
 | 
						|
		Class: dns.ClassINET,
 | 
						|
		TTL:   uint32(defaultTTL / time.Second),
 | 
						|
	}, dns.CNAMEResource{
 | 
						|
		CNAME: name,
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func marshalNS(queryName dns.Name, nss []*net.NS, builder *dns.Builder) error {
 | 
						|
	for _, ns := range nss {
 | 
						|
		name, err := dns.NewName(ns.Host)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		err = builder.NSResource(dns.ResourceHeader{
 | 
						|
			Name:  queryName,
 | 
						|
			Type:  dns.TypeNS,
 | 
						|
			Class: dns.ClassINET,
 | 
						|
			TTL:   uint32(defaultTTL / time.Second),
 | 
						|
		}, dns.NSResource{NS: name})
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func marshalSRV(queryName dns.Name, srvs []*net.SRV, builder *dns.Builder) error {
 | 
						|
	for _, s := range srvs {
 | 
						|
		srvName, err := dns.NewName(s.Target)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		err = builder.SRVResource(dns.ResourceHeader{
 | 
						|
			Name:  queryName,
 | 
						|
			Type:  dns.TypeSRV,
 | 
						|
			Class: dns.ClassINET,
 | 
						|
			TTL:   uint32(defaultTTL / time.Second),
 | 
						|
		}, dns.SRVResource{
 | 
						|
			Target:   srvName,
 | 
						|
			Priority: s.Priority,
 | 
						|
			Port:     s.Port,
 | 
						|
			Weight:   s.Weight,
 | 
						|
		})
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// marshalResponse serializes the DNS response into a new buffer.
 | 
						|
func marshalResponse(resp *response) ([]byte, error) {
 | 
						|
	resp.Header.Response = true
 | 
						|
	resp.Header.Authoritative = true
 | 
						|
	if resp.Header.RecursionDesired {
 | 
						|
		resp.Header.RecursionAvailable = true
 | 
						|
	}
 | 
						|
 | 
						|
	builder := dns.NewBuilder(nil, resp.Header)
 | 
						|
 | 
						|
	// TODO(bradfitz): I'm not sure why this wasn't enabled
 | 
						|
	// before, but for now (2021-12-09) enable it at least when
 | 
						|
	// there's more than 1 record (which was never the case
 | 
						|
	// before), where it really helps.
 | 
						|
	if len(resp.IPs) > 1 {
 | 
						|
		builder.EnableCompression()
 | 
						|
	}
 | 
						|
 | 
						|
	isSuccess := resp.Header.RCode == dns.RCodeSuccess
 | 
						|
 | 
						|
	if resp.Question.Type != 0 || isSuccess {
 | 
						|
		err := builder.StartQuestions()
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		err = builder.Question(resp.Question)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Only successful responses contain answers.
 | 
						|
	if !isSuccess {
 | 
						|
		return builder.Finish()
 | 
						|
	}
 | 
						|
 | 
						|
	err := builder.StartAnswers()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	switch resp.Question.Type {
 | 
						|
	case dns.TypeA, dns.TypeAAAA, dns.TypeALL:
 | 
						|
		if err := marshalIP(resp.Question.Name, resp.IP, &builder); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		for _, ip := range resp.IPs {
 | 
						|
			if err := marshalIP(resp.Question.Name, ip, &builder); err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case dns.TypePTR:
 | 
						|
		err = marshalPTRRecord(resp.Question.Name, resp.Name, &builder)
 | 
						|
	case dns.TypeTXT:
 | 
						|
		err = marshalTXT(resp.Question.Name, resp.TXT, &builder)
 | 
						|
	case dns.TypeCNAME:
 | 
						|
		err = marshalCNAME(resp.Question.Name, resp.CNAME, &builder)
 | 
						|
	case dns.TypeSRV:
 | 
						|
		err = marshalSRV(resp.Question.Name, resp.SRVs, &builder)
 | 
						|
	case dns.TypeNS:
 | 
						|
		err = marshalNS(resp.Question.Name, resp.NSs, &builder)
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return builder.Finish()
 | 
						|
}
 | 
						|
 | 
						|
const (
 | 
						|
	rdnsv4Suffix = ".in-addr.arpa."
 | 
						|
	rdnsv6Suffix = ".ip6.arpa."
 | 
						|
)
 | 
						|
 | 
						|
// hasRDNSBonjourPrefix reports whether name has a Bonjour Service Prefix..
 | 
						|
//
 | 
						|
// https://tools.ietf.org/html/rfc6763 lists
 | 
						|
// "five special RR names" for Bonjour service discovery:
 | 
						|
//
 | 
						|
//	 b._dns-sd._udp.<domain>.
 | 
						|
//	db._dns-sd._udp.<domain>.
 | 
						|
//	 r._dns-sd._udp.<domain>.
 | 
						|
//	dr._dns-sd._udp.<domain>.
 | 
						|
//	lb._dns-sd._udp.<domain>.
 | 
						|
func hasRDNSBonjourPrefix(name dnsname.FQDN) bool {
 | 
						|
	s := name.WithTrailingDot()
 | 
						|
	base, rest, ok := strings.Cut(s, ".")
 | 
						|
	if !ok {
 | 
						|
		return false // shouldn't happen
 | 
						|
	}
 | 
						|
	switch base {
 | 
						|
	case "b", "db", "r", "dr", "lb":
 | 
						|
	default:
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	return strings.HasPrefix(rest, "_dns-sd._udp.")
 | 
						|
}
 | 
						|
 | 
						|
// rawNameToLower converts a raw DNS name to a string, lowercasing it.
 | 
						|
func rawNameToLower(name []byte) string {
 | 
						|
	var sb strings.Builder
 | 
						|
	sb.Grow(len(name))
 | 
						|
 | 
						|
	for _, b := range name {
 | 
						|
		if 'A' <= b && b <= 'Z' {
 | 
						|
			b = b - 'A' + 'a'
 | 
						|
		}
 | 
						|
		sb.WriteByte(b)
 | 
						|
	}
 | 
						|
 | 
						|
	return sb.String()
 | 
						|
}
 | 
						|
 | 
						|
// ptrNameToIPv4 transforms a PTR name representing an IPv4 address to said address.
 | 
						|
// Such names are IPv4 labels in reverse order followed by .in-addr.arpa.
 | 
						|
// For example,
 | 
						|
//
 | 
						|
//	4.3.2.1.in-addr.arpa
 | 
						|
//
 | 
						|
// is transformed to
 | 
						|
//
 | 
						|
//	1.2.3.4
 | 
						|
func rdnsNameToIPv4(name dnsname.FQDN) (ip netip.Addr, ok bool) {
 | 
						|
	s := strings.TrimSuffix(name.WithTrailingDot(), rdnsv4Suffix)
 | 
						|
	ip, err := netip.ParseAddr(s)
 | 
						|
	if err != nil {
 | 
						|
		return netip.Addr{}, false
 | 
						|
	}
 | 
						|
	if !ip.Is4() {
 | 
						|
		return netip.Addr{}, false
 | 
						|
	}
 | 
						|
	b := ip.As4()
 | 
						|
	return netaddr.IPv4(b[3], b[2], b[1], b[0]), true
 | 
						|
}
 | 
						|
 | 
						|
// ptrNameToIPv6 transforms a PTR name representing an IPv6 address to said address.
 | 
						|
// Such names are dot-separated nibbles in reverse order followed by .ip6.arpa.
 | 
						|
// For example,
 | 
						|
//
 | 
						|
//	b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.
 | 
						|
//
 | 
						|
// is transformed to
 | 
						|
//
 | 
						|
//	2001:db8::567:89ab
 | 
						|
func rdnsNameToIPv6(name dnsname.FQDN) (ip netip.Addr, ok bool) {
 | 
						|
	var b [32]byte
 | 
						|
	var ipb [16]byte
 | 
						|
 | 
						|
	s := strings.TrimSuffix(name.WithTrailingDot(), rdnsv6Suffix)
 | 
						|
	// 32 nibbles and 31 dots between them.
 | 
						|
	if len(s) != 63 {
 | 
						|
		return netip.Addr{}, false
 | 
						|
	}
 | 
						|
 | 
						|
	// Dots and hex digits alternate.
 | 
						|
	prevDot := true
 | 
						|
	// i ranges over name backward; j ranges over b forward.
 | 
						|
	for i, j := len(s)-1, 0; i >= 0; i-- {
 | 
						|
		thisDot := (s[i] == '.')
 | 
						|
		if prevDot == thisDot {
 | 
						|
			return netip.Addr{}, false
 | 
						|
		}
 | 
						|
		prevDot = thisDot
 | 
						|
 | 
						|
		if !thisDot {
 | 
						|
			// This is safe assuming alternation.
 | 
						|
			// We do not check that non-dots are hex digits: hex.Decode below will do that.
 | 
						|
			b[j] = s[i]
 | 
						|
			j++
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	_, err := hex.Decode(ipb[:], b[:])
 | 
						|
	if err != nil {
 | 
						|
		return netip.Addr{}, false
 | 
						|
	}
 | 
						|
 | 
						|
	return netip.AddrFrom16(ipb), true
 | 
						|
}
 | 
						|
 | 
						|
// respondReverse returns a DNS response to a PTR query.
 | 
						|
// It is assumed that resp.Question is populated by respond before this is called.
 | 
						|
func (r *Resolver) respondReverse(query []byte, name dnsname.FQDN, resp *response) ([]byte, error) {
 | 
						|
	if hasRDNSBonjourPrefix(name) {
 | 
						|
		metricDNSReverseMissBonjour.Add(1)
 | 
						|
		return nil, errNotOurName
 | 
						|
	}
 | 
						|
 | 
						|
	resp.Name, resp.Header.RCode = r.resolveLocalReverse(name)
 | 
						|
	if resp.Header.RCode == dns.RCodeRefused {
 | 
						|
		metricDNSReverseMissOther.Add(1)
 | 
						|
		return nil, errNotOurName
 | 
						|
	}
 | 
						|
 | 
						|
	metricDNSMagicDNSSuccessReverse.Add(1)
 | 
						|
	return marshalResponse(resp)
 | 
						|
}
 | 
						|
 | 
						|
// respond returns a DNS response to query if it can be resolved locally.
 | 
						|
// Otherwise, it returns errNotOurName.
 | 
						|
func (r *Resolver) respond(query []byte) ([]byte, error) {
 | 
						|
	parser := dnsParserPool.Get().(*dnsParser)
 | 
						|
	defer dnsParserPool.Put(parser)
 | 
						|
 | 
						|
	// ParseQuery is sufficiently fast to run on every DNS packet.
 | 
						|
	// This is considerably simpler than extracting the name by hand
 | 
						|
	// to shave off microseconds in case of delegation.
 | 
						|
	err := parser.parseQuery(query)
 | 
						|
	// We will not return this error: it is the sender's fault.
 | 
						|
	if err != nil {
 | 
						|
		if errors.Is(err, dns.ErrSectionDone) {
 | 
						|
			metricDNSErrorParseNoQ.Add(1)
 | 
						|
			r.logf("parseQuery(%02x): no DNS questions", query)
 | 
						|
		} else {
 | 
						|
			metricDNSErrorParseQuery.Add(1)
 | 
						|
			r.logf("parseQuery(%02x): %v", query, err)
 | 
						|
		}
 | 
						|
		resp := parser.response()
 | 
						|
		resp.Header.RCode = dns.RCodeFormatError
 | 
						|
		return marshalResponse(resp)
 | 
						|
	}
 | 
						|
	rawName := parser.Question.Name.Data[:parser.Question.Name.Length]
 | 
						|
	name, err := dnsname.ToFQDN(rawNameToLower(rawName))
 | 
						|
	if err != nil {
 | 
						|
		metricDNSErrorNotFQDN.Add(1)
 | 
						|
		// DNS packet unexpectedly contains an invalid FQDN.
 | 
						|
		resp := parser.response()
 | 
						|
		resp.Header.RCode = dns.RCodeFormatError
 | 
						|
		return marshalResponse(resp)
 | 
						|
	}
 | 
						|
 | 
						|
	// Always try to handle reverse lookups; delegate inside when not found.
 | 
						|
	// This way, queries for existent nodes do not leak,
 | 
						|
	// but we behave gracefully if non-Tailscale nodes exist in CGNATRange.
 | 
						|
	if parser.Question.Type == dns.TypePTR {
 | 
						|
		return r.respondReverse(query, name, parser.response())
 | 
						|
	}
 | 
						|
 | 
						|
	ip, rcode := r.resolveLocal(name, parser.Question.Type)
 | 
						|
	if rcode == dns.RCodeRefused {
 | 
						|
		return nil, errNotOurName // sentinel error return value: it requests forwarding
 | 
						|
	}
 | 
						|
 | 
						|
	resp := parser.response()
 | 
						|
	resp.Header.RCode = rcode
 | 
						|
	resp.IP = ip
 | 
						|
	metricDNSMagicDNSSuccessName.Add(1)
 | 
						|
	return marshalResponse(resp)
 | 
						|
}
 | 
						|
 | 
						|
// unARPA maps from "4.4.8.8.in-addr.arpa." to "8.8.4.4", etc.
 | 
						|
func unARPA(a string) (ipStr string, ok bool) {
 | 
						|
	const suf4 = ".in-addr.arpa."
 | 
						|
	if s, ok := strings.CutSuffix(a, suf4); ok {
 | 
						|
		// Parse and reverse octets.
 | 
						|
		ip, err := netip.ParseAddr(s)
 | 
						|
		if err != nil || !ip.Is4() {
 | 
						|
			return "", false
 | 
						|
		}
 | 
						|
		a4 := ip.As4()
 | 
						|
		return netaddr.IPv4(a4[3], a4[2], a4[1], a4[0]).String(), true
 | 
						|
	}
 | 
						|
	const suf6 = ".ip6.arpa."
 | 
						|
	if len(a) == len("e.0.0.2.0.0.0.0.0.0.0.0.0.0.0.0.b.0.8.0.a.0.0.4.0.b.8.f.7.0.6.2.ip6.arpa.") &&
 | 
						|
		strings.HasSuffix(a, suf6) {
 | 
						|
		var hx [32]byte
 | 
						|
		var a16 [16]byte
 | 
						|
		for i := range hx {
 | 
						|
			hx[31-i] = a[i*2]
 | 
						|
			if a[i*2+1] != '.' {
 | 
						|
				return "", false
 | 
						|
			}
 | 
						|
		}
 | 
						|
		hex.Decode(a16[:], hx[:])
 | 
						|
		return netip.AddrFrom16(a16).Unmap().String(), true
 | 
						|
	}
 | 
						|
	return "", false
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
var (
 | 
						|
	metricDNSQueryLocal       = clientmetric.NewCounter("dns_query_local")
 | 
						|
	metricDNSQueryErrorClosed = clientmetric.NewCounter("dns_query_local_error_closed")
 | 
						|
 | 
						|
	metricDNSErrorParseNoQ   = clientmetric.NewCounter("dns_query_respond_error_no_question")
 | 
						|
	metricDNSErrorParseQuery = clientmetric.NewCounter("dns_query_respond_error_parse")
 | 
						|
	metricDNSErrorNotFQDN    = clientmetric.NewCounter("dns_query_respond_error_not_fqdn")
 | 
						|
 | 
						|
	metricDNSMagicDNSSuccessName    = clientmetric.NewCounter("dns_query_magic_success_name")
 | 
						|
	metricDNSMagicDNSSuccessReverse = clientmetric.NewCounter("dns_query_magic_success_reverse")
 | 
						|
 | 
						|
	metricDNSExitProxyQuery           = clientmetric.NewCounter("dns_exit_node_query")
 | 
						|
	metricDNSExitProxyErrorName       = clientmetric.NewCounter("dns_exit_node_error_name")
 | 
						|
	metricDNSExitProxyErrorForward    = clientmetric.NewCounter("dns_exit_node_error_forward")
 | 
						|
	metricDNSExitProxyErrorResolvConf = clientmetric.NewCounter("dns_exit_node_error_resolvconf")
 | 
						|
 | 
						|
	metricDNSFwd                     = clientmetric.NewCounter("dns_query_fwd")
 | 
						|
	metricDNSFwdDropBonjour          = clientmetric.NewCounter("dns_query_fwd_drop_bonjour")
 | 
						|
	metricDNSFwdErrorName            = clientmetric.NewCounter("dns_query_fwd_error_name")
 | 
						|
	metricDNSFwdErrorNoUpstream      = clientmetric.NewCounter("dns_query_fwd_error_no_upstream")
 | 
						|
	metricDNSFwdSuccess              = clientmetric.NewCounter("dns_query_fwd_success")
 | 
						|
	metricDNSFwdErrorContext         = clientmetric.NewCounter("dns_query_fwd_error_context")
 | 
						|
	metricDNSFwdErrorContextGotError = clientmetric.NewCounter("dns_query_fwd_error_context_got_error")
 | 
						|
 | 
						|
	metricDNSFwdErrorType = clientmetric.NewCounter("dns_query_fwd_error_type")
 | 
						|
	metricDNSFwdTruncated = clientmetric.NewCounter("dns_query_fwd_truncated")
 | 
						|
 | 
						|
	metricDNSFwdUDP            = clientmetric.NewCounter("dns_query_fwd_udp")       // on entry
 | 
						|
	metricDNSFwdUDPWrote       = clientmetric.NewCounter("dns_query_fwd_udp_wrote") // sent UDP packet
 | 
						|
	metricDNSFwdUDPErrorWrite  = clientmetric.NewCounter("dns_query_fwd_udp_error_write")
 | 
						|
	metricDNSFwdUDPErrorServer = clientmetric.NewCounter("dns_query_fwd_udp_error_server")
 | 
						|
	metricDNSFwdUDPErrorTxID   = clientmetric.NewCounter("dns_query_fwd_udp_error_txid")
 | 
						|
	metricDNSFwdUDPErrorRead   = clientmetric.NewCounter("dns_query_fwd_udp_error_read")
 | 
						|
	metricDNSFwdUDPSuccess     = clientmetric.NewCounter("dns_query_fwd_udp_success")
 | 
						|
 | 
						|
	metricDNSFwdTCP            = clientmetric.NewCounter("dns_query_fwd_tcp")       // on entry
 | 
						|
	metricDNSFwdTCPWrote       = clientmetric.NewCounter("dns_query_fwd_tcp_wrote") // sent TCP packet
 | 
						|
	metricDNSFwdTCPErrorWrite  = clientmetric.NewCounter("dns_query_fwd_tcp_error_write")
 | 
						|
	metricDNSFwdTCPErrorServer = clientmetric.NewCounter("dns_query_fwd_tcp_error_server")
 | 
						|
	metricDNSFwdTCPErrorTxID   = clientmetric.NewCounter("dns_query_fwd_tcp_error_txid")
 | 
						|
	metricDNSFwdTCPErrorRead   = clientmetric.NewCounter("dns_query_fwd_tcp_error_read")
 | 
						|
	metricDNSFwdTCPSuccess     = clientmetric.NewCounter("dns_query_fwd_tcp_success")
 | 
						|
 | 
						|
	metricDNSFwdDoH               = clientmetric.NewCounter("dns_query_fwd_doh")
 | 
						|
	metricDNSFwdDoHErrorStatus    = clientmetric.NewCounter("dns_query_fwd_doh_error_status")
 | 
						|
	metricDNSFwdDoHErrorCT        = clientmetric.NewCounter("dns_query_fwd_doh_error_content_type")
 | 
						|
	metricDNSFwdDoHErrorTransport = clientmetric.NewCounter("dns_query_fwd_doh_error_transport")
 | 
						|
	metricDNSFwdDoHErrorBody      = clientmetric.NewCounter("dns_query_fwd_doh_error_body")
 | 
						|
 | 
						|
	metricDNSResolveLocal             = clientmetric.NewCounter("dns_resolve_local")
 | 
						|
	metricDNSResolveLocalErrorOnion   = clientmetric.NewCounter("dns_resolve_local_error_onion")
 | 
						|
	metricDNSResolveLocalErrorMissing = clientmetric.NewCounter("dns_resolve_local_error_missing")
 | 
						|
	metricDNSResolveLocalErrorRefused = clientmetric.NewCounter("dns_resolve_local_error_refused")
 | 
						|
	metricDNSResolveLocalOKA          = clientmetric.NewCounter("dns_resolve_local_ok_a")
 | 
						|
	metricDNSResolveLocalOKAAAA       = clientmetric.NewCounter("dns_resolve_local_ok_aaaa")
 | 
						|
	metricDNSResolveLocalOKAll        = clientmetric.NewCounter("dns_resolve_local_ok_all")
 | 
						|
	metricDNSResolveLocalNoA          = clientmetric.NewCounter("dns_resolve_local_no_a")
 | 
						|
	metricDNSResolveLocalNoAAAA       = clientmetric.NewCounter("dns_resolve_local_no_aaaa")
 | 
						|
	metricDNSResolveLocalNoAll        = clientmetric.NewCounter("dns_resolve_local_no_all")
 | 
						|
	metricDNSResolveNotImplType       = clientmetric.NewCounter("dns_resolve_local_not_impl_type")
 | 
						|
	metricDNSResolveNoRecordType      = clientmetric.NewCounter("dns_resolve_local_no_record_type")
 | 
						|
 | 
						|
	metricDNSReverseMissBonjour = clientmetric.NewCounter("dns_reverse_miss_bonjour")
 | 
						|
	metricDNSReverseMissOther   = clientmetric.NewCounter("dns_reverse_miss_other")
 | 
						|
)
 |