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:
David Anderson
2021-04-06 22:00:59 -07:00
committed by Dave Anderson
parent 939861773d
commit da4cc8bbb4
3 changed files with 213 additions and 31 deletions

View File

@@ -5,6 +5,7 @@
package dns
import (
"strings"
"time"
"inet.af/netaddr"
@@ -50,49 +51,151 @@ func NewManager(logf logger.Logf, oscfg OSConfigurator, linkMon *monitor.Mon) *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 {
m.logf("Set: %+v", cfg)
if len(cfg.DefaultResolvers) == 0 {
// TODO: make other settings work even if you didn't set a
// default resolver. For now, no default resolvers == no
// managed DNS config.
cfg = Config{}
if false {
// Temporary, for danderson to test things.
forceSplitDNSForTesting(&cfg)
}
resolverCfg := resolver.Config{
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)
}
}
rcfg, ocfg := m.compileConfig(cfg)
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
}
if err := m.os.SetDNS(osCfg); err != nil {
if err := m.os.SetDNS(ocfg); err != nil {
return err
}
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 {
return m.resolver.EnqueueRequest(bs, from)
}