wgengine/router: dns: unify on *BSD, multimode on Linux, Magic DNS (#536)
Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
This commit is contained in:
committed by
GitHub
parent
6e8f0860af
commit
30bbbe9467
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user