Compare commits

...

1 Commits

Author SHA1 Message Date
Dmytro Shynkevych dd933a3a60
wip 2020-08-27 16:47:36 -04:00
25 changed files with 397 additions and 205 deletions

View File

@ -963,13 +963,8 @@ func (b *LocalBackend) authReconfig() {
domains := nm.DNS.Domains domains := nm.DNS.Domains
proxied := nm.DNS.Proxied proxied := nm.DNS.Proxied
if proxied { if proxied {
if len(nm.DNS.Nameservers) == 0 { // Domains for proxying should come first to avoid leaking queries.
b.logf("[unexpected] dns proxied but no nameservers") domains = append(domainsForProxying(nm), domains...)
proxied = false
} else {
// Domains for proxying should come first to avoid leaking queries.
domains = append(domainsForProxying(nm), domains...)
}
} }
rcfg.DNS = dns.Config{ rcfg.DNS = dns.Config{
Nameservers: nm.DNS.Nameservers, Nameservers: nm.DNS.Nameservers,

View File

@ -5,8 +5,9 @@
package dns package dns
import ( import (
"inet.af/netaddr" "net"
"inet.af/netaddr"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
@ -65,12 +66,11 @@ func (lhs Config) Equal(rhs Config) bool {
type ManagerConfig struct { type ManagerConfig struct {
// logf is the logger for the manager to use. // logf is the logger for the manager to use.
Logf logger.Logf 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 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. // Cleanup indicates that the manager is created for cleanup only.
// A no-op manager will be instantiated if the system needs no cleanup. // A no-op manager will be instantiated if the system needs no cleanup.
Cleanup bool 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
} }

View File

@ -12,6 +12,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
@ -100,21 +101,38 @@ func isResolvedRunning() bool {
return err == nil 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. // generated from the given configuration, creating a backup of its old state.
// //
// This way of configuring DNS is precarious, since it does not react // This way of configuring DNS is precarious, since it does not react
// to the disappearance of the Tailscale interface. // to the disappearance of the Tailscale interface.
// The caller must call Down before program shutdown // The caller must call Down before program shutdown
// or as cleanup if the program terminates unexpectedly. // or as cleanup if the program terminates unexpectedly.
type directManager struct{} type directManager struct {
oldConfig Config
func newDirectManager(mconfig ManagerConfig) managerImpl { setUpstreams func([]net.Addr)
return directManager{}
} }
// Up implements managerImpl. func newDirectManager(mconfig ManagerConfig) Manager {
func (m directManager) Up(config Config) error { 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. // Write the tsConf file.
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
writeResolvConf(buf, config.Nameservers, config.Domains) writeResolvConf(buf, config.Nameservers, config.Domains)
@ -159,7 +177,7 @@ func (m directManager) Up(config Config) error {
return nil return nil
} }
// Down implements managerImpl. // Down implements Manager.
func (m directManager) Down() error { func (m directManager) Down() error {
if _, err := os.Stat(backupConf); err != nil { if _, err := os.Stat(backupConf); err != nil {
// If the backup file does not exist, then Up never ran successfully. // If the backup file does not exist, then Up never ran successfully.

View File

@ -5,90 +5,50 @@
package dns package dns
import ( import (
"time"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
// reconfigTimeout is the time interval within which Manager.{Up,Down} should complete. // Manager manages system DNS settings.
// type Manager interface {
// This is particularly useful because certain conditions can cause indefinite hangs // Set updates system DNS settings to match the given configuration.
// (such as improper dbus auth followed by contextless dbus.Object.Call). Set(Config) error
// Such operations should be wrapped in a timeout context. // Down undoes the effects of Set.
const reconfigTimeout = time.Second //lint:ignore U1000 used on Linux at least, maybe others later // It is idempotent and performs no action if Set has never been called.
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.
Down() error Down() error
} }
// Manager manages system DNS settings. type wrappedManager struct {
type Manager struct { logf logger.Logf
logf logger.Logf impl Manager
config Config
impl managerImpl
config Config
mconfig ManagerConfig
} }
// NewManagers created a new manager from the given config. // NewManager creates a new manager from the given config.
func NewManager(mconfig ManagerConfig) *Manager { func NewManager(mconfig ManagerConfig) Manager {
mconfig.Logf = logger.WithPrefix(mconfig.Logf, "dns: ") mconfig.Logf = logger.WithPrefix(mconfig.Logf, "dns: ")
m := &Manager{ m := &wrappedManager{
logf: mconfig.Logf, logf: mconfig.Logf,
impl: newManager(mconfig), impl: newManager(mconfig),
config: Config{PerDomain: mconfig.PerDomain},
mconfig: mconfig,
} }
m.logf("using %T", m.impl) m.logf("using %T", m.impl)
return m return m
} }
func (m *Manager) Set(config Config) error { func (m *wrappedManager) Set(config Config) error {
if config.Equal(m.config) { if config.Equal(m.config) {
return nil return nil
} }
m.logf("Set: %+v", config) m.logf("set: %+v", config)
if len(config.Nameservers) == 0 { err := m.impl.Set(config)
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.
if err == nil { if err == nil {
m.config = config m.config = config
} }
return err return err
} }
func (m *Manager) Up() error { func (m *wrappedManager) Down() error {
return m.impl.Up(m.config)
}
func (m *Manager) Down() error {
return m.impl.Down() return m.impl.Down()
} }

View File

@ -6,7 +6,7 @@
package dns 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. // 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, // This is currently not implemented. Editing /etc/resolv.conf does not work,
// as most applications use the system resolver, which disregards it. // as most applications use the system resolver, which disregards it.

View File

@ -4,7 +4,7 @@
package dns package dns
func newManager(mconfig ManagerConfig) managerImpl { func newManager(mconfig ManagerConfig) Manager {
switch { switch {
case isResolvconfActive(): case isResolvconfActive():
return newResolvconfManager(mconfig) return newResolvconfManager(mconfig)

View File

@ -4,24 +4,177 @@
package dns package dns
func newManager(mconfig ManagerConfig) managerImpl { import (
switch { "context"
// systemd-resolved should only activate per-domain. "sync"
case isResolvedActive() && mconfig.PerDomain: "time"
if mconfig.Cleanup {
return newNoopManager(mconfig) "tailscale.com/logtail/backoff"
} else { "tailscale.com/types/logger"
return newResolvedManager(mconfig) )
}
case isNMActive(): var reconfigTimeout = 5 * time.Second
if mconfig.Cleanup {
return newNoopManager(mconfig) type dnsMode uint8
} else {
return newNMManager(mconfig) const (
} noMode dnsMode = iota
case isResolvconfActive(): nmMode
return newResolvconfManager(mconfig) 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: 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
}

View File

@ -4,6 +4,6 @@
package dns package dns
func newManager(mconfig ManagerConfig) managerImpl { func newManager(mconfig ManagerConfig) Manager {
return newDirectManager(mconfig) return newDirectManager(mconfig)
} }

View File

@ -25,7 +25,7 @@ type windowsManager struct {
guid string guid string
} }
func newManager(mconfig ManagerConfig) managerImpl { func newManager(mconfig ManagerConfig) Manager {
return windowsManager{ return windowsManager{
logf: mconfig.Logf, logf: mconfig.Logf,
guid: tun.WintunGUID, guid: tun.WintunGUID,
@ -110,7 +110,8 @@ func (m windowsManager) setDomains(path string, oldDomains, newDomains []string)
return nil return nil
} }
func (m windowsManager) Up(config Config) error { // Set implements Manager.
func (m windowsManager) Set(config Config) error {
var ipsv4 []string var ipsv4 []string
var ipsv6 []string var ipsv6 []string
@ -150,6 +151,7 @@ func (m windowsManager) Up(config Config) error {
return nil return nil
} }
// Down implements Manager.
func (m windowsManager) Down() error { func (m windowsManager) Down() error {
return m.Up(Config{Nameservers: nil, Domains: nil}) return m.Set(Config{Nameservers: nil, Domains: nil})
} }

View File

@ -12,8 +12,8 @@ import (
"context" "context"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"net"
"os" "os"
"os/exec"
"unsafe" "unsafe"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
@ -36,14 +36,6 @@ func init() {
// isNMActive determines if NetworkManager is currently managing system DNS settings. // isNMActive determines if NetworkManager is currently managing system DNS settings.
func isNMActive() bool { 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") f, err := os.Open("/etc/resolv.conf")
if err != nil { if err != nil {
return false return false
@ -64,21 +56,73 @@ func isNMActive() bool {
return false return false
} }
// nmManager uses the NetworkManager DBus API. func nmDNSMode() string {
type nmManager struct { ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout)
interfaceName string 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 { // nmManager uses the NetworkManager DBus API.
return nmManager{ 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, 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 type nmConnectionSettings map[string]map[string]dbus.Variant
// Up implements managerImpl. // Set implements Manager.
func (m nmManager) Up(config Config) error { 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) ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout)
defer cancel() defer cancel()
@ -215,7 +259,7 @@ func (m nmManager) Up(config Config) error {
return nil return nil
} }
// Down implements managerImpl. // Down implements Manager.
func (m nmManager) Down() error { func (m nmManager) Down() error {
return m.Up(Config{Nameservers: nil, Domains: nil}) return m.Set(Config{Nameservers: nil, Domains: nil})
} }

View File

@ -6,12 +6,12 @@ package dns
type noopManager struct{} type noopManager struct{}
// Up implements managerImpl. // Set implements Manager.
func (m noopManager) Up(Config) error { return nil } func (noopManager) Set(Config) error { return nil }
// Down implements managerImpl. // Down implements Manager.
func (m noopManager) Down() error { return nil } func (noopManager) Down() error { return nil }
func newNoopManager(mconfig ManagerConfig) managerImpl { func newNoopManager(mconfig ManagerConfig) Manager {
return noopManager{} return noopManager{}
} }

View File

@ -10,6 +10,7 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "fmt"
"net"
"os" "os"
"os/exec" "os/exec"
) )
@ -96,15 +97,24 @@ func getResolvconfImpl() resolvconfImpl {
} }
type resolvconfManager struct { 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() impl := getResolvconfImpl()
mconfig.Logf("resolvconf implementation is %s", impl) mconfig.Logf("resolvconf implementation is %s", impl)
return resolvconfManager{ oldConfig, err := readResolvConf()
impl: impl, 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. // when running resolvconfLegacy, hopefully placing our config first.
const resolvconfConfigName = "tun-tailscale.inet" const resolvconfConfigName = "tun-tailscale.inet"
// Up implements managerImpl. // Set implements Manager.
func (m resolvconfManager) Up(config Config) error { 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) stdin := new(bytes.Buffer)
writeResolvConf(stdin, config.Nameservers, config.Domains) // dns_direct.go writeResolvConf(stdin, config.Nameservers, config.Domains) // dns_direct.go
@ -137,8 +153,8 @@ func (m resolvconfManager) Up(config Config) error {
return nil return nil
} }
// Down implements managerImpl. // Down implements Manager.
func (m resolvconfManager) Down() error { func (m *resolvconfManager) Down() error {
var cmd *exec.Cmd var cmd *exec.Cmd
switch m.impl { switch m.impl {
case resolvconfOpenresolv: case resolvconfOpenresolv:

View File

@ -10,7 +10,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"os/exec"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@ -49,18 +48,6 @@ type resolvedLinkDomain struct {
// isResolvedActive determines if resolved is currently managing system DNS settings. // isResolvedActive determines if resolved is currently managing system DNS settings.
func isResolvedActive() bool { 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() config, err := readResolvConf()
if err != nil { if err != nil {
return false return false
@ -77,12 +64,12 @@ func isResolvedActive() bool {
// resolvedManager uses the systemd-resolved DBus API. // resolvedManager uses the systemd-resolved DBus API.
type resolvedManager struct{} type resolvedManager struct{}
func newResolvedManager(mconfig ManagerConfig) managerImpl { func newResolvedManager(mconfig ManagerConfig) Manager {
return resolvedManager{} return resolvedManager{}
} }
// Up implements managerImpl. // Set implements Manager.
func (m resolvedManager) Up(config Config) error { func (m resolvedManager) Set(config Config) error {
ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout) ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout)
defer cancel() defer cancel()
@ -151,7 +138,7 @@ func (m resolvedManager) Up(config Config) error {
return nil return nil
} }
// Down implements managerImpl. // Down implements Manager.
func (m resolvedManager) Down() error { func (m resolvedManager) Down() error {
ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout) ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout)
defer cancel() defer cancel()

View File

@ -12,6 +12,7 @@ import (
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/wgengine/router/dns" "tailscale.com/wgengine/router/dns"
"tailscale.com/wgengine/tsdns"
) )
// Router is responsible for managing the system network stack. // Router is responsible for managing the system network stack.
@ -30,11 +31,17 @@ type Router interface {
Close() error Close() error
} }
// New returns a new Router for the current platform, using the type InitConfig struct {
// provided tun device. Logf logger.Logf
func New(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) { Wgdev *device.Device
logf = logger.WithPrefix(logf, "router: ") Tun tun.Device
return newUserspaceRouter(logf, wgdev, tundev) 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 // Cleanup restores the system network configuration to its original state

View File

@ -10,8 +10,8 @@ import (
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) { func newUserspaceRouter(cfg InitConfig) (Router, error) {
return newUserspaceBSDRouter(logf, wgdev, tundev) return newUserspaceBSDRouter(cfg)
} }
func cleanup(logger.Logf, string) { func cleanup(logger.Logf, string) {

View File

@ -12,10 +12,10 @@ import (
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
func newUserspaceRouter(logf logger.Logf, tunname string, dev *device.Device, tuntap tun.Device, netChanged func()) Router { func newUserspaceRouter(cfg InitConfig) Router {
return NewFakeRouter(logf, tunname, dev, tuntap, netChanged) return NewFakeRouter(cfg)
} }
func cleanup(logf logger.Logf, interfaceName string) { func cleanup(logger.Logf, string) {
// Nothing to do here. // Nothing to do here.
} }

View File

@ -5,15 +5,13 @@
package router package router
import ( import (
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"tailscale.com/types/logger" "tailscale.com/types/logger"
) )
// NewFakeRouter returns a Router that does nothing when called and // NewFakeRouter returns a Router that does nothing when called and
// always returns nil errors. // always returns nil errors.
func NewFake(logf logger.Logf, _ *device.Device, _ tun.Device) (Router, error) { func NewFake(cfg InitConfig) (Router, error) {
return fakeRouter{logf: logf}, nil return fakeRouter{logf: cfg.Logf}, nil
} }
type fakeRouter struct { type fakeRouter struct {

View File

@ -15,8 +15,8 @@ import (
// Work is currently underway for an in-kernel FreeBSD implementation of wireguard // Work is currently underway for an in-kernel FreeBSD implementation of wireguard
// https://svnweb.freebsd.org/base?view=revision&revision=357986 // https://svnweb.freebsd.org/base?view=revision&revision=357986
func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) { func newUserspaceRouter(cfg InitConfig) (Router, error) {
return newUserspaceBSDRouter(logf, nil, tundev) return newUserspaceBSDRouter(cfg)
} }
func cleanup(logf logger.Logf, interfaceName string) { func cleanup(logf logger.Logf, interfaceName string) {

View File

@ -10,8 +10,6 @@ import (
"strings" "strings"
"github.com/coreos/go-iptables/iptables" "github.com/coreos/go-iptables/iptables"
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -85,14 +83,14 @@ type linuxRouter struct {
snatSubnetRoutes bool snatSubnetRoutes bool
netfilterMode NetfilterMode netfilterMode NetfilterMode
dns *dns.Manager dns dns.Manager
ipt4 netfilterRunner ipt4 netfilterRunner
cmd commandRunner cmd commandRunner
} }
func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (Router, error) { func newUserspaceRouter(cfg InitConfig) (Router, error) {
tunname, err := tunDev.Name() tunname, err := cfg.Tun.Name()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -102,20 +100,21 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (
return nil, err 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() _, err := exec.Command("ip", "rule").Output()
ipRuleAvailable := (err == nil) ipRuleAvailable := (err == nil)
mconfig := dns.ManagerConfig{ mconfig := dns.ManagerConfig{
Logf: logf, Logf: cfg.Logf,
InterfaceName: tunname, InterfaceName: tunname,
SetUpstreams: cfg.Resolver.SetUpstreams,
} }
return &linuxRouter{ return &linuxRouter{
logf: logf, logf: cfg.Logf,
ipRuleAvailable: ipRuleAvailable, ipRuleAvailable: ipRuleAvailable,
tunname: tunname, tunname: tunname,
netfilterMode: NetfilterOff, netfilterMode: NetfilterOff,

View File

@ -241,7 +241,7 @@ nat/POSTROUTING -j ts-postrouting
} }
fake := NewFakeOS(t) fake := NewFakeOS(t)
router, err := newUserspaceRouterAdvanced(t.Logf, "tailscale0", fake, fake) router, err := newUserspaceRouterAdvanced(InitConfig{Logf: t.Logf}, "tailscale0", fake, fake)
if err != nil { if err != nil {
t.Fatalf("failed to create router: %v", err) t.Fatalf("failed to create router: %v", err)
} }

View File

@ -27,22 +27,23 @@ type openbsdRouter struct {
local netaddr.IPPrefix local netaddr.IPPrefix
routes map[netaddr.IPPrefix]struct{} routes map[netaddr.IPPrefix]struct{}
dns *dns.Manager dns dns.Manager
} }
func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) { func newUserspaceRouter(cfg InitConfig) (Router, error) {
tunname, err := tundev.Name() tunname, err := cfg.Tun.Name()
if err != nil { if err != nil {
return nil, err return nil, err
} }
mconfig := dns.ManagerConfig{ mconfig := dns.ManagerConfig{
Logf: logf, Logf: cfg.Logf,
InterfaceName: tunname, InterfaceName: tunname,
SetUpstreams: cfg.Resolver.SetUpstreams,
} }
return &openbsdRouter{ return &openbsdRouter{
logf: logf, logf: cfg.Logf,
tunname: tunname, tunname: tunname,
dns: dns.NewManager(mconfig), dns: dns.NewManager(mconfig),
}, nil }, nil

View File

@ -26,22 +26,23 @@ type userspaceBSDRouter struct {
local netaddr.IPPrefix local netaddr.IPPrefix
routes map[netaddr.IPPrefix]struct{} routes map[netaddr.IPPrefix]struct{}
dns *dns.Manager dns dns.Manager
} }
func newUserspaceBSDRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) { func newUserspaceBSDRouter(cfg InitConfig) (Router, error) {
tunname, err := tundev.Name() tunname, err := cfg.Tun.Name()
if err != nil { if err != nil {
return nil, err return nil, err
} }
mconfig := dns.ManagerConfig{ mconfig := dns.ManagerConfig{
Logf: logf, Logf: cfg.Logf,
InterfaceName: tunname, InterfaceName: tunname,
SetUpstreams: cfg.Resolver.Upstreams,
} }
return &userspaceBSDRouter{ return &userspaceBSDRouter{
logf: logf, logf: cfg.Logf,
tunname: tunname, tunname: tunname,
dns: dns.NewManager(mconfig), dns: dns.NewManager(mconfig),
}, nil }, nil

View File

@ -21,25 +21,25 @@ type winRouter struct {
nativeTun *tun.NativeTun nativeTun *tun.NativeTun
wgdev *device.Device wgdev *device.Device
routeChangeCallback *winipcfg.RouteChangeCallback routeChangeCallback *winipcfg.RouteChangeCallback
dns *dns.Manager dns dns.Manager
} }
func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) { func newUserspaceRouter(cfg InitConfig) (Router, error) {
tunname, err := tundev.Name() tunname, err := cfg.Tun.Name()
if err != nil { if err != nil {
return nil, err return nil, err
} }
nativeTun := tundev.(*tun.NativeTun) nativeTun := cfg.Tun.(*tun.NativeTun)
guid := nativeTun.GUID().String() guid := nativeTun.GUID().String()
mconfig := dns.ManagerConfig{ mconfig := dns.ManagerConfig{
Logf: logf, Logf: cfg.Logf,
InterfaceName: guid, InterfaceName: guid,
} }
return &winRouter{ return &winRouter{
logf: logf, logf: cfg.Logf,
wgdev: wgdev, wgdev: cfg.Wgdev,
tunname: tunname, tunname: tunname,
nativeTun: nativeTun, nativeTun: nativeTun,
dns: dns.NewManager(mconfig), dns: dns.NewManager(mconfig),

View File

@ -152,6 +152,7 @@ func (r *Resolver) SetMap(m *Map) {
func (r *Resolver) SetUpstreams(upstreams []net.Addr) { func (r *Resolver) SetUpstreams(upstreams []net.Addr) {
if r.forwarder != nil { if r.forwarder != nil {
r.forwarder.setUpstreams(upstreams) r.forwarder.setUpstreams(upstreams)
r.logf("set upstreams: %v", upstreams)
} }
} }

View File

@ -121,7 +121,7 @@ type userspaceEngine struct {
// RouterGen is the signature for a function that creates a // RouterGen is the signature for a function that creates a
// router.Router. // 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 { type EngineConfig struct {
// Logf is the logging function used by the engine. // 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: routerCfg := router.InitConfig{
// routers do not Read or Write, but do access native interfaces. Logf: logf,
e.router, err = conf.RouterGen(logf, e.wgdev, e.tundev.Unwrap()) 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 { if err != nil {
e.magicConn.Close() e.magicConn.Close()
return nil, err return nil, err
@ -857,17 +863,21 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
if routerChanged { if routerChanged {
if routerCfg.DNS.Proxied { if routerCfg.DNS.Proxied {
ips := routerCfg.DNS.Nameservers if len(routerCfg.DNS.Nameservers) == 0 {
upstreams := make([]net.Addr, len(ips)) routerCfg.DNS.PerDomain = true
for i, ip := range ips { } else {
stdIP := ip.IPAddr() ips := routerCfg.DNS.Nameservers
upstreams[i] = &net.UDPAddr{ upstreams := make([]net.Addr, len(ips))
IP: stdIP.IP, for i, ip := range ips {
Port: 53, stdIP := ip.IPAddr()
Zone: stdIP.Zone, 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()} routerCfg.DNS.Nameservers = []netaddr.IP{tsaddr.TailscaleServiceIP()}
} }
e.logf("wgengine: Reconfig: configuring router") e.logf("wgengine: Reconfig: configuring router")