summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDmytro Shynkevych <dmytro@tailscale.com>2020-08-26 10:38:10 -0400
committerDmytro Shynkevych <dmytro@tailscale.com>2020-08-27 16:47:36 -0400
commitdd933a3a6011d265c68656945f39339c82ec73bf (patch)
tree17c68fdd7f83718af66803e6e2985cf3be26a9d1
parent483141094cea60580d0e05868bd2f5d98d91b1f8 (diff)
downloadtailscale-dshynkev/dns-autoset.tar.xz
tailscale-dshynkev/dns-autoset.zip
-rw-r--r--ipn/local.go9
-rw-r--r--wgengine/router/dns/config.go10
-rw-r--r--wgengine/router/dns/direct.go32
-rw-r--r--wgengine/router/dns/manager.go74
-rw-r--r--wgengine/router/dns/manager_default.go2
-rw-r--r--wgengine/router/dns/manager_freebsd.go2
-rw-r--r--wgengine/router/dns/manager_linux.go181
-rw-r--r--wgengine/router/dns/manager_openbsd.go2
-rw-r--r--wgengine/router/dns/manager_windows.go8
-rw-r--r--wgengine/router/dns/nm.go74
-rw-r--r--wgengine/router/dns/noop.go10
-rw-r--r--wgengine/router/dns/resolvconf.go32
-rw-r--r--wgengine/router/dns/resolved.go21
-rw-r--r--wgengine/router/router.go17
-rw-r--r--wgengine/router/router_darwin.go4
-rw-r--r--wgengine/router/router_default.go6
-rw-r--r--wgengine/router/router_fake.go6
-rw-r--r--wgengine/router/router_freebsd.go4
-rw-r--r--wgengine/router/router_linux.go17
-rw-r--r--wgengine/router/router_linux_test.go2
-rw-r--r--wgengine/router/router_openbsd.go11
-rw-r--r--wgengine/router/router_userspace_bsd.go11
-rw-r--r--wgengine/router/router_windows.go14
-rw-r--r--wgengine/tsdns/tsdns.go1
-rw-r--r--wgengine/userspace.go36
25 files changed, 389 insertions, 197 deletions
diff --git a/ipn/local.go b/ipn/local.go
index 41d87c981..f6785f795 100644
--- a/ipn/local.go
+++ b/ipn/local.go
@@ -963,13 +963,8 @@ func (b *LocalBackend) authReconfig() {
domains := nm.DNS.Domains
proxied := nm.DNS.Proxied
if proxied {
- if len(nm.DNS.Nameservers) == 0 {
- b.logf("[unexpected] dns proxied but no nameservers")
- proxied = false
- } else {
- // Domains for proxying should come first to avoid leaking queries.
- domains = append(domainsForProxying(nm), domains...)
- }
+ // Domains for proxying should come first to avoid leaking queries.
+ domains = append(domainsForProxying(nm), domains...)
}
rcfg.DNS = dns.Config{
Nameservers: nm.DNS.Nameservers,
diff --git a/wgengine/router/dns/config.go b/wgengine/router/dns/config.go
index 64d3a2972..53991b5b5 100644
--- a/wgengine/router/dns/config.go
+++ b/wgengine/router/dns/config.go
@@ -5,8 +5,9 @@
package dns
import (
- "inet.af/netaddr"
+ "net"
+ "inet.af/netaddr"
"tailscale.com/types/logger"
)
@@ -65,12 +66,11 @@ func (lhs Config) Equal(rhs Config) bool {
type ManagerConfig struct {
// logf is the logger for the manager to use.
Logf logger.Logf
- // InterfaceNAme is the name of the interface with which DNS settings should be associated.
+ // InterfaceName is the name of the interface with which DNS settings should be associated.
InterfaceName string
+ // SetNameservers is the function to which upstream nameservers should be passed.
+ SetUpstreams func([]net.Addr)
// Cleanup indicates that the manager is created for cleanup only.
// A no-op manager will be instantiated if the system needs no cleanup.
Cleanup bool
- // PerDomain indicates that a manager capable of per-domain configuration is preferred.
- // Certain managers are per-domain only; they will not be considered if this is false.
- PerDomain bool
}
diff --git a/wgengine/router/dns/direct.go b/wgengine/router/dns/direct.go
index 62814c5f6..8b56fbd2b 100644
--- a/wgengine/router/dns/direct.go
+++ b/wgengine/router/dns/direct.go
@@ -12,6 +12,7 @@ import (
"fmt"
"io"
"io/ioutil"
+ "net"
"os"
"os/exec"
"runtime"
@@ -100,21 +101,38 @@ func isResolvedRunning() bool {
return err == nil
}
-// directManager is a managerImpl which replaces /etc/resolv.conf with a file
+// directManager is a Manager which replaces /etc/resolv.conf with a file
// generated from the given configuration, creating a backup of its old state.
//
// This way of configuring DNS is precarious, since it does not react
// to the disappearance of the Tailscale interface.
// The caller must call Down before program shutdown
// or as cleanup if the program terminates unexpectedly.
-type directManager struct{}
+type directManager struct {
+ oldConfig Config
+ setUpstreams func([]net.Addr)
+}
+
+func newDirectManager(mconfig ManagerConfig) Manager {
+ oldConfig, err := readResolvConf()
+ if err != nil {
+ mconfig.Logf("reading old config: %v", err)
+ }
-func newDirectManager(mconfig ManagerConfig) managerImpl {
- return directManager{}
+ return directManager{
+ oldConfig: oldConfig,
+ setUpstreams: mconfig.SetUpstreams,
+ }
}
-// Up implements managerImpl.
-func (m directManager) Up(config Config) error {
+// Set implements Manager.
+func (m directManager) Set(config Config) error {
+ if len(config.Nameservers) == 0 && len(config.Domains) == 0 {
+ return m.Down()
+ }
+
+ config = prepareGlobalConfig(config, m.oldConfig, m.setUpstreams)
+
// Write the tsConf file.
buf := new(bytes.Buffer)
writeResolvConf(buf, config.Nameservers, config.Domains)
@@ -159,7 +177,7 @@ func (m directManager) Up(config Config) error {
return nil
}
-// Down implements managerImpl.
+// Down implements Manager.
func (m directManager) Down() error {
if _, err := os.Stat(backupConf); err != nil {
// If the backup file does not exist, then Up never ran successfully.
diff --git a/wgengine/router/dns/manager.go b/wgengine/router/dns/manager.go
index 74c97f1a4..5b99458bf 100644
--- a/wgengine/router/dns/manager.go
+++ b/wgengine/router/dns/manager.go
@@ -5,90 +5,50 @@
package dns
import (
- "time"
-
"tailscale.com/types/logger"
)
-// reconfigTimeout is the time interval within which Manager.{Up,Down} should complete.
-//
-// This is particularly useful because certain conditions can cause indefinite hangs
-// (such as improper dbus auth followed by contextless dbus.Object.Call).
-// Such operations should be wrapped in a timeout context.
-const reconfigTimeout = time.Second //lint:ignore U1000 used on Linux at least, maybe others later
-
-type managerImpl interface {
- // Up updates system DNS settings to match the given configuration.
- Up(Config) error
- // Down undoes the effects of Up.
- // It is idempotent and performs no action if Up has never been called.
+// Manager manages system DNS settings.
+type Manager interface {
+ // Set updates system DNS settings to match the given configuration.
+ Set(Config) error
+ // Down undoes the effects of Set.
+ // It is idempotent and performs no action if Set has never been called.
Down() error
}
-// Manager manages system DNS settings.
-type Manager struct {
- logf logger.Logf
-
- impl managerImpl
-
- config Config
- mconfig ManagerConfig
+type wrappedManager struct {
+ logf logger.Logf
+ impl Manager
+ config Config
}
-// NewManagers created a new manager from the given config.
-func NewManager(mconfig ManagerConfig) *Manager {
+// NewManager creates a new manager from the given config.
+func NewManager(mconfig ManagerConfig) Manager {
mconfig.Logf = logger.WithPrefix(mconfig.Logf, "dns: ")
- m := &Manager{
+ m := &wrappedManager{
logf: mconfig.Logf,
impl: newManager(mconfig),
-
- config: Config{PerDomain: mconfig.PerDomain},
- mconfig: mconfig,
}
m.logf("using %T", m.impl)
return m
}
-func (m *Manager) Set(config Config) error {
+func (m *wrappedManager) Set(config Config) error {
if config.Equal(m.config) {
return nil
}
- m.logf("Set: %+v", config)
+ m.logf("set: %+v", config)
- if len(config.Nameservers) == 0 {
- err := m.impl.Down()
- // If we save the config, we will not retry next time. Only do this on success.
- if err == nil {
- m.config = config
- }
- return err
- }
-
- // Switching to and from per-domain mode may require a change of manager.
- if config.PerDomain != m.config.PerDomain {
- if err := m.impl.Down(); err != nil {
- return err
- }
- m.mconfig.PerDomain = config.PerDomain
- m.impl = newManager(m.mconfig)
- m.logf("switched to %T", m.impl)
- }
-
- err := m.impl.Up(config)
- // If we save the config, we will not retry next time. Only do this on success.
+ err := m.impl.Set(config)
if err == nil {
m.config = config
}
-
return err
}
-func (m *Manager) Up() error {
- return m.impl.Up(m.config)
-}
-
-func (m *Manager) Down() error {
+func (m *wrappedManager) Down() error {
return m.impl.Down()
}
diff --git a/wgengine/router/dns/manager_default.go b/wgengine/router/dns/manager_default.go
index 04c8bb811..34b58e0e2 100644
--- a/wgengine/router/dns/manager_default.go
+++ b/wgengine/router/dns/manager_default.go
@@ -6,7 +6,7 @@
package dns
-func newManager(mconfig ManagerConfig) managerImpl {
+func newManager(mconfig ManagerConfig) Manager {
// TODO(dmytro): on darwin, we should use a macOS-specific method such as scutil.
// This is currently not implemented. Editing /etc/resolv.conf does not work,
// as most applications use the system resolver, which disregards it.
diff --git a/wgengine/router/dns/manager_freebsd.go b/wgengine/router/dns/manager_freebsd.go
index 232635f7e..59eb567ac 100644
--- a/wgengine/router/dns/manager_freebsd.go
+++ b/wgengine/router/dns/manager_freebsd.go
@@ -4,7 +4,7 @@
package dns
-func newManager(mconfig ManagerConfig) managerImpl {
+func newManager(mconfig ManagerConfig) Manager {
switch {
case isResolvconfActive():
return newResolvconfManager(mconfig)
diff --git a/wgengine/router/dns/manager_linux.go b/wgengine/router/dns/manager_linux.go
index f53aed7d3..f869dce30 100644
--- a/wgengine/router/dns/manager_linux.go
+++ b/wgengine/router/dns/manager_linux.go
@@ -4,24 +4,177 @@
package dns
-func newManager(mconfig ManagerConfig) managerImpl {
+import (
+ "context"
+ "sync"
+ "time"
+
+ "tailscale.com/logtail/backoff"
+ "tailscale.com/types/logger"
+)
+
+var reconfigTimeout = 5 * time.Second
+
+type dnsMode uint8
+
+const (
+ noMode dnsMode = iota
+ nmMode
+ resolvedMode
+ resolvconfMode
+ directMode
+)
+
+func (m dnsMode) String() string {
+ switch m {
+ case noMode:
+ return "none"
+ case nmMode:
+ return "NetworkManager"
+ case resolvedMode:
+ return "systemd-resolved"
+ case resolvconfMode:
+ return "resolvconf"
+ case directMode:
+ return "direct"
+ default:
+ return "???"
+ }
+}
+
+// linuxManager manages system configuration asynchronously with backoff on errors.
+// This is useful because nmManager and resolvedManager cannot be used
+// until the Tailscale network interface is ready from the point of view
+// of NetworkManager/systemd-resolved, which can take a unspecified amount of time.
+type linuxManager struct {
+ logf logger.Logf
+ mconfig ManagerConfig
+
+ config chan Config
+
+ ctx context.Context
+ cancel context.CancelFunc
+ wg sync.WaitGroup
+}
+
+func configToMode(config Config) dnsMode {
switch {
- // systemd-resolved should only activate per-domain.
- case isResolvedActive() && mconfig.PerDomain:
- if mconfig.Cleanup {
- return newNoopManager(mconfig)
- } else {
- return newResolvedManager(mconfig)
- }
case isNMActive():
- if mconfig.Cleanup {
+ return nmMode
+ case isResolvedActive() && config.PerDomain:
+ return resolvedMode
+ case isResolvconfActive():
+ return resolvconfMode
+ default:
+ return directMode
+ }
+}
+
+func (m *linuxManager) modeToImpl(mode dnsMode) Manager {
+ switch mode {
+ case nmMode:
+ return newNMManager(m.mconfig)
+ case resolvedMode:
+ return newResolvedManager(m.mconfig)
+ case resolvconfMode:
+ return newResolvconfManager(m.mconfig)
+ case directMode:
+ return newDirectManager(m.mconfig)
+ default:
+ return newNoopManager(m.mconfig)
+ }
+}
+
+func newManager(mconfig ManagerConfig) Manager {
+ // For cleanup, don't try anything fancy.
+ if mconfig.Cleanup {
+ switch {
+ case isNMActive(), isResolvedActive():
return newNoopManager(mconfig)
- } else {
- return newNMManager(mconfig)
+ case isResolvconfActive():
+ return newResolvconfManager(mconfig)
+ default:
+ return newDirectManager(mconfig)
}
- case isResolvconfActive():
- return newResolvconfManager(mconfig)
+ }
+
+ return &linuxManager{
+ logf: mconfig.Logf,
+ mconfig: mconfig,
+ config: make(chan Config, 1),
+ }
+}
+
+func (m *linuxManager) background() {
+ defer m.wg.Done()
+
+ var mode dnsMode
+ var impl Manager
+ var config Config
+
+ bo := backoff.NewBackoff("dns", m.logf, 30*time.Second)
+ for {
+ select {
+ case <-m.ctx.Done():
+ if err := impl.Down(); err != nil {
+ m.logf("stop: down: %v", err)
+ }
+ return
+ case config = <-m.config:
+ // continue
+ }
+
+ newMode := configToMode(config)
+ if newMode != mode {
+ m.logf("changing mode: %v -> %v", mode, newMode)
+ // If a non-noop manager was active, deactivate it first.
+ if mode != noMode {
+ if err := impl.Down(); err != nil {
+ m.logf("mode change: down: %v", err)
+ }
+ }
+ mode = newMode
+ impl = m.modeToImpl(newMode)
+ }
+
+ err := impl.Set(config)
+ if err != nil {
+ m.logf("set: %v", err)
+ // Force another iteration.
+ select {
+ case m.config <- config:
+ // continue
+ default:
+ // continue
+ }
+ }
+ bo.BackOff(m.ctx, err)
+ }
+}
+
+// Set implements Manager.
+func (m *linuxManager) Set(config Config) error {
+ if m.ctx == nil {
+ m.ctx, m.cancel = context.WithCancel(context.Background())
+ m.wg.Add(1)
+ go m.background()
+ }
+ select {
+ case <-m.ctx.Done():
+ return nil
+ case m.config <- config:
+ // continue
default:
- return newDirectManager(mconfig)
+ <-m.config
+ m.config <- config
}
+ return nil
+}
+
+// Down implements Manager.
+func (m *linuxManager) Down() error {
+ m.cancel()
+ m.wg.Wait()
+ m.ctx = nil
+ return nil
}
diff --git a/wgengine/router/dns/manager_openbsd.go b/wgengine/router/dns/manager_openbsd.go
index 228e3cca5..309e35478 100644
--- a/wgengine/router/dns/manager_openbsd.go
+++ b/wgengine/router/dns/manager_openbsd.go
@@ -4,6 +4,6 @@
package dns
-func newManager(mconfig ManagerConfig) managerImpl {
+func newManager(mconfig ManagerConfig) Manager {
return newDirectManager(mconfig)
}
diff --git a/wgengine/router/dns/manager_windows.go b/wgengine/router/dns/manager_windows.go
index a768b4312..07ee811a4 100644
--- a/wgengine/router/dns/manager_windows.go
+++ b/wgengine/router/dns/manager_windows.go
@@ -25,7 +25,7 @@ type windowsManager struct {
guid string
}
-func newManager(mconfig ManagerConfig) managerImpl {
+func newManager(mconfig ManagerConfig) Manager {
return windowsManager{
logf: mconfig.Logf,
guid: tun.WintunGUID,
@@ -110,7 +110,8 @@ func (m windowsManager) setDomains(path string, oldDomains, newDomains []string)
return nil
}
-func (m windowsManager) Up(config Config) error {
+// Set implements Manager.
+func (m windowsManager) Set(config Config) error {
var ipsv4 []string
var ipsv6 []string
@@ -150,6 +151,7 @@ func (m windowsManager) Up(config Config) error {
return nil
}
+// Down implements Manager.
func (m windowsManager) Down() error {
- return m.Up(Config{Nameservers: nil, Domains: nil})
+ return m.Set(Config{Nameservers: nil, Domains: nil})
}
diff --git a/wgengine/router/dns/nm.go b/wgengine/router/dns/nm.go
index 2f707c233..795e94c14 100644
--- a/wgengine/router/dns/nm.go
+++ b/wgengine/router/dns/nm.go
@@ -12,8 +12,8 @@ import (
"context"
"encoding/binary"
"fmt"
+ "net"
"os"
- "os/exec"
"unsafe"
"github.com/godbus/dbus/v5"
@@ -36,14 +36,6 @@ func init() {
// isNMActive determines if NetworkManager is currently managing system DNS settings.
func isNMActive() bool {
- // This is somewhat tricky because NetworkManager supports a number
- // of DNS configuration modes. In all cases, we expect it to be installed
- // and /etc/resolv.conf to contain a mention of NetworkManager in the comments.
- _, err := exec.LookPath("NetworkManager")
- if err != nil {
- return false
- }
-
f, err := os.Open("/etc/resolv.conf")
if err != nil {
return false
@@ -64,21 +56,73 @@ func isNMActive() bool {
return false
}
+func nmDNSMode() string {
+ ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout)
+ defer cancel()
+
+ // conn is a shared connection whose lifecycle is managed by the dbus package.
+ // We should not interfere with that by closing it.
+ conn, err := dbus.SystemBus()
+ if err != nil {
+ return ""
+ }
+
+ dnsManager := conn.Object(
+ "org.freedesktop.NetworkManager",
+ dbus.ObjectPath("/org/freedesktop/NetworkManager/DnsManager"),
+ )
+
+ var dnsMode string
+ err = dnsManager.CallWithContext(
+ ctx, "org.freedesktop.DBus.Properties.Get", 0,
+ "org.freedesktop.NetworkManager.DnsManager", "Mode",
+ ).Store(&dnsMode)
+ if err != nil {
+ return ""
+ }
+
+ return dnsMode
+}
+
// nmManager uses the NetworkManager DBus API.
type nmManager struct {
+ global bool
interfaceName string
+ oldConfig Config
+ setUpstreams func([]net.Addr)
}
-func newNMManager(mconfig ManagerConfig) managerImpl {
- return nmManager{
+func newNMManager(mconfig ManagerConfig) Manager {
+ mode := nmDNSMode()
+ mconfig.Logf("NetworkManager DNS mode is %q", mode)
+
+ global := mode == "default"
+ m := &nmManager{
+ global: global,
interfaceName: mconfig.InterfaceName,
+ setUpstreams: mconfig.SetUpstreams,
}
+
+ if global {
+ oldConfig, err := readResolvConf()
+ if err != nil {
+ mconfig.Logf("reading old config: %v", err)
+ } else {
+ m.oldConfig = oldConfig
+ }
+ }
+
+ return m
}
type nmConnectionSettings map[string]map[string]dbus.Variant
-// Up implements managerImpl.
-func (m nmManager) Up(config Config) error {
+// Set implements Manager.
+func (m nmManager) Set(config Config) error {
+ if m.global && !(len(config.Nameservers) == 0 && len(config.Domains) == 0) {
+ config = prepareGlobalConfig(config, m.oldConfig, m.setUpstreams)
+ }
+
ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout)
defer cancel()
@@ -215,7 +259,7 @@ func (m nmManager) Up(config Config) error {
return nil
}
-// Down implements managerImpl.
+// Down implements Manager.
func (m nmManager) Down() error {
- return m.Up(Config{Nameservers: nil, Domains: nil})
+ return m.Set(Config{Nameservers: nil, Domains: nil})
}
diff --git a/wgengine/router/dns/noop.go b/wgengine/router/dns/noop.go
index 35c07a232..a4f885e38 100644
--- a/wgengine/router/dns/noop.go
+++ b/wgengine/router/dns/noop.go
@@ -6,12 +6,12 @@ package dns
type noopManager struct{}
-// Up implements managerImpl.
-func (m noopManager) Up(Config) error { return nil }
+// Set implements Manager.
+func (noopManager) Set(Config) error { return nil }
-// Down implements managerImpl.
-func (m noopManager) Down() error { return nil }
+// Down implements Manager.
+func (noopManager) Down() error { return nil }
-func newNoopManager(mconfig ManagerConfig) managerImpl {
+func newNoopManager(mconfig ManagerConfig) Manager {
return noopManager{}
}
diff --git a/wgengine/router/dns/resolvconf.go b/wgengine/router/dns/resolvconf.go
index 8bf97ee88..fb7137e95 100644
--- a/wgengine/router/dns/resolvconf.go
+++ b/wgengine/router/dns/resolvconf.go
@@ -10,6 +10,7 @@ import (
"bufio"
"bytes"
"fmt"
+ "net"
"os"
"os/exec"
)
@@ -96,15 +97,24 @@ func getResolvconfImpl() resolvconfImpl {
}
type resolvconfManager struct {
- impl resolvconfImpl
+ impl resolvconfImpl
+ oldConfig Config
+ setUpstreams func([]net.Addr)
}
-func newResolvconfManager(mconfig ManagerConfig) managerImpl {
+func newResolvconfManager(mconfig ManagerConfig) Manager {
impl := getResolvconfImpl()
mconfig.Logf("resolvconf implementation is %s", impl)
- return resolvconfManager{
- impl: impl,
+ oldConfig, err := readResolvConf()
+ if err != nil {
+ mconfig.Logf("reading old config: %v", err)
+ }
+
+ return &resolvconfManager{
+ impl: impl,
+ oldConfig: oldConfig,
+ setUpstreams: mconfig.SetUpstreams,
}
}
@@ -113,8 +123,14 @@ func newResolvconfManager(mconfig ManagerConfig) managerImpl {
// when running resolvconfLegacy, hopefully placing our config first.
const resolvconfConfigName = "tun-tailscale.inet"
-// Up implements managerImpl.
-func (m resolvconfManager) Up(config Config) error {
+// Set implements Manager.
+func (m *resolvconfManager) Set(config Config) error {
+ if len(config.Nameservers) == 0 && len(config.Domains) == 0 {
+ return m.Down()
+ }
+
+ config = prepareGlobalConfig(config, m.oldConfig, m.setUpstreams)
+
stdin := new(bytes.Buffer)
writeResolvConf(stdin, config.Nameservers, config.Domains) // dns_direct.go
@@ -137,8 +153,8 @@ func (m resolvconfManager) Up(config Config) error {
return nil
}
-// Down implements managerImpl.
-func (m resolvconfManager) Down() error {
+// Down implements Manager.
+func (m *resolvconfManager) Down() error {
var cmd *exec.Cmd
switch m.impl {
case resolvconfOpenresolv:
diff --git a/wgengine/router/dns/resolved.go b/wgengine/router/dns/resolved.go
index 9d8c40d90..fa51c1ef1 100644
--- a/wgengine/router/dns/resolved.go
+++ b/wgengine/router/dns/resolved.go
@@ -10,7 +10,6 @@ import (
"context"
"errors"
"fmt"
- "os/exec"
"github.com/godbus/dbus/v5"
"golang.org/x/sys/unix"
@@ -49,18 +48,6 @@ type resolvedLinkDomain struct {
// isResolvedActive determines if resolved is currently managing system DNS settings.
func isResolvedActive() bool {
- // systemd-resolved is never installed without systemd.
- _, err := exec.LookPath("systemctl")
- if err != nil {
- return false
- }
-
- // is-active exits with code 3 if the service is not active.
- err = exec.Command("systemctl", "is-active", "systemd-resolved").Run()
- if err != nil {
- return false
- }
-
config, err := readResolvConf()
if err != nil {
return false
@@ -77,12 +64,12 @@ func isResolvedActive() bool {
// resolvedManager uses the systemd-resolved DBus API.
type resolvedManager struct{}
-func newResolvedManager(mconfig ManagerConfig) managerImpl {
+func newResolvedManager(mconfig ManagerConfig) Manager {
return resolvedManager{}
}
-// Up implements managerImpl.
-func (m resolvedManager) Up(config Config) error {
+// Set implements Manager.
+func (m resolvedManager) Set(config Config) error {
ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout)
defer cancel()
@@ -151,7 +138,7 @@ func (m resolvedManager) Up(config Config) error {
return nil
}
-// Down implements managerImpl.
+// Down implements Manager.
func (m resolvedManager) Down() error {
ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout)
defer cancel()
diff --git a/wgengine/router/router.go b/wgengine/router/router.go
index c65a0b806..b0b8ffd00 100644
--- a/wgengine/router/router.go
+++ b/wgengine/router/router.go
@@ -12,6 +12,7 @@ import (
"inet.af/netaddr"
"tailscale.com/types/logger"
"tailscale.com/wgengine/router/dns"
+ "tailscale.com/wgengine/tsdns"
)
// Router is responsible for managing the system network stack.
@@ -30,11 +31,17 @@ type Router interface {
Close() error
}
-// New returns a new Router for the current platform, using the
-// provided tun device.
-func New(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) {
- logf = logger.WithPrefix(logf, "router: ")
- return newUserspaceRouter(logf, wgdev, tundev)
+type InitConfig struct {
+ Logf logger.Logf
+ Wgdev *device.Device
+ Tun tun.Device
+ Resolver *tsdns.Resolver
+}
+
+// New returns a new Router for the current platform, using the provided config.
+func New(cfg InitConfig) (Router, error) {
+ cfg.Logf = logger.WithPrefix(cfg.Logf, "router: ")
+ return newUserspaceRouter(cfg)
}
// Cleanup restores the system network configuration to its original state
diff --git a/wgengine/router/router_darwin.go b/wgengine/router/router_darwin.go
index 26b689355..612817481 100644
--- a/wgengine/router/router_darwin.go
+++ b/wgengine/router/router_darwin.go
@@ -10,8 +10,8 @@ import (
"tailscale.com/types/logger"
)
-func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) {
- return newUserspaceBSDRouter(logf, wgdev, tundev)
+func newUserspaceRouter(cfg InitConfig) (Router, error) {
+ return newUserspaceBSDRouter(cfg)
}
func cleanup(logger.Logf, string) {
diff --git a/wgengine/router/router_default.go b/wgengine/router/router_default.go
index 4dda1ec29..d9e5ccaa9 100644
--- a/wgengine/router/router_default.go
+++ b/wgengine/router/router_default.go
@@ -12,10 +12,10 @@ import (
"tailscale.com/types/logger"
)
-func newUserspaceRouter(logf logger.Logf, tunname string, dev *device.Device, tuntap tun.Device, netChanged func()) Router {
- return NewFakeRouter(logf, tunname, dev, tuntap, netChanged)
+func newUserspaceRouter(cfg InitConfig) Router {
+ return NewFakeRouter(cfg)
}
-func cleanup(logf logger.Logf, interfaceName string) {
+func cleanup(logger.Logf, string) {
// Nothing to do here.
}
diff --git a/wgengine/router/router_fake.go b/wgengine/router/router_fake.go
index 9c1543d6b..587ddd36f 100644
--- a/wgengine/router/router_fake.go
+++ b/wgengine/router/router_fake.go
@@ -5,15 +5,13 @@
package router
import (
- "github.com/tailscale/wireguard-go/device"
- "github.com/tailscale/wireguard-go/tun"
"tailscale.com/types/logger"
)
// NewFakeRouter returns a Router that does nothing when called and
// always returns nil errors.
-func NewFake(logf logger.Logf, _ *device.Device, _ tun.Device) (Router, error) {
- return fakeRouter{logf: logf}, nil
+func NewFake(cfg InitConfig) (Router, error) {
+ return fakeRouter{logf: cfg.Logf}, nil
}
type fakeRouter struct {
diff --git a/wgengine/router/router_freebsd.go b/wgengine/router/router_freebsd.go
index e56e3f82d..bb81c0318 100644
--- a/wgengine/router/router_freebsd.go
+++ b/wgengine/router/router_freebsd.go
@@ -15,8 +15,8 @@ import (
// Work is currently underway for an in-kernel FreeBSD implementation of wireguard
// https://svnweb.freebsd.org/base?view=revision&revision=357986
-func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
- return newUserspaceBSDRouter(logf, nil, tundev)
+func newUserspaceRouter(cfg InitConfig) (Router, error) {
+ return newUserspaceBSDRouter(cfg)
}
func cleanup(logf logger.Logf, interfaceName string) {
diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go
index ea28c3450..66a8469de 100644
--- a/wgengine/router/router_linux.go
+++ b/wgengine/router/router_linux.go
@@ -10,8 +10,6 @@ import (
"strings"
"github.com/coreos/go-iptables/iptables"
- "github.com/tailscale/wireguard-go/device"
- "github.com/tailscale/wireguard-go/tun"
"inet.af/netaddr"
"tailscale.com/net/tsaddr"
"tailscale.com/types/logger"
@@ -85,14 +83,14 @@ type linuxRouter struct {
snatSubnetRoutes bool
netfilterMode NetfilterMode
- dns *dns.Manager
+ dns dns.Manager
ipt4 netfilterRunner
cmd commandRunner
}
-func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (Router, error) {
- tunname, err := tunDev.Name()
+func newUserspaceRouter(cfg InitConfig) (Router, error) {
+ tunname, err := cfg.Tun.Name()
if err != nil {
return nil, err
}
@@ -102,20 +100,21 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (
return nil, err
}
- return newUserspaceRouterAdvanced(logf, tunname, ipt4, osCommandRunner{})
+ return newUserspaceRouterAdvanced(cfg, tunname, ipt4, osCommandRunner{})
}
-func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, netfilter netfilterRunner, cmd commandRunner) (Router, error) {
+func newUserspaceRouterAdvanced(cfg InitConfig, tunname string, netfilter netfilterRunner, cmd commandRunner) (Router, error) {
_, err := exec.Command("ip", "rule").Output()
ipRuleAvailable := (err == nil)
mconfig := dns.ManagerConfig{
- Logf: logf,
+ Logf: cfg.Logf,
InterfaceName: tunname,
+ SetUpstreams: cfg.Resolver.SetUpstreams,
}
return &linuxRouter{
- logf: logf,
+ logf: cfg.Logf,
ipRuleAvailable: ipRuleAvailable,
tunname: tunname,
netfilterMode: NetfilterOff,
diff --git a/wgengine/router/router_linux_test.go b/wgengine/router/router_linux_test.go
index 1ecfbf250..f9e2bac54 100644
--- a/wgengine/router/router_linux_test.go
+++ b/wgengine/router/router_linux_test.go
@@ -241,7 +241,7 @@ nat/POSTROUTING -j ts-postrouting
}
fake := NewFakeOS(t)
- router, err := newUserspaceRouterAdvanced(t.Logf, "tailscale0", fake, fake)
+ router, err := newUserspaceRouterAdvanced(InitConfig{Logf: t.Logf}, "tailscale0", fake, fake)
if err != nil {
t.Fatalf("failed to create router: %v", err)
}
diff --git a/wgengine/router/router_openbsd.go b/wgengine/router/router_openbsd.go
index 334f89a29..2463361e2 100644
--- a/wgengine/router/router_openbsd.go
+++ b/wgengine/router/router_openbsd.go
@@ -27,22 +27,23 @@ type openbsdRouter struct {
local netaddr.IPPrefix
routes map[netaddr.IPPrefix]struct{}
- dns *dns.Manager
+ dns dns.Manager
}
-func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
- tunname, err := tundev.Name()
+func newUserspaceRouter(cfg InitConfig) (Router, error) {
+ tunname, err := cfg.Tun.Name()
if err != nil {
return nil, err
}
mconfig := dns.ManagerConfig{
- Logf: logf,
+ Logf: cfg.Logf,
InterfaceName: tunname,
+ SetUpstreams: cfg.Resolver.SetUpstreams,
}
return &openbsdRouter{
- logf: logf,
+ logf: cfg.Logf,
tunname: tunname,
dns: dns.NewManager(mconfig),
}, nil
diff --git a/wgengine/router/router_userspace_bsd.go b/wgengine/router/router_userspace_bsd.go
index b177a34a9..2d02329ad 100644
--- a/wgengine/router/router_userspace_bsd.go
+++ b/wgengine/router/router_userspace_bsd.go
@@ -26,22 +26,23 @@ type userspaceBSDRouter struct {
local netaddr.IPPrefix
routes map[netaddr.IPPrefix]struct{}
- dns *dns.Manager
+ dns dns.Manager
}
-func newUserspaceBSDRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
- tunname, err := tundev.Name()
+func newUserspaceBSDRouter(cfg InitConfig) (Router, error) {
+ tunname, err := cfg.Tun.Name()
if err != nil {
return nil, err
}
mconfig := dns.ManagerConfig{
- Logf: logf,
+ Logf: cfg.Logf,
InterfaceName: tunname,
+ SetUpstreams: cfg.Resolver.Upstreams,
}
return &userspaceBSDRouter{
- logf: logf,
+ logf: cfg.Logf,
tunname: tunname,
dns: dns.NewManager(mconfig),
}, nil
diff --git a/wgengine/router/router_windows.go b/wgengine/router/router_windows.go
index 60652297c..d7cadede4 100644
--- a/wgengine/router/router_windows.go
+++ b/wgengine/router/router_windows.go
@@ -21,25 +21,25 @@ type winRouter struct {
nativeTun *tun.NativeTun
wgdev *device.Device
routeChangeCallback *winipcfg.RouteChangeCallback
- dns *dns.Manager
+ dns dns.Manager
}
-func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) {
- tunname, err := tundev.Name()
+func newUserspaceRouter(cfg InitConfig) (Router, error) {
+ tunname, err := cfg.Tun.Name()
if err != nil {
return nil, err
}
- nativeTun := tundev.(*tun.NativeTun)
+ nativeTun := cfg.Tun.(*tun.NativeTun)
guid := nativeTun.GUID().String()
mconfig := dns.ManagerConfig{
- Logf: logf,
+ Logf: cfg.Logf,
InterfaceName: guid,
}
return &winRouter{
- logf: logf,
- wgdev: wgdev,
+ logf: cfg.Logf,
+ wgdev: cfg.Wgdev,
tunname: tunname,
nativeTun: nativeTun,
dns: dns.NewManager(mconfig),
diff --git a/wgengine/tsdns/tsdns.go b/wgengine/tsdns/tsdns.go
index 930f0f8ea..558a38621 100644
--- a/wgengine/tsdns/tsdns.go
+++ b/wgengine/tsdns/tsdns.go
@@ -152,6 +152,7 @@ func (r *Resolver) SetMap(m *Map) {
func (r *Resolver) SetUpstreams(upstreams []net.Addr) {
if r.forwarder != nil {
r.forwarder.setUpstreams(upstreams)
+ r.logf("set upstreams: %v", upstreams)
}
}
diff --git a/wgengine/userspace.go b/wgengine/userspace.go
index 7f7070b35..5a7fbe472 100644
--- a/wgengine/userspace.go
+++ b/wgengine/userspace.go
@@ -121,7 +121,7 @@ type userspaceEngine struct {
// RouterGen is the signature for a function that creates a
// router.Router.
-type RouterGen func(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (router.Router, error)
+type RouterGen func(router.InitConfig) (router.Router, error)
type EngineConfig struct {
// Logf is the logging function used by the engine.
@@ -309,9 +309,15 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
}
}()
- // Pass the underlying tun.(*NativeDevice) to the router:
- // routers do not Read or Write, but do access native interfaces.
- e.router, err = conf.RouterGen(logf, e.wgdev, e.tundev.Unwrap())
+ routerCfg := router.InitConfig{
+ Logf: logf,
+ Wgdev: e.wgdev,
+ // Pass the unwrapped tun.(*NativeDevice) to the router:
+ // routers do not Read or Write, but do access native interfaces.
+ Tun: conf.TUN,
+ Resolver: e.resolver,
+ }
+ e.router, err = conf.RouterGen(routerCfg)
if err != nil {
e.magicConn.Close()
return nil, err
@@ -857,17 +863,21 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
if routerChanged {
if routerCfg.DNS.Proxied {
- ips := routerCfg.DNS.Nameservers
- upstreams := make([]net.Addr, len(ips))
- for i, ip := range ips {
- stdIP := ip.IPAddr()
- upstreams[i] = &net.UDPAddr{
- IP: stdIP.IP,
- Port: 53,
- Zone: stdIP.Zone,
+ if len(routerCfg.DNS.Nameservers) == 0 {
+ routerCfg.DNS.PerDomain = true
+ } else {
+ ips := routerCfg.DNS.Nameservers
+ upstreams := make([]net.Addr, len(ips))
+ for i, ip := range ips {
+ stdIP := ip.IPAddr()
+ upstreams[i] = &net.UDPAddr{
+ IP: stdIP.IP,
+ Port: 53,
+ Zone: stdIP.Zone,
+ }
}
+ e.resolver.SetUpstreams(upstreams)
}
- e.resolver.SetUpstreams(upstreams)
routerCfg.DNS.Nameservers = []netaddr.IP{tsaddr.TailscaleServiceIP()}
}
e.logf("wgengine: Reconfig: configuring router")