wgengine/router: dns: unify on *BSD, multimode on Linux, Magic DNS (#536)
Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>main
parent
6e8f0860af
commit
30bbbe9467
@ -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 router |
||||
|
||||
import ( |
||||
"time" |
||||
|
||||
"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 |
||||
} |
||||
|
||||
// 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 |
||||
|
||||
// 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,151 @@ |
||||
// 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
|
||||
|
||||
package router |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"os" |
||||
"strings" |
||||
|
||||
"inet.af/netaddr" |
||||
"tailscale.com/atomicfile" |
||||
) |
||||
|
||||
const ( |
||||
tsConf = "/etc/resolv.tailscale.conf" |
||||
backupConf = "/etc/resolv.pre-tailscale-backup.conf" |
||||
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) { |
||||
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 { |
||||
io.WriteString(w, "nameserver ") |
||||
io.WriteString(w, ns.String()) |
||||
io.WriteString(w, "\n") |
||||
} |
||||
if len(domains) > 0 { |
||||
io.WriteString(w, "search") |
||||
for _, domain := range domains { |
||||
io.WriteString(w, " ") |
||||
io.WriteString(w, domain) |
||||
} |
||||
io.WriteString(w, "\n") |
||||
} |
||||
} |
||||
|
||||
// dnsReadConfig reads DNS configuration from /etc/resolv.conf.
|
||||
func dnsReadConfig() (DNSConfig, error) { |
||||
var config DNSConfig |
||||
|
||||
f, err := os.Open("/etc/resolv.conf") |
||||
if err != nil { |
||||
return config, err |
||||
} |
||||
|
||||
scanner := bufio.NewScanner(f) |
||||
for scanner.Scan() { |
||||
line := strings.TrimSpace(scanner.Text()) |
||||
|
||||
if strings.HasPrefix(line, "nameserver") { |
||||
nameserver := strings.TrimPrefix(line, "nameserver") |
||||
nameserver = strings.TrimSpace(nameserver) |
||||
ip, err := netaddr.ParseIP(nameserver) |
||||
if err != nil { |
||||
return config, err |
||||
} |
||||
config.Nameservers = append(config.Nameservers, ip) |
||||
continue |
||||
} |
||||
|
||||
if strings.HasPrefix(line, "search") { |
||||
domain := strings.TrimPrefix(line, "search") |
||||
domain = strings.TrimSpace(domain) |
||||
config.Domains = append(config.Domains, domain) |
||||
continue |
||||
} |
||||
} |
||||
|
||||
return config, nil |
||||
} |
||||
|
||||
// dnsDirectUp 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 { |
||||
// Write the tsConf file.
|
||||
buf := new(bytes.Buffer) |
||||
dnsWriteConfig(buf, config.Nameservers, config.Domains) |
||||
if err := atomicfile.WriteFile(tsConf, buf.Bytes(), 0644); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if linkPath, err := os.Readlink(resolvConf); err != nil { |
||||
// Remove any old backup that may exist.
|
||||
os.Remove(backupConf) |
||||
|
||||
// Backup the existing /etc/resolv.conf file.
|
||||
contents, err := ioutil.ReadFile(resolvConf) |
||||
// If the original did not exist, still back up an empty file.
|
||||
// The presence of a backup file is the way we know that Up ran.
|
||||
if err != nil && !os.IsNotExist(err) { |
||||
return err |
||||
} |
||||
if err := atomicfile.WriteFile(backupConf, contents, 0644); err != nil { |
||||
return err |
||||
} |
||||
} else if linkPath != tsConf { |
||||
// Backup the existing symlink.
|
||||
os.Remove(backupConf) |
||||
if err := os.Symlink(linkPath, backupConf); err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
// Nothing to do, resolvConf already points to tsConf.
|
||||
return nil |
||||
} |
||||
|
||||
os.Remove(resolvConf) |
||||
if err := os.Symlink(tsConf, resolvConf); err != nil { |
||||
return err |
||||
} |
||||
|
||||
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 { |
||||
if _, err := os.Stat(backupConf); err != nil { |
||||
// If the backup file does not exist, then Up never ran successfully.
|
||||
if os.IsNotExist(err) { |
||||
return nil |
||||
} |
||||
return err |
||||
} |
||||
|
||||
if ln, err := os.Readlink(resolvConf); err != nil { |
||||
return err |
||||
} else if ln != tsConf { |
||||
return fmt.Errorf("resolv.conf is not a symlink to %s", tsConf) |
||||
} |
||||
if err := os.Rename(backupConf, resolvConf); err != nil { |
||||
return err |
||||
} |
||||
os.Remove(tsConf) |
||||
return nil |
||||
} |
||||
@ -0,0 +1,209 @@ |
||||
// 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
|
||||
|
||||
package router |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"context" |
||||
"encoding/binary" |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
|
||||
"github.com/godbus/dbus/v5" |
||||
) |
||||
|
||||
type nmSettings map[string]map[string]dbus.Variant |
||||
|
||||
// nmIsActive determines if NetworkManager is currently managing system DNS settings.
|
||||
func nmIsActive() bool { |
||||
// This is somewhat tricky because NetworkManager supports a number
|
||||
// of DNS configuration modes. In all cases, we expect it to be installed
|
||||
// and /etc/resolv.conf to contain a mention of NetworkManager in the comments.
|
||||
_, err := exec.LookPath("NetworkManager") |
||||
if err != nil { |
||||
return false |
||||
} |
||||
|
||||
f, err := os.Open("/etc/resolv.conf") |
||||
if err != nil { |
||||
return false |
||||
} |
||||
defer f.Close() |
||||
|
||||
scanner := bufio.NewScanner(f) |
||||
for scanner.Scan() { |
||||
line := scanner.Bytes() |
||||
// Look for the word "NetworkManager" until comments end.
|
||||
if len(line) > 0 && line[0] != '#' { |
||||
return false |
||||
} |
||||
if bytes.Contains(line, []byte("NetworkManager")) { |
||||
return true |
||||
} |
||||
} |
||||
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) |
||||
defer cancel() |
||||
|
||||
conn, err := dbus.SystemBus() |
||||
if err != nil { |
||||
return fmt.Errorf("connecting to system bus: %w", err) |
||||
} |
||||
defer conn.Close() |
||||
|
||||
// This is how we get at the DNS settings:
|
||||
// org.freedesktop.NetworkManager
|
||||
// ⇩
|
||||
// org.freedesktop.NetworkManager.Device
|
||||
// (describes a network interface)
|
||||
// ⇩
|
||||
// org.freedesktop.NetworkManager.Connection.Active
|
||||
// (active instance of a connection initialized from settings)
|
||||
// ⇩
|
||||
// org.freedesktop.NetworkManager.Connection
|
||||
// (connection settings)
|
||||
// contains {dns, dns-priority, dns-search}
|
||||
//
|
||||
// Ref: https://developer.gnome.org/NetworkManager/stable/settings-ipv4.html.
|
||||
|
||||
nm := conn.Object( |
||||
"org.freedesktop.NetworkManager", |
||||
dbus.ObjectPath("/org/freedesktop/NetworkManager"), |
||||
) |
||||
|
||||
var devicePath dbus.ObjectPath |
||||
err = nm.CallWithContext( |
||||
ctx, "org.freedesktop.NetworkManager.GetDeviceByIpIface", 0, |
||||
interfaceName, |
||||
).Store(&devicePath) |
||||
if err != nil { |
||||
return fmt.Errorf("GetDeviceByIpIface: %w", err) |
||||
} |
||||
device := conn.Object("org.freedesktop.NetworkManager", devicePath) |
||||
|
||||
var activeConnPath dbus.ObjectPath |
||||
err = device.CallWithContext( |
||||
ctx, "org.freedesktop.DBus.Properties.Get", 0, |
||||
"org.freedesktop.NetworkManager.Device", "ActiveConnection", |
||||
).Store(&activeConnPath) |
||||
if err != nil { |
||||
return fmt.Errorf("getting ActiveConnection: %w", err) |
||||
} |
||||
activeConn := conn.Object("org.freedesktop.NetworkManager", activeConnPath) |
||||
|
||||
var connPath dbus.ObjectPath |
||||
err = activeConn.CallWithContext( |
||||
ctx, "org.freedesktop.DBus.Properties.Get", 0, |
||||
"org.freedesktop.NetworkManager.Connection.Active", "Connection", |
||||
).Store(&connPath) |
||||
if err != nil { |
||||
return fmt.Errorf("getting Connection: %w", err) |
||||
} |
||||
connection := conn.Object("org.freedesktop.NetworkManager", connPath) |
||||
|
||||
// Note: strictly speaking, the following is not safe.
|
||||
//
|
||||
// It appears that the way to update connection settings
|
||||
// in NetworkManager is to get an entire connection settings object,
|
||||
// modify the fields we are interested in, then submit the modified object.
|
||||
//
|
||||
// This is unfortunate: if the network state changes in the meantime
|
||||
// (most relevantly to us, if routes change), we will overwrite those changes.
|
||||
//
|
||||
// That said, fortunately, this should have no real effect, as Tailscale routes
|
||||
// do not seem to show up in NetworkManager at all,
|
||||
// so they are presumably immune from being tampered with.
|
||||
|
||||
var settings nmSettings |
||||
err = connection.CallWithContext( |
||||
ctx, "org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0, |
||||
).Store(&settings) |
||||
if err != nil { |
||||
return fmt.Errorf("getting Settings: %w", err) |
||||
} |
||||
|
||||
// Frustratingly, NetworkManager represents IPv4 addresses as uint32s,
|
||||
// although IPv6 addresses are represented as byte arrays.
|
||||
// Perform the conversion here.
|
||||
var ( |
||||
dnsv4 []uint32 |
||||
dnsv6 [][]byte |
||||
) |
||||
for _, ip := range config.Nameservers { |
||||
b := ip.As16() |
||||
if ip.Is4() { |
||||
dnsv4 = append(dnsv4, binary.BigEndian.Uint32(b[12:])) |
||||
} else { |
||||
dnsv6 = append(dnsv6, b[:]) |
||||
} |
||||
} |
||||
|
||||
ipv4Map := settings["ipv4"] |
||||
ipv4Map["dns"] = dbus.MakeVariant(dnsv4) |
||||
ipv4Map["dns-search"] = dbus.MakeVariant(config.Domains) |
||||
// dns-priority = -1 ensures that we have priority
|
||||
// over other interfaces, except those exploiting this same trick.
|
||||
// Ref: https://bugs.launchpad.net/ubuntu/+source/network-manager/+bug/1211110/comments/92.
|
||||
ipv4Map["dns-priority"] = dbus.MakeVariant(-1) |
||||
// In principle, we should not need set this to true,
|
||||
// as our interface does not configure any automatic DNS settings (presumably via DHCP).
|
||||
// All the same, better to be safe.
|
||||
ipv4Map["ignore-auto-dns"] = dbus.MakeVariant(true) |
||||
|
||||
ipv6Map := settings["ipv6"] |
||||
// This is a hack.
|
||||
// Methods "disabled", "ignore", "link-local" (IPv6 default) prevent us from setting DNS.
|
||||
// It seems that our only recourse is "manual" or "auto".
|
||||
// "manual" requires addresses, so we use "auto", which will assign us a random IPv6 /64.
|
||||
ipv6Map["method"] = dbus.MakeVariant("auto") |
||||
// Our IPv6 config is a fake, so it should never become the default route.
|
||||
ipv6Map["never-default"] = dbus.MakeVariant(true) |
||||
// Moreover, we should ignore all autoconfigured routes (hopefully none), as they are bogus.
|
||||
ipv6Map["ignore-auto-routes"] = dbus.MakeVariant(true) |
||||
|
||||
// Finally, set the actual DNS config.
|
||||
ipv6Map["dns"] = dbus.MakeVariant(dnsv6) |
||||
ipv6Map["dns-search"] = dbus.MakeVariant(config.Domains) |
||||
ipv6Map["dns-priority"] = dbus.MakeVariant(-1) |
||||
ipv6Map["ignore-auto-dns"] = dbus.MakeVariant(true) |
||||
|
||||
// deprecatedProperties are the properties in interface settings
|
||||
// that are deprecated by NetworkManager.
|
||||
//
|
||||
// In practice, this means that they are returned for reading,
|
||||
// but submitting a settings object with them present fails
|
||||
// with hard-to-diagnose errors. They must be removed.
|
||||
deprecatedProperties := []string{ |
||||
"addresses", "routes", |
||||
} |
||||
|
||||
for _, property := range deprecatedProperties { |
||||
delete(ipv4Map, property) |
||||
delete(ipv6Map, property) |
||||
} |
||||
|
||||
err = connection.CallWithContext( |
||||
ctx, "org.freedesktop.NetworkManager.Settings.Connection.UpdateUnsaved", 0, settings, |
||||
).Store() |
||||
if err != nil { |
||||
return fmt.Errorf("setting Settings: %w", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// dnsNetworkManagerDown undoes the changes made by dnsNetworkManagerUp.
|
||||
func dnsNetworkManagerDown(interfaceName string) error { |
||||
return dnsNetworkManagerUp(DNSConfig{Nameservers: nil, Domains: nil}, interfaceName) |
||||
} |
||||
@ -0,0 +1,84 @@ |
||||
// 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
|
||||
|
||||
package router |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"fmt" |
||||
"os" |
||||
"os/exec" |
||||
) |
||||
|
||||
// resolvconfIsActive indicates whether the system appears to be using resolvconf.
|
||||
// If this is true, then dnsManualUp should be avoided:
|
||||
// resolvconf has exclusive ownership of /etc/resolv.conf.
|
||||
func resolvconfIsActive() 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.
|
||||
// Such a shim may not behave as expected: in particular, systemd-resolved
|
||||
// does not seem to respect the exclusive mode -x, saying:
|
||||
// -x Send DNS traffic preferably over this interface
|
||||
// whereas e.g. openresolv sends DNS traffix _exclusively_ over that interface,
|
||||
// or not at all (in case of another exclusive-mode request later in time).
|
||||
//
|
||||
// Moreover, resolvconf may be installed but unused, in which case we should
|
||||
// not use it either, lest we clobber existing configuration.
|
||||
//
|
||||
// To handle all the above correctly, we scan the comments in /etc/resolv.conf
|
||||
// to ensure that it was generated by a resolvconf implementation.
|
||||
_, err := exec.LookPath("resolvconf") |
||||
if err != nil { |
||||
return false |
||||
} |
||||
|
||||
f, err := os.Open("/etc/resolv.conf") |
||||
if err != nil { |
||||
return false |
||||
} |
||||
defer f.Close() |
||||
|
||||
scanner := bufio.NewScanner(f) |
||||
for scanner.Scan() { |
||||
line := scanner.Bytes() |
||||
// Look for the word "resolvconf" until comments end.
|
||||
if len(line) > 0 && line[0] != '#' { |
||||
return false |
||||
} |
||||
if bytes.Contains(line, []byte("resolvconf")) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// dnsResolvconfUp invokes the resolvconf binary to associate
|
||||
// the given DNS configuration the Tailscale interface.
|
||||
func dnsResolvconfUp(config DNSConfig, interfaceName string) error { |
||||
stdin := new(bytes.Buffer) |
||||
dnsWriteConfig(stdin, config.Nameservers, config.Domains) // dns_direct.go
|
||||
|
||||
cmd := exec.Command("resolvconf", "-m", "0", "-x", "-a", interfaceName+".inet") |
||||
cmd.Stdin = stdin |
||||
out, err := cmd.CombinedOutput() |
||||
if err != nil { |
||||
return fmt.Errorf("running %s: %s", cmd, out) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// dnsResolvconfDown undoes the action of dnsResolvconfUp.
|
||||
func dnsResolvconfDown(interfaceName string) error { |
||||
cmd := exec.Command("resolvconf", "-f", "-d", interfaceName+".inet") |
||||
out, err := cmd.CombinedOutput() |
||||
if err != nil { |
||||
return fmt.Errorf("running %s: %s", cmd, out) |
||||
} |
||||
return nil |
||||
} |
||||
@ -0,0 +1,177 @@ |
||||
// 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
|
||||
|
||||
package router |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"fmt" |
||||
"os/exec" |
||||
|
||||
"github.com/godbus/dbus/v5" |
||||
"golang.org/x/sys/unix" |
||||
"inet.af/netaddr" |
||||
"tailscale.com/net/interfaces" |
||||
) |
||||
|
||||
// resolvedListenAddr is the listen address of the resolved stub resolver.
|
||||
//
|
||||
// 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.
|
||||
// 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}, ...})
|
||||
// which includes at least the interface with the default route, i.e. not us.
|
||||
// This does not work for us: there is a possibility of getting NXDOMAIN
|
||||
// from the other nameservers before we are asked or get a chance to respond.
|
||||
// We consider this case as lacking resolved support and fall through to dnsDirect.
|
||||
//
|
||||
// While it may seem that we need to read a config option to get at this,
|
||||
// this address is, in fact, hard-coded into resolved.
|
||||
var resolvedListenAddr = netaddr.IPv4(127, 0, 0, 53) |
||||
|
||||
var errNotReady = errors.New("interface not ready") |
||||
|
||||
type resolvedLinkNameserver struct { |
||||
Family int32 |
||||
Address []byte |
||||
} |
||||
|
||||
type resolvedLinkDomain struct { |
||||
Domain string |
||||
RoutingOnly bool |
||||
} |
||||
|
||||
// resolvedIsActive determines if resolved is currently managing system DNS settings.
|
||||
func resolvedIsActive() 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 := dnsReadConfig() |
||||
if err != nil { |
||||
return false |
||||
} |
||||
|
||||
// The sole nameserver must be the systemd-resolved stub.
|
||||
if len(config.Nameservers) == 1 && config.Nameservers[0] == resolvedListenAddr { |
||||
return true |
||||
} |
||||
|
||||
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) |
||||
defer cancel() |
||||
|
||||
conn, err := dbus.SystemBus() |
||||
if err != nil { |
||||
return fmt.Errorf("connecting to system bus: %w", err) |
||||
} |
||||
defer conn.Close() |
||||
|
||||
resolved := conn.Object( |
||||
"org.freedesktop.resolve1", |
||||
dbus.ObjectPath("/org/freedesktop/resolve1"), |
||||
) |
||||
|
||||
_, iface, err := interfaces.Tailscale() |
||||
if err != nil { |
||||
return fmt.Errorf("getting interface index: %w", err) |
||||
} |
||||
if iface == nil { |
||||
return errNotReady |
||||
} |
||||
|
||||
var linkNameservers = make([]resolvedLinkNameserver, len(config.Nameservers)) |
||||
for i, server := range config.Nameservers { |
||||
ip := server.As16() |
||||
if server.Is4() { |
||||
linkNameservers[i] = resolvedLinkNameserver{ |
||||
Family: unix.AF_INET, |
||||
Address: ip[12:], |
||||
} |
||||
} else { |
||||
linkNameservers[i] = resolvedLinkNameserver{ |
||||
Family: unix.AF_INET6, |
||||
Address: ip[:], |
||||
} |
||||
} |
||||
} |
||||
|
||||
err = resolved.CallWithContext( |
||||
ctx, "org.freedesktop.resolve1.Manager.SetLinkDNS", 0, |
||||
iface.Index, linkNameservers, |
||||
).Store() |
||||
if err != nil { |
||||
return fmt.Errorf("SetLinkDNS: %w", err) |
||||
} |
||||
|
||||
var linkDomains = make([]resolvedLinkDomain, len(config.Domains)) |
||||
for i, domain := range config.Domains { |
||||
linkDomains[i] = resolvedLinkDomain{ |
||||
Domain: domain, |
||||
RoutingOnly: false, |
||||
} |
||||
} |
||||
|
||||
err = resolved.CallWithContext( |
||||
ctx, "org.freedesktop.resolve1.Manager.SetLinkDomains", 0, |
||||
iface.Index, linkDomains, |
||||
).Store() |
||||
if err != nil { |
||||
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) |
||||
defer cancel() |
||||
|
||||
conn, err := dbus.SystemBus() |
||||
if err != nil { |
||||
return fmt.Errorf("connecting to system bus: %w", err) |
||||
} |
||||
|
||||
resolved := conn.Object( |
||||
"org.freedesktop.resolve1", |
||||
dbus.ObjectPath("/org/freedesktop/resolve1"), |
||||
) |
||||
|
||||
_, iface, err := interfaces.Tailscale() |
||||
if err != nil { |
||||
return fmt.Errorf("getting interface index: %w", err) |
||||
} |
||||
if iface == nil { |
||||
return errNotReady |
||||
} |
||||
|
||||
err = resolved.CallWithContext( |
||||
ctx, "org.freedesktop.resolve1.Manager.RevertLink", 0, |
||||
iface.Index, |
||||
).Store() |
||||
if err != nil { |
||||
return fmt.Errorf("RevertLink: %w", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
Loading…
Reference in new issue