mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-22 08:51:41 +00:00
net/dns: split resolvconfManager into a debian and an openresolv manager.
Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
parent
5480189313
commit
58760f7b82
@ -9,7 +9,12 @@ import "tailscale.com/types/logger"
|
|||||||
func NewOSConfigurator(logf logger.Logf, _ string) OSConfigurator {
|
func NewOSConfigurator(logf logger.Logf, _ string) OSConfigurator {
|
||||||
switch {
|
switch {
|
||||||
case isResolvconfActive():
|
case isResolvconfActive():
|
||||||
return newResolvconfManager(logf)
|
if resolvconfIsOpenresolv() {
|
||||||
|
return newOpenresolvManager()
|
||||||
|
} else {
|
||||||
|
// Debian resolvconf
|
||||||
|
return newResolvconfManager(logf)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return newDirectManager()
|
return newDirectManager()
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,12 @@ func NewOSConfigurator(logf logger.Logf, interfaceName string) OSConfigurator {
|
|||||||
// case isNMActive():
|
// case isNMActive():
|
||||||
// return newNMManager(interfaceName)
|
// return newNMManager(interfaceName)
|
||||||
case isResolvconfActive():
|
case isResolvconfActive():
|
||||||
return newResolvconfManager(logf)
|
if resolvconfIsOpenresolv() {
|
||||||
|
return newOpenresolvManager()
|
||||||
|
} else {
|
||||||
|
// Debian resolvconf
|
||||||
|
return newResolvconfManager(logf)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return newDirectManager()
|
return newDirectManager()
|
||||||
}
|
}
|
||||||
|
61
net/dns/openresolv.go
Normal file
61
net/dns/openresolv.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) 2021 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 dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// resolvconfIsOpenresolv reports whether the `resolvconf` binary on
|
||||||
|
// the system is the openresolv implementation.
|
||||||
|
func resolvconfIsOpenresolv() bool {
|
||||||
|
bs, err := exec.Command("resolvconf", "--version").CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
// Either resolvconf isn't installed, or it's not openresolv.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes.Contains(bs, []byte("openresolv "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// openresolvManager manages DNS configuration using the openresolv
|
||||||
|
// implementation of the `resolvconf` program.
|
||||||
|
type openresolvManager struct{}
|
||||||
|
|
||||||
|
func newOpenresolvManager() openresolvManager {
|
||||||
|
return openresolvManager{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m openresolvManager) SetDNS(config OSConfig) error {
|
||||||
|
var stdin bytes.Buffer
|
||||||
|
writeResolvConf(&stdin, config.Nameservers, config.SearchDomains)
|
||||||
|
|
||||||
|
cmd := exec.Command("resolvconf", "-m", "0", "-x", "-a", "tailscale")
|
||||||
|
cmd.Stdin = &stdin
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("running %s: %s", cmd, out)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m openresolvManager) SupportsSplitDNS() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m openresolvManager) GetBaseConfig() (OSConfig, error) {
|
||||||
|
return OSConfig{}, ErrGetBaseConfigNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m openresolvManager) Close() error {
|
||||||
|
cmd := exec.Command("resolvconf", "-f", "-d", "tailscale")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("running %s: %s", cmd, out)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -86,62 +86,21 @@ func isResolvconfActive() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolvconfImpl enumerates supported implementations of the resolvconf CLI.
|
// resolvconfManager manages DNS configuration using the Debian
|
||||||
type resolvconfImpl uint8
|
// implementation of the `resolvconf` program, written by Thomas Hood.
|
||||||
|
|
||||||
const (
|
|
||||||
// resolvconfOpenresolv is the implementation packaged as "openresolv" on Ubuntu.
|
|
||||||
// It supports exclusive mode and interface metrics.
|
|
||||||
resolvconfOpenresolv resolvconfImpl = iota
|
|
||||||
// resolvconfLegacy is the implementation by Thomas Hood packaged as "resolvconf" on Ubuntu.
|
|
||||||
// It does not support exclusive mode or interface metrics.
|
|
||||||
resolvconfLegacy
|
|
||||||
)
|
|
||||||
|
|
||||||
func (impl resolvconfImpl) String() string {
|
|
||||||
switch impl {
|
|
||||||
case resolvconfOpenresolv:
|
|
||||||
return "openresolv"
|
|
||||||
case resolvconfLegacy:
|
|
||||||
return "legacy"
|
|
||||||
default:
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getResolvconfImpl returns the implementation of resolvconf that appears to be in use.
|
|
||||||
func getResolvconfImpl() resolvconfImpl {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
type resolvconfManager struct {
|
type resolvconfManager struct {
|
||||||
logf logger.Logf
|
logf logger.Logf
|
||||||
impl resolvconfImpl
|
scriptInstalled bool // libc update script has been installed
|
||||||
workaroundApplied bool // libc update script has been installed.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newResolvconfManager(logf logger.Logf) *resolvconfManager {
|
func newResolvconfManager(logf logger.Logf) *resolvconfManager {
|
||||||
impl := getResolvconfImpl()
|
|
||||||
logf("resolvconf implementation is %s", impl)
|
|
||||||
|
|
||||||
return &resolvconfManager{
|
return &resolvconfManager{
|
||||||
logf: logf,
|
logf: logf,
|
||||||
impl: impl,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *resolvconfManager) SetDNS(config OSConfig) error {
|
func (m *resolvconfManager) SetDNS(config OSConfig) error {
|
||||||
if m.impl == resolvconfLegacy && !m.workaroundApplied {
|
if !m.scriptInstalled {
|
||||||
m.logf("injecting resolvconf workaround script")
|
m.logf("injecting resolvconf workaround script")
|
||||||
if err := os.MkdirAll(resolvconfLibcHookPath, 0755); err != nil {
|
if err := os.MkdirAll(resolvconfLibcHookPath, 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -149,22 +108,17 @@ func (m *resolvconfManager) SetDNS(config OSConfig) error {
|
|||||||
if err := atomicfile.WriteFile(resolvconfHookPath, legacyResolvconfScript, 0755); err != nil {
|
if err := atomicfile.WriteFile(resolvconfHookPath, legacyResolvconfScript, 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.workaroundApplied = true
|
m.scriptInstalled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
stdin := new(bytes.Buffer)
|
stdin := new(bytes.Buffer)
|
||||||
writeResolvConf(stdin, config.Nameservers, config.SearchDomains) // dns_direct.go
|
writeResolvConf(stdin, config.Nameservers, config.SearchDomains) // dns_direct.go
|
||||||
|
|
||||||
var cmd *exec.Cmd
|
// This resolvconf implementation doesn't support exclusive mode
|
||||||
switch m.impl {
|
// or interface priorities, so it will end up blending our
|
||||||
case resolvconfOpenresolv:
|
// configuration with other sources. However, this will get fixed
|
||||||
// Request maximal priority (metric 0) and exclusive mode.
|
// up by the script we injected above.
|
||||||
cmd = exec.Command("resolvconf", "-m", "0", "-x", "-a", resolvconfConfigName)
|
cmd := exec.Command("resolvconf", "-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
|
cmd.Stdin = stdin
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -183,21 +137,13 @@ func (m *resolvconfManager) GetBaseConfig() (OSConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *resolvconfManager) Close() error {
|
func (m *resolvconfManager) Close() error {
|
||||||
var cmd *exec.Cmd
|
cmd := exec.Command("resolvconf", "-d", resolvconfConfigName)
|
||||||
switch m.impl {
|
|
||||||
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()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("running %s: %s", cmd, out)
|
return fmt.Errorf("running %s: %s", cmd, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.workaroundApplied {
|
if m.scriptInstalled {
|
||||||
m.logf("removing resolvconf workaround script")
|
m.logf("removing resolvconf workaround script")
|
||||||
os.Remove(resolvconfHookPath) // Best-effort
|
os.Remove(resolvconfHookPath) // Best-effort
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user