mirror of https://github.com/tailscale/tailscale/
all: dns refactor, add Proxied and PerDomain flags from control (#615)
Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>reviewable/pr630/r1
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 "???"
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
@ -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})
|
||||||
|
}
|
@ -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{}
|
||||||
|
}
|
Loading…
Reference in New Issue