mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-18 20:51:45 +00:00
net/dns: handle all possible translations of high-level DNS config.
With this change, all OSes can sort-of do split DNS, except that the default upstream is hardcoded to 8.8.8.8 pending further plumbing. Additionally, Windows 8-10 can do split DNS fully correctly, without the 8.8.8.8 hack. Part of #953. Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
parent
939861773d
commit
da4cc8bbb4
@ -1618,10 +1618,9 @@ func (b *LocalBackend) initPeerAPIListener() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// magicDNSRootDomains returns the subset of nm.DNS.Domains that are the search domains for MagicDNS.
|
// magicDNSRootDomains returns the subset of nm.DNS.Domains that are the search domains for MagicDNS.
|
||||||
// Each entry has a trailing period.
|
|
||||||
func magicDNSRootDomains(nm *netmap.NetworkMap) []string {
|
func magicDNSRootDomains(nm *netmap.NetworkMap) []string {
|
||||||
if v := nm.MagicDNSSuffix(); v != "" {
|
if v := nm.MagicDNSSuffix(); v != "" {
|
||||||
return []string{strings.Trim(v, ".") + "."}
|
return []string{strings.Trim(v, ".")}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -35,3 +35,83 @@ type Config struct {
|
|||||||
// return NXDOMAIN.
|
// return NXDOMAIN.
|
||||||
AuthoritativeSuffixes []string
|
AuthoritativeSuffixes []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// needsAnyResolvers reports whether c requires a resolver to be set
|
||||||
|
// at the OS level.
|
||||||
|
func (c Config) needsOSResolver() bool {
|
||||||
|
return c.hasDefaultResolvers() || c.hasRoutes() || c.hasHosts()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) hasRoutes() bool {
|
||||||
|
return len(c.Routes) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasDefaultResolversOnly reports whether the only resolvers in c are
|
||||||
|
// DefaultResolvers.
|
||||||
|
func (c Config) hasDefaultResolversOnly() bool {
|
||||||
|
return c.hasDefaultResolvers() && !c.hasRoutes() && !c.hasHosts()
|
||||||
|
}
|
||||||
|
|
||||||
|
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() []netaddr.IPPort {
|
||||||
|
var first []netaddr.IPPort
|
||||||
|
for _, resolvers := range c.Routes {
|
||||||
|
if first == nil {
|
||||||
|
first = resolvers
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !sameIPPorts(first, resolvers) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return first
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasHosts reports whether c requires resolution of MagicDNS hosts or
|
||||||
|
// domains.
|
||||||
|
func (c Config) hasHosts() bool {
|
||||||
|
return len(c.Hosts) > 0 || len(c.AuthoritativeSuffixes) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchDomains returns the list of match suffixes needed by Routes,
|
||||||
|
// AuthoritativeSuffixes. Hosts is not considered as we assume that
|
||||||
|
// they're covered by AuthoritativeSuffixes for now.
|
||||||
|
func (c Config) matchDomains() []string {
|
||||||
|
ret := make([]string, 0, len(c.Routes)+len(c.AuthoritativeSuffixes))
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for _, suffix := range c.AuthoritativeSuffixes {
|
||||||
|
if seen[suffix] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret = append(ret, suffix)
|
||||||
|
seen[suffix] = true
|
||||||
|
}
|
||||||
|
for suffix := range c.Routes {
|
||||||
|
if seen[suffix] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret = append(ret, suffix)
|
||||||
|
seen[suffix] = true
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameIPPorts(a, b []netaddr.IPPort) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range a {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
@ -50,49 +51,151 @@ func NewManager(logf logger.Logf, oscfg OSConfigurator, linkMon *monitor.Mon) *M
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// forceSplitDNSForTesting alters cfg to be a split DNS configuration
|
||||||
|
// that only captures search paths. It's intended for testing split
|
||||||
|
// DNS until the functionality is linked up in the admin panel.
|
||||||
|
func forceSplitDNSForTesting(cfg *Config) {
|
||||||
|
if len(cfg.DefaultResolvers) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Routes == nil {
|
||||||
|
cfg.Routes = map[string][]netaddr.IPPort{}
|
||||||
|
}
|
||||||
|
for _, search := range cfg.SearchDomains {
|
||||||
|
cfg.Routes[search] = cfg.DefaultResolvers
|
||||||
|
}
|
||||||
|
cfg.DefaultResolvers = nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) Set(cfg Config) error {
|
func (m *Manager) Set(cfg Config) error {
|
||||||
m.logf("Set: %+v", cfg)
|
m.logf("Set: %+v", cfg)
|
||||||
|
|
||||||
if len(cfg.DefaultResolvers) == 0 {
|
if false {
|
||||||
// TODO: make other settings work even if you didn't set a
|
// Temporary, for danderson to test things.
|
||||||
// default resolver. For now, no default resolvers == no
|
forceSplitDNSForTesting(&cfg)
|
||||||
// managed DNS config.
|
|
||||||
cfg = Config{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resolverCfg := resolver.Config{
|
rcfg, ocfg := m.compileConfig(cfg)
|
||||||
Hosts: cfg.Hosts,
|
|
||||||
LocalDomains: cfg.AuthoritativeSuffixes,
|
|
||||||
Routes: map[string][]netaddr.IPPort{},
|
|
||||||
}
|
|
||||||
osCfg := OSConfig{
|
|
||||||
SearchDomains: cfg.SearchDomains,
|
|
||||||
}
|
|
||||||
// We must proxy through quad-100 if MagicDNS hosts are in
|
|
||||||
// use, or there are any per-domain routes.
|
|
||||||
mustProxy := len(cfg.Hosts) > 0 || len(cfg.Routes) > 0
|
|
||||||
if mustProxy {
|
|
||||||
osCfg.Nameservers = []netaddr.IP{tsaddr.TailscaleServiceIP()}
|
|
||||||
resolverCfg.Routes["."] = cfg.DefaultResolvers
|
|
||||||
for suffix, resolvers := range cfg.Routes {
|
|
||||||
resolverCfg.Routes[suffix] = resolvers
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, resolver := range cfg.DefaultResolvers {
|
|
||||||
osCfg.Nameservers = append(osCfg.Nameservers, resolver.IP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.resolver.SetConfig(resolverCfg); err != nil {
|
m.logf("Resolvercfg: %+v", rcfg)
|
||||||
|
m.logf("OScfg: %+v", ocfg)
|
||||||
|
|
||||||
|
if err := m.resolver.SetConfig(rcfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := m.os.SetDNS(osCfg); err != nil {
|
if err := m.os.SetDNS(ocfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compileConfig converts cfg into a quad-100 resolver configuration
|
||||||
|
// and an OS-level configuration.
|
||||||
|
func (m *Manager) compileConfig(cfg Config) (resolver.Config, OSConfig) {
|
||||||
|
// Deal with trivial configs first.
|
||||||
|
switch {
|
||||||
|
case !cfg.needsOSResolver():
|
||||||
|
// Set search domains, but nothing else. This also covers the
|
||||||
|
// case where cfg is entirely zero, in which case these
|
||||||
|
// configs clear all Tailscale DNS settings.
|
||||||
|
return resolver.Config{}, OSConfig{
|
||||||
|
SearchDomains: cfg.SearchDomains,
|
||||||
|
}
|
||||||
|
case cfg.hasDefaultResolversOnly():
|
||||||
|
// Trivial CorpDNS configuration, just override the OS
|
||||||
|
// resolver.
|
||||||
|
return resolver.Config{}, OSConfig{
|
||||||
|
Nameservers: toIPsOnly(cfg.DefaultResolvers),
|
||||||
|
SearchDomains: cfg.SearchDomains,
|
||||||
|
}
|
||||||
|
case cfg.hasDefaultResolvers():
|
||||||
|
// Default resolvers plus other stuff always ends up proxying
|
||||||
|
// through quad-100.
|
||||||
|
rcfg := resolver.Config{
|
||||||
|
Routes: map[string][]netaddr.IPPort{
|
||||||
|
".": cfg.DefaultResolvers,
|
||||||
|
},
|
||||||
|
Hosts: cfg.Hosts,
|
||||||
|
LocalDomains: addFQDNDots(cfg.AuthoritativeSuffixes),
|
||||||
|
}
|
||||||
|
for suffix, resolvers := range cfg.Routes {
|
||||||
|
rcfg.Routes[suffix+"."] = resolvers
|
||||||
|
}
|
||||||
|
ocfg := OSConfig{
|
||||||
|
Nameservers: []netaddr.IP{tsaddr.TailscaleServiceIP()},
|
||||||
|
SearchDomains: cfg.SearchDomains,
|
||||||
|
}
|
||||||
|
return rcfg, ocfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// From this point on, we're figuring out split DNS
|
||||||
|
// configurations. The possible cases don't return directly any
|
||||||
|
// more, because as a final step we have to handle the case where
|
||||||
|
// the OS can't do split DNS.
|
||||||
|
var rcfg resolver.Config
|
||||||
|
var ocfg OSConfig
|
||||||
|
|
||||||
|
if !cfg.hasHosts() && cfg.singleResolverSet() != nil && m.os.SupportsSplitDNS() {
|
||||||
|
// Split DNS configuration requested, where all split domains
|
||||||
|
// go to the same resolvers. We can let the OS do it.
|
||||||
|
return resolver.Config{}, OSConfig{
|
||||||
|
Nameservers: toIPsOnly(cfg.singleResolverSet()),
|
||||||
|
SearchDomains: cfg.SearchDomains,
|
||||||
|
MatchDomains: cfg.matchDomains(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split DNS configuration with either multiple upstream routes,
|
||||||
|
// or routes + MagicDNS, or just MagicDNS, or on an OS that cannot
|
||||||
|
// split-DNS. Install a split config pointing at quad-100.
|
||||||
|
rcfg = resolver.Config{
|
||||||
|
Routes: map[string][]netaddr.IPPort{},
|
||||||
|
Hosts: cfg.Hosts,
|
||||||
|
LocalDomains: addFQDNDots(cfg.AuthoritativeSuffixes),
|
||||||
|
}
|
||||||
|
for suffix, resolvers := range cfg.Routes {
|
||||||
|
rcfg.Routes[suffix+"."] = resolvers
|
||||||
|
}
|
||||||
|
ocfg = OSConfig{
|
||||||
|
Nameservers: []netaddr.IP{tsaddr.TailscaleServiceIP()},
|
||||||
|
SearchDomains: cfg.SearchDomains,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the OS can't do native split-dns, read out the underlying
|
||||||
|
// resolver config and blend it into our config.
|
||||||
|
// TODO: for now, use quad-8 as the upstream until more plumbing
|
||||||
|
// is done.
|
||||||
|
if m.os.SupportsSplitDNS() {
|
||||||
|
ocfg.MatchDomains = cfg.matchDomains()
|
||||||
|
} else {
|
||||||
|
rcfg.Routes["."] = []netaddr.IPPort{netaddr.MustParseIPPort("8.8.8.8:53")}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rcfg, ocfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFQDNDots(domains []string) []string {
|
||||||
|
ret := make([]string, 0, len(domains))
|
||||||
|
for _, dom := range domains {
|
||||||
|
ret = append(ret, strings.TrimSuffix(dom, ".")+".")
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// toIPsOnly returns only the IP portion of ipps.
|
||||||
|
// TODO: this discards port information on the assumption that we're
|
||||||
|
// always pointing at port 53.
|
||||||
|
// https://github.com/tailscale/tailscale/issues/1666 tracks making
|
||||||
|
// that not true, if we ever want to.
|
||||||
|
func toIPsOnly(ipps []netaddr.IPPort) (ret []netaddr.IP) {
|
||||||
|
for _, ipp := range ipps {
|
||||||
|
ret = append(ret, ipp.IP)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) EnqueueRequest(bs []byte, from netaddr.IPPort) error {
|
func (m *Manager) EnqueueRequest(bs []byte, from netaddr.IPPort) error {
|
||||||
return m.resolver.EnqueueRequest(bs, from)
|
return m.resolver.EnqueueRequest(bs, from)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user