mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-11-04 02:01:14 +01:00 
			
		
		
		
	This updates all source files to use a new standard header for copyright and license declaration. Notably, copyright no longer includes a date, and we now use the standard SPDX-License-Identifier header. This commit was done almost entirely mechanically with perl, and then some minimal manual fixes. Updates #6865 Signed-off-by: Will Norris <will@tailscale.com>
		
			
				
	
	
		
			182 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			182 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) Tailscale Inc & AUTHORS
 | 
						|
// SPDX-License-Identifier: BSD-3-Clause
 | 
						|
 | 
						|
package dns
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"fmt"
 | 
						|
	"net/netip"
 | 
						|
	"sort"
 | 
						|
 | 
						|
	"tailscale.com/net/dns/publicdns"
 | 
						|
	"tailscale.com/net/dns/resolver"
 | 
						|
	"tailscale.com/net/tsaddr"
 | 
						|
	"tailscale.com/types/dnstype"
 | 
						|
	"tailscale.com/util/dnsname"
 | 
						|
)
 | 
						|
 | 
						|
// Config is a DNS configuration.
 | 
						|
type Config struct {
 | 
						|
	// DefaultResolvers are the DNS resolvers to use for DNS names
 | 
						|
	// which aren't covered by more specific per-domain routes below.
 | 
						|
	// If empty, the OS's default resolvers (the ones that predate
 | 
						|
	// Tailscale altering the configuration) are used.
 | 
						|
	DefaultResolvers []*dnstype.Resolver
 | 
						|
	// Routes maps a DNS suffix to the resolvers that should be used
 | 
						|
	// for queries that fall within that suffix.
 | 
						|
	// If a query doesn't match any entry in Routes, the
 | 
						|
	// DefaultResolvers are used.
 | 
						|
	// A Routes entry with no resolvers means the route should be
 | 
						|
	// authoritatively answered using the contents of Hosts.
 | 
						|
	Routes map[dnsname.FQDN][]*dnstype.Resolver
 | 
						|
	// SearchDomains are DNS suffixes to try when expanding
 | 
						|
	// single-label queries.
 | 
						|
	SearchDomains []dnsname.FQDN
 | 
						|
	// Hosts maps DNS FQDNs to their IPs, which can be a mix of IPv4
 | 
						|
	// and IPv6.
 | 
						|
	// Queries matching entries in Hosts are resolved locally by
 | 
						|
	// 100.100.100.100 without leaving the machine.
 | 
						|
	// Adding an entry to Hosts merely creates the record. If you want
 | 
						|
	// it to resolve, you also need to add appropriate routes to
 | 
						|
	// Routes.
 | 
						|
	Hosts map[dnsname.FQDN][]netip.Addr
 | 
						|
	// OnlyIPv6, if true, uses the IPv6 service IP (for MagicDNS)
 | 
						|
	// instead of the IPv4 version (100.100.100.100).
 | 
						|
	OnlyIPv6 bool
 | 
						|
}
 | 
						|
 | 
						|
func (c *Config) serviceIP() netip.Addr {
 | 
						|
	if c.OnlyIPv6 {
 | 
						|
		return tsaddr.TailscaleServiceIPv6()
 | 
						|
	}
 | 
						|
	return tsaddr.TailscaleServiceIP()
 | 
						|
}
 | 
						|
 | 
						|
// WriteToBufioWriter write a debug version of c for logs to w, omitting
 | 
						|
// spammy stuff like *.arpa entries and replacing it with a total count.
 | 
						|
func (c *Config) WriteToBufioWriter(w *bufio.Writer) {
 | 
						|
	w.WriteString("{DefaultResolvers:")
 | 
						|
	resolver.WriteDNSResolvers(w, c.DefaultResolvers)
 | 
						|
 | 
						|
	w.WriteString(" Routes:")
 | 
						|
	resolver.WriteRoutes(w, c.Routes)
 | 
						|
 | 
						|
	fmt.Fprintf(w, " SearchDomains:%v", c.SearchDomains)
 | 
						|
	fmt.Fprintf(w, " Hosts:%v", len(c.Hosts))
 | 
						|
	w.WriteString("}")
 | 
						|
}
 | 
						|
 | 
						|
// needsAnyResolvers reports whether c requires a resolver to be set
 | 
						|
// at the OS level.
 | 
						|
func (c Config) needsOSResolver() bool {
 | 
						|
	return c.hasDefaultResolvers() || c.hasRoutes()
 | 
						|
}
 | 
						|
 | 
						|
func (c Config) hasRoutes() bool {
 | 
						|
	return len(c.Routes) > 0
 | 
						|
}
 | 
						|
 | 
						|
// hasDefaultIPResolversOnly reports whether the only resolvers in c are
 | 
						|
// DefaultResolvers, and that those resolvers are simple IP addresses
 | 
						|
// that speak regular port 53 DNS.
 | 
						|
func (c Config) hasDefaultIPResolversOnly() bool {
 | 
						|
	if !c.hasDefaultResolvers() || c.hasRoutes() {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	for _, r := range c.DefaultResolvers {
 | 
						|
		if ipp, ok := r.IPPort(); !ok || ipp.Port() != 53 || publicdns.IPIsDoHOnlyServer(ipp.Addr()) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
// hasHostsWithoutSplitDNSRoutes reports whether c contains any Host entries
 | 
						|
// that aren't covered by a SplitDNS route suffix.
 | 
						|
func (c Config) hasHostsWithoutSplitDNSRoutes() bool {
 | 
						|
	// TODO(bradfitz): this could be more efficient, but we imagine
 | 
						|
	// the number of SplitDNS routes and/or hosts will be small.
 | 
						|
	for host := range c.Hosts {
 | 
						|
		if !c.hasSplitDNSRouteForHost(host) {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// hasSplitDNSRouteForHost reports whether c contains a SplitDNS route
 | 
						|
// that contains hosts.
 | 
						|
func (c Config) hasSplitDNSRouteForHost(host dnsname.FQDN) bool {
 | 
						|
	for route := range c.Routes {
 | 
						|
		if route.Contains(host) {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func (c Config) hasDefaultResolvers() bool {
 | 
						|
	return len(c.DefaultResolvers) > 0
 | 
						|
}
 | 
						|
 | 
						|
// singleResolverSet returns the resolvers used by c.Routes if all
 | 
						|
// routes use the same resolvers, or nil if multiple sets of resolvers
 | 
						|
// are specified.
 | 
						|
func (c Config) singleResolverSet() []*dnstype.Resolver {
 | 
						|
	var (
 | 
						|
		prev            []*dnstype.Resolver
 | 
						|
		prevInitialized bool
 | 
						|
	)
 | 
						|
	for _, resolvers := range c.Routes {
 | 
						|
		if !prevInitialized {
 | 
						|
			prev = resolvers
 | 
						|
			prevInitialized = true
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if !sameResolverNames(prev, resolvers) {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return prev
 | 
						|
}
 | 
						|
 | 
						|
// matchDomains returns the list of match suffixes needed by Routes.
 | 
						|
func (c Config) matchDomains() []dnsname.FQDN {
 | 
						|
	ret := make([]dnsname.FQDN, 0, len(c.Routes))
 | 
						|
	for suffix := range c.Routes {
 | 
						|
		ret = append(ret, suffix)
 | 
						|
	}
 | 
						|
	sort.Slice(ret, func(i, j int) bool {
 | 
						|
		return ret[i].WithTrailingDot() < ret[j].WithTrailingDot()
 | 
						|
	})
 | 
						|
	return ret
 | 
						|
}
 | 
						|
 | 
						|
func sameResolverNames(a, b []*dnstype.Resolver) bool {
 | 
						|
	if len(a) != len(b) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	for i := range a {
 | 
						|
		if a[i].Addr != b[i].Addr {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		if !sameIPs(a[i].BootstrapResolution, b[i].BootstrapResolution) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func sameIPs(a, b []netip.Addr) bool {
 | 
						|
	if len(a) != len(b) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	for i := range a {
 | 
						|
		if a[i] != b[i] {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 |