mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-06 21:01:56 +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:
committed by
Dave Anderson
parent
939861773d
commit
da4cc8bbb4
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user