all: dns refactor, add Proxied and PerDomain flags from control (#615)
Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
This commit is contained in:

committed by
GitHub

parent
43b271cb26
commit
28e52a0492
@ -1,74 +0,0 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
// DNSConfig is the subset of Config that contains DNS parameters.
|
||||
type DNSConfig struct {
|
||||
// Nameservers are the IP addresses of the nameservers to use.
|
||||
Nameservers []netaddr.IP
|
||||
// Domains are the search domains to use.
|
||||
Domains []string
|
||||
}
|
||||
|
||||
// EquivalentTo determines whether its argument and receiver
|
||||
// represent equivalent DNS configurations (then DNS reconfig is a no-op).
|
||||
func (lhs DNSConfig) EquivalentTo(rhs DNSConfig) bool {
|
||||
if len(lhs.Nameservers) != len(rhs.Nameservers) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(lhs.Domains) != len(rhs.Domains) {
|
||||
return false
|
||||
}
|
||||
|
||||
// With how we perform resolution order shouldn't matter,
|
||||
// but it is unlikely that we will encounter different orders.
|
||||
for i, server := range lhs.Nameservers {
|
||||
if rhs.Nameservers[i] != server {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for i, domain := range lhs.Domains {
|
||||
if rhs.Domains[i] != domain {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// dnsMode determines how DNS settings are managed.
|
||||
type dnsMode uint8
|
||||
|
||||
const (
|
||||
// dnsDirect indicates that /etc/resolv.conf is edited directly.
|
||||
dnsDirect dnsMode = iota
|
||||
// dnsResolvconf indicates that a resolvconf binary is used.
|
||||
dnsResolvconf
|
||||
// dnsNetworkManager indicates that the NetworkManaer DBus API is used.
|
||||
dnsNetworkManager
|
||||
// dnsResolved indicates that the systemd-resolved DBus API is used.
|
||||
dnsResolved
|
||||
)
|
||||
|
||||
func (m dnsMode) String() string {
|
||||
switch m {
|
||||
case dnsDirect:
|
||||
return "direct"
|
||||
case dnsResolvconf:
|
||||
return "resolvconf"
|
||||
case dnsNetworkManager:
|
||||
return "networkmanager"
|
||||
case dnsResolved:
|
||||
return "resolved"
|
||||
default:
|
||||
return "???"
|
||||
}
|
||||
}
|
75
wgengine/router/dns/config.go
Normal file
75
wgengine/router/dns/config.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"inet.af/netaddr"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// Config is the set of parameters that uniquely determine
|
||||
// the state to which a manager should bring system DNS settings.
|
||||
type Config struct {
|
||||
// Nameservers are the IP addresses of the nameservers to use.
|
||||
Nameservers []netaddr.IP
|
||||
// Domains are the search domains to use.
|
||||
Domains []string
|
||||
// PerDomain indicates whether it is preferred to use Nameservers
|
||||
// only for DNS queries for subdomains of Domains.
|
||||
// Note that Nameservers may still be applied to all queries
|
||||
// if the manager does not support per-domain settings.
|
||||
PerDomain bool
|
||||
// Proxied indicates whether DNS requests are proxied through a tsdns.Resolver.
|
||||
Proxied bool
|
||||
}
|
||||
|
||||
// Equal determines whether its argument and receiver
|
||||
// represent equivalent DNS configurations (then DNS reconfig is a no-op).
|
||||
func (lhs Config) Equal(rhs Config) bool {
|
||||
if lhs.Proxied != rhs.Proxied || lhs.PerDomain != rhs.PerDomain {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(lhs.Nameservers) != len(rhs.Nameservers) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(lhs.Domains) != len(rhs.Domains) {
|
||||
return false
|
||||
}
|
||||
|
||||
// With how we perform resolution order shouldn't matter,
|
||||
// but it is unlikely that we will encounter different orders.
|
||||
for i, server := range lhs.Nameservers {
|
||||
if rhs.Nameservers[i] != server {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// The order of domains, on the other hand, is significant.
|
||||
for i, domain := range lhs.Domains {
|
||||
if rhs.Domains[i] != domain {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ManagerConfig is the set of parameters from which
|
||||
// a manager implementation is chosen and initialized.
|
||||
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 string
|
||||
// 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
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
|
||||
// +build linux freebsd openbsd
|
||||
|
||||
package router
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -27,8 +27,8 @@ const (
|
||||
resolvConf = "/etc/resolv.conf"
|
||||
)
|
||||
|
||||
// dnsWriteConfig writes DNS configuration in resolv.conf format to the given writer.
|
||||
func dnsWriteConfig(w io.Writer, servers []netaddr.IP, domains []string) {
|
||||
// writeResolvConf writes DNS configuration in resolv.conf format to the given writer.
|
||||
func writeResolvConf(w io.Writer, servers []netaddr.IP, domains []string) {
|
||||
io.WriteString(w, "# resolv.conf(5) file generated by tailscale\n")
|
||||
io.WriteString(w, "# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN\n\n")
|
||||
for _, ns := range servers {
|
||||
@ -46,9 +46,9 @@ func dnsWriteConfig(w io.Writer, servers []netaddr.IP, domains []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// dnsReadConfig reads DNS configuration from /etc/resolv.conf.
|
||||
func dnsReadConfig() (DNSConfig, error) {
|
||||
var config DNSConfig
|
||||
// readResolvConf reads DNS configuration from /etc/resolv.conf.
|
||||
func readResolvConf() (Config, error) {
|
||||
var config Config
|
||||
|
||||
f, err := os.Open("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
@ -100,17 +100,24 @@ func isResolvedRunning() bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// dnsDirectUp replaces /etc/resolv.conf with a file generated
|
||||
// from the given configuration, creating a backup of its old state.
|
||||
// directManager is a managerImpl 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 dnsDirectDown before program shutdown
|
||||
// and ensure that router.Cleanup is run if the program terminates unexpectedly.
|
||||
func dnsDirectUp(config DNSConfig) error {
|
||||
// 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{}
|
||||
}
|
||||
|
||||
// Up implements managerImpl.
|
||||
func (m directManager) Up(config Config) error {
|
||||
// Write the tsConf file.
|
||||
buf := new(bytes.Buffer)
|
||||
dnsWriteConfig(buf, config.Nameservers, config.Domains)
|
||||
writeResolvConf(buf, config.Nameservers, config.Domains)
|
||||
if err := atomicfile.WriteFile(tsConf, buf.Bytes(), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -152,9 +159,8 @@ func dnsDirectUp(config DNSConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// dnsDirectDown restores /etc/resolv.conf to its state before dnsDirectUp.
|
||||
// It is idempotent and behaves correctly even if dnsDirectUp has never been run.
|
||||
func dnsDirectDown() error {
|
||||
// Down implements managerImpl.
|
||||
func (m directManager) Down() error {
|
||||
if _, err := os.Stat(backupConf); err != nil {
|
||||
// If the backup file does not exist, then Up never ran successfully.
|
||||
if os.IsNotExist(err) {
|
94
wgengine/router/dns/manager.go
Normal file
94
wgengine/router/dns/manager.go
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Manager manages system DNS settings.
|
||||
type Manager struct {
|
||||
logf logger.Logf
|
||||
|
||||
impl managerImpl
|
||||
|
||||
config Config
|
||||
mconfig ManagerConfig
|
||||
}
|
||||
|
||||
// NewManagers created a new manager from the given config.
|
||||
func NewManager(mconfig ManagerConfig) *Manager {
|
||||
mconfig.Logf = logger.WithPrefix(mconfig.Logf, "dns: ")
|
||||
m := &Manager{
|
||||
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 {
|
||||
if config.Equal(m.config) {
|
||||
return nil
|
||||
}
|
||||
|
||||
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.
|
||||
if err == nil {
|
||||
m.config = config
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) Up() error {
|
||||
return m.impl.Up(m.config)
|
||||
}
|
||||
|
||||
func (m *Manager) Down() error {
|
||||
return m.impl.Down()
|
||||
}
|
14
wgengine/router/dns/manager_default.go
Normal file
14
wgengine/router/dns/manager_default.go
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !linux,!freebsd,!openbsd,!windows
|
||||
|
||||
package dns
|
||||
|
||||
func newManager(mconfig ManagerConfig) managerImpl {
|
||||
// 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.
|
||||
return newNoopManager(mconfig)
|
||||
}
|
14
wgengine/router/dns/manager_freebsd.go
Normal file
14
wgengine/router/dns/manager_freebsd.go
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dns
|
||||
|
||||
func newManager(mconfig ManagerConfig) managerImpl {
|
||||
switch {
|
||||
case isResolvconfActive():
|
||||
return newResolvconfManager(mconfig)
|
||||
default:
|
||||
return newDirectManager(mconfig)
|
||||
}
|
||||
}
|
27
wgengine/router/dns/manager_linux.go
Normal file
27
wgengine/router/dns/manager_linux.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
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)
|
||||
default:
|
||||
return newDirectManager(mconfig)
|
||||
}
|
||||
}
|
9
wgengine/router/dns/manager_openbsd.go
Normal file
9
wgengine/router/dns/manager_openbsd.go
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dns
|
||||
|
||||
func newManager(mconfig ManagerConfig) managerImpl {
|
||||
return newDirectManager(mconfig)
|
||||
}
|
83
wgengine/router/dns/manager_windows.go
Normal file
83
wgengine/router/dns/manager_windows.go
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
type windowsManager struct {
|
||||
logf logger.Logf
|
||||
guid string
|
||||
}
|
||||
|
||||
func newManager(mconfig ManagerConfig) managerImpl {
|
||||
return windowsManager{
|
||||
logf: mconfig.Logf,
|
||||
guid: tun.WintunGUID,
|
||||
}
|
||||
}
|
||||
|
||||
func setRegistry(path, nameservers, domains string) error {
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, path, registry.READ|registry.SET_VALUE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening %s: %w", path, err)
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
err = key.SetStringValue("NameServer", nameservers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting %s/NameServer: %w", path, err)
|
||||
}
|
||||
|
||||
err = key.SetStringValue("Domain", domains)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting %s/Domain: %w", path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m windowsManager) Up(config Config) error {
|
||||
var ipsv4 []string
|
||||
var ipsv6 []string
|
||||
for _, ip := range config.Nameservers {
|
||||
if ip.Is4() {
|
||||
ipsv4 = append(ipsv4, ip.String())
|
||||
} else {
|
||||
ipsv6 = append(ipsv6, ip.String())
|
||||
}
|
||||
}
|
||||
nsv4 := strings.Join(ipsv4, ",")
|
||||
nsv6 := strings.Join(ipsv6, ",")
|
||||
|
||||
var domains string
|
||||
if len(config.Domains) > 0 {
|
||||
if len(config.Domains) > 1 {
|
||||
m.logf("only a single search domain is supported")
|
||||
}
|
||||
domains = config.Domains[0]
|
||||
}
|
||||
|
||||
v4Path := `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\` + m.guid
|
||||
if err := setRegistry(v4Path, nsv4, domains); err != nil {
|
||||
return err
|
||||
}
|
||||
v6Path := `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces\` + m.guid
|
||||
if err := setRegistry(v6Path, nsv6, domains); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m windowsManager) Down() error {
|
||||
return m.Up(Config{Nameservers: nil, Domains: nil})
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
|
||||
// +build linux
|
||||
|
||||
package router
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -20,8 +20,8 @@ import (
|
||||
|
||||
type nmConnectionSettings map[string]map[string]dbus.Variant
|
||||
|
||||
// nmIsActive determines if NetworkManager is currently managing system DNS settings.
|
||||
func nmIsActive() bool {
|
||||
// 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.
|
||||
@ -50,10 +50,20 @@ func nmIsActive() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// dnsNetworkManagerUp updates the DNS config for the Tailscale interface
|
||||
// through the NetworkManager DBus API.
|
||||
func dnsNetworkManagerUp(config DNSConfig, interfaceName string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), dnsReconfigTimeout)
|
||||
// nmManager uses the NetworkManager DBus API.
|
||||
type nmManager struct {
|
||||
interfaceName string
|
||||
}
|
||||
|
||||
func newNMManager(mconfig ManagerConfig) managerImpl {
|
||||
return nmManager{
|
||||
interfaceName: mconfig.InterfaceName,
|
||||
}
|
||||
}
|
||||
|
||||
// Up implements managerImpl.
|
||||
func (m nmManager) Up(config Config) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout)
|
||||
defer cancel()
|
||||
|
||||
// conn is a shared connection whose lifecycle is managed by the dbus package.
|
||||
@ -90,7 +100,7 @@ func dnsNetworkManagerUp(config DNSConfig, interfaceName string) error {
|
||||
var devicePath dbus.ObjectPath
|
||||
err = nm.CallWithContext(
|
||||
ctx, "org.freedesktop.NetworkManager.GetDeviceByIpIface", 0,
|
||||
interfaceName,
|
||||
m.interfaceName,
|
||||
).Store(&devicePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getDeviceByIpIface: %w", err)
|
||||
@ -189,7 +199,7 @@ func dnsNetworkManagerUp(config DNSConfig, interfaceName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// dnsNetworkManagerDown undoes the changes made by dnsNetworkManagerUp.
|
||||
func dnsNetworkManagerDown(interfaceName string) error {
|
||||
return dnsNetworkManagerUp(DNSConfig{Nameservers: nil, Domains: nil}, interfaceName)
|
||||
// Down implements managerImpl.
|
||||
func (m nmManager) Down() error {
|
||||
return m.Up(Config{Nameservers: nil, Domains: nil})
|
||||
}
|
17
wgengine/router/dns/noop.go
Normal file
17
wgengine/router/dns/noop.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dns
|
||||
|
||||
type noopManager struct{}
|
||||
|
||||
// Up implements managerImpl.
|
||||
func (m noopManager) Up(Config) error { return nil }
|
||||
|
||||
// Down implements managerImpl.
|
||||
func (m noopManager) Down() error { return nil }
|
||||
|
||||
func newNoopManager(mconfig ManagerConfig) managerImpl {
|
||||
return noopManager{}
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
|
||||
// +build linux freebsd
|
||||
|
||||
package router
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -14,10 +14,10 @@ import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// resolvconfIsActive indicates whether the system appears to be using resolvconf.
|
||||
// If this is true, then dnsManualUp should be avoided:
|
||||
// isResolvconfActive indicates whether the system appears to be using resolvconf.
|
||||
// If this is true, then directManager should be avoided:
|
||||
// resolvconf has exclusive ownership of /etc/resolv.conf.
|
||||
func resolvconfIsActive() bool {
|
||||
func isResolvconfActive() bool {
|
||||
// Sanity-check first: if there is no resolvconf binary, then this is fruitless.
|
||||
//
|
||||
// However, this binary may be a shim like the one systemd-resolved provides.
|
||||
@ -57,21 +57,31 @@ func resolvconfIsActive() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// resolvconfImplementation enumerates supported implementations of the resolvconf CLI.
|
||||
type resolvconfImplementation uint8
|
||||
// resolvconfImpl enumerates supported implementations of the resolvconf CLI.
|
||||
type resolvconfImpl uint8
|
||||
|
||||
const (
|
||||
// resolvconfOpenresolv is the implementation packaged as "openresolv" on Ubuntu.
|
||||
// It supports exclusive mode and interface metrics.
|
||||
resolvconfOpenresolv resolvconfImplementation = iota
|
||||
resolvconfOpenresolv resolvconfImpl = iota
|
||||
// resolvconfLegacy is the implementation by Thomas Hood packaged as "resolvconf" on Ubuntu.
|
||||
// It does not support exclusive mode or interface metrics.
|
||||
resolvconfLegacy
|
||||
)
|
||||
|
||||
// getResolvconfImplementation returns the implementation of resolvconf
|
||||
// that appears to be in use.
|
||||
func getResolvconfImplementation() resolvconfImplementation {
|
||||
func (impl resolvconfImpl) String() string {
|
||||
switch impl {
|
||||
case resolvconfOpenresolv:
|
||||
return "openresolv"
|
||||
case resolvconfLegacy:
|
||||
return "legacy"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// getResolvconfImpl returns the implementation of resolvconf that appears to be in use.
|
||||
func getResolvconfImpl() resolvconfImpl {
|
||||
err := exec.Command("resolvconf", "-v").Run()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
@ -85,21 +95,31 @@ func getResolvconfImplementation() resolvconfImplementation {
|
||||
return resolvconfOpenresolv
|
||||
}
|
||||
|
||||
type resolvconfManager struct {
|
||||
impl resolvconfImpl
|
||||
}
|
||||
|
||||
func newResolvconfManager(mconfig ManagerConfig) managerImpl {
|
||||
impl := getResolvconfImpl()
|
||||
mconfig.Logf("resolvconf implementation is %s", impl)
|
||||
|
||||
return resolvconfManager{
|
||||
impl: impl,
|
||||
}
|
||||
}
|
||||
|
||||
// resolvconfConfigName is the name of the config submitted to resolvconf.
|
||||
// It has this form to match the "tun*" rule in interface-order
|
||||
// when running resolvconfLegacy, hopefully placing our config first.
|
||||
const resolvconfConfigName = "tun-tailscale.inet"
|
||||
|
||||
// dnsResolvconfUp invokes the resolvconf binary to associate
|
||||
// the given DNS configuration the Tailscale interface.
|
||||
func dnsResolvconfUp(config DNSConfig, interfaceName string) error {
|
||||
implementation := getResolvconfImplementation()
|
||||
|
||||
// Up implements managerImpl.
|
||||
func (m resolvconfManager) Up(config Config) error {
|
||||
stdin := new(bytes.Buffer)
|
||||
dnsWriteConfig(stdin, config.Nameservers, config.Domains) // dns_direct.go
|
||||
writeResolvConf(stdin, config.Nameservers, config.Domains) // dns_direct.go
|
||||
|
||||
var cmd *exec.Cmd
|
||||
switch implementation {
|
||||
switch m.impl {
|
||||
case resolvconfOpenresolv:
|
||||
// Request maximal priority (metric 0) and exclusive mode.
|
||||
cmd = exec.Command("resolvconf", "-m", "0", "-x", "-a", resolvconfConfigName)
|
||||
@ -117,12 +137,10 @@ func dnsResolvconfUp(config DNSConfig, interfaceName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// dnsResolvconfDown undoes the action of dnsResolvconfUp.
|
||||
func dnsResolvconfDown(interfaceName string) error {
|
||||
implementation := getResolvconfImplementation()
|
||||
|
||||
// Down implements managerImpl.
|
||||
func (m resolvconfManager) Down() error {
|
||||
var cmd *exec.Cmd
|
||||
switch implementation {
|
||||
switch m.impl {
|
||||
case resolvconfOpenresolv:
|
||||
cmd = exec.Command("resolvconf", "-f", "-d", resolvconfConfigName)
|
||||
case resolvconfLegacy:
|
@ -4,14 +4,13 @@
|
||||
|
||||
// +build linux
|
||||
|
||||
package router
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"golang.org/x/sys/unix"
|
||||
@ -23,7 +22,7 @@ import (
|
||||
//
|
||||
// We only consider resolved to be the system resolver if the stub resolver is;
|
||||
// that is, if this address is the sole nameserver in /etc/resolved.conf.
|
||||
// In other cases, resolved may still be managing the system DNS configuration directly.
|
||||
// In other cases, resolved may be managing the system DNS configuration directly.
|
||||
// Then the nameserver list will be a concatenation of those for all
|
||||
// the interfaces that register their interest in being a default resolver with
|
||||
// SetLinkDomains([]{{"~.", true}, ...})
|
||||
@ -36,13 +35,6 @@ import (
|
||||
// this address is, in fact, hard-coded into resolved.
|
||||
var resolvedListenAddr = netaddr.IPv4(127, 0, 0, 53)
|
||||
|
||||
// dnsReconfigTimeout is the timeout for DNS reconfiguration.
|
||||
//
|
||||
// This is 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 dnsReconfigTimeout = time.Second
|
||||
|
||||
var errNotReady = errors.New("interface not ready")
|
||||
|
||||
type resolvedLinkNameserver struct {
|
||||
@ -55,8 +47,8 @@ type resolvedLinkDomain struct {
|
||||
RoutingOnly bool
|
||||
}
|
||||
|
||||
// resolvedIsActive determines if resolved is currently managing system DNS settings.
|
||||
func resolvedIsActive() bool {
|
||||
// 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 {
|
||||
@ -69,7 +61,7 @@ func resolvedIsActive() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
config, err := dnsReadConfig()
|
||||
config, err := readResolvConf()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@ -82,10 +74,16 @@ func resolvedIsActive() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// dnsResolvedUp sets the DNS parameters for the Tailscale interface
|
||||
// to given nameservers and search domains using the resolved DBus API.
|
||||
func dnsResolvedUp(config DNSConfig) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), dnsReconfigTimeout)
|
||||
// resolvedManager uses the systemd-resolved DBus API.
|
||||
type resolvedManager struct{}
|
||||
|
||||
func newResolvedManager(mconfig ManagerConfig) managerImpl {
|
||||
return resolvedManager{}
|
||||
}
|
||||
|
||||
// Up implements managerImpl.
|
||||
func (m resolvedManager) Up(config Config) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout)
|
||||
defer cancel()
|
||||
|
||||
// conn is a shared connection whose lifecycle is managed by the dbus package.
|
||||
@ -100,6 +98,8 @@ func dnsResolvedUp(config DNSConfig) error {
|
||||
dbus.ObjectPath("/org/freedesktop/resolve1"),
|
||||
)
|
||||
|
||||
// In principle, we could persist this in the manager struct
|
||||
// if we knew that interface indices are persistent. This does not seem to be the case.
|
||||
_, iface, err := interfaces.Tailscale()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting interface index: %w", err)
|
||||
@ -129,7 +129,7 @@ func dnsResolvedUp(config DNSConfig) error {
|
||||
iface.Index, linkNameservers,
|
||||
).Store()
|
||||
if err != nil {
|
||||
return fmt.Errorf("SetLinkDNS: %w", err)
|
||||
return fmt.Errorf("setLinkDNS: %w", err)
|
||||
}
|
||||
|
||||
var linkDomains = make([]resolvedLinkDomain, len(config.Domains))
|
||||
@ -145,15 +145,15 @@ func dnsResolvedUp(config DNSConfig) error {
|
||||
iface.Index, linkDomains,
|
||||
).Store()
|
||||
if err != nil {
|
||||
return fmt.Errorf("SetLinkDomains: %w", err)
|
||||
return fmt.Errorf("setLinkDomains: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dnsResolvedDown undoes the changes made by dnsResolvedUp.
|
||||
func dnsResolvedDown() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), dnsReconfigTimeout)
|
||||
// Down implements managerImpl.
|
||||
func (m resolvedManager) Down() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout)
|
||||
defer cancel()
|
||||
|
||||
// conn is a shared connection whose lifecycle is managed by the dbus package.
|
@ -21,7 +21,6 @@ import (
|
||||
"github.com/tailscale/wireguard-go/device"
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"tailscale.com/wgengine/winnet"
|
||||
)
|
||||
|
||||
@ -157,28 +156,6 @@ func monitorDefaultRoutes(device *device.Device, autoMTU bool, tun *tun.NativeTu
|
||||
return cb, nil
|
||||
}
|
||||
|
||||
func setDNSDomains(g windows.GUID, dnsDomains []string) {
|
||||
gs := g.String()
|
||||
log.Printf("setDNSDomains(%v) guid=%v\n", dnsDomains, gs)
|
||||
p := `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\` + gs
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, p, registry.READ|registry.SET_VALUE)
|
||||
if err != nil {
|
||||
log.Printf("setDNSDomains(%v): open: %v\n", p, err)
|
||||
return
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
// Windows only supports a single per-interface DNS domain.
|
||||
dom := ""
|
||||
if len(dnsDomains) > 0 {
|
||||
dom = dnsDomains[0]
|
||||
}
|
||||
err = key.SetStringValue("Domain", dom)
|
||||
if err != nil {
|
||||
log.Printf("setDNSDomains(%v): SetStringValue: %v\n", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
func setFirewall(ifcGUID *windows.GUID) (bool, error) {
|
||||
c := ole.Connection{}
|
||||
err := c.Initialize()
|
||||
@ -262,8 +239,6 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) error {
|
||||
}
|
||||
}()
|
||||
|
||||
setDNSDomains(guid, cfg.Domains)
|
||||
|
||||
routes := []winipcfg.RouteData{}
|
||||
var firstGateway4 *net.IP
|
||||
var firstGateway6 *net.IP
|
||||
@ -358,16 +333,6 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) error {
|
||||
errAcc = err
|
||||
}
|
||||
|
||||
var dnsIPs []net.IP
|
||||
for _, ip := range cfg.Nameservers {
|
||||
dnsIPs = append(dnsIPs, ip.IPAddr().IP)
|
||||
}
|
||||
err = iface.SetDNS(dnsIPs)
|
||||
if err != nil && errAcc == nil {
|
||||
log.Printf("setdns: %v\n", err)
|
||||
errAcc = err
|
||||
}
|
||||
|
||||
ipif, err := iface.GetIpInterface(winipcfg.AF_INET)
|
||||
if err != nil {
|
||||
log.Printf("getipif: %v\n", err)
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine/router/dns"
|
||||
)
|
||||
|
||||
// Router is responsible for managing the system network stack.
|
||||
@ -40,6 +41,15 @@ func New(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, err
|
||||
// in case the Tailscale daemon terminated without closing the router.
|
||||
// No other state needs to be instantiated before this runs.
|
||||
func Cleanup(logf logger.Logf, interfaceName string) {
|
||||
mconfig := dns.ManagerConfig{
|
||||
Logf: logf,
|
||||
InterfaceName: interfaceName,
|
||||
Cleanup: true,
|
||||
}
|
||||
dns := dns.NewManager(mconfig)
|
||||
if err := dns.Down(); err != nil {
|
||||
logf("dns down: %v", err)
|
||||
}
|
||||
cleanup(logf, interfaceName)
|
||||
}
|
||||
|
||||
@ -72,7 +82,7 @@ type Config struct {
|
||||
LocalAddrs []netaddr.IPPrefix
|
||||
Routes []netaddr.IPPrefix // routes to point into the Tailscale interface
|
||||
|
||||
DNSConfig
|
||||
DNS dns.Config
|
||||
|
||||
// Linux-only things below, ignored on other platforms.
|
||||
|
||||
|
@ -14,10 +14,6 @@ func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Devic
|
||||
return newUserspaceBSDRouter(logf, wgdev, tundev)
|
||||
}
|
||||
|
||||
// TODO(dmytro): the following 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.
|
||||
|
||||
func upDNS(DNSConfig, string) error { return nil }
|
||||
func downDNS(string) error { return nil }
|
||||
func cleanup(logger.Logf, string) {}
|
||||
func cleanup(logger.Logf, string) {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
@ -16,6 +16,6 @@ func newUserspaceRouter(logf logger.Logf, tunname string, dev *device.Device, tu
|
||||
return NewFakeRouter(logf, tunname, dev, tuntap, netChanged)
|
||||
}
|
||||
|
||||
func cleanup() error {
|
||||
return nil
|
||||
func cleanup(logf logger.Logf, interfaceName string) {
|
||||
// Nothing to do here.
|
||||
}
|
||||
|
@ -5,8 +5,6 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tailscale/wireguard-go/device"
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"tailscale.com/types/logger"
|
||||
@ -21,42 +19,7 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (
|
||||
return newUserspaceBSDRouter(logf, nil, tundev)
|
||||
}
|
||||
|
||||
func upDNS(config DNSConfig, interfaceName string) error {
|
||||
if len(config.Nameservers) == 0 {
|
||||
return downDNS(interfaceName)
|
||||
}
|
||||
|
||||
if resolvconfIsActive() {
|
||||
if err := dnsResolvconfUp(config, interfaceName); err != nil {
|
||||
return fmt.Errorf("resolvconf: %w")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := dnsDirectUp(config); err != nil {
|
||||
return fmt.Errorf("direct: %w")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func downDNS(interfaceName string) error {
|
||||
if resolvconfIsActive() {
|
||||
if err := dnsResolvconfDown(interfaceName); err != nil {
|
||||
return fmt.Errorf("resolvconf: %w")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := dnsDirectDown(); err != nil {
|
||||
return fmt.Errorf("direct: %w")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanup(logf logger.Logf, interfaceName string) {
|
||||
if err := downDNS(interfaceName); err != nil {
|
||||
logf("dns down: %v", err)
|
||||
}
|
||||
// If the interface was left behind, ifconfig down will not remove it.
|
||||
// In fact, this will leave a system in a tainted state where starting tailscaled
|
||||
// will result in "interface tailscale0 already exists"
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine/router/dns"
|
||||
)
|
||||
|
||||
// The following bits are added to packet marks for Tailscale use.
|
||||
@ -84,8 +85,7 @@ type linuxRouter struct {
|
||||
snatSubnetRoutes bool
|
||||
netfilterMode NetfilterMode
|
||||
|
||||
dnsMode dnsMode
|
||||
dnsConfig DNSConfig
|
||||
dns *dns.Manager
|
||||
|
||||
ipt4 netfilterRunner
|
||||
cmd commandRunner
|
||||
@ -109,6 +109,11 @@ func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, netfilter netf
|
||||
_, err := exec.Command("ip", "rule").Output()
|
||||
ipRuleAvailable := (err == nil)
|
||||
|
||||
mconfig := dns.ManagerConfig{
|
||||
Logf: logf,
|
||||
InterfaceName: tunname,
|
||||
}
|
||||
|
||||
return &linuxRouter{
|
||||
logf: logf,
|
||||
ipRuleAvailable: ipRuleAvailable,
|
||||
@ -116,6 +121,7 @@ func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, netfilter netf
|
||||
netfilterMode: NetfilterOff,
|
||||
ipt4: netfilter,
|
||||
cmd: cmd,
|
||||
dns: dns.NewManager(mconfig),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -133,26 +139,12 @@ func (r *linuxRouter) Up() error {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
// TODO(dmytro): enable resolved when per-domain resolvers are desired.
|
||||
case resolvedIsActive():
|
||||
r.dnsMode = dnsDirect
|
||||
// r.dnsMode = dnsResolved
|
||||
case nmIsActive():
|
||||
r.dnsMode = dnsNetworkManager
|
||||
case resolvconfIsActive():
|
||||
r.dnsMode = dnsResolvconf
|
||||
default:
|
||||
r.dnsMode = dnsDirect
|
||||
}
|
||||
r.logf("dns mode: %v", r.dnsMode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *linuxRouter) Close() error {
|
||||
if err := r.downDNS(); err != nil {
|
||||
return err
|
||||
if err := r.dns.Down(); err != nil {
|
||||
return fmt.Errorf("dns down: %v", err)
|
||||
}
|
||||
if err := r.downInterface(); err != nil {
|
||||
return err
|
||||
@ -206,12 +198,8 @@ func (r *linuxRouter) Set(cfg *Config) error {
|
||||
}
|
||||
r.snatSubnetRoutes = cfg.SNATSubnetRoutes
|
||||
|
||||
if !r.dnsConfig.EquivalentTo(cfg.DNSConfig) {
|
||||
if err := r.upDNS(cfg.DNSConfig); err != nil {
|
||||
r.logf("dns up: %v", err)
|
||||
} else {
|
||||
r.dnsConfig = cfg.DNSConfig
|
||||
}
|
||||
if err := r.dns.Set(cfg.DNS); err != nil {
|
||||
return fmt.Errorf("dns set: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -855,68 +843,6 @@ func normalizeCIDR(cidr netaddr.IPPrefix) string {
|
||||
return fmt.Sprintf("%s/%d", nip, cidr.Bits)
|
||||
}
|
||||
|
||||
// upDNS updates the system DNS configuration to the given one.
|
||||
func (r *linuxRouter) upDNS(config DNSConfig) error {
|
||||
if len(config.Nameservers) == 0 {
|
||||
return r.downDNS()
|
||||
}
|
||||
|
||||
switch r.dnsMode {
|
||||
case dnsResolved:
|
||||
if err := dnsResolvedUp(config); err != nil {
|
||||
return fmt.Errorf("resolved: %w", err)
|
||||
}
|
||||
case dnsResolvconf:
|
||||
if err := dnsResolvconfUp(config, r.tunname); err != nil {
|
||||
return fmt.Errorf("resolvconf: %w", err)
|
||||
}
|
||||
case dnsNetworkManager:
|
||||
if err := dnsNetworkManagerUp(config, r.tunname); err != nil {
|
||||
return fmt.Errorf("network manager: %w", err)
|
||||
}
|
||||
case dnsDirect:
|
||||
if err := dnsDirectUp(config); err != nil {
|
||||
return fmt.Errorf("direct: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// downDNS restores system DNS configuration to its state before upDNS.
|
||||
// It is idempotent (in particular, it does nothing if upDNS was never run).
|
||||
func (r *linuxRouter) downDNS() error {
|
||||
switch r.dnsMode {
|
||||
case dnsResolved:
|
||||
if err := dnsResolvedDown(); err != nil {
|
||||
return fmt.Errorf("resolved: %w", err)
|
||||
}
|
||||
case dnsResolvconf:
|
||||
if err := dnsResolvconfDown(r.tunname); err != nil {
|
||||
return fmt.Errorf("resolvconf: %w", err)
|
||||
}
|
||||
case dnsNetworkManager:
|
||||
if err := dnsNetworkManagerDown(r.tunname); err != nil {
|
||||
return fmt.Errorf("network manager: %w", err)
|
||||
}
|
||||
case dnsDirect:
|
||||
if err := dnsDirectDown(); err != nil {
|
||||
return fmt.Errorf("direct: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanup(logf logger.Logf, interfaceName string) {
|
||||
// Note: we need not do anything for dnsResolved,
|
||||
// as its settings are interface-bound and get cleaned up for us.
|
||||
switch {
|
||||
case resolvconfIsActive():
|
||||
if err := dnsResolvconfDown(interfaceName); err != nil {
|
||||
logf("down down: resolvconf: %v", err)
|
||||
}
|
||||
default:
|
||||
if err := dnsDirectDown(); err != nil {
|
||||
logf("dns down: direct: %v", err)
|
||||
}
|
||||
}
|
||||
// TODO(dmytro): clean up iptables.
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine/router/dns"
|
||||
)
|
||||
|
||||
// For now this router only supports the WireGuard userspace implementation.
|
||||
@ -26,7 +27,7 @@ type openbsdRouter struct {
|
||||
local netaddr.IPPrefix
|
||||
routes map[netaddr.IPPrefix]struct{}
|
||||
|
||||
dnsConfig DNSConfig
|
||||
dns *dns.Manager
|
||||
}
|
||||
|
||||
func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
|
||||
@ -34,9 +35,16 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mconfig := dns.ManagerConfig{
|
||||
Logf: logf,
|
||||
InterfaceName: tunname,
|
||||
}
|
||||
|
||||
return &openbsdRouter{
|
||||
logf: logf,
|
||||
tunname: tunname,
|
||||
dns: dns.NewManager(mconfig),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -62,7 +70,10 @@ func (r *openbsdRouter) Set(cfg *Config) error {
|
||||
}
|
||||
|
||||
// TODO: support configuring multiple local addrs on interface.
|
||||
if len(cfg.LocalAddrs) != 1 {
|
||||
if len(cfg.LocalAddrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(cfg.LocalAddrs) > 1 {
|
||||
return errors.New("freebsd doesn't support setting multiple local addrs yet")
|
||||
}
|
||||
localAddr := cfg.LocalAddrs[0]
|
||||
@ -155,26 +166,22 @@ func (r *openbsdRouter) Set(cfg *Config) error {
|
||||
r.local = localAddr
|
||||
r.routes = newRoutes
|
||||
|
||||
if !r.dnsConfig.EquivalentTo(cfg.DNSConfig) {
|
||||
if err := dnsDirectUp(cfg.DNSConfig); err != nil {
|
||||
errq = fmt.Errorf("dns up: direct: %v", err)
|
||||
} else {
|
||||
r.dnsConfig = cfg.DNSConfig
|
||||
}
|
||||
if err := r.dns.Set(cfg.DNS); err != nil {
|
||||
errq = fmt.Errorf("dns set: %v", err)
|
||||
}
|
||||
|
||||
return errq
|
||||
}
|
||||
|
||||
func (r *openbsdRouter) Close() error {
|
||||
if err := r.dns.Down(); err != nil {
|
||||
return fmt.Errorf("dns down: %v", err)
|
||||
}
|
||||
cleanup(r.logf, r.tunname)
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanup(logf logger.Logf, interfaceName string) {
|
||||
if err := dnsDirectDown(); err != nil {
|
||||
logf("dns down: direct: %v", err)
|
||||
}
|
||||
out, err := cmd("ifconfig", interfaceName, "down").CombinedOutput()
|
||||
if err != nil {
|
||||
logf("ifconfig down: %v\n%s", err, out)
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine/router/dns"
|
||||
)
|
||||
|
||||
type userspaceBSDRouter struct {
|
||||
@ -24,7 +25,7 @@ type userspaceBSDRouter struct {
|
||||
local netaddr.IPPrefix
|
||||
routes map[netaddr.IPPrefix]struct{}
|
||||
|
||||
dnsConfig DNSConfig
|
||||
dns *dns.Manager
|
||||
}
|
||||
|
||||
func newUserspaceBSDRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
|
||||
@ -32,9 +33,16 @@ func newUserspaceBSDRouter(logf logger.Logf, _ *device.Device, tundev tun.Device
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mconfig := dns.ManagerConfig{
|
||||
Logf: logf,
|
||||
InterfaceName: tunname,
|
||||
}
|
||||
|
||||
return &userspaceBSDRouter{
|
||||
logf: logf,
|
||||
tunname: tunname,
|
||||
dns: dns.NewManager(mconfig),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -141,19 +149,15 @@ func (r *userspaceBSDRouter) Set(cfg *Config) error {
|
||||
r.local = localAddr
|
||||
r.routes = newRoutes
|
||||
|
||||
if !r.dnsConfig.EquivalentTo(cfg.DNSConfig) {
|
||||
if err := upDNS(cfg.DNSConfig, r.tunname); err != nil {
|
||||
errq = fmt.Errorf("dns up: %v", err)
|
||||
} else {
|
||||
r.dnsConfig = cfg.DNSConfig
|
||||
}
|
||||
if err := r.dns.Set(cfg.DNS); err != nil {
|
||||
errq = fmt.Errorf("dns set: %v", err)
|
||||
}
|
||||
|
||||
return errq
|
||||
}
|
||||
|
||||
func (r *userspaceBSDRouter) Close() error {
|
||||
if err := downDNS(r.tunname); err != nil {
|
||||
if err := r.dns.Down(); err != nil {
|
||||
r.logf("dns down: %v", err)
|
||||
}
|
||||
// No interface cleanup is necessary during normal shutdown.
|
||||
|
@ -5,12 +5,14 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
winipcfg "github.com/tailscale/winipcfg-go"
|
||||
"github.com/tailscale/wireguard-go/device"
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine/router/dns"
|
||||
)
|
||||
|
||||
type winRouter struct {
|
||||
@ -19,6 +21,7 @@ type winRouter struct {
|
||||
nativeTun *tun.NativeTun
|
||||
wgdev *device.Device
|
||||
routeChangeCallback *winipcfg.RouteChangeCallback
|
||||
dns *dns.Manager
|
||||
}
|
||||
|
||||
func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) {
|
||||
@ -26,11 +29,20 @@ func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Devic
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nativeTun := tundev.(*tun.NativeTun)
|
||||
guid := nativeTun.GUID().String()
|
||||
mconfig := dns.ManagerConfig{
|
||||
Logf: logf,
|
||||
InterfaceName: guid,
|
||||
}
|
||||
|
||||
return &winRouter{
|
||||
logf: logf,
|
||||
wgdev: wgdev,
|
||||
tunname: tunname,
|
||||
nativeTun: tundev.(*tun.NativeTun),
|
||||
nativeTun: nativeTun,
|
||||
dns: dns.NewManager(mconfig),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -55,10 +67,18 @@ func (r *winRouter) Set(cfg *Config) error {
|
||||
r.logf("ConfigureInterface: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.dns.Set(cfg.DNS); err != nil {
|
||||
return fmt.Errorf("dns set: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *winRouter) Close() error {
|
||||
if err := r.dns.Down(); err != nil {
|
||||
return fmt.Errorf("dns down: %w", err)
|
||||
}
|
||||
if r.routeChangeCallback != nil {
|
||||
r.routeChangeCallback.Unregister()
|
||||
}
|
||||
@ -66,5 +86,5 @@ func (r *winRouter) Close() error {
|
||||
}
|
||||
|
||||
func cleanup(logf logger.Logf, interfaceName string) {
|
||||
// DNS is interface-bound, so nothing to do here.
|
||||
// Nothing to do here.
|
||||
}
|
||||
|
Reference in New Issue
Block a user