summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2021-11-24 14:42:33 -0800
committerBrad Fitzpatrick <bradfitz@tailscale.com>2021-11-29 10:05:23 -0800
commit4c7ee0c9f9d18210c318820efddc290a3536a7e7 (patch)
tree1cf4cff61884d18892891d243ed8ae2cdadb6d39
parentc2efe46f72db74031cfc82dca992ed22bb59257c (diff)
downloadtailscale-bradfitz/exit_node_forward_dns.tar.xz
tailscale-bradfitz/exit_node_forward_dns.zip
net/dns: make exit node DNS ask OSConfigurator for backup resolversbradfitz/exit_node_forward_dns
Updates #1713 Change-Id: I7be9dab2b2c03749b4c2d99f9f45c11422ac915a Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--net/dns/debian_resolvconf.go5
-rw-r--r--net/dns/direct.go19
-rw-r--r--net/dns/manager.go5
-rw-r--r--net/dns/manager_test.go12
-rw-r--r--net/dns/manager_windows.go5
-rw-r--r--net/dns/nm.go5
-rw-r--r--net/dns/noop.go7
-rw-r--r--net/dns/openresolv.go6
-rw-r--r--net/dns/osconfig.go31
-rw-r--r--net/dns/resolved.go5
-rw-r--r--net/dns/resolver/tsdns.go59
11 files changed, 124 insertions, 35 deletions
diff --git a/net/dns/debian_resolvconf.go b/net/dns/debian_resolvconf.go
index ab9a7cf79..8e170f6bd 100644
--- a/net/dns/debian_resolvconf.go
+++ b/net/dns/debian_resolvconf.go
@@ -18,6 +18,7 @@ import (
"path/filepath"
"tailscale.com/atomicfile"
+ "tailscale.com/types/dnstype"
"tailscale.com/types/logger"
)
@@ -173,6 +174,10 @@ func (m *resolvconfManager) GetBaseConfig() (OSConfig, error) {
return readResolv(&conf)
}
+func (m *resolvconfManager) GetExitNodeForwardResolver() ([]dnstype.Resolver, error) {
+ return getExitNodeForwardResolverFromBaseConfig(m)
+}
+
func (m *resolvconfManager) Close() error {
if err := m.deleteTailscaleConfig(); err != nil {
return err
diff --git a/net/dns/direct.go b/net/dns/direct.go
index 388eda68f..22b85436d 100644
--- a/net/dns/direct.go
+++ b/net/dns/direct.go
@@ -18,6 +18,7 @@ import (
"strings"
"inet.af/netaddr"
+ "tailscale.com/types/dnstype"
"tailscale.com/types/logger"
"tailscale.com/util/dnsname"
)
@@ -183,6 +184,24 @@ func (m *directManager) readResolvFile(path string) (OSConfig, error) {
return readResolv(bytes.NewReader(b))
}
+func (m *directManager) GetExitNodeForwardResolver() (ret []dnstype.Resolver, retErr error) {
+ for _, filename := range []string{backupConf, resolvConf} {
+ if oc, err := m.readResolvFile(filename); err == nil {
+ for _, ip := range oc.Nameservers {
+ if ip != netaddr.IPv4(100, 100, 100, 100) {
+ ret = append(ret, dnstype.Resolver{Addr: netaddr.IPPortFrom(ip, 53).String()})
+ }
+ }
+ if len(ret) > 0 {
+ return ret, nil
+ }
+ } else if !os.IsNotExist(err) && retErr == nil {
+ retErr = err
+ }
+ }
+ return nil, retErr
+}
+
// ownedByTailscale reports whether /etc/resolv.conf seems to be a
// tailscale-managed file.
func (m *directManager) ownedByTailscale() (bool, error) {
diff --git a/net/dns/manager.go b/net/dns/manager.go
index 6fd6d8f3a..928d20a45 100644
--- a/net/dns/manager.go
+++ b/net/dns/manager.go
@@ -62,6 +62,11 @@ func (m *Manager) Set(cfg Config) error {
if err != nil {
return err
}
+ exitNodeBackupResolvers, err := m.os.GetExitNodeForwardResolver()
+ if err != nil {
+ return err
+ }
+ rcfg.ExitNodeBackupResolvers = exitNodeBackupResolvers
m.logf("Resolvercfg: %v", logger.ArgWriter(func(w *bufio.Writer) {
rcfg.WriteToBufioWriter(w)
diff --git a/net/dns/manager_test.go b/net/dns/manager_test.go
index 05ec7c06f..a42da2919 100644
--- a/net/dns/manager_test.go
+++ b/net/dns/manager_test.go
@@ -45,6 +45,10 @@ func (c *fakeOSConfigurator) GetBaseConfig() (OSConfig, error) {
return c.BaseConfig, nil
}
+func (c *fakeOSConfigurator) GetExitNodeForwardResolver() ([]dnstype.Resolver, error) {
+ return getExitNodeForwardResolverFromBaseConfig(c)
+}
+
func (c *fakeOSConfigurator) Close() error { return nil }
func TestManager(t *testing.T) {
@@ -213,6 +217,7 @@ func TestManager(t *testing.T) {
Routes: upstreams(
".", "8.8.8.8:53",
"corp.com.", "2.2.2.2:53"),
+ ExitNodeBackupResolvers: []dnstype.Resolver{{Addr: "8.8.8.8:53"}},
},
},
{
@@ -249,6 +254,7 @@ func TestManager(t *testing.T) {
".", "8.8.8.8:53",
"corp.com.", "2.2.2.2:53",
"bigco.net.", "3.3.3.3:53"),
+ ExitNodeBackupResolvers: []dnstype.Resolver{{Addr: "8.8.8.8:53"}},
},
},
{
@@ -293,7 +299,8 @@ func TestManager(t *testing.T) {
Hosts: hosts(
"dave.ts.com.", "1.2.3.4",
"bradfitz.ts.com.", "2.3.4.5"),
- LocalDomains: fqdns("ts.com."),
+ LocalDomains: fqdns("ts.com."),
+ ExitNodeBackupResolvers: []dnstype.Resolver{{Addr: "8.8.8.8:53"}},
},
},
{
@@ -342,7 +349,8 @@ func TestManager(t *testing.T) {
Hosts: hosts(
"dave.ts.com.", "1.2.3.4",
"bradfitz.ts.com.", "2.3.4.5"),
- LocalDomains: fqdns("ts.com."),
+ LocalDomains: fqdns("ts.com."),
+ ExitNodeBackupResolvers: []dnstype.Resolver{{Addr: "8.8.8.8:53"}},
},
},
{
diff --git a/net/dns/manager_windows.go b/net/dns/manager_windows.go
index f6eb7b3ff..cea204ed0 100644
--- a/net/dns/manager_windows.go
+++ b/net/dns/manager_windows.go
@@ -17,6 +17,7 @@ import (
"golang.org/x/sys/windows/registry"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"inet.af/netaddr"
+ "tailscale.com/types/dnstype"
"tailscale.com/types/logger"
"tailscale.com/util/dnsname"
)
@@ -340,6 +341,10 @@ func (m windowsManager) GetBaseConfig() (OSConfig, error) {
}, nil
}
+func (m windowsManager) GetExitNodeForwardResolver() ([]dnstype.Resolver, error) {
+ return getExitNodeForwardResolverFromBaseConfig(m)
+}
+
// getBasePrimaryResolver returns a guess of the non-Tailscale primary
// resolver on the system.
// It's used on Windows 7 to emulate split DNS by trying to figure out
diff --git a/net/dns/nm.go b/net/dns/nm.go
index 5733098d2..e5386adc8 100644
--- a/net/dns/nm.go
+++ b/net/dns/nm.go
@@ -16,6 +16,7 @@ import (
"github.com/godbus/dbus/v5"
"inet.af/netaddr"
"tailscale.com/net/interfaces"
+ "tailscale.com/types/dnstype"
"tailscale.com/util/dnsname"
"tailscale.com/util/endian"
)
@@ -374,6 +375,10 @@ func (m *nmManager) GetBaseConfig() (OSConfig, error) {
return ret, nil
}
+func (m *nmManager) GetExitNodeForwardResolver() ([]dnstype.Resolver, error) {
+ return getExitNodeForwardResolverFromBaseConfig(m)
+}
+
func (m *nmManager) Close() error {
// No need to do anything on close, NetworkManager will delete our
// settings when the tailscale interface goes away.
diff --git a/net/dns/noop.go b/net/dns/noop.go
index f08ecc036..23fc49399 100644
--- a/net/dns/noop.go
+++ b/net/dns/noop.go
@@ -4,14 +4,21 @@
package dns
+import "tailscale.com/types/dnstype"
+
type noopManager struct{}
+var _ OSConfigurator = noopManager{}
+
func (m noopManager) SetDNS(OSConfig) error { return nil }
func (m noopManager) SupportsSplitDNS() bool { return false }
func (m noopManager) Close() error { return nil }
func (m noopManager) GetBaseConfig() (OSConfig, error) {
return OSConfig{}, ErrGetBaseConfigNotSupported
}
+func (m noopManager) GetExitNodeForwardResolver() ([]dnstype.Resolver, error) {
+ return nil, nil
+}
func NewNoopManager() (noopManager, error) {
return noopManager{}, nil
diff --git a/net/dns/openresolv.go b/net/dns/openresolv.go
index 13b72438f..34f2da5b3 100644
--- a/net/dns/openresolv.go
+++ b/net/dns/openresolv.go
@@ -12,6 +12,8 @@ import (
"fmt"
"os/exec"
"strings"
+
+ "tailscale.com/types/dnstype"
)
// openresolvManager manages DNS configuration using the openresolv
@@ -90,6 +92,10 @@ func (m openresolvManager) GetBaseConfig() (OSConfig, error) {
return readResolv(&buf)
}
+func (m openresolvManager) GetExitNodeForwardResolver() ([]dnstype.Resolver, error) {
+ return getExitNodeForwardResolverFromBaseConfig(m)
+}
+
func (m openresolvManager) Close() error {
return m.deleteTailscaleConfig()
}
diff --git a/net/dns/osconfig.go b/net/dns/osconfig.go
index 866760a8e..e40a7e8e2 100644
--- a/net/dns/osconfig.go
+++ b/net/dns/osconfig.go
@@ -8,6 +8,7 @@ import (
"errors"
"inet.af/netaddr"
+ "tailscale.com/types/dnstype"
"tailscale.com/util/dnsname"
)
@@ -18,20 +19,35 @@ type OSConfigurator interface {
// configuration is removed.
// SetDNS must not be called after Close.
SetDNS(cfg OSConfig) error
+
// SupportsSplitDNS reports whether the configurator is capable of
// installing a resolver only for specific DNS suffixes. If false,
// the configurator can only set a global resolver.
SupportsSplitDNS() bool
+
// GetBaseConfig returns the OS's "base" configuration, i.e. the
// resolver settings the OS would use without Tailscale
// contributing any configuration.
// GetBaseConfig must return the tailscale-free base config even
// after SetDNS has been called to set a Tailscale configuration.
// Only works when SupportsSplitDNS=false.
-
+ //
// Implementations that don't support getting the base config must
// return ErrGetBaseConfigNotSupported.
GetBaseConfig() (OSConfig, error)
+
+ // GetExitNodeForwardResolver returns the resolver(s) that should
+ // be used as a fallback for the exit node's DNS-over-HTTP peerapi
+ // to send DNS queries from peers on to, in the case where the tailnet
+ // doesn't have global DNS servers configured.
+ //
+ // For example, on Linux with systemd-resolved, this will
+ // return 127.0.0.53:53.
+ //
+ // On other systems, it'll usually be the value of
+ // GetBaseConfig.Nameservers.
+ GetExitNodeForwardResolver() ([]dnstype.Resolver, error)
+
// Close removes Tailscale-related DNS configuration from the OS.
Close() error
}
@@ -90,3 +106,16 @@ func (a OSConfig) Equal(b OSConfig) bool {
// OSConfigurator.GetBaseConfig returns when the OSConfigurator
// doesn't support reading the underlying configuration out of the OS.
var ErrGetBaseConfigNotSupported = errors.New("getting OS base config is not supported")
+
+func getExitNodeForwardResolverFromBaseConfig(o OSConfigurator) (ret []dnstype.Resolver, retErr error) {
+ oc, err := o.GetBaseConfig()
+ if err != nil {
+ return nil, err
+ }
+ for _, ip := range oc.Nameservers {
+ if ip != netaddr.IPv4(100, 100, 100, 100) {
+ ret = append(ret, dnstype.Resolver{Addr: netaddr.IPPortFrom(ip, 53).String()})
+ }
+ }
+ return ret, nil
+}
diff --git a/net/dns/resolved.go b/net/dns/resolved.go
index 9cd859e79..024717203 100644
--- a/net/dns/resolved.go
+++ b/net/dns/resolved.go
@@ -19,6 +19,7 @@ import (
"golang.org/x/sys/unix"
"inet.af/netaddr"
"tailscale.com/health"
+ "tailscale.com/types/dnstype"
"tailscale.com/types/logger"
"tailscale.com/util/dnsname"
)
@@ -332,6 +333,10 @@ func (m *resolvedManager) GetBaseConfig() (OSConfig, error) {
return OSConfig{}, ErrGetBaseConfigNotSupported
}
+func (m *resolvedManager) GetExitNodeForwardResolver() ([]dnstype.Resolver, error) {
+ return []dnstype.Resolver{{Addr: "127.0.0.53:53"}}, nil
+}
+
func (m *resolvedManager) Close() error {
m.cancelSyncer()
diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go
index ee67995e4..a5b60dbbd 100644
--- a/net/dns/resolver/tsdns.go
+++ b/net/dns/resolver/tsdns.go
@@ -83,6 +83,14 @@ type Config struct {
// LocalDomains is a list of DNS name suffixes that should not be
// routed to upstream resolvers.
LocalDomains []dnsname.FQDN
+ // ExitNodeBackupResolvers are where the local node when
+ // acting as an exit node and serving a DNS proxy should
+ // forward DNS requests to in the case where there are no
+ // routes found. For example, for Linux systemd-resolved
+ // machines this is likely 127.0.0.53:53.
+ // If it's empty, there are no backups and the OS should
+ // be queried directly using its OS-level DNS APIs.
+ ExitNodeBackupResolvers []dnstype.Resolver
}
// WriteToBufioWriter write a debug version of c for logs to w, omitting
@@ -202,10 +210,11 @@ type Resolver struct {
wg sync.WaitGroup
// mu guards the following fields from being updated while used.
- mu sync.Mutex
- localDomains []dnsname.FQDN
- hostToIP map[dnsname.FQDN][]netaddr.IP
- ipToHost map[netaddr.IP]dnsname.FQDN
+ mu sync.Mutex
+ localDomains []dnsname.FQDN
+ hostToIP map[dnsname.FQDN][]netaddr.IP
+ ipToHost map[netaddr.IP]dnsname.FQDN
+ exitNodeBackupResolvers []dnstype.Resolver
}
type ForwardLinkSelector interface {
@@ -253,9 +262,16 @@ func (r *Resolver) SetConfig(cfg Config) error {
r.localDomains = cfg.LocalDomains
r.hostToIP = cfg.Hosts
r.ipToHost = reverse
+ r.exitNodeBackupResolvers = append([]dnstype.Resolver(nil), cfg.ExitNodeBackupResolvers...)
return nil
}
+func (r *Resolver) exitNodeForwardResolvers() []dnstype.Resolver {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ return r.exitNodeBackupResolvers
+}
+
// 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() {
@@ -312,34 +328,13 @@ func (r *Resolver) HandleExitNodeDNSQuery(ctx context.Context, q []byte, from ne
err = r.forwarder.forwardWithDestChan(ctx, packet{q, from}, ch)
if err == errNoUpstreams {
- // Handle to the system resolver.
- switch runtime.GOOS {
- case "linux":
- // Assume for now that we don't have an upstream because
- // they're using systemd-resolved and we're in Split DNS mode
- // where we don't know the base config.
- //
- // TODO(bradfitz): this is a lazy assumption. Do better, and
- // maybe move the HandleExitNodeDNSQuery method to the dns.Manager
- // instead? But this works for now.
- err = r.forwarder.forwardWithDestChan(ctx, packet{q, from}, ch, resolverAndDelay{
- name: dnstype.Resolver{
- Addr: "127.0.0.1:53",
- },
- })
- default:
- // TODO(bradfitz): if we're on an exit node
- // on, say, Windows, we need to parse the DNS
- // packet in q and call OS-native APIs for
- // each question. But we'll want to strip out
- // questions for MagicDNS names probably, so
- // they don't loop back into
- // 100.100.100.100. We don't want to resolve
- // MagicDNS names across Tailnets once we
- // permit sharing exit nodes.
- //
- // For now, just return an error.
- return nil, err
+ backup := r.exitNodeForwardResolvers()
+ if len(backup) > 0 {
+ var extra []resolverAndDelay
+ for _, v := range backup {
+ extra = append(extra, resolverAndDelay{name: v})
+ }
+ err = r.forwarder.forwardWithDestChan(ctx, packet{q, from}, ch, extra...)
}
}
if err != nil {