all: dns refactor, add Proxied and PerDomain flags from control (#615)
Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>main
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