2020-07-14 14:12:00 +01:00
|
|
|
// 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.
|
|
|
|
|
2020-07-31 21:27:09 +01:00
|
|
|
package dns
|
2020-07-14 14:12:00 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
2021-04-10 03:00:33 +01:00
|
|
|
_ "embed"
|
2020-07-14 14:12:00 +01:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2021-04-10 03:00:33 +01:00
|
|
|
"path/filepath"
|
2021-04-02 07:26:52 +01:00
|
|
|
|
2021-04-10 03:00:33 +01:00
|
|
|
"tailscale.com/atomicfile"
|
2021-04-02 07:26:52 +01:00
|
|
|
"tailscale.com/types/logger"
|
2020-07-14 14:12:00 +01:00
|
|
|
)
|
|
|
|
|
2021-04-10 03:00:33 +01:00
|
|
|
//go:embed resolvconf-workaround.sh
|
|
|
|
var legacyResolvconfScript []byte
|
|
|
|
|
|
|
|
// resolvconfConfigName is the name of the config submitted to
|
|
|
|
// resolvconf.
|
|
|
|
// The name starts with 'tun' in order to match the hardcoded
|
|
|
|
// interface order in debian resolvconf, which will place this
|
|
|
|
// configuration ahead of regular network links. In theory, this
|
|
|
|
// doesn't matter because we then fix things up to ensure our config
|
|
|
|
// is the only one in use, but in case that fails, this will make our
|
|
|
|
// configuration slightly preferred.
|
|
|
|
// The 'inet' suffix has no specific meaning, but conventionally
|
|
|
|
// resolvconf implementations encourage adding a suffix roughly
|
|
|
|
// indicating where the config came from, and "inet" is the "none of
|
|
|
|
// the above" value (rather than, say, "ppp" or "dhcp").
|
|
|
|
const resolvconfConfigName = "tun-tailscale.inet"
|
|
|
|
|
|
|
|
// resolvconfLibcHookPath is the directory containing libc update
|
|
|
|
// scripts, which are run by Debian resolvconf when /etc/resolv.conf
|
|
|
|
// has been updated.
|
|
|
|
const resolvconfLibcHookPath = "/etc/resolvconf/update-libc.d"
|
|
|
|
|
|
|
|
// resolvconfHookPath is the name of the libc hook script we install
|
|
|
|
// to force Tailscale's DNS config to take effect.
|
|
|
|
var resolvconfHookPath = filepath.Join(resolvconfLibcHookPath, "tailscale")
|
|
|
|
|
2020-07-31 21:27:09 +01:00
|
|
|
// isResolvconfActive indicates whether the system appears to be using resolvconf.
|
|
|
|
// If this is true, then directManager should be avoided:
|
2020-07-14 14:12:00 +01:00
|
|
|
// resolvconf has exclusive ownership of /etc/resolv.conf.
|
2020-07-31 21:27:09 +01:00
|
|
|
func isResolvconfActive() bool {
|
2020-07-14 14:12:00 +01:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2021-04-11 02:55:05 +01:00
|
|
|
// resolvconfManager manages DNS configuration using the Debian
|
|
|
|
// implementation of the `resolvconf` program, written by Thomas Hood.
|
2020-07-31 21:27:09 +01:00
|
|
|
type resolvconfManager struct {
|
2021-04-11 02:55:05 +01:00
|
|
|
logf logger.Logf
|
|
|
|
scriptInstalled bool // libc update script has been installed
|
2020-07-31 21:27:09 +01:00
|
|
|
}
|
|
|
|
|
2021-04-10 03:00:33 +01:00
|
|
|
func newResolvconfManager(logf logger.Logf) *resolvconfManager {
|
|
|
|
return &resolvconfManager{
|
|
|
|
logf: logf,
|
2020-07-31 21:27:09 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-10 03:00:33 +01:00
|
|
|
func (m *resolvconfManager) SetDNS(config OSConfig) error {
|
2021-04-11 02:55:05 +01:00
|
|
|
if !m.scriptInstalled {
|
2021-04-10 03:00:33 +01:00
|
|
|
m.logf("injecting resolvconf workaround script")
|
|
|
|
if err := os.MkdirAll(resolvconfLibcHookPath, 0755); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := atomicfile.WriteFile(resolvconfHookPath, legacyResolvconfScript, 0755); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-04-11 02:55:05 +01:00
|
|
|
m.scriptInstalled = true
|
2021-04-10 03:00:33 +01:00
|
|
|
}
|
2020-07-23 19:34:39 +01:00
|
|
|
|
2020-07-14 14:12:00 +01:00
|
|
|
stdin := new(bytes.Buffer)
|
2021-04-06 23:21:32 +01:00
|
|
|
writeResolvConf(stdin, config.Nameservers, config.SearchDomains) // dns_direct.go
|
2020-07-14 14:12:00 +01:00
|
|
|
|
2021-04-11 02:55:05 +01:00
|
|
|
// This resolvconf implementation doesn't support exclusive mode
|
|
|
|
// or interface priorities, so it will end up blending our
|
|
|
|
// configuration with other sources. However, this will get fixed
|
|
|
|
// up by the script we injected above.
|
|
|
|
cmd := exec.Command("resolvconf", "-a", resolvconfConfigName)
|
2020-07-14 14:12:00 +01:00
|
|
|
cmd.Stdin = stdin
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("running %s: %s", cmd, out)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-04-10 03:00:33 +01:00
|
|
|
func (m *resolvconfManager) SupportsSplitDNS() bool {
|
2021-04-05 21:05:47 +01:00
|
|
|
return false
|
2021-04-02 10:17:50 +01:00
|
|
|
}
|
|
|
|
|
2021-04-10 03:00:33 +01:00
|
|
|
func (m *resolvconfManager) GetBaseConfig() (OSConfig, error) {
|
2021-04-07 23:39:26 +01:00
|
|
|
return OSConfig{}, ErrGetBaseConfigNotSupported
|
2021-04-07 08:31:31 +01:00
|
|
|
}
|
|
|
|
|
2021-04-10 03:00:33 +01:00
|
|
|
func (m *resolvconfManager) Close() error {
|
2021-04-11 02:55:05 +01:00
|
|
|
cmd := exec.Command("resolvconf", "-d", resolvconfConfigName)
|
2020-07-14 14:12:00 +01:00
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("running %s: %s", cmd, out)
|
|
|
|
}
|
2020-07-23 19:34:39 +01:00
|
|
|
|
2021-04-11 02:55:05 +01:00
|
|
|
if m.scriptInstalled {
|
2021-04-10 03:00:33 +01:00
|
|
|
m.logf("removing resolvconf workaround script")
|
|
|
|
os.Remove(resolvconfHookPath) // Best-effort
|
|
|
|
}
|
|
|
|
|
2020-07-14 14:12:00 +01:00
|
|
|
return nil
|
|
|
|
}
|