mirror of
https://github.com/tailscale/tailscale.git
synced 2025-03-29 04:22:24 +00:00
net/dns: implement a DNS override workaround for legacy resolvconf.
Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
parent
1a371b93be
commit
5480189313
@ -216,6 +216,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
debug/elf from rsc.io/goversion/version
|
debug/elf from rsc.io/goversion/version
|
||||||
debug/macho from rsc.io/goversion/version
|
debug/macho from rsc.io/goversion/version
|
||||||
debug/pe from rsc.io/goversion/version
|
debug/pe from rsc.io/goversion/version
|
||||||
|
embed from tailscale.com/net/dns
|
||||||
encoding from encoding/json+
|
encoding from encoding/json+
|
||||||
encoding/asn1 from crypto/x509+
|
encoding/asn1 from crypto/x509+
|
||||||
encoding/base64 from encoding/json+
|
encoding/base64 from encoding/json+
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build linux freebsd openbsd
|
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
55
net/dns/resolvconf-workaround.sh
Normal file
55
net/dns/resolvconf-workaround.sh
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# This script is a workaround for a vpn-unfriendly behavior of the
|
||||||
|
# original resolvconf by Thomas Hood. Unlike the `openresolv`
|
||||||
|
# implementation (whose binary is also called resolvconf,
|
||||||
|
# confusingly), the original resolvconf lacks a way to specify
|
||||||
|
# "exclusive mode" for a provider configuration. In practice, this
|
||||||
|
# means that if Tailscale wants to install a DNS configuration, that
|
||||||
|
# config will get "blended" with the configs from other sources,
|
||||||
|
# rather than override those other sources.
|
||||||
|
#
|
||||||
|
# This script gets installed at /etc/resolvconf/update-libc.d, which
|
||||||
|
# is a directory of hook scripts that get run after resolvconf's libc
|
||||||
|
# helper has finished rewriting /etc/resolv.conf. It's meant to notify
|
||||||
|
# consumers of resolv.conf of a new configuration.
|
||||||
|
#
|
||||||
|
# Instead, we use that hook mechanism to reach into resolvconf's
|
||||||
|
# stuff, and rewrite the libc-generated resolv.conf to exclusively
|
||||||
|
# contain Tailscale's configuration - effectively implementing
|
||||||
|
# exclusive mode ourselves in post-production.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ -n "$TAILSCALE_RESOLVCONF_HOOK_LOOP" ]; then
|
||||||
|
# Hook script being invoked by itself, skip.
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f tun-tailscale.inet ]; then
|
||||||
|
# Tailscale isn't trying to manage DNS, do nothing.
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep resolvconf /etc/resolv.conf >/dev/null; then
|
||||||
|
# resolvconf isn't managing /etc/resolv.conf, do nothing.
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Write out a modified /etc/resolv.conf containing just our config.
|
||||||
|
(
|
||||||
|
if [ -f /etc/resolvconf/resolv.conf.d/head ]; then
|
||||||
|
cat /etc/resolvconf/resolv.conf.d/head
|
||||||
|
fi
|
||||||
|
echo "# Tailscale workaround applied to set exclusive DNS configuration."
|
||||||
|
cat tun-tailscale.inet
|
||||||
|
) >/etc/resolv.conf
|
||||||
|
|
||||||
|
if [ -d /etc/resolvconf/update-libc.d ] ; then
|
||||||
|
# Re-notify libc watchers that we've changed resolv.conf again.
|
||||||
|
export TAILSCALE_RESOLVCONF_HOOK_LOOP=1
|
||||||
|
exec run-parts /etc/resolvconf/update-libc.d
|
||||||
|
fi
|
@ -2,20 +2,47 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build linux freebsd
|
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"tailscale.com/atomicfile"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//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")
|
||||||
|
|
||||||
// isResolvconfActive indicates whether the system appears to be using resolvconf.
|
// isResolvconfActive indicates whether the system appears to be using resolvconf.
|
||||||
// If this is true, then directManager should be avoided:
|
// If this is true, then directManager should be avoided:
|
||||||
// resolvconf has exclusive ownership of /etc/resolv.conf.
|
// resolvconf has exclusive ownership of /etc/resolv.conf.
|
||||||
@ -98,24 +125,33 @@ func getResolvconfImpl() resolvconfImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type resolvconfManager struct {
|
type resolvconfManager struct {
|
||||||
impl resolvconfImpl
|
logf logger.Logf
|
||||||
|
impl resolvconfImpl
|
||||||
|
workaroundApplied bool // libc update script has been installed.
|
||||||
}
|
}
|
||||||
|
|
||||||
func newResolvconfManager(logf logger.Logf) resolvconfManager {
|
func newResolvconfManager(logf logger.Logf) *resolvconfManager {
|
||||||
impl := getResolvconfImpl()
|
impl := getResolvconfImpl()
|
||||||
logf("resolvconf implementation is %s", impl)
|
logf("resolvconf implementation is %s", impl)
|
||||||
|
|
||||||
return resolvconfManager{
|
return &resolvconfManager{
|
||||||
|
logf: logf,
|
||||||
impl: impl,
|
impl: impl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolvconfConfigName is the name of the config submitted to resolvconf.
|
func (m *resolvconfManager) SetDNS(config OSConfig) error {
|
||||||
// It has this form to match the "tun*" rule in interface-order
|
if m.impl == resolvconfLegacy && !m.workaroundApplied {
|
||||||
// when running resolvconfLegacy, hopefully placing our config first.
|
m.logf("injecting resolvconf workaround script")
|
||||||
const resolvconfConfigName = "tun-tailscale.inet"
|
if err := os.MkdirAll(resolvconfLibcHookPath, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := atomicfile.WriteFile(resolvconfHookPath, legacyResolvconfScript, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.workaroundApplied = true
|
||||||
|
}
|
||||||
|
|
||||||
func (m resolvconfManager) SetDNS(config OSConfig) error {
|
|
||||||
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
|
||||||
|
|
||||||
@ -138,15 +174,15 @@ func (m resolvconfManager) SetDNS(config OSConfig) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m resolvconfManager) SupportsSplitDNS() bool {
|
func (m *resolvconfManager) SupportsSplitDNS() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m resolvconfManager) GetBaseConfig() (OSConfig, error) {
|
func (m *resolvconfManager) GetBaseConfig() (OSConfig, error) {
|
||||||
return OSConfig{}, ErrGetBaseConfigNotSupported
|
return OSConfig{}, ErrGetBaseConfigNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m resolvconfManager) Close() error {
|
func (m *resolvconfManager) Close() error {
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
switch m.impl {
|
switch m.impl {
|
||||||
case resolvconfOpenresolv:
|
case resolvconfOpenresolv:
|
||||||
@ -161,5 +197,10 @@ func (m resolvconfManager) Close() error {
|
|||||||
return fmt.Errorf("running %s: %s", cmd, out)
|
return fmt.Errorf("running %s: %s", cmd, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m.workaroundApplied {
|
||||||
|
m.logf("removing resolvconf workaround script")
|
||||||
|
os.Remove(resolvconfHookPath) // Best-effort
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user