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{} - -func newDirectManager(mconfig ManagerConfig) managerImpl { - return directManager{} +type directManager struct { + oldConfig Config + setUpstreams func([]net.Addr) } -// Up implements managerImpl. -func (m directManager) Up(config Config) error { +func newDirectManager(mconfig ManagerConfig) Manager { + oldConfig, err := readResolvConf() + if err != nil { + mconfig.Logf("reading old config: %v", err) + } + + return directManager{ + oldConfig: oldConfig, + setUpstreams: mconfig.SetUpstreams, + } +} + +// 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 { - 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 newNoopManager(mconfig) - } else { - return newNMManager(mconfig) - } - case isResolvconfActive(): - return newResolvconfManager(mconfig) +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 newDirectManager(mconfig) + 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 { + case isNMActive(): + 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) + case isResolvconfActive(): + return newResolvconfManager(mconfig) + default: + return newDirectManager(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: + <-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 } -// nmManager uses the NetworkManager DBus API. -type nmManager struct { - interfaceName string +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 } -func newNMManager(mconfig ManagerConfig) managerImpl { - return nmManager{ +// nmManager uses the NetworkManager DBus API. +type nmManager struct { + global bool + interfaceName string + oldConfig Config + setUpstreams func([]net.Addr) +} + +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")