wgengine/router: dns: unify on *BSD, multimode on Linux, Magic DNS (#536)
Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>reviewable/pr553/r1
parent
6e8f0860af
commit
30bbbe9467
1
go.mod
1
go.mod
|
@ -9,6 +9,7 @@ require (
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||||
github.com/gliderlabs/ssh v0.2.2
|
github.com/gliderlabs/ssh v0.2.2
|
||||||
github.com/go-ole/go-ole v1.2.4
|
github.com/go-ole/go-ole v1.2.4
|
||||||
|
github.com/godbus/dbus/v5 v5.0.3
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
|
||||||
github.com/google/go-cmp v0.4.0
|
github.com/google/go-cmp v0.4.0
|
||||||
github.com/goreleaser/nfpm v1.1.10
|
github.com/goreleaser/nfpm v1.1.10
|
||||||
|
|
3
go.sum
3
go.sum
|
@ -30,6 +30,9 @@ github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||||
|
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
|
|
|
@ -50,7 +50,10 @@ func getVal() []interface{} {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&router.Config{
|
&router.Config{
|
||||||
DNS: []netaddr.IP{netaddr.IPv4(8, 8, 8, 8)},
|
DNSConfig: router.DNSConfig{
|
||||||
|
Nameservers: []netaddr.IP{netaddr.IPv4(8, 8, 8, 8)},
|
||||||
|
Domains: []string{"tailscale.net"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"key1": "val1",
|
"key1": "val1",
|
||||||
|
|
|
@ -471,7 +471,7 @@ func (b *LocalBackend) updateDNSMap(netMap *controlclient.NetworkMap) {
|
||||||
// Like PeerStatus.SimpleHostName()
|
// Like PeerStatus.SimpleHostName()
|
||||||
domain = strings.TrimSuffix(domain, ".local")
|
domain = strings.TrimSuffix(domain, ".local")
|
||||||
domain = strings.TrimSuffix(domain, ".localdomain")
|
domain = strings.TrimSuffix(domain, ".localdomain")
|
||||||
domain = domain + ".tailscale.us"
|
domain = domain + ".b.tailscale.net"
|
||||||
domainToIP[domain] = netaddr.IPFrom16(peer.Addresses[0].IP.Addr)
|
domainToIP[domain] = netaddr.IPFrom16(peer.Addresses[0].IP.Addr)
|
||||||
}
|
}
|
||||||
b.e.SetDNSMap(tsdns.NewMap(domainToIP))
|
b.e.SetDNSMap(tsdns.NewMap(domainToIP))
|
||||||
|
@ -868,11 +868,13 @@ func routerConfig(cfg *wgcfg.Config, prefs *Prefs, dnsDomains []string) *router.
|
||||||
|
|
||||||
rs := &router.Config{
|
rs := &router.Config{
|
||||||
LocalAddrs: wgCIDRToNetaddr(addrs),
|
LocalAddrs: wgCIDRToNetaddr(addrs),
|
||||||
DNS: wgIPToNetaddr(cfg.DNS),
|
|
||||||
DNSDomains: dnsDomains,
|
|
||||||
SubnetRoutes: wgCIDRToNetaddr(prefs.AdvertiseRoutes),
|
SubnetRoutes: wgCIDRToNetaddr(prefs.AdvertiseRoutes),
|
||||||
SNATSubnetRoutes: !prefs.NoSNAT,
|
SNATSubnetRoutes: !prefs.NoSNAT,
|
||||||
NetfilterMode: prefs.NetfilterMode,
|
NetfilterMode: prefs.NetfilterMode,
|
||||||
|
DNSConfig: router.DNSConfig{
|
||||||
|
Nameservers: wgIPToNetaddr(cfg.DNS),
|
||||||
|
Domains: dnsDomains,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, peer := range cfg.Peers {
|
for _, peer := range cfg.Peers {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -262,7 +262,7 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
setDNSDomains(guid, cfg.DNSDomains)
|
setDNSDomains(guid, cfg.Domains)
|
||||||
|
|
||||||
routes := []winipcfg.RouteData{}
|
routes := []winipcfg.RouteData{}
|
||||||
var firstGateway4 *net.IP
|
var firstGateway4 *net.IP
|
||||||
|
@ -359,7 +359,7 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var dnsIPs []net.IP
|
var dnsIPs []net.IP
|
||||||
for _, ip := range cfg.DNS {
|
for _, ip := range cfg.Nameservers {
|
||||||
dnsIPs = append(dnsIPs, ip.IPAddr().IP)
|
dnsIPs = append(dnsIPs, ip.IPAddr().IP)
|
||||||
}
|
}
|
||||||
err = iface.SetDNS(dnsIPs)
|
err = iface.SetDNS(dnsIPs)
|
||||||
|
|
|
@ -32,6 +32,7 @@ type Router interface {
|
||||||
// New returns a new Router for the current platform, using the
|
// New returns a new Router for the current platform, using the
|
||||||
// provided tun device.
|
// provided tun device.
|
||||||
func New(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) {
|
func New(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) {
|
||||||
|
logf = logger.WithPrefix(logf, "router: ")
|
||||||
return newUserspaceRouter(logf, wgdev, tundev)
|
return newUserspaceRouter(logf, wgdev, tundev)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ func New(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, err
|
||||||
// in case the Tailscale daemon terminated without closing the router.
|
// in case the Tailscale daemon terminated without closing the router.
|
||||||
// No other state needs to be instantiated before this runs.
|
// No other state needs to be instantiated before this runs.
|
||||||
func Cleanup(logf logger.Logf, interfaceName string) {
|
func Cleanup(logf logger.Logf, interfaceName string) {
|
||||||
// TODO(dmytro): implement this.
|
cleanup(logf, interfaceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetfilterMode is the firewall management mode to use when
|
// NetfilterMode is the firewall management mode to use when
|
||||||
|
@ -69,10 +70,10 @@ func (m NetfilterMode) String() string {
|
||||||
// the OS's network stack.
|
// the OS's network stack.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
LocalAddrs []netaddr.IPPrefix
|
LocalAddrs []netaddr.IPPrefix
|
||||||
DNS []netaddr.IP
|
|
||||||
DNSDomains []string
|
|
||||||
Routes []netaddr.IPPrefix // routes to point into the Tailscale interface
|
Routes []netaddr.IPPrefix // routes to point into the Tailscale interface
|
||||||
|
|
||||||
|
DNSConfig
|
||||||
|
|
||||||
// Linux-only things below, ignored on other platforms.
|
// Linux-only things below, ignored on other platforms.
|
||||||
|
|
||||||
SubnetRoutes []netaddr.IPPrefix // subnets being advertised to other Tailscale nodes
|
SubnetRoutes []netaddr.IPPrefix // subnets being advertised to other Tailscale nodes
|
||||||
|
|
|
@ -52,3 +52,13 @@ func (r *darwinRouter) Up() error {
|
||||||
}
|
}
|
||||||
return r.Router.Up()
|
return r.Router.Up()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func upDNS(config DNSConfig, interfaceName string) error {
|
||||||
|
// Handled by IPNExtension
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func downDNS(interfaceName string) error {
|
||||||
|
// Handled by IPNExtension
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -15,3 +15,7 @@ import (
|
||||||
func newUserspaceRouter(logf logger.Logf, tunname string, dev *device.Device, tuntap tun.Device, netChanged func()) Router {
|
func newUserspaceRouter(logf logger.Logf, tunname string, dev *device.Device, tuntap tun.Device, netChanged func()) Router {
|
||||||
return NewFakeRouter(logf, tunname, dev, tuntap, netChanged)
|
return NewFakeRouter(logf, tunname, dev, tuntap, netChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanup() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/tailscale/wireguard-go/device"
|
"github.com/tailscale/wireguard-go/device"
|
||||||
"github.com/tailscale/wireguard-go/tun"
|
"github.com/tailscale/wireguard-go/tun"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
|
@ -18,3 +20,35 @@ import (
|
||||||
func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
|
func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
|
||||||
return newUserspaceBSDRouter(logf, nil, tundev)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -5,19 +5,14 @@
|
||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/coreos/go-iptables/iptables"
|
"github.com/coreos/go-iptables/iptables"
|
||||||
"github.com/tailscale/wireguard-go/device"
|
"github.com/tailscale/wireguard-go/device"
|
||||||
"github.com/tailscale/wireguard-go/tun"
|
"github.com/tailscale/wireguard-go/tun"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/atomicfile"
|
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
)
|
)
|
||||||
|
@ -73,6 +68,9 @@ type linuxRouter struct {
|
||||||
snatSubnetRoutes bool
|
snatSubnetRoutes bool
|
||||||
netfilterMode NetfilterMode
|
netfilterMode NetfilterMode
|
||||||
|
|
||||||
|
dnsMode dnsMode
|
||||||
|
dnsConfig DNSConfig
|
||||||
|
|
||||||
ipt4 netfilterRunner
|
ipt4 netfilterRunner
|
||||||
cmd commandRunner
|
cmd commandRunner
|
||||||
}
|
}
|
||||||
|
@ -119,10 +117,27 @@ func (r *linuxRouter) Up() error {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *linuxRouter) down() error {
|
func (r *linuxRouter) Close() error {
|
||||||
|
if err := r.downDNS(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := r.downInterface(); err != nil {
|
if err := r.downInterface(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -139,20 +154,6 @@ func (r *linuxRouter) down() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *linuxRouter) Close() error {
|
|
||||||
var ret error
|
|
||||||
if ret = r.restoreResolvConf(); ret != nil {
|
|
||||||
r.logf("failed to restore system resolv.conf: %v", ret)
|
|
||||||
}
|
|
||||||
if err := r.down(); err != nil {
|
|
||||||
if ret == nil {
|
|
||||||
ret = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set implements the Router interface.
|
// Set implements the Router interface.
|
||||||
func (r *linuxRouter) Set(cfg *Config) error {
|
func (r *linuxRouter) Set(cfg *Config) error {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
|
@ -189,12 +190,14 @@ func (r *linuxRouter) Set(cfg *Config) error {
|
||||||
}
|
}
|
||||||
r.snatSubnetRoutes = cfg.SNATSubnetRoutes
|
r.snatSubnetRoutes = cfg.SNATSubnetRoutes
|
||||||
|
|
||||||
// TODO: this:
|
if !r.dnsConfig.EquivalentTo(cfg.DNSConfig) {
|
||||||
if false {
|
if err := r.upDNS(cfg.DNSConfig); err != nil {
|
||||||
if err := r.replaceResolvConf(cfg.DNS, cfg.DNSDomains); err != nil {
|
r.logf("dns up: %v", err)
|
||||||
return fmt.Errorf("replacing resolv.conf failed: %w", err)
|
} else {
|
||||||
|
r.dnsConfig = cfg.DNSConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,102 +318,6 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
tsConf = "/etc/resolv.tailscale.conf"
|
|
||||||
backupConf = "/etc/resolv.pre-tailscale-backup.conf"
|
|
||||||
resolvConf = "/etc/resolv.conf"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *linuxRouter) replaceResolvConf(servers []netaddr.IP, domains []string) error {
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return r.restoreResolvConf()
|
|
||||||
}
|
|
||||||
|
|
||||||
// First write the tsConf file.
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
fmt.Fprintf(buf, "# resolv.conf(5) file generated by tailscale\n")
|
|
||||||
fmt.Fprintf(buf, "# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN\n\n")
|
|
||||||
for _, ns := range servers {
|
|
||||||
fmt.Fprintf(buf, "nameserver %s\n", ns)
|
|
||||||
}
|
|
||||||
if len(domains) > 0 {
|
|
||||||
fmt.Fprintf(buf, "search "+strings.Join(domains, " ")+"\n")
|
|
||||||
}
|
|
||||||
f, err := ioutil.TempFile(filepath.Dir(tsConf), filepath.Base(tsConf)+".*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
if err := atomicfile.WriteFile(f.Name(), buf.Bytes(), 0644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
os.Chmod(f.Name(), 0644) // ioutil.TempFile creates the file with 0600
|
|
||||||
if err := os.Rename(f.Name(), tsConf); 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 os.IsNotExist(err) {
|
|
||||||
// No existing /etc/resolv.conf file to backup.
|
|
||||||
// Nothing to do.
|
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
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 nil
|
|
||||||
}
|
|
||||||
|
|
||||||
out, _ := exec.Command("service", "systemd-resolved", "restart").CombinedOutput()
|
|
||||||
if len(out) > 0 {
|
|
||||||
r.logf("service systemd-resolved restart: %s", out)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *linuxRouter) restoreResolvConf() error {
|
|
||||||
if _, err := os.Stat(backupConf); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil // no backup resolv.conf to restore
|
|
||||||
}
|
|
||||||
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) // best effort removal of tsConf file
|
|
||||||
out, _ := exec.Command("service", "systemd-resolved", "restart").CombinedOutput()
|
|
||||||
if len(out) > 0 {
|
|
||||||
r.logf("service systemd-resolved restart: %s", out)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// addAddress adds an IP/mask to the tunnel interface. Fails if the
|
// addAddress adds an IP/mask to the tunnel interface. Fails if the
|
||||||
// address is already assigned to the interface, or if the addition
|
// address is already assigned to the interface, or if the addition
|
||||||
// fails.
|
// fails.
|
||||||
|
@ -932,3 +839,69 @@ func normalizeCIDR(cidr netaddr.IPPrefix) string {
|
||||||
nip := ncidr.IP.Mask(ncidr.Mask)
|
nip := ncidr.IP.Mask(ncidr.Mask)
|
||||||
return fmt.Sprintf("%s/%d", nip, cidr.Bits)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,20 +5,14 @@
|
||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tailscale/wireguard-go/device"
|
"github.com/tailscale/wireguard-go/device"
|
||||||
"github.com/tailscale/wireguard-go/tun"
|
"github.com/tailscale/wireguard-go/tun"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/atomicfile"
|
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,6 +25,8 @@ type openbsdRouter struct {
|
||||||
tunname string
|
tunname string
|
||||||
local netaddr.IPPrefix
|
local netaddr.IPPrefix
|
||||||
routes map[netaddr.IPPrefix]struct{}
|
routes map[netaddr.IPPrefix]struct{}
|
||||||
|
|
||||||
|
dnsConfig DNSConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
|
func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
|
||||||
|
@ -159,112 +155,28 @@ func (r *openbsdRouter) Set(cfg *Config) error {
|
||||||
r.local = localAddr
|
r.local = localAddr
|
||||||
r.routes = newRoutes
|
r.routes = newRoutes
|
||||||
|
|
||||||
if err := r.replaceResolvConf(cfg.DNS, cfg.DNSDomains); err != nil {
|
if !r.dnsConfig.EquivalentTo(cfg.DNSConfig) {
|
||||||
errq = fmt.Errorf("replacing resolv.conf failed: %v", err)
|
if err := dnsDirectUp(cfg.DNSConfig); err != nil {
|
||||||
|
errq = fmt.Errorf("dns up: direct: %v", err)
|
||||||
|
} else {
|
||||||
|
r.dnsConfig = cfg.DNSConfig
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errq
|
return errq
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *openbsdRouter) Close() error {
|
func (r *openbsdRouter) Close() error {
|
||||||
out, err := cmd("ifconfig", r.tunname, "down").CombinedOutput()
|
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 {
|
if err != nil {
|
||||||
r.logf("running ifconfig failed: %v\n%s", err, out)
|
logf("ifconfig down: %v\n%s", err, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.restoreResolvConf(); err != nil {
|
|
||||||
r.logf("failed to restore system resolv.conf: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
tsConf = "/etc/resolv.tailscale.conf"
|
|
||||||
backupConf = "/etc/resolv.pre-tailscale-backup.conf"
|
|
||||||
resolvConf = "/etc/resolv.conf"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *openbsdRouter) replaceResolvConf(servers []netaddr.IP, domains []string) error {
|
|
||||||
if len(servers) == 0 {
|
|
||||||
return r.restoreResolvConf()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the tsConf file.
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
fmt.Fprintf(buf, "# resolv.conf(5) file generated by tailscale\n")
|
|
||||||
fmt.Fprintf(buf, "# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN\n\n")
|
|
||||||
for _, ns := range servers {
|
|
||||||
fmt.Fprintf(buf, "nameserver %s\n", ns)
|
|
||||||
}
|
|
||||||
if len(domains) > 0 {
|
|
||||||
fmt.Fprintf(buf, "search "+strings.Join(domains, " ")+"\n")
|
|
||||||
}
|
|
||||||
tf, err := ioutil.TempFile(filepath.Dir(tsConf), filepath.Base(tsConf)+".*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tempName := tf.Name()
|
|
||||||
tf.Close()
|
|
||||||
|
|
||||||
if err := atomicfile.WriteFile(tempName, buf.Bytes(), 0644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := os.Rename(tempName, tsConf); 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 os.IsNotExist(err) {
|
|
||||||
// No existing /etc/resolv.conf file to backup.
|
|
||||||
// Nothing to do.
|
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
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 nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *openbsdRouter) restoreResolvConf() error {
|
|
||||||
if _, err := os.Stat(backupConf); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil // No backup resolv.conf to restore.
|
|
||||||
}
|
|
||||||
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) // Best effort removal.
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ type userspaceBSDRouter struct {
|
||||||
tunname string
|
tunname string
|
||||||
local netaddr.IPPrefix
|
local netaddr.IPPrefix
|
||||||
routes map[netaddr.IPPrefix]struct{}
|
routes map[netaddr.IPPrefix]struct{}
|
||||||
|
|
||||||
|
dnsConfig DNSConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUserspaceBSDRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
|
func newUserspaceBSDRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
|
||||||
|
@ -36,7 +38,7 @@ func newUserspaceBSDRouter(logf logger.Logf, _ *device.Device, tundev tun.Device
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *userspaceBSDRouter) cmd(args ...string) *exec.Cmd {
|
func cmd(args ...string) *exec.Cmd {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
log.Fatalf("exec.Cmd(%#v) invalid; need argv[0]\n", args)
|
log.Fatalf("exec.Cmd(%#v) invalid; need argv[0]\n", args)
|
||||||
}
|
}
|
||||||
|
@ -45,7 +47,7 @@ func (r *userspaceBSDRouter) cmd(args ...string) *exec.Cmd {
|
||||||
|
|
||||||
func (r *userspaceBSDRouter) Up() error {
|
func (r *userspaceBSDRouter) Up() error {
|
||||||
ifup := []string{"ifconfig", r.tunname, "up"}
|
ifup := []string{"ifconfig", r.tunname, "up"}
|
||||||
if out, err := r.cmd(ifup...).CombinedOutput(); err != nil {
|
if out, err := cmd(ifup...).CombinedOutput(); err != nil {
|
||||||
r.logf("running ifconfig failed: %v\n%s", err, out)
|
r.logf("running ifconfig failed: %v\n%s", err, out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -73,7 +75,7 @@ func (r *userspaceBSDRouter) Set(cfg *Config) error {
|
||||||
if r.local != (netaddr.IPPrefix{}) {
|
if r.local != (netaddr.IPPrefix{}) {
|
||||||
addrdel := []string{"ifconfig", r.tunname,
|
addrdel := []string{"ifconfig", r.tunname,
|
||||||
"inet", r.local.String(), "-alias"}
|
"inet", r.local.String(), "-alias"}
|
||||||
out, err := r.cmd(addrdel...).CombinedOutput()
|
out, err := cmd(addrdel...).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logf("addr del failed: %v: %v\n%s", addrdel, err, out)
|
r.logf("addr del failed: %v: %v\n%s", addrdel, err, out)
|
||||||
if errq == nil {
|
if errq == nil {
|
||||||
|
@ -85,7 +87,7 @@ func (r *userspaceBSDRouter) Set(cfg *Config) error {
|
||||||
// Add the interface.
|
// Add the interface.
|
||||||
addradd := []string{"ifconfig", r.tunname,
|
addradd := []string{"ifconfig", r.tunname,
|
||||||
"inet", localAddr.String(), localAddr.IP.String()}
|
"inet", localAddr.String(), localAddr.IP.String()}
|
||||||
out, err := r.cmd(addradd...).CombinedOutput()
|
out, err := cmd(addradd...).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logf("addr add failed: %v: %v\n%s", addradd, err, out)
|
r.logf("addr add failed: %v: %v\n%s", addradd, err, out)
|
||||||
if errq == nil {
|
if errq == nil {
|
||||||
|
@ -107,7 +109,7 @@ func (r *userspaceBSDRouter) Set(cfg *Config) error {
|
||||||
routedel := []string{"route", "-q", "-n",
|
routedel := []string{"route", "-q", "-n",
|
||||||
"del", "-inet", nstr,
|
"del", "-inet", nstr,
|
||||||
"-iface", r.tunname}
|
"-iface", r.tunname}
|
||||||
out, err := r.cmd(routedel...).CombinedOutput()
|
out, err := cmd(routedel...).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logf("route del failed: %v: %v\n%s", routedel, err, out)
|
r.logf("route del failed: %v: %v\n%s", routedel, err, out)
|
||||||
if errq == nil {
|
if errq == nil {
|
||||||
|
@ -125,7 +127,7 @@ func (r *userspaceBSDRouter) Set(cfg *Config) error {
|
||||||
routeadd := []string{"route", "-q", "-n",
|
routeadd := []string{"route", "-q", "-n",
|
||||||
"add", "-inet", nstr,
|
"add", "-inet", nstr,
|
||||||
"-iface", r.tunname}
|
"-iface", r.tunname}
|
||||||
out, err := r.cmd(routeadd...).CombinedOutput()
|
out, err := cmd(routeadd...).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logf("addr add failed: %v: %v\n%s", routeadd, err, out)
|
r.logf("addr add failed: %v: %v\n%s", routeadd, err, out)
|
||||||
if errq == nil {
|
if errq == nil {
|
||||||
|
@ -139,18 +141,29 @@ func (r *userspaceBSDRouter) Set(cfg *Config) error {
|
||||||
r.local = localAddr
|
r.local = localAddr
|
||||||
r.routes = newRoutes
|
r.routes = newRoutes
|
||||||
|
|
||||||
if err := r.replaceResolvConf(cfg.DNS, cfg.DNSDomains); err != nil {
|
if !r.dnsConfig.EquivalentTo(cfg.DNSConfig) {
|
||||||
errq = fmt.Errorf("replacing resolv.conf failed: %v", err)
|
if err := upDNS(cfg.DNSConfig, r.tunname); err != nil {
|
||||||
|
errq = fmt.Errorf("dns up: %v", err)
|
||||||
|
} else {
|
||||||
|
r.dnsConfig = cfg.DNSConfig
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errq
|
return errq
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *userspaceBSDRouter) Close() error {
|
func (r *userspaceBSDRouter) Close() error {
|
||||||
|
cleanup(r.logf, r.tunname)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(mbaillie): these are no-ops for now. They could re-use the Linux funcs
|
func cleanup(logf logger.Logf, interfaceName string) {
|
||||||
// (sans systemd parts), but I note Linux DNS is disabled(?) so leaving for now.
|
if err := downDNS(interfaceName); err != nil {
|
||||||
func (r *userspaceBSDRouter) replaceResolvConf(_ []netaddr.IP, _ []string) error { return nil }
|
logf("dns down: %v", err)
|
||||||
func (r *userspaceBSDRouter) restoreResolvConf() error { return nil }
|
}
|
||||||
|
|
||||||
|
ifup := []string{"ifconfig", interfaceName, "down"}
|
||||||
|
if out, err := cmd(ifup...).CombinedOutput(); err != nil {
|
||||||
|
logf("ifconfig down: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -64,3 +64,7 @@ func (r *winRouter) Close() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanup(logf logger.Logf, interfaceName string) {
|
||||||
|
// DNS is interface-bound, so nothing to do here.
|
||||||
|
}
|
||||||
|
|
|
@ -57,6 +57,9 @@ const (
|
||||||
magicDNSPort = 53
|
magicDNSPort = 53
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// magicDNSDomain is the parent domain for Tailscale nodes.
|
||||||
|
const magicDNSDomain = "b.tailscale.net"
|
||||||
|
|
||||||
type userspaceEngine struct {
|
type userspaceEngine struct {
|
||||||
logf logger.Logf
|
logf logger.Logf
|
||||||
reqCh chan struct{}
|
reqCh chan struct{}
|
||||||
|
@ -180,7 +183,7 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
|
||||||
reqCh: make(chan struct{}, 1),
|
reqCh: make(chan struct{}, 1),
|
||||||
waitCh: make(chan struct{}),
|
waitCh: make(chan struct{}),
|
||||||
tundev: tstun.WrapTUN(logf, conf.TUN),
|
tundev: tstun.WrapTUN(logf, conf.TUN),
|
||||||
resolver: tsdns.NewResolver(logf, "tailscale.us"),
|
resolver: tsdns.NewResolver(logf, magicDNSDomain),
|
||||||
useTailscaleDNS: conf.UseTailscaleDNS,
|
useTailscaleDNS: conf.UseTailscaleDNS,
|
||||||
pingers: make(map[wgcfg.Key]*pinger),
|
pingers: make(map[wgcfg.Key]*pinger),
|
||||||
}
|
}
|
||||||
|
@ -548,8 +551,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
|
||||||
if !addr.IP.Is4() {
|
if !addr.IP.Is4() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bs := addr.IP.As16()
|
localAddrs[packet.IPFromNetaddr(addr.IP)] = true
|
||||||
localAddrs[packet.NewIP(net.IP(bs[12:16]))] = true
|
|
||||||
}
|
}
|
||||||
e.localAddrs.Store(localAddrs)
|
e.localAddrs.Store(localAddrs)
|
||||||
|
|
||||||
|
@ -565,6 +567,13 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
|
||||||
}
|
}
|
||||||
e.mu.Unlock()
|
e.mu.Unlock()
|
||||||
|
|
||||||
|
// If the only nameserver is quad 100 (Magic DNS), set up the resolver appropriately.
|
||||||
|
if len(routerCfg.Nameservers) == 1 && routerCfg.Nameservers[0] == packet.IP(magicDNSIP).Netaddr() {
|
||||||
|
// TODO(dmytro): plumb dnsReadConfig here instead of hardcoding this.
|
||||||
|
e.resolver.SetNameservers([]string{"8.8.8.8:53"})
|
||||||
|
routerCfg.Domains = append([]string{magicDNSDomain}, routerCfg.Domains...)
|
||||||
|
}
|
||||||
|
|
||||||
engineChanged := updateSig(&e.lastEngineSig, cfg)
|
engineChanged := updateSig(&e.lastEngineSig, cfg)
|
||||||
routerChanged := updateSig(&e.lastRouterSig, routerCfg)
|
routerChanged := updateSig(&e.lastRouterSig, routerCfg)
|
||||||
if !engineChanged && !routerChanged {
|
if !engineChanged && !routerChanged {
|
||||||
|
|
Loading…
Reference in New Issue