Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+70
-15
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build (linux && !android) || freebsd || openbsd
|
||||
@@ -7,10 +7,14 @@ package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
@@ -39,6 +43,38 @@ func (m openresolvManager) logCmdErr(cmd *exec.Cmd, err error) {
|
||||
m.logf("error running command %s stderr=%q exitCode=%d: %v", commandStr, exerr.Stderr, exerr.ExitCode(), err)
|
||||
}
|
||||
|
||||
// openresolvNoSnippetsExitCode is the exit status resolvconf returns when a
|
||||
// requested config snippet does not exist. Asking for all snippets when none
|
||||
// are registered returns this status too, because openresolv treats the empty
|
||||
// result as a missing snippet rather than as an empty list.
|
||||
const openresolvNoSnippetsExitCode = 2
|
||||
|
||||
// readSnippets runs resolvconf with the given arguments and returns its stdout.
|
||||
// An exit status of openresolvNoSnippetsExitCode is not an error: it returns no
|
||||
// output and a nil error. Other failures are logged and returned.
|
||||
//
|
||||
// Callers must pass either "-i" with no arguments or "-l" with explicit snippet
|
||||
// names. Only those forms produce empty stdout alongside
|
||||
// openresolvNoSnippetsExitCode; "-i" with snippet names prints the ones that do
|
||||
// exist and still exits 2, so its output would be silently dropped.
|
||||
//
|
||||
// Stderr is excluded from the returned bytes so that diagnostics like "No
|
||||
// resolv.conf for key foo" are never parsed as snippet names or resolv.conf
|
||||
// lines. logCmdErr still logs stderr when a command fails.
|
||||
func (m openresolvManager) readSnippets(args ...string) ([]byte, error) {
|
||||
cmd := exec.Command("resolvconf", args...)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
if ee, ok := errors.AsType[*exec.ExitError](err); ok && ee.ExitCode() == openresolvNoSnippetsExitCode {
|
||||
m.logf("[v1] resolvconf %q found no matching config snippets", args)
|
||||
return nil, nil
|
||||
}
|
||||
m.logCmdErr(cmd, err)
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (m openresolvManager) deleteTailscaleConfig() error {
|
||||
cmd := exec.Command("resolvconf", "-f", "-d", "tailscale")
|
||||
out, err := cmd.CombinedOutput()
|
||||
@@ -75,18 +111,21 @@ func (m openresolvManager) GetBaseConfig() (OSConfig, error) {
|
||||
// List the names of all config snippets openresolv is aware
|
||||
// of. Snippets get listed in priority order (most to least),
|
||||
// which we'll exploit later.
|
||||
bs, err := exec.Command("resolvconf", "-i").CombinedOutput()
|
||||
bs, err := m.readSnippets("-i")
|
||||
if err != nil {
|
||||
return OSConfig{}, err
|
||||
}
|
||||
|
||||
// Remove the "tailscale" snippet from the list.
|
||||
args := []string{"-l"}
|
||||
for _, f := range strings.Split(strings.TrimSpace(string(bs)), " ") {
|
||||
if f == "tailscale" {
|
||||
continue
|
||||
}
|
||||
args = append(args, f)
|
||||
others := slices.DeleteFunc(strings.Fields(string(bs)), func(f string) bool {
|
||||
return f == "tailscale"
|
||||
})
|
||||
if len(others) == 0 {
|
||||
// There are no other snippets, so there is no base config to read.
|
||||
// Returning early is required, not merely an optimization: a
|
||||
// "resolvconf -l" with no snippet names lists every snippet,
|
||||
// including Tailscale's own, which would make quad-100 its own
|
||||
// upstream. See tailscale/tailscale#20825.
|
||||
return OSConfig{}, nil
|
||||
}
|
||||
|
||||
// List all resolvconf snippets except our own, and parse that as
|
||||
@@ -100,14 +139,30 @@ func (m openresolvManager) GetBaseConfig() (OSConfig, error) {
|
||||
// practice, openresolv uses are generally quite limited, and boil
|
||||
// down to 1-2 DHCP leases, for which the correct outcome is a
|
||||
// blended config like the one we produce here.
|
||||
var buf bytes.Buffer
|
||||
cmd := exec.Command("resolvconf", args...)
|
||||
cmd.Stdout = &buf
|
||||
if err := cmd.Run(); err != nil {
|
||||
m.logCmdErr(cmd, err)
|
||||
out, err := m.readSnippets(append([]string{"-l"}, others...)...)
|
||||
if err != nil {
|
||||
return OSConfig{}, err
|
||||
}
|
||||
return readResolv(&buf)
|
||||
cfg, err := readResolv(bytes.NewReader(out))
|
||||
if err != nil {
|
||||
return OSConfig{}, err
|
||||
}
|
||||
|
||||
// Forwarding to the Tailscale service IPs would make quad-100 send
|
||||
// queries to itself, in an infinite loop, so drop them if another
|
||||
// snippet names them. See tailscale/tailscale#7816.
|
||||
var removed bool
|
||||
cfg.Nameservers = slices.DeleteFunc(cfg.Nameservers, func(ip netip.Addr) bool {
|
||||
if ip == tsaddr.TailscaleServiceIP() || ip == tsaddr.TailscaleServiceIPv6() {
|
||||
removed = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if removed {
|
||||
m.logf("[v1] dropped Tailscale service IP from openresolv base config")
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (m openresolvManager) Close() error {
|
||||
|
||||
Reference in new issue
Block a user