From c53ab3111dbc4272e62d93e88eec7f054848a7a7 Mon Sep 17 00:00:00 2001 From: Dmytro Shynkevych Date: Thu, 23 Jul 2020 14:34:39 -0400 Subject: [PATCH] wgengine/router: support legacy resolvconf Signed-off-by: Dmytro Shynkevych --- wgengine/router/dns_resolvconf.go | 59 +++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/wgengine/router/dns_resolvconf.go b/wgengine/router/dns_resolvconf.go index 204147ce9..b0795edd6 100644 --- a/wgengine/router/dns_resolvconf.go +++ b/wgengine/router/dns_resolvconf.go @@ -57,13 +57,57 @@ func resolvconfIsActive() bool { return false } +// resolvconfImplementation enumerates supported implementations of the resolvconf CLI. +type resolvconfImplementation uint8 + +const ( + // resolvconfOpenresolv is the implementation packaged as "openresolv" on Ubuntu. + // It supports exclusive mode and interface metrics. + resolvconfOpenresolv resolvconfImplementation = iota + // resolvconfLegacy is the implementation by Thomas Hood packaged as "resolvconf" on Ubuntu. + // It does not support exclusive mode or interface metrics. + resolvconfLegacy +) + +// getResolvconfImplementation returns the implementation of resolvconf +// that appears to be in use. +func getResolvconfImplementation() resolvconfImplementation { + err := exec.Command("resolvconf", "-v").Run() + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + // Thomas Hood's resolvconf has a minimal flag set + // and exits with code 99 when passed an unknown flag. + if exitErr.ExitCode() == 99 { + return resolvconfLegacy + } + } + } + return resolvconfOpenresolv +} + +// resolvconfConfigName is the name of the config submitted to resolvconf. +// It has this form to match the "tun*" rule in interface-order +// when running resolvconfLegacy, hopefully placing our config first. +const resolvconfConfigName = "tun-tailscale.inet" + // dnsResolvconfUp invokes the resolvconf binary to associate // the given DNS configuration the Tailscale interface. func dnsResolvconfUp(config DNSConfig, interfaceName string) error { + implementation := getResolvconfImplementation() + stdin := new(bytes.Buffer) dnsWriteConfig(stdin, config.Nameservers, config.Domains) // dns_direct.go - cmd := exec.Command("resolvconf", "-m", "0", "-x", "-a", interfaceName+".inet") + var cmd *exec.Cmd + switch implementation { + case resolvconfOpenresolv: + // Request maximal priority (metric 0) and exclusive mode. + cmd = exec.Command("resolvconf", "-m", "0", "-x", "-a", resolvconfConfigName) + case resolvconfLegacy: + // This does not quite give us the desired behavior (queries leak), + // but there is nothing else we can do without messing with other interfaces' settings. + cmd = exec.Command("resolvconf", "-a", resolvconfConfigName) + } cmd.Stdin = stdin out, err := cmd.CombinedOutput() if err != nil { @@ -75,10 +119,21 @@ func dnsResolvconfUp(config DNSConfig, interfaceName string) error { // dnsResolvconfDown undoes the action of dnsResolvconfUp. func dnsResolvconfDown(interfaceName string) error { - cmd := exec.Command("resolvconf", "-f", "-d", interfaceName+".inet") + implementation := getResolvconfImplementation() + + var cmd *exec.Cmd + switch implementation { + case resolvconfOpenresolv: + cmd = exec.Command("resolvconf", "-f", "-d", resolvconfConfigName) + case resolvconfLegacy: + // resolvconfLegacy lacks the -f flag. + // Instead, it succeeds even when the config does not exist. + cmd = exec.Command("resolvconf", "-d", resolvconfConfigName) + } out, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("running %s: %s", cmd, out) } + return nil }