Update
This commit is contained in:
44
vendor/tailscale.com/net/dns/config.go
generated
vendored
44
vendor/tailscale.com/net/dns/config.go
generated
vendored
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:generate go run tailscale.com/cmd/viewer --type=Config --clonefunc
|
||||
|
||||
// Package dns contains code to configure and manage DNS settings.
|
||||
package dns
|
||||
|
||||
@@ -8,8 +10,12 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/dns/publicdns"
|
||||
"tailscale.com/net/dns/resolver"
|
||||
"tailscale.com/net/tsaddr"
|
||||
@@ -47,11 +53,28 @@ type Config struct {
|
||||
OnlyIPv6 bool
|
||||
}
|
||||
|
||||
func (c *Config) serviceIP() netip.Addr {
|
||||
var magicDNSDualStack = envknob.RegisterBool("TS_DEBUG_MAGIC_DNS_DUAL_STACK")
|
||||
|
||||
// serviceIPs returns the list of service IPs where MagicDNS is reachable.
|
||||
//
|
||||
// The provided knobs may be nil.
|
||||
func (c *Config) serviceIPs(knobs *controlknobs.Knobs) []netip.Addr {
|
||||
if c.OnlyIPv6 {
|
||||
return tsaddr.TailscaleServiceIPv6()
|
||||
return []netip.Addr{tsaddr.TailscaleServiceIPv6()}
|
||||
}
|
||||
return tsaddr.TailscaleServiceIP()
|
||||
|
||||
// TODO(bradfitz,mikeodr,raggi): include IPv6 here too; tailscale/tailscale#15404
|
||||
// And add a controlknobs knob to disable dual stack.
|
||||
//
|
||||
// For now, opt-in for testing.
|
||||
if magicDNSDualStack() {
|
||||
return []netip.Addr{
|
||||
tsaddr.TailscaleServiceIP(),
|
||||
tsaddr.TailscaleServiceIPv6(),
|
||||
}
|
||||
}
|
||||
|
||||
return []netip.Addr{tsaddr.TailscaleServiceIP()}
|
||||
}
|
||||
|
||||
// WriteToBufioWriter write a debug version of c for logs to w, omitting
|
||||
@@ -162,21 +185,16 @@ func sameResolverNames(a, b []*dnstype.Resolver) bool {
|
||||
if a[i].Addr != b[i].Addr {
|
||||
return false
|
||||
}
|
||||
if !sameIPs(a[i].BootstrapResolution, b[i].BootstrapResolution) {
|
||||
if !slices.Equal(a[i].BootstrapResolution, b[i].BootstrapResolution) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func sameIPs(a, b []netip.Addr) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
func (c *Config) Equal(o *Config) bool {
|
||||
if c == nil || o == nil {
|
||||
return c == o
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return reflect.DeepEqual(c, o)
|
||||
}
|
||||
|
||||
59
vendor/tailscale.com/net/dns/dbus.go
generated
vendored
Normal file
59
vendor/tailscale.com/net/dns/dbus.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && !android && !ts_omit_dbus
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
func init() {
|
||||
optDBusPing.Set(dbusPing)
|
||||
optDBusReadString.Set(dbusReadString)
|
||||
}
|
||||
|
||||
func dbusPing(name, objectPath string) error {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
// DBus probably not running.
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
obj := conn.Object(name, dbus.ObjectPath(objectPath))
|
||||
call := obj.CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0)
|
||||
return call.Err
|
||||
}
|
||||
|
||||
// dbusReadString reads a string property from the provided name and object
|
||||
// path. property must be in "interface.member" notation.
|
||||
func dbusReadString(name, objectPath, iface, member string) (string, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
// DBus probably not running.
|
||||
return "", err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
obj := conn.Object(name, dbus.ObjectPath(objectPath))
|
||||
|
||||
var result dbus.Variant
|
||||
err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, iface, member).Store(&result)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if s, ok := result.Value().(string); ok {
|
||||
return s, nil
|
||||
}
|
||||
return result.String(), nil
|
||||
}
|
||||
2
vendor/tailscale.com/net/dns/debian_resolvconf.go
generated
vendored
2
vendor/tailscale.com/net/dns/debian_resolvconf.go
generated
vendored
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux || freebsd || openbsd
|
||||
//go:build (linux && !android) || freebsd || openbsd
|
||||
|
||||
package dns
|
||||
|
||||
|
||||
108
vendor/tailscale.com/net/dns/direct.go
generated
vendored
108
vendor/tailscale.com/net/dns/direct.go
generated
vendored
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !android && !ios
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
@@ -19,13 +21,16 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/net/dns/resolvconffile"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
|
||||
@@ -132,6 +137,11 @@ type directManager struct {
|
||||
// but is better than having non-functioning DNS.
|
||||
renameBroken bool
|
||||
|
||||
trampleCount atomic.Int64
|
||||
trampleTimer *time.Timer
|
||||
eventClient *eventbus.Client
|
||||
trampleDNSPub *eventbus.Publisher[TrampleDNS]
|
||||
|
||||
ctx context.Context // valid until Close
|
||||
ctxClose context.CancelFunc // closes ctx
|
||||
|
||||
@@ -142,11 +152,13 @@ type directManager struct {
|
||||
}
|
||||
|
||||
//lint:ignore U1000 used in manager_{freebsd,openbsd}.go
|
||||
func newDirectManager(logf logger.Logf, health *health.Tracker) *directManager {
|
||||
return newDirectManagerOnFS(logf, health, directFS{})
|
||||
func newDirectManager(logf logger.Logf, health *health.Tracker, bus *eventbus.Bus) *directManager {
|
||||
return newDirectManagerOnFS(logf, health, bus, directFS{})
|
||||
}
|
||||
|
||||
func newDirectManagerOnFS(logf logger.Logf, health *health.Tracker, fs wholeFileFS) *directManager {
|
||||
var trampleWatchDuration = 5 * time.Second
|
||||
|
||||
func newDirectManagerOnFS(logf logger.Logf, health *health.Tracker, bus *eventbus.Bus, fs wholeFileFS) *directManager {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
m := &directManager{
|
||||
logf: logf,
|
||||
@@ -155,6 +167,13 @@ func newDirectManagerOnFS(logf logger.Logf, health *health.Tracker, fs wholeFile
|
||||
ctx: ctx,
|
||||
ctxClose: cancel,
|
||||
}
|
||||
if bus != nil {
|
||||
m.eventClient = bus.Client("dns.directManager")
|
||||
m.trampleDNSPub = eventbus.Publish[TrampleDNS](m.eventClient)
|
||||
}
|
||||
m.trampleTimer = time.AfterFunc(trampleWatchDuration, func() {
|
||||
m.trampleCount.Store(0)
|
||||
})
|
||||
go m.runFileWatcher()
|
||||
return m
|
||||
}
|
||||
@@ -413,8 +432,91 @@ func (m *directManager) GetBaseConfig() (OSConfig, error) {
|
||||
return oscfg, nil
|
||||
}
|
||||
|
||||
// HookWatchFile is a hook for watching file changes, for platforms that support it.
|
||||
// The function is called with a directory and filename to watch, and a callback
|
||||
// to call when the file changes. It returns an error if the watch could not be set up.
|
||||
var HookWatchFile feature.Hook[func(ctx context.Context, dir, filename string, cb func()) error]
|
||||
|
||||
func (m *directManager) runFileWatcher() {
|
||||
watchFile, ok := HookWatchFile.GetOk()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := watchFile(m.ctx, "/etc/", resolvConf, m.checkForFileTrample); err != nil {
|
||||
// This is all best effort for now, so surface warnings to users.
|
||||
m.logf("dns: inotify: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
var resolvTrampleWarnable = health.Register(&health.Warnable{
|
||||
Code: "resolv-conf-overwritten",
|
||||
Severity: health.SeverityMedium,
|
||||
Title: "DNS configuration issue",
|
||||
Text: health.StaticMessage("System DNS config not ideal. /etc/resolv.conf overwritten. See https://tailscale.com/s/dns-fight"),
|
||||
})
|
||||
|
||||
// checkForFileTrample checks whether /etc/resolv.conf has been trampled
|
||||
// by another program on the system. (e.g. a DHCP client)
|
||||
func (m *directManager) checkForFileTrample() {
|
||||
m.mu.Lock()
|
||||
want := m.wantResolvConf
|
||||
lastWarn := m.lastWarnContents
|
||||
m.mu.Unlock()
|
||||
|
||||
if want == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cur, err := m.fs.ReadFile(resolvConf)
|
||||
if err != nil {
|
||||
m.logf("trample: read error: %v", err)
|
||||
return
|
||||
}
|
||||
if bytes.Equal(cur, want) {
|
||||
m.health.SetHealthy(resolvTrampleWarnable)
|
||||
if lastWarn != nil {
|
||||
m.mu.Lock()
|
||||
m.lastWarnContents = nil
|
||||
m.mu.Unlock()
|
||||
m.logf("trample: resolv.conf again matches expected content")
|
||||
}
|
||||
return
|
||||
}
|
||||
if bytes.Equal(cur, lastWarn) {
|
||||
// We already logged about this, so not worth doing it again.
|
||||
return
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.lastWarnContents = cur
|
||||
m.mu.Unlock()
|
||||
|
||||
show := cur
|
||||
if len(show) > 1024 {
|
||||
show = show[:1024]
|
||||
}
|
||||
m.logf("trample: resolv.conf changed from what we expected. did some other program interfere? current contents: %q", show)
|
||||
m.health.SetUnhealthy(resolvTrampleWarnable, nil)
|
||||
if m.trampleDNSPub != nil {
|
||||
n := m.trampleCount.Add(1)
|
||||
|
||||
if n < 10 {
|
||||
m.trampleDNSPub.Publish(TrampleDNS{
|
||||
LastTrample: time.Now(),
|
||||
TramplesInTimeout: n,
|
||||
})
|
||||
m.trampleTimer.Reset(trampleWatchDuration)
|
||||
} else {
|
||||
m.logf("trample: resolv.conf overwritten %d times, no longer attempting to replace it.", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *directManager) Close() error {
|
||||
m.ctxClose()
|
||||
if m.eventClient != nil {
|
||||
m.eventClient.Close()
|
||||
}
|
||||
|
||||
// We used to keep a file for the tailscale config and symlinked
|
||||
// to it, but then we stopped because /etc/resolv.conf being a
|
||||
|
||||
102
vendor/tailscale.com/net/dns/direct_linux.go
generated
vendored
102
vendor/tailscale.com/net/dns/direct_linux.go
generated
vendored
@@ -1,102 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/illarion/gonotify/v3"
|
||||
"tailscale.com/health"
|
||||
)
|
||||
|
||||
func (m *directManager) runFileWatcher() {
|
||||
if err := watchFile(m.ctx, "/etc/", resolvConf, m.checkForFileTrample); err != nil {
|
||||
// This is all best effort for now, so surface warnings to users.
|
||||
m.logf("dns: inotify: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// watchFile sets up an inotify watch for a given directory and
|
||||
// calls the callback function every time a particular file is changed.
|
||||
// The filename should be located in the provided directory.
|
||||
func watchFile(ctx context.Context, dir, filename string, cb func()) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
const events = gonotify.IN_ATTRIB |
|
||||
gonotify.IN_CLOSE_WRITE |
|
||||
gonotify.IN_CREATE |
|
||||
gonotify.IN_DELETE |
|
||||
gonotify.IN_MODIFY |
|
||||
gonotify.IN_MOVE
|
||||
|
||||
watcher, err := gonotify.NewDirWatcher(ctx, events, dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("NewDirWatcher: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.C:
|
||||
if event.Name == filename {
|
||||
cb()
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var resolvTrampleWarnable = health.Register(&health.Warnable{
|
||||
Code: "resolv-conf-overwritten",
|
||||
Severity: health.SeverityMedium,
|
||||
Title: "Linux DNS configuration issue",
|
||||
Text: health.StaticMessage("Linux DNS config not ideal. /etc/resolv.conf overwritten. See https://tailscale.com/s/dns-fight"),
|
||||
})
|
||||
|
||||
// checkForFileTrample checks whether /etc/resolv.conf has been trampled
|
||||
// by another program on the system. (e.g. a DHCP client)
|
||||
func (m *directManager) checkForFileTrample() {
|
||||
m.mu.Lock()
|
||||
want := m.wantResolvConf
|
||||
lastWarn := m.lastWarnContents
|
||||
m.mu.Unlock()
|
||||
|
||||
if want == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cur, err := m.fs.ReadFile(resolvConf)
|
||||
if err != nil {
|
||||
m.logf("trample: read error: %v", err)
|
||||
return
|
||||
}
|
||||
if bytes.Equal(cur, want) {
|
||||
m.health.SetHealthy(resolvTrampleWarnable)
|
||||
if lastWarn != nil {
|
||||
m.mu.Lock()
|
||||
m.lastWarnContents = nil
|
||||
m.mu.Unlock()
|
||||
m.logf("trample: resolv.conf again matches expected content")
|
||||
}
|
||||
return
|
||||
}
|
||||
if bytes.Equal(cur, lastWarn) {
|
||||
// We already logged about this, so not worth doing it again.
|
||||
return
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.lastWarnContents = cur
|
||||
m.mu.Unlock()
|
||||
|
||||
show := cur
|
||||
if len(show) > 1024 {
|
||||
show = show[:1024]
|
||||
}
|
||||
m.logf("trample: resolv.conf changed from what we expected. did some other program interfere? current contents: %q", show)
|
||||
m.health.SetUnhealthy(resolvTrampleWarnable, nil)
|
||||
}
|
||||
10
vendor/tailscale.com/net/dns/direct_notlinux.go
generated
vendored
10
vendor/tailscale.com/net/dns/direct_notlinux.go
generated
vendored
@@ -1,10 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !linux
|
||||
|
||||
package dns
|
||||
|
||||
func (m *directManager) runFileWatcher() {
|
||||
// Not implemented on other platforms. Maybe it could resort to polling.
|
||||
}
|
||||
74
vendor/tailscale.com/net/dns/dns_clone.go
generated
vendored
Normal file
74
vendor/tailscale.com/net/dns/dns_clone.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by tailscale.com/cmd/cloner; DO NOT EDIT.
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/util/dnsname"
|
||||
)
|
||||
|
||||
// Clone makes a deep copy of Config.
|
||||
// The result aliases no memory with the original.
|
||||
func (src *Config) Clone() *Config {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
dst := new(Config)
|
||||
*dst = *src
|
||||
if src.DefaultResolvers != nil {
|
||||
dst.DefaultResolvers = make([]*dnstype.Resolver, len(src.DefaultResolvers))
|
||||
for i := range dst.DefaultResolvers {
|
||||
if src.DefaultResolvers[i] == nil {
|
||||
dst.DefaultResolvers[i] = nil
|
||||
} else {
|
||||
dst.DefaultResolvers[i] = src.DefaultResolvers[i].Clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
if dst.Routes != nil {
|
||||
dst.Routes = map[dnsname.FQDN][]*dnstype.Resolver{}
|
||||
for k := range src.Routes {
|
||||
dst.Routes[k] = append([]*dnstype.Resolver{}, src.Routes[k]...)
|
||||
}
|
||||
}
|
||||
dst.SearchDomains = append(src.SearchDomains[:0:0], src.SearchDomains...)
|
||||
if dst.Hosts != nil {
|
||||
dst.Hosts = map[dnsname.FQDN][]netip.Addr{}
|
||||
for k := range src.Hosts {
|
||||
dst.Hosts[k] = append([]netip.Addr{}, src.Hosts[k]...)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _ConfigCloneNeedsRegeneration = Config(struct {
|
||||
DefaultResolvers []*dnstype.Resolver
|
||||
Routes map[dnsname.FQDN][]*dnstype.Resolver
|
||||
SearchDomains []dnsname.FQDN
|
||||
Hosts map[dnsname.FQDN][]netip.Addr
|
||||
OnlyIPv6 bool
|
||||
}{})
|
||||
|
||||
// Clone duplicates src into dst and reports whether it succeeded.
|
||||
// To succeed, <src, dst> must be of types <*T, *T> or <*T, **T>,
|
||||
// where T is one of Config.
|
||||
func Clone(dst, src any) bool {
|
||||
switch src := src.(type) {
|
||||
case *Config:
|
||||
switch dst := dst.(type) {
|
||||
case *Config:
|
||||
*dst = *src.Clone()
|
||||
return true
|
||||
case **Config:
|
||||
*dst = src.Clone()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
138
vendor/tailscale.com/net/dns/dns_view.go
generated
vendored
Normal file
138
vendor/tailscale.com/net/dns/dns_view.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by tailscale/cmd/viewer; DO NOT EDIT.
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
jsonv1 "encoding/json"
|
||||
"errors"
|
||||
"net/netip"
|
||||
|
||||
jsonv2 "github.com/go-json-experiment/json"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/dnsname"
|
||||
)
|
||||
|
||||
//go:generate go run tailscale.com/cmd/cloner -clonefunc=true -type=Config
|
||||
|
||||
// View returns a read-only view of Config.
|
||||
func (p *Config) View() ConfigView {
|
||||
return ConfigView{ж: p}
|
||||
}
|
||||
|
||||
// ConfigView provides a read-only view over Config.
|
||||
//
|
||||
// Its methods should only be called if `Valid()` returns true.
|
||||
type ConfigView struct {
|
||||
// ж is the underlying mutable value, named with a hard-to-type
|
||||
// character that looks pointy like a pointer.
|
||||
// It is named distinctively to make you think of how dangerous it is to escape
|
||||
// to callers. You must not let callers be able to mutate it.
|
||||
ж *Config
|
||||
}
|
||||
|
||||
// Valid reports whether v's underlying value is non-nil.
|
||||
func (v ConfigView) Valid() bool { return v.ж != nil }
|
||||
|
||||
// AsStruct returns a clone of the underlying value which aliases no memory with
|
||||
// the original.
|
||||
func (v ConfigView) AsStruct() *Config {
|
||||
if v.ж == nil {
|
||||
return nil
|
||||
}
|
||||
return v.ж.Clone()
|
||||
}
|
||||
|
||||
// MarshalJSON implements [jsonv1.Marshaler].
|
||||
func (v ConfigView) MarshalJSON() ([]byte, error) {
|
||||
return jsonv1.Marshal(v.ж)
|
||||
}
|
||||
|
||||
// MarshalJSONTo implements [jsonv2.MarshalerTo].
|
||||
func (v ConfigView) MarshalJSONTo(enc *jsontext.Encoder) error {
|
||||
return jsonv2.MarshalEncode(enc, v.ж)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements [jsonv1.Unmarshaler].
|
||||
func (v *ConfigView) UnmarshalJSON(b []byte) error {
|
||||
if v.ж != nil {
|
||||
return errors.New("already initialized")
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
var x Config
|
||||
if err := jsonv1.Unmarshal(b, &x); err != nil {
|
||||
return err
|
||||
}
|
||||
v.ж = &x
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
|
||||
func (v *ConfigView) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||
if v.ж != nil {
|
||||
return errors.New("already initialized")
|
||||
}
|
||||
var x Config
|
||||
if err := jsonv2.UnmarshalDecode(dec, &x); err != nil {
|
||||
return err
|
||||
}
|
||||
v.ж = &x
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultResolvers are the DNS resolvers to use for DNS names
|
||||
// which aren't covered by more specific per-domain routes below.
|
||||
// If empty, the OS's default resolvers (the ones that predate
|
||||
// Tailscale altering the configuration) are used.
|
||||
func (v ConfigView) DefaultResolvers() views.SliceView[*dnstype.Resolver, dnstype.ResolverView] {
|
||||
return views.SliceOfViews[*dnstype.Resolver, dnstype.ResolverView](v.ж.DefaultResolvers)
|
||||
}
|
||||
|
||||
// Routes maps a DNS suffix to the resolvers that should be used
|
||||
// for queries that fall within that suffix.
|
||||
// If a query doesn't match any entry in Routes, the
|
||||
// DefaultResolvers are used.
|
||||
// A Routes entry with no resolvers means the route should be
|
||||
// authoritatively answered using the contents of Hosts.
|
||||
func (v ConfigView) Routes() views.MapFn[dnsname.FQDN, []*dnstype.Resolver, views.SliceView[*dnstype.Resolver, dnstype.ResolverView]] {
|
||||
return views.MapFnOf(v.ж.Routes, func(t []*dnstype.Resolver) views.SliceView[*dnstype.Resolver, dnstype.ResolverView] {
|
||||
return views.SliceOfViews[*dnstype.Resolver, dnstype.ResolverView](t)
|
||||
})
|
||||
}
|
||||
|
||||
// SearchDomains are DNS suffixes to try when expanding
|
||||
// single-label queries.
|
||||
func (v ConfigView) SearchDomains() views.Slice[dnsname.FQDN] {
|
||||
return views.SliceOf(v.ж.SearchDomains)
|
||||
}
|
||||
|
||||
// Hosts maps DNS FQDNs to their IPs, which can be a mix of IPv4
|
||||
// and IPv6.
|
||||
// Queries matching entries in Hosts are resolved locally by
|
||||
// 100.100.100.100 without leaving the machine.
|
||||
// Adding an entry to Hosts merely creates the record. If you want
|
||||
// it to resolve, you also need to add appropriate routes to
|
||||
// Routes.
|
||||
func (v ConfigView) Hosts() views.MapSlice[dnsname.FQDN, netip.Addr] {
|
||||
return views.MapSliceOf(v.ж.Hosts)
|
||||
}
|
||||
|
||||
// OnlyIPv6, if true, uses the IPv6 service IP (for MagicDNS)
|
||||
// instead of the IPv4 version (100.100.100.100).
|
||||
func (v ConfigView) OnlyIPv6() bool { return v.ж.OnlyIPv6 }
|
||||
func (v ConfigView) Equal(v2 ConfigView) bool { return v.ж.Equal(v2.ж) }
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _ConfigViewNeedsRegeneration = Config(struct {
|
||||
DefaultResolvers []*dnstype.Resolver
|
||||
Routes map[dnsname.FQDN][]*dnstype.Resolver
|
||||
SearchDomains []dnsname.FQDN
|
||||
Hosts map[dnsname.FQDN][]netip.Addr
|
||||
OnlyIPv6 bool
|
||||
}{})
|
||||
127
vendor/tailscale.com/net/dns/manager.go
generated
vendored
127
vendor/tailscale.com/net/dns/manager.go
generated
vendored
@@ -20,17 +20,19 @@ import (
|
||||
"time"
|
||||
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/net/dns/resolver"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tstime/rate"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/slicesx"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -53,6 +55,8 @@ type Manager struct {
|
||||
logf logger.Logf
|
||||
health *health.Tracker
|
||||
|
||||
eventClient *eventbus.Client
|
||||
|
||||
activeQueriesAtomic int32
|
||||
|
||||
ctx context.Context // good until Down
|
||||
@@ -63,16 +67,17 @@ type Manager struct {
|
||||
knobs *controlknobs.Knobs // or nil
|
||||
goos string // if empty, gets set to runtime.GOOS
|
||||
|
||||
mu sync.Mutex // guards following
|
||||
// config is the last configuration we successfully compiled or nil if there
|
||||
// was any failure applying the last configuration.
|
||||
config *Config
|
||||
mu sync.Mutex // guards following
|
||||
config *Config // Tracks the last viable DNS configuration set by Set. nil on failures other than compilation failures or if set has never been called.
|
||||
}
|
||||
|
||||
// NewManagers created a new manager from the given config.
|
||||
// NewManager created a new manager from the given config.
|
||||
//
|
||||
// knobs may be nil.
|
||||
func NewManager(logf logger.Logf, oscfg OSConfigurator, health *health.Tracker, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector, knobs *controlknobs.Knobs, goos string) *Manager {
|
||||
func NewManager(logf logger.Logf, oscfg OSConfigurator, health *health.Tracker, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector, knobs *controlknobs.Knobs, goos string, bus *eventbus.Bus) *Manager {
|
||||
if !buildfeatures.HasDNS {
|
||||
return nil
|
||||
}
|
||||
if dialer == nil {
|
||||
panic("nil Dialer")
|
||||
}
|
||||
@@ -93,19 +98,17 @@ func NewManager(logf logger.Logf, oscfg OSConfigurator, health *health.Tracker,
|
||||
goos: goos,
|
||||
}
|
||||
|
||||
// Rate limit our attempts to correct our DNS configuration.
|
||||
// This is done on incoming queries, we don't want to spam it.
|
||||
limiter := rate.NewLimiter(1.0/5.0, 1)
|
||||
|
||||
// This will recompile the DNS config, which in turn will requery the system
|
||||
// DNS settings. The recovery func should triggered only when we are missing
|
||||
// upstream nameservers and require them to forward a query.
|
||||
m.resolver.SetMissingUpstreamRecovery(func() {
|
||||
if limiter.Allow() {
|
||||
m.logf("resolution failed due to missing upstream nameservers. Recompiling DNS configuration.")
|
||||
if err := m.RecompileDNSConfig(); err != nil {
|
||||
m.logf("config recompilation failed: %v", err)
|
||||
}
|
||||
m.eventClient = bus.Client("dns.Manager")
|
||||
eventbus.SubscribeFunc(m.eventClient, func(trample TrampleDNS) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.config == nil {
|
||||
m.logf("resolve.conf was trampled, but there is no DNS config")
|
||||
return
|
||||
}
|
||||
m.logf("resolve.conf was trampled, setting existing config again")
|
||||
if err := m.setLocked(*m.config); err != nil {
|
||||
m.logf("error setting DNS config: %s", err)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -115,9 +118,14 @@ func NewManager(logf logger.Logf, oscfg OSConfigurator, health *health.Tracker,
|
||||
}
|
||||
|
||||
// Resolver returns the Manager's DNS Resolver.
|
||||
func (m *Manager) Resolver() *resolver.Resolver { return m.resolver }
|
||||
func (m *Manager) Resolver() *resolver.Resolver {
|
||||
if !buildfeatures.HasDNS {
|
||||
return nil
|
||||
}
|
||||
return m.resolver
|
||||
}
|
||||
|
||||
// RecompileDNSConfig sets the DNS config to the current value, which has
|
||||
// RecompileDNSConfig recompiles the last attempted DNS configuration, which has
|
||||
// the side effect of re-querying the OS's interface nameservers. This should be used
|
||||
// on platforms where the interface nameservers can change. Darwin, for example,
|
||||
// where the nameservers aren't always available when we process a major interface
|
||||
@@ -127,17 +135,23 @@ func (m *Manager) Resolver() *resolver.Resolver { return m.resolver }
|
||||
// give a better or different result than when [Manager.Set] was last called. The
|
||||
// logic for making that determination is up to the caller.
|
||||
//
|
||||
// It returns [ErrNoDNSConfig] if the [Manager] has no existing DNS configuration.
|
||||
// It returns [ErrNoDNSConfig] if [Manager.Set] has never been called.
|
||||
func (m *Manager) RecompileDNSConfig() error {
|
||||
if !buildfeatures.HasDNS {
|
||||
return nil
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.config == nil {
|
||||
return ErrNoDNSConfig
|
||||
if m.config != nil {
|
||||
return m.setLocked(*m.config)
|
||||
}
|
||||
return m.setLocked(*m.config)
|
||||
return ErrNoDNSConfig
|
||||
}
|
||||
|
||||
func (m *Manager) Set(cfg Config) error {
|
||||
if !buildfeatures.HasDNS {
|
||||
return nil
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.setLocked(cfg)
|
||||
@@ -145,6 +159,9 @@ func (m *Manager) Set(cfg Config) error {
|
||||
|
||||
// GetBaseConfig returns the current base OS DNS configuration as provided by the OSConfigurator.
|
||||
func (m *Manager) GetBaseConfig() (OSConfig, error) {
|
||||
if !buildfeatures.HasDNS {
|
||||
panic("unreachable")
|
||||
}
|
||||
return m.os.GetBaseConfig()
|
||||
}
|
||||
|
||||
@@ -154,15 +171,15 @@ func (m *Manager) GetBaseConfig() (OSConfig, error) {
|
||||
func (m *Manager) setLocked(cfg Config) error {
|
||||
syncs.AssertLocked(&m.mu)
|
||||
|
||||
// On errors, the 'set' config is cleared.
|
||||
m.config = nil
|
||||
|
||||
m.logf("Set: %v", logger.ArgWriter(func(w *bufio.Writer) {
|
||||
cfg.WriteToBufioWriter(w)
|
||||
}))
|
||||
|
||||
rcfg, ocfg, err := m.compileConfig(cfg)
|
||||
if err != nil {
|
||||
// On a compilation failure, set m.config set for later reuse by
|
||||
// [Manager.RecompileDNSConfig] and return the error.
|
||||
m.config = &cfg
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -174,10 +191,10 @@ func (m *Manager) setLocked(cfg Config) error {
|
||||
}))
|
||||
|
||||
if err := m.resolver.SetConfig(rcfg); err != nil {
|
||||
m.config = nil
|
||||
return err
|
||||
}
|
||||
if err := m.os.SetDNS(ocfg); err != nil {
|
||||
m.health.SetUnhealthy(osConfigurationSetWarnable, health.Args{health.ArgError: err.Error()})
|
||||
if err := m.setDNSLocked(ocfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -187,6 +204,15 @@ func (m *Manager) setLocked(cfg Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) setDNSLocked(ocfg OSConfig) error {
|
||||
if err := m.os.SetDNS(ocfg); err != nil {
|
||||
m.config = nil
|
||||
m.health.SetUnhealthy(osConfigurationSetWarnable, health.Args{health.ArgError: err.Error()})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// compileHostEntries creates a list of single-label resolutions possible
|
||||
// from the configured hosts and search domains.
|
||||
// The entries are compiled in the order of the search domains, then the hosts.
|
||||
@@ -284,7 +310,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
|
||||
|
||||
// Deal with trivial configs first.
|
||||
switch {
|
||||
case !cfg.needsOSResolver():
|
||||
case !cfg.needsOSResolver() || runtime.GOOS == "plan9":
|
||||
// Set search domains, but nothing else. This also covers the
|
||||
// case where cfg is entirely zero, in which case these
|
||||
// configs clear all Tailscale DNS settings.
|
||||
@@ -307,7 +333,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
|
||||
// through quad-100.
|
||||
rcfg.Routes = routes
|
||||
rcfg.Routes["."] = cfg.DefaultResolvers
|
||||
ocfg.Nameservers = []netip.Addr{cfg.serviceIP()}
|
||||
ocfg.Nameservers = cfg.serviceIPs(m.knobs)
|
||||
return rcfg, ocfg, nil
|
||||
}
|
||||
|
||||
@@ -345,7 +371,7 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
|
||||
// or routes + MagicDNS, or just MagicDNS, or on an OS that cannot
|
||||
// split-DNS. Install a split config pointing at quad-100.
|
||||
rcfg.Routes = routes
|
||||
ocfg.Nameservers = []netip.Addr{cfg.serviceIP()}
|
||||
ocfg.Nameservers = cfg.serviceIPs(m.knobs)
|
||||
|
||||
var baseCfg *OSConfig // base config; non-nil if/when known
|
||||
|
||||
@@ -355,7 +381,10 @@ func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig
|
||||
// that as the forwarder for all DNS traffic that quad-100 doesn't handle.
|
||||
if isApple || !m.os.SupportsSplitDNS() {
|
||||
// If the OS can't do native split-dns, read out the underlying
|
||||
// resolver config and blend it into our config.
|
||||
// resolver config and blend it into our config. On apple platforms, [OSConfigurator.GetBaseConfig]
|
||||
// has a tendency to temporarily fail if called immediately following
|
||||
// an interface change. These failures should be retried if/when the OS
|
||||
// indicates that the DNS configuration has changed via [RecompileDNSConfig].
|
||||
cfg, err := m.os.GetBaseConfig()
|
||||
if err == nil {
|
||||
baseCfg = &cfg
|
||||
@@ -451,6 +480,13 @@ const (
|
||||
maxReqSizeTCP = 4096
|
||||
)
|
||||
|
||||
// TrampleDNS is an an event indicating we detected that DNS config was
|
||||
// overwritten by another process.
|
||||
type TrampleDNS struct {
|
||||
LastTrample time.Time
|
||||
TramplesInTimeout int64
|
||||
}
|
||||
|
||||
// dnsTCPSession services DNS requests sent over TCP.
|
||||
type dnsTCPSession struct {
|
||||
m *Manager
|
||||
@@ -572,15 +608,22 @@ func (m *Manager) HandleTCPConn(conn net.Conn, srcAddr netip.AddrPort) {
|
||||
}
|
||||
|
||||
func (m *Manager) Down() error {
|
||||
if !buildfeatures.HasDNS {
|
||||
return nil
|
||||
}
|
||||
m.ctxCancel()
|
||||
if err := m.os.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
m.eventClient.Close()
|
||||
m.resolver.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) FlushCaches() error {
|
||||
if !buildfeatures.HasDNS {
|
||||
return nil
|
||||
}
|
||||
return flushCaches()
|
||||
}
|
||||
|
||||
@@ -589,20 +632,22 @@ func (m *Manager) FlushCaches() error {
|
||||
// No other state needs to be instantiated before this runs.
|
||||
//
|
||||
// health must not be nil
|
||||
func CleanUp(logf logger.Logf, netMon *netmon.Monitor, health *health.Tracker, interfaceName string) {
|
||||
oscfg, err := NewOSConfigurator(logf, nil, nil, interfaceName)
|
||||
func CleanUp(logf logger.Logf, netMon *netmon.Monitor, bus *eventbus.Bus, health *health.Tracker, interfaceName string) {
|
||||
if !buildfeatures.HasDNS {
|
||||
return
|
||||
}
|
||||
oscfg, err := NewOSConfigurator(logf, health, bus, policyclient.Get(), nil, interfaceName)
|
||||
if err != nil {
|
||||
logf("creating dns cleanup: %v", err)
|
||||
return
|
||||
}
|
||||
d := &tsdial.Dialer{Logf: logf}
|
||||
d.SetNetMon(netMon)
|
||||
dns := NewManager(logf, oscfg, health, d, nil, nil, runtime.GOOS)
|
||||
d.SetBus(bus)
|
||||
dns := NewManager(logf, oscfg, health, d, nil, nil, runtime.GOOS, bus)
|
||||
if err := dns.Down(); err != nil {
|
||||
logf("dns down: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
metricDNSQueryErrorQueue = clientmetric.NewCounter("dns_query_local_error_queue")
|
||||
)
|
||||
var metricDNSQueryErrorQueue = clientmetric.NewCounter("dns_query_local_error_queue")
|
||||
|
||||
6
vendor/tailscale.com/net/dns/manager_darwin.go
generated
vendored
6
vendor/tailscale.com/net/dns/manager_darwin.go
generated
vendored
@@ -13,13 +13,15 @@ import (
|
||||
"tailscale.com/net/dns/resolvconffile"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
// NewOSConfigurator creates a new OS configurator.
|
||||
//
|
||||
// The health tracker and the knobs may be nil and are ignored on this platform.
|
||||
func NewOSConfigurator(logf logger.Logf, _ *health.Tracker, _ *controlknobs.Knobs, ifName string) (OSConfigurator, error) {
|
||||
// The health tracker, bus and the knobs may be nil and are ignored on this platform.
|
||||
func NewOSConfigurator(logf logger.Logf, _ *health.Tracker, _ *eventbus.Bus, _ policyclient.Client, _ *controlknobs.Knobs, ifName string) (OSConfigurator, error) {
|
||||
return &darwinConfigurator{logf: logf, ifName: ifName}, nil
|
||||
}
|
||||
|
||||
|
||||
6
vendor/tailscale.com/net/dns/manager_default.go
generated
vendored
6
vendor/tailscale.com/net/dns/manager_default.go
generated
vendored
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !linux && !freebsd && !openbsd && !windows && !darwin && !illumos && !solaris
|
||||
//go:build (!linux || android) && !freebsd && !openbsd && !windows && !darwin && !illumos && !solaris && !plan9
|
||||
|
||||
package dns
|
||||
|
||||
@@ -9,11 +9,13 @@ import (
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
// NewOSConfigurator creates a new OS configurator.
|
||||
//
|
||||
// The health tracker and the knobs may be nil and are ignored on this platform.
|
||||
func NewOSConfigurator(logger.Logf, *health.Tracker, *controlknobs.Knobs, string) (OSConfigurator, error) {
|
||||
func NewOSConfigurator(logger.Logf, *health.Tracker, *eventbus.Bus, policyclient.Client, *controlknobs.Knobs, string) (OSConfigurator, error) {
|
||||
return NewNoopManager()
|
||||
}
|
||||
|
||||
12
vendor/tailscale.com/net/dns/manager_freebsd.go
generated
vendored
12
vendor/tailscale.com/net/dns/manager_freebsd.go
generated
vendored
@@ -10,15 +10,17 @@ import (
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
// NewOSConfigurator creates a new OS configurator.
|
||||
//
|
||||
// The health tracker may be nil; the knobs may be nil and are ignored on this platform.
|
||||
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ *controlknobs.Knobs, _ string) (OSConfigurator, error) {
|
||||
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, bus *eventbus.Bus, _ policyclient.Client, _ *controlknobs.Knobs, _ string) (OSConfigurator, error) {
|
||||
bs, err := os.ReadFile("/etc/resolv.conf")
|
||||
if os.IsNotExist(err) {
|
||||
return newDirectManager(logf, health), nil
|
||||
return newDirectManager(logf, health, bus), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading /etc/resolv.conf: %w", err)
|
||||
@@ -28,16 +30,16 @@ func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ *controlknobs
|
||||
case "resolvconf":
|
||||
switch resolvconfStyle() {
|
||||
case "":
|
||||
return newDirectManager(logf, health), nil
|
||||
return newDirectManager(logf, health, bus), nil
|
||||
case "debian":
|
||||
return newDebianResolvconfManager(logf)
|
||||
case "openresolv":
|
||||
return newOpenresolvManager(logf)
|
||||
default:
|
||||
logf("[unexpected] got unknown flavor of resolvconf %q, falling back to direct manager", resolvconfStyle())
|
||||
return newDirectManager(logf, health), nil
|
||||
return newDirectManager(logf, health, bus), nil
|
||||
}
|
||||
default:
|
||||
return newDirectManager(logf, health), nil
|
||||
return newDirectManager(logf, health, bus), nil
|
||||
}
|
||||
}
|
||||
|
||||
167
vendor/tailscale.com/net/dns/manager_linux.go
generated
vendored
167
vendor/tailscale.com/net/dns/manager_linux.go
generated
vendored
@@ -1,11 +1,12 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && !android
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -13,13 +14,16 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/cmpver"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
|
||||
type kv struct {
|
||||
@@ -32,18 +36,59 @@ func (kv kv) String() string {
|
||||
|
||||
var publishOnce sync.Once
|
||||
|
||||
// reconfigTimeout is the time interval within which Manager.{Up,Down} should complete.
|
||||
//
|
||||
// This is particularly useful because certain conditions can cause indefinite hangs
|
||||
// (such as improper dbus auth followed by contextless dbus.Object.Call).
|
||||
// Such operations should be wrapped in a timeout context.
|
||||
const reconfigTimeout = time.Second
|
||||
|
||||
// Set unless ts_omit_networkmanager
|
||||
var (
|
||||
optNewNMManager feature.Hook[func(ifName string) (OSConfigurator, error)]
|
||||
optNMIsUsingResolved feature.Hook[func() error]
|
||||
optNMVersionBetween feature.Hook[func(v1, v2 string) (bool, error)]
|
||||
)
|
||||
|
||||
// Set unless ts_omit_resolved
|
||||
var (
|
||||
optNewResolvedManager feature.Hook[func(logf logger.Logf, health *health.Tracker, interfaceName string) (OSConfigurator, error)]
|
||||
)
|
||||
|
||||
// Set unless ts_omit_dbus
|
||||
var (
|
||||
optDBusPing feature.Hook[func(name, objectPath string) error]
|
||||
optDBusReadString feature.Hook[func(name, objectPath, iface, member string) (string, error)]
|
||||
)
|
||||
|
||||
// NewOSConfigurator created a new OS configurator.
|
||||
//
|
||||
// The health tracker may be nil; the knobs may be nil and are ignored on this platform.
|
||||
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ *controlknobs.Knobs, interfaceName string) (ret OSConfigurator, err error) {
|
||||
env := newOSConfigEnv{
|
||||
fs: directFS{},
|
||||
dbusPing: dbusPing,
|
||||
dbusReadString: dbusReadString,
|
||||
nmIsUsingResolved: nmIsUsingResolved,
|
||||
nmVersionBetween: nmVersionBetween,
|
||||
resolvconfStyle: resolvconfStyle,
|
||||
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, bus *eventbus.Bus, _ policyclient.Client, _ *controlknobs.Knobs, interfaceName string) (ret OSConfigurator, err error) {
|
||||
if !buildfeatures.HasDNS || distro.Get() == distro.JetKVM {
|
||||
return NewNoopManager()
|
||||
}
|
||||
|
||||
env := newOSConfigEnv{
|
||||
fs: directFS{},
|
||||
resolvconfStyle: resolvconfStyle,
|
||||
}
|
||||
if f, ok := optDBusPing.GetOk(); ok {
|
||||
env.dbusPing = f
|
||||
} else {
|
||||
env.dbusPing = func(_, _ string) error { return errors.ErrUnsupported }
|
||||
}
|
||||
if f, ok := optDBusReadString.GetOk(); ok {
|
||||
env.dbusReadString = f
|
||||
} else {
|
||||
env.dbusReadString = func(_, _, _, _ string) (string, error) { return "", errors.ErrUnsupported }
|
||||
}
|
||||
if f, ok := optNMIsUsingResolved.GetOk(); ok {
|
||||
env.nmIsUsingResolved = f
|
||||
} else {
|
||||
env.nmIsUsingResolved = func() error { return errors.ErrUnsupported }
|
||||
}
|
||||
env.nmVersionBetween, _ = optNMVersionBetween.GetOk() // GetOk to not panic if nil; unused if optNMIsUsingResolved returns an error
|
||||
mode, err := dnsMode(logf, health, env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -56,19 +101,26 @@ func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ *controlknobs
|
||||
logf("dns: using %q mode", mode)
|
||||
switch mode {
|
||||
case "direct":
|
||||
return newDirectManagerOnFS(logf, health, env.fs), nil
|
||||
return newDirectManagerOnFS(logf, health, bus, env.fs), nil
|
||||
case "systemd-resolved":
|
||||
return newResolvedManager(logf, health, interfaceName)
|
||||
if f, ok := optNewResolvedManager.GetOk(); ok {
|
||||
return f(logf, health, interfaceName)
|
||||
}
|
||||
return nil, fmt.Errorf("tailscaled was built without DNS %q support", mode)
|
||||
case "network-manager":
|
||||
return newNMManager(interfaceName)
|
||||
if f, ok := optNewNMManager.GetOk(); ok {
|
||||
return f(interfaceName)
|
||||
}
|
||||
return nil, fmt.Errorf("tailscaled was built without DNS %q support", mode)
|
||||
case "debian-resolvconf":
|
||||
return newDebianResolvconfManager(logf)
|
||||
case "openresolv":
|
||||
return newOpenresolvManager(logf)
|
||||
default:
|
||||
logf("[unexpected] detected unknown DNS mode %q, using direct manager as last resort", mode)
|
||||
return newDirectManagerOnFS(logf, health, env.fs), nil
|
||||
}
|
||||
|
||||
return newDirectManagerOnFS(logf, health, bus, env.fs), nil
|
||||
}
|
||||
|
||||
// newOSConfigEnv are the funcs newOSConfigurator needs, pulled out for testing.
|
||||
@@ -284,50 +336,6 @@ func dnsMode(logf logger.Logf, health *health.Tracker, env newOSConfigEnv) (ret
|
||||
}
|
||||
}
|
||||
|
||||
func nmVersionBetween(first, last string) (bool, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
// DBus probably not running.
|
||||
return false, err
|
||||
}
|
||||
|
||||
nm := conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager"))
|
||||
v, err := nm.GetProperty("org.freedesktop.NetworkManager.Version")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
version, ok := v.Value().(string)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("unexpected type %T for NM version", v.Value())
|
||||
}
|
||||
|
||||
outside := cmpver.Compare(version, first) < 0 || cmpver.Compare(version, last) > 0
|
||||
return !outside, nil
|
||||
}
|
||||
|
||||
func nmIsUsingResolved() error {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
// DBus probably not running.
|
||||
return err
|
||||
}
|
||||
|
||||
nm := conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager/DnsManager"))
|
||||
v, err := nm.GetProperty("org.freedesktop.NetworkManager.DnsManager.Mode")
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting NM mode: %w", err)
|
||||
}
|
||||
mode, ok := v.Value().(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for NM DNS mode", v.Value())
|
||||
}
|
||||
if mode != "systemd-resolved" {
|
||||
return errors.New("NetworkManager is not using systemd-resolved for DNS")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolvedIsActuallyResolver reports whether the system is using
|
||||
// systemd-resolved as the resolver. There are two different ways to
|
||||
// use systemd-resolved:
|
||||
@@ -388,44 +396,3 @@ func isLibnssResolveUsed(env newOSConfigEnv) error {
|
||||
}
|
||||
return fmt.Errorf("libnss_resolve not used")
|
||||
}
|
||||
|
||||
func dbusPing(name, objectPath string) error {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
// DBus probably not running.
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
obj := conn.Object(name, dbus.ObjectPath(objectPath))
|
||||
call := obj.CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0)
|
||||
return call.Err
|
||||
}
|
||||
|
||||
// dbusReadString reads a string property from the provided name and object
|
||||
// path. property must be in "interface.member" notation.
|
||||
func dbusReadString(name, objectPath, iface, member string) (string, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
// DBus probably not running.
|
||||
return "", err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
obj := conn.Object(name, dbus.ObjectPath(objectPath))
|
||||
|
||||
var result dbus.Variant
|
||||
err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, iface, member).Store(&result)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if s, ok := result.Value().(string); ok {
|
||||
return s, nil
|
||||
}
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
12
vendor/tailscale.com/net/dns/manager_openbsd.go
generated
vendored
12
vendor/tailscale.com/net/dns/manager_openbsd.go
generated
vendored
@@ -11,6 +11,8 @@ import (
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
type kv struct {
|
||||
@@ -24,8 +26,8 @@ func (kv kv) String() string {
|
||||
// NewOSConfigurator created a new OS configurator.
|
||||
//
|
||||
// The health tracker may be nil; the knobs may be nil and are ignored on this platform.
|
||||
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ *controlknobs.Knobs, interfaceName string) (OSConfigurator, error) {
|
||||
return newOSConfigurator(logf, health, interfaceName,
|
||||
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, bus *eventbus.Bus, _ policyclient.Client, _ *controlknobs.Knobs, interfaceName string) (OSConfigurator, error) {
|
||||
return newOSConfigurator(logf, health, bus, interfaceName,
|
||||
newOSConfigEnv{
|
||||
rcIsResolvd: rcIsResolvd,
|
||||
fs: directFS{},
|
||||
@@ -38,7 +40,7 @@ type newOSConfigEnv struct {
|
||||
rcIsResolvd func(resolvConfContents []byte) bool
|
||||
}
|
||||
|
||||
func newOSConfigurator(logf logger.Logf, health *health.Tracker, interfaceName string, env newOSConfigEnv) (ret OSConfigurator, err error) {
|
||||
func newOSConfigurator(logf logger.Logf, health *health.Tracker, bus *eventbus.Bus, interfaceName string, env newOSConfigEnv) (ret OSConfigurator, err error) {
|
||||
var debug []kv
|
||||
dbg := func(k, v string) {
|
||||
debug = append(debug, kv{k, v})
|
||||
@@ -53,7 +55,7 @@ func newOSConfigurator(logf logger.Logf, health *health.Tracker, interfaceName s
|
||||
bs, err := env.fs.ReadFile(resolvConf)
|
||||
if os.IsNotExist(err) {
|
||||
dbg("rc", "missing")
|
||||
return newDirectManager(logf, health), nil
|
||||
return newDirectManager(logf, health, bus), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading /etc/resolv.conf: %w", err)
|
||||
@@ -65,7 +67,7 @@ func newOSConfigurator(logf logger.Logf, health *health.Tracker, interfaceName s
|
||||
}
|
||||
|
||||
dbg("resolvd", "missing")
|
||||
return newDirectManager(logf, health), nil
|
||||
return newDirectManager(logf, health, bus), nil
|
||||
}
|
||||
|
||||
func rcIsResolvd(resolvConfContents []byte) bool {
|
||||
|
||||
183
vendor/tailscale.com/net/dns/manager_plan9.go
generated
vendored
Normal file
183
vendor/tailscale.com/net/dns/manager_plan9.go
generated
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// TODO: man 6 ndb | grep -e 'suffix.*same line'
|
||||
// to detect Russ's https://9fans.topicbox.com/groups/9fans/T9c9d81b5801a0820/ndb-suffix-specific-dns-changes
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/set"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
func NewOSConfigurator(logf logger.Logf, ht *health.Tracker, _ *eventbus.Bus, _ policyclient.Client, knobs *controlknobs.Knobs, interfaceName string) (OSConfigurator, error) {
|
||||
return &plan9DNSManager{
|
||||
logf: logf,
|
||||
ht: ht,
|
||||
knobs: knobs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type plan9DNSManager struct {
|
||||
logf logger.Logf
|
||||
ht *health.Tracker
|
||||
knobs *controlknobs.Knobs
|
||||
}
|
||||
|
||||
// netNDBBytesWithoutTailscale returns raw (the contents of /net/ndb) with any
|
||||
// Tailscale bits removed.
|
||||
func netNDBBytesWithoutTailscale(raw []byte) ([]byte, error) {
|
||||
var ret bytes.Buffer
|
||||
bs := bufio.NewScanner(bytes.NewReader(raw))
|
||||
removeLine := set.Set[string]{}
|
||||
for bs.Scan() {
|
||||
t := bs.Text()
|
||||
if rest, ok := strings.CutPrefix(t, "#tailscaled-added-line:"); ok {
|
||||
removeLine.Add(strings.TrimSpace(rest))
|
||||
continue
|
||||
}
|
||||
trimmed := strings.TrimSpace(t)
|
||||
if removeLine.Contains(trimmed) {
|
||||
removeLine.Delete(trimmed)
|
||||
continue
|
||||
}
|
||||
|
||||
// Also remove any DNS line referencing *.ts.net. This is
|
||||
// Tailscale-specific (and won't work with, say, Headscale), but
|
||||
// the Headscale case will be covered by the #tailscaled-added-line
|
||||
// logic above, assuming the user didn't delete those comments.
|
||||
if (strings.HasPrefix(trimmed, "dns=") || strings.Contains(trimmed, "dnsdomain=")) &&
|
||||
strings.HasSuffix(trimmed, ".ts.net") {
|
||||
continue
|
||||
}
|
||||
|
||||
ret.WriteString(t)
|
||||
ret.WriteByte('\n')
|
||||
}
|
||||
return ret.Bytes(), bs.Err()
|
||||
}
|
||||
|
||||
// setNDBSuffix adds lines to tsFree (the contents of /net/ndb already cleaned
|
||||
// of Tailscale-added lines) to add the optional DNS search domain (e.g.
|
||||
// "foo.ts.net") and DNS server to it.
|
||||
func setNDBSuffix(tsFree []byte, suffix string) []byte {
|
||||
suffix = strings.TrimSuffix(suffix, ".")
|
||||
if suffix == "" {
|
||||
return tsFree
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
bs := bufio.NewScanner(bytes.NewReader(tsFree))
|
||||
var added []string
|
||||
addLine := func(s string) {
|
||||
added = append(added, strings.TrimSpace(s))
|
||||
buf.WriteString(s)
|
||||
}
|
||||
for bs.Scan() {
|
||||
buf.Write(bs.Bytes())
|
||||
buf.WriteByte('\n')
|
||||
|
||||
t := bs.Text()
|
||||
if suffix != "" && len(added) == 0 && strings.HasPrefix(t, "\tdns=") {
|
||||
addLine(fmt.Sprintf("\tdns=100.100.100.100 suffix=%s\n", suffix))
|
||||
addLine(fmt.Sprintf("\tdnsdomain=%s\n", suffix))
|
||||
}
|
||||
}
|
||||
bufTrim := bytes.TrimLeftFunc(buf.Bytes(), unicode.IsSpace)
|
||||
if len(added) == 0 {
|
||||
return bufTrim
|
||||
}
|
||||
var ret bytes.Buffer
|
||||
for _, s := range added {
|
||||
ret.WriteString("#tailscaled-added-line: ")
|
||||
ret.WriteString(s)
|
||||
ret.WriteString("\n")
|
||||
}
|
||||
ret.WriteString("\n")
|
||||
ret.Write(bufTrim)
|
||||
return ret.Bytes()
|
||||
}
|
||||
|
||||
func (m *plan9DNSManager) SetDNS(c OSConfig) error {
|
||||
ndbOnDisk, err := os.ReadFile("/net/ndb")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tsFree, err := netNDBBytesWithoutTailscale(ndbOnDisk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var suffix string
|
||||
if len(c.SearchDomains) > 0 {
|
||||
suffix = string(c.SearchDomains[0])
|
||||
}
|
||||
|
||||
newBuf := setNDBSuffix(tsFree, suffix)
|
||||
if !bytes.Equal(newBuf, ndbOnDisk) {
|
||||
if err := os.WriteFile("/net/ndb", newBuf, 0644); err != nil {
|
||||
return fmt.Errorf("writing /net/ndb: %w", err)
|
||||
}
|
||||
if f, err := os.OpenFile("/net/dns", os.O_RDWR, 0); err == nil {
|
||||
if _, err := io.WriteString(f, "refresh\n"); err != nil {
|
||||
f.Close()
|
||||
return fmt.Errorf("/net/dns refresh write: %w", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("/net/dns refresh close: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *plan9DNSManager) SupportsSplitDNS() bool { return false }
|
||||
|
||||
func (m *plan9DNSManager) Close() error {
|
||||
// TODO(bradfitz): remove the Tailscale bits from /net/ndb ideally
|
||||
return nil
|
||||
}
|
||||
|
||||
var dnsRegex = regexp.MustCompile(`\bdns=(\d+\.\d+\.\d+\.\d+)\b`)
|
||||
|
||||
func (m *plan9DNSManager) GetBaseConfig() (OSConfig, error) {
|
||||
var oc OSConfig
|
||||
f, err := os.Open("/net/ndb")
|
||||
if err != nil {
|
||||
return oc, err
|
||||
}
|
||||
defer f.Close()
|
||||
bs := bufio.NewScanner(f)
|
||||
for bs.Scan() {
|
||||
m := dnsRegex.FindSubmatch(bs.Bytes())
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
addr, err := netip.ParseAddr(string(m[1]))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
oc.Nameservers = append(oc.Nameservers, addr)
|
||||
}
|
||||
if err := bs.Err(); err != nil {
|
||||
return oc, err
|
||||
}
|
||||
|
||||
return oc, nil
|
||||
}
|
||||
6
vendor/tailscale.com/net/dns/manager_solaris.go
generated
vendored
6
vendor/tailscale.com/net/dns/manager_solaris.go
generated
vendored
@@ -7,8 +7,10 @@ import (
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ *controlknobs.Knobs, iface string) (OSConfigurator, error) {
|
||||
return newDirectManager(logf, health), nil
|
||||
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, bus *eventbus.Bus, _ policyclient.Client, _ *controlknobs.Knobs, iface string) (OSConfigurator, error) {
|
||||
return newDirectManager(logf, health, bus), nil
|
||||
}
|
||||
|
||||
99
vendor/tailscale.com/net/dns/manager_windows.go
generated
vendored
99
vendor/tailscale.com/net/dns/manager_windows.go
generated
vendored
@@ -16,7 +16,6 @@ import (
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -27,8 +26,13 @@ import (
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/eventbus"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/util/syspolicy/ptype"
|
||||
"tailscale.com/util/winutil"
|
||||
)
|
||||
|
||||
@@ -44,19 +48,26 @@ type windowsManager struct {
|
||||
knobs *controlknobs.Knobs // or nil
|
||||
nrptDB *nrptRuleDatabase
|
||||
wslManager *wslManager
|
||||
polc policyclient.Client
|
||||
|
||||
mu sync.Mutex
|
||||
unregisterPolicyChangeCb func() // called when the manager is closing
|
||||
|
||||
mu syncs.Mutex
|
||||
closing bool
|
||||
}
|
||||
|
||||
// NewOSConfigurator created a new OS configurator.
|
||||
//
|
||||
// The health tracker and the knobs may be nil.
|
||||
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, knobs *controlknobs.Knobs, interfaceName string) (OSConfigurator, error) {
|
||||
// The health tracker, eventbus and the knobs may be nil.
|
||||
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, bus *eventbus.Bus, polc policyclient.Client, knobs *controlknobs.Knobs, interfaceName string) (OSConfigurator, error) {
|
||||
if polc == nil {
|
||||
panic("nil policyclient.Client")
|
||||
}
|
||||
ret := &windowsManager{
|
||||
logf: logf,
|
||||
guid: interfaceName,
|
||||
knobs: knobs,
|
||||
polc: polc,
|
||||
wslManager: newWSLManager(logf, health),
|
||||
}
|
||||
|
||||
@@ -64,6 +75,11 @@ func NewOSConfigurator(logf logger.Logf, health *health.Tracker, knobs *controlk
|
||||
ret.nrptDB = newNRPTRuleDatabase(logf)
|
||||
}
|
||||
|
||||
var err error
|
||||
if ret.unregisterPolicyChangeCb, err = polc.RegisterChangeCallback(ret.sysPolicyChanged); err != nil {
|
||||
logf("error registering policy change callback: %v", err) // non-fatal
|
||||
}
|
||||
|
||||
go func() {
|
||||
// Log WSL status once at startup.
|
||||
if distros, err := wslDistros(); err != nil {
|
||||
@@ -148,7 +164,7 @@ func setTailscaleHosts(logf logger.Logf, prevHostsFile []byte, hosts []*HostEntr
|
||||
header = "# TailscaleHostsSectionStart"
|
||||
footer = "# TailscaleHostsSectionEnd"
|
||||
)
|
||||
var comments = []string{
|
||||
comments := []string{
|
||||
"# This section contains MagicDNS entries for Tailscale.",
|
||||
"# Do not edit this section manually.",
|
||||
}
|
||||
@@ -362,11 +378,9 @@ func (m *windowsManager) SetDNS(cfg OSConfig) error {
|
||||
// configuration only, routing one set of things to the "split"
|
||||
// resolver and the rest to the primary.
|
||||
|
||||
// Unconditionally disable dynamic DNS updates and NetBIOS on our
|
||||
// interfaces.
|
||||
if err := m.disableDynamicUpdates(); err != nil {
|
||||
m.logf("disableDynamicUpdates error: %v\n", err)
|
||||
}
|
||||
// Reconfigure DNS registration according to the [syspolicy.DNSRegistration]
|
||||
// policy setting, and unconditionally disable NetBIOS on our interfaces.
|
||||
m.reconfigureDNSRegistration()
|
||||
if err := m.disableNetBIOS(); err != nil {
|
||||
m.logf("disableNetBIOS error: %v\n", err)
|
||||
}
|
||||
@@ -485,6 +499,10 @@ func (m *windowsManager) Close() error {
|
||||
m.closing = true
|
||||
m.mu.Unlock()
|
||||
|
||||
if m.unregisterPolicyChangeCb != nil {
|
||||
m.unregisterPolicyChangeCb()
|
||||
}
|
||||
|
||||
err := m.SetDNS(OSConfig{})
|
||||
if m.nrptDB != nil {
|
||||
m.nrptDB.Close()
|
||||
@@ -493,15 +511,62 @@ func (m *windowsManager) Close() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// disableDynamicUpdates sets the appropriate registry values to prevent the
|
||||
// Windows DHCP client from sending dynamic DNS updates for our interface to
|
||||
// AD domain controllers.
|
||||
func (m *windowsManager) disableDynamicUpdates() error {
|
||||
// sysPolicyChanged is a callback triggered by [syspolicy] when it detects
|
||||
// a change in one or more syspolicy settings.
|
||||
func (m *windowsManager) sysPolicyChanged(policy policyclient.PolicyChange) {
|
||||
if policy.HasChanged(pkey.EnableDNSRegistration) {
|
||||
m.reconfigureDNSRegistration()
|
||||
}
|
||||
}
|
||||
|
||||
// reconfigureDNSRegistration configures the DNS registration settings
|
||||
// using the [syspolicy.DNSRegistration] policy setting, if it is set.
|
||||
// If the policy is not configured, it disables DNS registration.
|
||||
func (m *windowsManager) reconfigureDNSRegistration() {
|
||||
// Disable DNS registration by default (if the policy setting is not configured).
|
||||
// This is primarily for historical reasons and to avoid breaking existing
|
||||
// setups that rely on this behavior.
|
||||
enableDNSRegistration, err := m.polc.GetPreferenceOption(pkey.EnableDNSRegistration, ptype.NeverByPolicy)
|
||||
if err != nil {
|
||||
m.logf("error getting DNSRegistration policy setting: %v", err) // non-fatal; we'll use the default
|
||||
}
|
||||
|
||||
if enableDNSRegistration.Show() {
|
||||
// "Show" reports whether the policy setting is configured as "user-decides".
|
||||
// The name is a bit unfortunate in this context, as we don't actually "show" anything.
|
||||
// Still, if the admin configured the policy as "user-decides", we shouldn't modify
|
||||
// the adapter's settings and should leave them up to the user (admin rights required)
|
||||
// or the system defaults.
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, if the policy setting is configured as "always" or "never",
|
||||
// we should configure the adapter accordingly.
|
||||
if err := m.configureDNSRegistration(enableDNSRegistration.IsAlways()); err != nil {
|
||||
m.logf("error configuring DNS registration: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// configureDNSRegistration sets the appropriate registry values to allow or prevent
|
||||
// the Windows DHCP client from registering Tailscale IP addresses with DNS
|
||||
// and sending dynamic updates for our interface to AD domain controllers.
|
||||
func (m *windowsManager) configureDNSRegistration(enabled bool) error {
|
||||
prefixen := []winutil.RegistryPathPrefix{
|
||||
winutil.IPv4TCPIPInterfacePrefix,
|
||||
winutil.IPv6TCPIPInterfacePrefix,
|
||||
}
|
||||
|
||||
var (
|
||||
registrationEnabled = uint32(0)
|
||||
disableDynamicUpdate = uint32(1)
|
||||
maxNumberOfAddressesToRegister = uint32(0)
|
||||
)
|
||||
if enabled {
|
||||
registrationEnabled = 1
|
||||
disableDynamicUpdate = 0
|
||||
maxNumberOfAddressesToRegister = 1
|
||||
}
|
||||
|
||||
for _, prefix := range prefixen {
|
||||
k, err := m.openInterfaceKey(prefix)
|
||||
if err != nil {
|
||||
@@ -509,13 +574,13 @@ func (m *windowsManager) disableDynamicUpdates() error {
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
if err := k.SetDWordValue("RegistrationEnabled", 0); err != nil {
|
||||
if err := k.SetDWordValue("RegistrationEnabled", registrationEnabled); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := k.SetDWordValue("DisableDynamicUpdate", 1); err != nil {
|
||||
if err := k.SetDWordValue("DisableDynamicUpdate", disableDynamicUpdate); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := k.SetDWordValue("MaxNumberOfAddressesToRegister", 0); err != nil {
|
||||
if err := k.SetDWordValue("MaxNumberOfAddressesToRegister", maxNumberOfAddressesToRegister); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
63
vendor/tailscale.com/net/dns/nm.go
generated
vendored
63
vendor/tailscale.com/net/dns/nm.go
generated
vendored
@@ -1,13 +1,14 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux
|
||||
//go:build linux && !android && !ts_omit_networkmanager
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/util/cmpver"
|
||||
"tailscale.com/util/dnsname"
|
||||
)
|
||||
|
||||
@@ -25,13 +27,6 @@ const (
|
||||
lowerPriority = int32(200) // lower than all builtin auto priorities
|
||||
)
|
||||
|
||||
// reconfigTimeout is the time interval within which Manager.{Up,Down} should complete.
|
||||
//
|
||||
// This is particularly useful because certain conditions can cause indefinite hangs
|
||||
// (such as improper dbus auth followed by contextless dbus.Object.Call).
|
||||
// Such operations should be wrapped in a timeout context.
|
||||
const reconfigTimeout = time.Second
|
||||
|
||||
// nmManager uses the NetworkManager DBus API.
|
||||
type nmManager struct {
|
||||
interfaceName string
|
||||
@@ -39,7 +34,13 @@ type nmManager struct {
|
||||
dnsManager dbus.BusObject
|
||||
}
|
||||
|
||||
func newNMManager(interfaceName string) (*nmManager, error) {
|
||||
func init() {
|
||||
optNewNMManager.Set(newNMManager)
|
||||
optNMIsUsingResolved.Set(nmIsUsingResolved)
|
||||
optNMVersionBetween.Set(nmVersionBetween)
|
||||
}
|
||||
|
||||
func newNMManager(interfaceName string) (OSConfigurator, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -389,3 +390,47 @@ func (m *nmManager) Close() error {
|
||||
// settings when the tailscale interface goes away.
|
||||
return nil
|
||||
}
|
||||
|
||||
func nmVersionBetween(first, last string) (bool, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
// DBus probably not running.
|
||||
return false, err
|
||||
}
|
||||
|
||||
nm := conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager"))
|
||||
v, err := nm.GetProperty("org.freedesktop.NetworkManager.Version")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
version, ok := v.Value().(string)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("unexpected type %T for NM version", v.Value())
|
||||
}
|
||||
|
||||
outside := cmpver.Compare(version, first) < 0 || cmpver.Compare(version, last) > 0
|
||||
return !outside, nil
|
||||
}
|
||||
|
||||
func nmIsUsingResolved() error {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
// DBus probably not running.
|
||||
return err
|
||||
}
|
||||
|
||||
nm := conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager/DnsManager"))
|
||||
v, err := nm.GetProperty("org.freedesktop.NetworkManager.DnsManager.Mode")
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting NM mode: %w", err)
|
||||
}
|
||||
mode, ok := v.Value().(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for NM DNS mode", v.Value())
|
||||
}
|
||||
if mode != "systemd-resolved" {
|
||||
return errors.New("NetworkManager is not using systemd-resolved for DNS")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
2
vendor/tailscale.com/net/dns/openresolv.go
generated
vendored
2
vendor/tailscale.com/net/dns/openresolv.go
generated
vendored
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux || freebsd || openbsd
|
||||
//go:build (linux && !android) || freebsd || openbsd
|
||||
|
||||
package dns
|
||||
|
||||
|
||||
5
vendor/tailscale.com/net/dns/osconfig.go
generated
vendored
5
vendor/tailscale.com/net/dns/osconfig.go
generated
vendored
@@ -11,6 +11,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/dnsname"
|
||||
)
|
||||
@@ -158,6 +159,10 @@ func (a OSConfig) Equal(b OSConfig) bool {
|
||||
// Fixes https://github.com/tailscale/tailscale/issues/5669
|
||||
func (a OSConfig) Format(f fmt.State, verb rune) {
|
||||
logger.ArgWriter(func(w *bufio.Writer) {
|
||||
if !buildfeatures.HasDNS {
|
||||
w.WriteString(`{DNS-unlinked}`)
|
||||
return
|
||||
}
|
||||
w.WriteString(`{Nameservers:[`)
|
||||
for i, ns := range a.Nameservers {
|
||||
if i != 0 {
|
||||
|
||||
5
vendor/tailscale.com/net/dns/publicdns/publicdns.go
generated
vendored
5
vendor/tailscale.com/net/dns/publicdns/publicdns.go
generated
vendored
@@ -17,6 +17,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
)
|
||||
|
||||
// dohOfIP maps from public DNS IPs to their DoH base URL.
|
||||
@@ -163,6 +165,9 @@ const (
|
||||
|
||||
// populate is called once to initialize the knownDoH and dohIPsOfBase maps.
|
||||
func populate() {
|
||||
if !buildfeatures.HasDNS {
|
||||
return
|
||||
}
|
||||
// Cloudflare
|
||||
// https://developers.cloudflare.com/1.1.1.1/ip-addresses/
|
||||
addDoH("1.1.1.1", "https://cloudflare-dns.com/dns-query")
|
||||
|
||||
621
vendor/tailscale.com/net/dns/recursive/recursive.go
generated
vendored
621
vendor/tailscale.com/net/dns/recursive/recursive.go
generated
vendored
@@ -1,621 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package recursive implements a simple recursive DNS resolver.
|
||||
package recursive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/util/multierr"
|
||||
"tailscale.com/util/slicesx"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxDepth is how deep from the root nameservers we'll recurse when
|
||||
// resolving; passing this limit will instead return an error.
|
||||
//
|
||||
// maxDepth must be at least 20 to resolve "console.aws.amazon.com",
|
||||
// which is a domain with a moderately complicated DNS setup. The
|
||||
// current value of 30 was chosen semi-arbitrarily to ensure that we
|
||||
// have about 50% headroom.
|
||||
maxDepth = 30
|
||||
// numStartingServers is the number of root nameservers that we use as
|
||||
// initial candidates for our recursion.
|
||||
numStartingServers = 3
|
||||
// udpQueryTimeout is the amount of time we wait for a UDP response
|
||||
// from a nameserver before falling back to a TCP connection.
|
||||
udpQueryTimeout = 5 * time.Second
|
||||
|
||||
// These constants aren't typed in the DNS package, so we create typed
|
||||
// versions here to avoid having to do repeated type casts.
|
||||
qtypeA dns.Type = dns.Type(dns.TypeA)
|
||||
qtypeAAAA dns.Type = dns.Type(dns.TypeAAAA)
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMaxDepth is returned when recursive resolving exceeds the maximum
|
||||
// depth limit for this package.
|
||||
ErrMaxDepth = fmt.Errorf("exceeded max depth %d when resolving", maxDepth)
|
||||
|
||||
// ErrAuthoritativeNoResponses is the error returned when an
|
||||
// authoritative nameserver indicates that there are no responses to
|
||||
// the given query.
|
||||
ErrAuthoritativeNoResponses = errors.New("authoritative server returned no responses")
|
||||
|
||||
// ErrNoResponses is returned when our resolution process completes
|
||||
// with no valid responses from any nameserver, but no authoritative
|
||||
// server explicitly returned NXDOMAIN.
|
||||
ErrNoResponses = errors.New("no responses to query")
|
||||
)
|
||||
|
||||
var rootServersV4 = []netip.Addr{
|
||||
netip.MustParseAddr("198.41.0.4"), // a.root-servers.net
|
||||
netip.MustParseAddr("170.247.170.2"), // b.root-servers.net
|
||||
netip.MustParseAddr("192.33.4.12"), // c.root-servers.net
|
||||
netip.MustParseAddr("199.7.91.13"), // d.root-servers.net
|
||||
netip.MustParseAddr("192.203.230.10"), // e.root-servers.net
|
||||
netip.MustParseAddr("192.5.5.241"), // f.root-servers.net
|
||||
netip.MustParseAddr("192.112.36.4"), // g.root-servers.net
|
||||
netip.MustParseAddr("198.97.190.53"), // h.root-servers.net
|
||||
netip.MustParseAddr("192.36.148.17"), // i.root-servers.net
|
||||
netip.MustParseAddr("192.58.128.30"), // j.root-servers.net
|
||||
netip.MustParseAddr("193.0.14.129"), // k.root-servers.net
|
||||
netip.MustParseAddr("199.7.83.42"), // l.root-servers.net
|
||||
netip.MustParseAddr("202.12.27.33"), // m.root-servers.net
|
||||
}
|
||||
|
||||
var rootServersV6 = []netip.Addr{
|
||||
netip.MustParseAddr("2001:503:ba3e::2:30"), // a.root-servers.net
|
||||
netip.MustParseAddr("2801:1b8:10::b"), // b.root-servers.net
|
||||
netip.MustParseAddr("2001:500:2::c"), // c.root-servers.net
|
||||
netip.MustParseAddr("2001:500:2d::d"), // d.root-servers.net
|
||||
netip.MustParseAddr("2001:500:a8::e"), // e.root-servers.net
|
||||
netip.MustParseAddr("2001:500:2f::f"), // f.root-servers.net
|
||||
netip.MustParseAddr("2001:500:12::d0d"), // g.root-servers.net
|
||||
netip.MustParseAddr("2001:500:1::53"), // h.root-servers.net
|
||||
netip.MustParseAddr("2001:7fe::53"), // i.root-servers.net
|
||||
netip.MustParseAddr("2001:503:c27::2:30"), // j.root-servers.net
|
||||
netip.MustParseAddr("2001:7fd::1"), // k.root-servers.net
|
||||
netip.MustParseAddr("2001:500:9f::42"), // l.root-servers.net
|
||||
netip.MustParseAddr("2001:dc3::35"), // m.root-servers.net
|
||||
}
|
||||
|
||||
var debug = envknob.RegisterBool("TS_DEBUG_RECURSIVE_DNS")
|
||||
|
||||
// Resolver is a recursive DNS resolver that is designed for looking up A and AAAA records.
|
||||
type Resolver struct {
|
||||
// Dialer is used to create outbound connections. If nil, a zero
|
||||
// net.Dialer will be used instead.
|
||||
Dialer netns.Dialer
|
||||
|
||||
// Logf is the logging function to use; if none is specified, then logs
|
||||
// will be dropped.
|
||||
Logf logger.Logf
|
||||
|
||||
// NoIPv6, if set, will prevent this package from querying for AAAA
|
||||
// records and will avoid contacting nameservers over IPv6.
|
||||
NoIPv6 bool
|
||||
|
||||
// Test mocks
|
||||
testQueryHook func(name dnsname.FQDN, nameserver netip.Addr, protocol string, qtype dns.Type) (*dns.Msg, error)
|
||||
testExchangeHook func(nameserver netip.Addr, network string, msg *dns.Msg) (*dns.Msg, error)
|
||||
rootServers []netip.Addr
|
||||
timeNow func() time.Time
|
||||
|
||||
// Caching
|
||||
// NOTE(andrew): if we make resolution parallel, this needs a mutex
|
||||
queryCache map[dnsQuery]dnsMsgWithExpiry
|
||||
|
||||
// Possible future additions:
|
||||
// - Additional nameservers? From the system maybe?
|
||||
// - NoIPv4 for IPv4
|
||||
// - DNS-over-HTTPS or DNS-over-TLS support
|
||||
}
|
||||
|
||||
// queryState stores all state during the course of a single query
|
||||
type queryState struct {
|
||||
// rootServers are the root nameservers to start from
|
||||
rootServers []netip.Addr
|
||||
|
||||
// TODO: metrics?
|
||||
}
|
||||
|
||||
type dnsQuery struct {
|
||||
nameserver netip.Addr
|
||||
name dnsname.FQDN
|
||||
qtype dns.Type
|
||||
}
|
||||
|
||||
func (q dnsQuery) String() string {
|
||||
return fmt.Sprintf("dnsQuery{nameserver:%q,name:%q,qtype:%v}", q.nameserver.String(), q.name, q.qtype)
|
||||
}
|
||||
|
||||
type dnsMsgWithExpiry struct {
|
||||
*dns.Msg
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
func (r *Resolver) now() time.Time {
|
||||
if r.timeNow != nil {
|
||||
return r.timeNow()
|
||||
}
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func (r *Resolver) logf(format string, args ...any) {
|
||||
if r.Logf == nil {
|
||||
return
|
||||
}
|
||||
r.Logf(format, args...)
|
||||
}
|
||||
|
||||
func (r *Resolver) depthlogf(depth int, format string, args ...any) {
|
||||
if r.Logf == nil || !debug() {
|
||||
return
|
||||
}
|
||||
prefix := fmt.Sprintf("[%d] %s", depth, strings.Repeat(" ", depth))
|
||||
r.Logf(prefix+format, args...)
|
||||
}
|
||||
|
||||
var defaultDialer net.Dialer
|
||||
|
||||
func (r *Resolver) dialer() netns.Dialer {
|
||||
if r.Dialer != nil {
|
||||
return r.Dialer
|
||||
}
|
||||
|
||||
return &defaultDialer
|
||||
}
|
||||
|
||||
func (r *Resolver) newState() *queryState {
|
||||
var rootServers []netip.Addr
|
||||
if len(r.rootServers) > 0 {
|
||||
rootServers = r.rootServers
|
||||
} else {
|
||||
// Select a random subset of root nameservers to start from, since if
|
||||
// we don't get responses from those, something else has probably gone
|
||||
// horribly wrong.
|
||||
roots4 := slices.Clone(rootServersV4)
|
||||
slicesx.Shuffle(roots4)
|
||||
roots4 = roots4[:numStartingServers]
|
||||
|
||||
var roots6 []netip.Addr
|
||||
if !r.NoIPv6 {
|
||||
roots6 = slices.Clone(rootServersV6)
|
||||
slicesx.Shuffle(roots6)
|
||||
roots6 = roots6[:numStartingServers]
|
||||
}
|
||||
|
||||
// Interleave the root servers so that we try to contact them over
|
||||
// IPv4, then IPv6, IPv4, IPv6, etc.
|
||||
rootServers = slicesx.Interleave(roots4, roots6)
|
||||
}
|
||||
|
||||
return &queryState{
|
||||
rootServers: rootServers,
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve will perform a recursive DNS resolution for the provided name,
|
||||
// starting at a randomly-chosen root DNS server, and return the A and AAAA
|
||||
// responses as a slice of netip.Addrs along with the minimum TTL for the
|
||||
// returned records.
|
||||
func (r *Resolver) Resolve(ctx context.Context, name string) (addrs []netip.Addr, minTTL time.Duration, err error) {
|
||||
dnsName, err := dnsname.ToFQDN(name)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
qstate := r.newState()
|
||||
|
||||
r.logf("querying IPv4 addresses for: %q", name)
|
||||
addrs4, minTTL4, err4 := r.resolveRecursiveFromRoot(ctx, qstate, 0, dnsName, qtypeA)
|
||||
|
||||
var (
|
||||
addrs6 []netip.Addr
|
||||
minTTL6 time.Duration
|
||||
err6 error
|
||||
)
|
||||
if !r.NoIPv6 {
|
||||
r.logf("querying IPv6 addresses for: %q", name)
|
||||
addrs6, minTTL6, err6 = r.resolveRecursiveFromRoot(ctx, qstate, 0, dnsName, qtypeAAAA)
|
||||
}
|
||||
|
||||
if err4 != nil && err6 != nil {
|
||||
if err4 == err6 {
|
||||
return nil, 0, err4
|
||||
}
|
||||
|
||||
return nil, 0, multierr.New(err4, err6)
|
||||
}
|
||||
if err4 != nil {
|
||||
return addrs6, minTTL6, nil
|
||||
} else if err6 != nil {
|
||||
return addrs4, minTTL4, nil
|
||||
}
|
||||
|
||||
minTTL = minTTL4
|
||||
if minTTL6 < minTTL {
|
||||
minTTL = minTTL6
|
||||
}
|
||||
|
||||
addrs = append(addrs4, addrs6...)
|
||||
if len(addrs) == 0 {
|
||||
return nil, 0, ErrNoResponses
|
||||
}
|
||||
|
||||
slicesx.Shuffle(addrs)
|
||||
return addrs, minTTL, nil
|
||||
}
|
||||
|
||||
func (r *Resolver) resolveRecursiveFromRoot(
|
||||
ctx context.Context,
|
||||
qstate *queryState,
|
||||
depth int,
|
||||
name dnsname.FQDN, // what we're querying
|
||||
qtype dns.Type,
|
||||
) ([]netip.Addr, time.Duration, error) {
|
||||
r.depthlogf(depth, "resolving %q from root (type: %v)", name, qtype)
|
||||
|
||||
var depthError bool
|
||||
for _, server := range qstate.rootServers {
|
||||
addrs, minTTL, err := r.resolveRecursive(ctx, qstate, depth, name, server, qtype)
|
||||
if err == nil {
|
||||
return addrs, minTTL, err
|
||||
} else if errors.Is(err, ErrAuthoritativeNoResponses) {
|
||||
return nil, 0, ErrAuthoritativeNoResponses
|
||||
} else if errors.Is(err, ErrMaxDepth) {
|
||||
depthError = true
|
||||
}
|
||||
}
|
||||
|
||||
if depthError {
|
||||
return nil, 0, ErrMaxDepth
|
||||
}
|
||||
return nil, 0, ErrNoResponses
|
||||
}
|
||||
|
||||
func (r *Resolver) resolveRecursive(
|
||||
ctx context.Context,
|
||||
qstate *queryState,
|
||||
depth int,
|
||||
name dnsname.FQDN, // what we're querying
|
||||
nameserver netip.Addr,
|
||||
qtype dns.Type,
|
||||
) ([]netip.Addr, time.Duration, error) {
|
||||
if depth == maxDepth {
|
||||
r.depthlogf(depth, "not recursing past maximum depth")
|
||||
return nil, 0, ErrMaxDepth
|
||||
}
|
||||
|
||||
// Ask this nameserver for an answer.
|
||||
resp, err := r.queryNameserver(ctx, depth, name, nameserver, qtype)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// If we get an actual answer from the nameserver, then return it.
|
||||
var (
|
||||
answers []netip.Addr
|
||||
cnames []dnsname.FQDN
|
||||
minTTL = 24 * 60 * 60 // 24 hours in seconds
|
||||
)
|
||||
for _, answer := range resp.Answer {
|
||||
if crec, ok := answer.(*dns.CNAME); ok {
|
||||
cnameFQDN, err := dnsname.ToFQDN(crec.Target)
|
||||
if err != nil {
|
||||
r.logf("bad CNAME %q returned: %v", crec.Target, err)
|
||||
continue
|
||||
}
|
||||
|
||||
cnames = append(cnames, cnameFQDN)
|
||||
continue
|
||||
}
|
||||
|
||||
addr := addrFromRecord(answer)
|
||||
if !addr.IsValid() {
|
||||
r.logf("[unexpected] invalid record in %T answer", answer)
|
||||
} else if addr.Is4() && qtype != qtypeA {
|
||||
r.logf("[unexpected] got IPv4 answer but qtype=%v", qtype)
|
||||
} else if addr.Is6() && qtype != qtypeAAAA {
|
||||
r.logf("[unexpected] got IPv6 answer but qtype=%v", qtype)
|
||||
} else {
|
||||
answers = append(answers, addr)
|
||||
minTTL = min(minTTL, int(answer.Header().Ttl))
|
||||
}
|
||||
}
|
||||
|
||||
if len(answers) > 0 {
|
||||
r.depthlogf(depth, "got answers for %q: %v", name, answers)
|
||||
return answers, time.Duration(minTTL) * time.Second, nil
|
||||
}
|
||||
|
||||
r.depthlogf(depth, "no answers for %q", name)
|
||||
|
||||
// If we have a non-zero number of CNAMEs, then try resolving those
|
||||
// (from the root again) and return the first one that succeeds.
|
||||
//
|
||||
// TODO: return the union of all responses?
|
||||
// TODO: parallelism?
|
||||
if len(cnames) > 0 {
|
||||
r.depthlogf(depth, "got CNAME responses for %q: %v", name, cnames)
|
||||
}
|
||||
var cnameDepthError bool
|
||||
for _, cname := range cnames {
|
||||
answers, minTTL, err := r.resolveRecursiveFromRoot(ctx, qstate, depth+1, cname, qtype)
|
||||
if err == nil {
|
||||
return answers, minTTL, nil
|
||||
} else if errors.Is(err, ErrAuthoritativeNoResponses) {
|
||||
return nil, 0, ErrAuthoritativeNoResponses
|
||||
} else if errors.Is(err, ErrMaxDepth) {
|
||||
cnameDepthError = true
|
||||
}
|
||||
}
|
||||
|
||||
// If this is an authoritative response, then we know that continuing
|
||||
// to look further is not going to result in any answers and we should
|
||||
// bail out.
|
||||
if resp.MsgHdr.Authoritative {
|
||||
// If we failed to recurse into a CNAME due to a depth limit,
|
||||
// propagate that here.
|
||||
if cnameDepthError {
|
||||
return nil, 0, ErrMaxDepth
|
||||
}
|
||||
|
||||
r.depthlogf(depth, "got authoritative response with no answers; stopping")
|
||||
return nil, 0, ErrAuthoritativeNoResponses
|
||||
}
|
||||
|
||||
r.depthlogf(depth, "got %d NS responses and %d ADDITIONAL responses for %q", len(resp.Ns), len(resp.Extra), name)
|
||||
|
||||
// No CNAMEs and no answers; see if we got any AUTHORITY responses,
|
||||
// which indicate which nameservers to query next.
|
||||
var authorities []dnsname.FQDN
|
||||
for _, rr := range resp.Ns {
|
||||
ns, ok := rr.(*dns.NS)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
nsName, err := dnsname.ToFQDN(ns.Ns)
|
||||
if err != nil {
|
||||
r.logf("unexpected bad NS name %q: %v", ns.Ns, err)
|
||||
continue
|
||||
}
|
||||
|
||||
authorities = append(authorities, nsName)
|
||||
}
|
||||
|
||||
// Also check for "glue" records, which are IP addresses provided by
|
||||
// the DNS server for authority responses; these are required when the
|
||||
// authority server is a subdomain of what's being resolved.
|
||||
glueRecords := make(map[dnsname.FQDN][]netip.Addr)
|
||||
for _, rr := range resp.Extra {
|
||||
name, err := dnsname.ToFQDN(rr.Header().Name)
|
||||
if err != nil {
|
||||
r.logf("unexpected bad Name %q in Extra addr: %v", rr.Header().Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if addr := addrFromRecord(rr); addr.IsValid() {
|
||||
glueRecords[name] = append(glueRecords[name], addr)
|
||||
} else {
|
||||
r.logf("unexpected bad Extra %T addr", rr)
|
||||
}
|
||||
}
|
||||
|
||||
// Try authorities with glue records first, to minimize the number of
|
||||
// additional DNS queries that we need to make.
|
||||
authoritiesGlue, authoritiesNoGlue := slicesx.Partition(authorities, func(aa dnsname.FQDN) bool {
|
||||
return len(glueRecords[aa]) > 0
|
||||
})
|
||||
|
||||
authorityDepthError := false
|
||||
|
||||
r.depthlogf(depth, "authorities with glue records for recursion: %v", authoritiesGlue)
|
||||
for _, authority := range authoritiesGlue {
|
||||
for _, nameserver := range glueRecords[authority] {
|
||||
answers, minTTL, err := r.resolveRecursive(ctx, qstate, depth+1, name, nameserver, qtype)
|
||||
if err == nil {
|
||||
return answers, minTTL, nil
|
||||
} else if errors.Is(err, ErrAuthoritativeNoResponses) {
|
||||
return nil, 0, ErrAuthoritativeNoResponses
|
||||
} else if errors.Is(err, ErrMaxDepth) {
|
||||
authorityDepthError = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.depthlogf(depth, "authorities with no glue records for recursion: %v", authoritiesNoGlue)
|
||||
for _, authority := range authoritiesNoGlue {
|
||||
// First, resolve the IP for the authority server from the
|
||||
// root, querying for both IPv4 and IPv6 addresses regardless
|
||||
// of what the current question type is.
|
||||
//
|
||||
// TODO: check for infinite recursion; it'll get caught by our
|
||||
// recursion depth, but we want to bail early.
|
||||
for _, authorityQtype := range []dns.Type{qtypeAAAA, qtypeA} {
|
||||
answers, _, err := r.resolveRecursiveFromRoot(ctx, qstate, depth+1, authority, authorityQtype)
|
||||
if err != nil {
|
||||
r.depthlogf(depth, "error querying authority %q: %v", authority, err)
|
||||
continue
|
||||
}
|
||||
r.depthlogf(depth, "resolved authority %q (type %v) to: %v", authority, authorityQtype, answers)
|
||||
|
||||
// Now, query this authority for the final address.
|
||||
for _, nameserver := range answers {
|
||||
answers, minTTL, err := r.resolveRecursive(ctx, qstate, depth+1, name, nameserver, qtype)
|
||||
if err == nil {
|
||||
return answers, minTTL, nil
|
||||
} else if errors.Is(err, ErrAuthoritativeNoResponses) {
|
||||
return nil, 0, ErrAuthoritativeNoResponses
|
||||
} else if errors.Is(err, ErrMaxDepth) {
|
||||
authorityDepthError = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if authorityDepthError {
|
||||
return nil, 0, ErrMaxDepth
|
||||
}
|
||||
return nil, 0, ErrNoResponses
|
||||
}
|
||||
|
||||
// queryNameserver sends a query for "name" to the nameserver "nameserver" for
|
||||
// records of type "qtype", trying both UDP and TCP connections as
|
||||
// appropriate.
|
||||
func (r *Resolver) queryNameserver(
|
||||
ctx context.Context,
|
||||
depth int,
|
||||
name dnsname.FQDN, // what we're querying
|
||||
nameserver netip.Addr, // destination of query
|
||||
qtype dns.Type,
|
||||
) (*dns.Msg, error) {
|
||||
// TODO(andrew): we should QNAME minimisation here to avoid sending the
|
||||
// full name to intermediate/root nameservers. See:
|
||||
// https://www.rfc-editor.org/rfc/rfc7816
|
||||
|
||||
// Handle the case where UDP is blocked by adding an explicit timeout
|
||||
// for the UDP portion of this query.
|
||||
udpCtx, udpCtxCancel := context.WithTimeout(ctx, udpQueryTimeout)
|
||||
defer udpCtxCancel()
|
||||
|
||||
msg, err := r.queryNameserverProto(udpCtx, depth, name, nameserver, "udp", qtype)
|
||||
if err == nil {
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
msg, err2 := r.queryNameserverProto(ctx, depth, name, nameserver, "tcp", qtype)
|
||||
if err2 == nil {
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
return nil, multierr.New(err, err2)
|
||||
}
|
||||
|
||||
// queryNameserverProto sends a query for "name" to the nameserver "nameserver"
|
||||
// for records of type "qtype" over the provided protocol (either "udp"
|
||||
// or "tcp"), and returns the DNS response or an error.
|
||||
func (r *Resolver) queryNameserverProto(
|
||||
ctx context.Context,
|
||||
depth int,
|
||||
name dnsname.FQDN, // what we're querying
|
||||
nameserver netip.Addr, // destination of query
|
||||
protocol string,
|
||||
qtype dns.Type,
|
||||
) (resp *dns.Msg, err error) {
|
||||
if r.testQueryHook != nil {
|
||||
return r.testQueryHook(name, nameserver, protocol, qtype)
|
||||
}
|
||||
|
||||
now := r.now()
|
||||
nameserverStr := nameserver.String()
|
||||
|
||||
cacheKey := dnsQuery{
|
||||
nameserver: nameserver,
|
||||
name: name,
|
||||
qtype: qtype,
|
||||
}
|
||||
cacheEntry, ok := r.queryCache[cacheKey]
|
||||
if ok && cacheEntry.expiresAt.Before(now) {
|
||||
r.depthlogf(depth, "using cached response from %s about %q (type: %v)", nameserverStr, name, qtype)
|
||||
return cacheEntry.Msg, nil
|
||||
}
|
||||
|
||||
var network string
|
||||
if nameserver.Is4() {
|
||||
network = protocol + "4"
|
||||
} else {
|
||||
network = protocol + "6"
|
||||
}
|
||||
|
||||
// Prepare a message asking for an appropriately-typed record
|
||||
// for the name we're querying.
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(name.WithTrailingDot(), uint16(qtype))
|
||||
|
||||
// Allow mocking out the network components with our exchange hook.
|
||||
if r.testExchangeHook != nil {
|
||||
resp, err = r.testExchangeHook(nameserver, network, m)
|
||||
} else {
|
||||
// Dial the current nameserver using our dialer.
|
||||
var nconn net.Conn
|
||||
nconn, err = r.dialer().DialContext(ctx, network, net.JoinHostPort(nameserverStr, "53"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var c dns.Client // TODO: share?
|
||||
conn := &dns.Conn{
|
||||
Conn: nconn,
|
||||
UDPSize: c.UDPSize,
|
||||
}
|
||||
|
||||
// Send the DNS request to the current nameserver.
|
||||
r.depthlogf(depth, "asking %s over %s about %q (type: %v)", nameserverStr, protocol, name, qtype)
|
||||
resp, _, err = c.ExchangeWithConnContext(ctx, m, conn)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the message was truncated and we're using UDP, re-run with TCP.
|
||||
if resp.MsgHdr.Truncated && protocol == "udp" {
|
||||
r.depthlogf(depth, "response message truncated; re-running query with TCP")
|
||||
resp, err = r.queryNameserverProto(ctx, depth, name, nameserver, "tcp", qtype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Find minimum expiry for all records in this message.
|
||||
var minTTL int
|
||||
for _, rr := range resp.Answer {
|
||||
minTTL = min(minTTL, int(rr.Header().Ttl))
|
||||
}
|
||||
for _, rr := range resp.Ns {
|
||||
minTTL = min(minTTL, int(rr.Header().Ttl))
|
||||
}
|
||||
for _, rr := range resp.Extra {
|
||||
minTTL = min(minTTL, int(rr.Header().Ttl))
|
||||
}
|
||||
|
||||
mak.Set(&r.queryCache, cacheKey, dnsMsgWithExpiry{
|
||||
Msg: resp,
|
||||
expiresAt: now.Add(time.Duration(minTTL) * time.Second),
|
||||
})
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func addrFromRecord(rr dns.RR) netip.Addr {
|
||||
switch v := rr.(type) {
|
||||
case *dns.A:
|
||||
ip, ok := netip.AddrFromSlice(v.A)
|
||||
if !ok || !ip.Is4() {
|
||||
return netip.Addr{}
|
||||
}
|
||||
return ip
|
||||
case *dns.AAAA:
|
||||
ip, ok := netip.AddrFromSlice(v.AAAA)
|
||||
if !ok || !ip.Is6() {
|
||||
return netip.Addr{}
|
||||
}
|
||||
return ip
|
||||
}
|
||||
return netip.Addr{}
|
||||
}
|
||||
10
vendor/tailscale.com/net/dns/resolved.go
generated
vendored
10
vendor/tailscale.com/net/dns/resolved.go
generated
vendored
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux
|
||||
//go:build linux && !android && !ts_omit_resolved
|
||||
|
||||
package dns
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"github.com/godbus/dbus/v5"
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/logtail/backoff"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/backoff"
|
||||
"tailscale.com/util/dnsname"
|
||||
)
|
||||
|
||||
@@ -70,7 +70,11 @@ type resolvedManager struct {
|
||||
configCR chan changeRequest // tracks OSConfigs changes and error responses
|
||||
}
|
||||
|
||||
func newResolvedManager(logf logger.Logf, health *health.Tracker, interfaceName string) (*resolvedManager, error) {
|
||||
func init() {
|
||||
optNewResolvedManager.Set(newResolvedManager)
|
||||
}
|
||||
|
||||
func newResolvedManager(logf logger.Logf, health *health.Tracker, interfaceName string) (OSConfigurator, error) {
|
||||
iface, err := net.InterfaceByName(interfaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
8
vendor/tailscale.com/net/dns/resolver/debug.go
generated
vendored
8
vendor/tailscale.com/net/dns/resolver/debug.go
generated
vendored
@@ -8,14 +8,18 @@ import (
|
||||
"html"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/syncs"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !buildfeatures.HasDNS {
|
||||
return
|
||||
}
|
||||
health.RegisterDebugHandler("dnsfwd", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
n, _ := strconv.Atoi(r.FormValue("n"))
|
||||
if n <= 0 {
|
||||
@@ -35,7 +39,7 @@ func init() {
|
||||
var fwdLogAtomic atomic.Pointer[fwdLog]
|
||||
|
||||
type fwdLog struct {
|
||||
mu sync.Mutex
|
||||
mu syncs.Mutex
|
||||
pos int // ent[pos] is next entry
|
||||
ent []fwdLogEntry
|
||||
}
|
||||
|
||||
109
vendor/tailscale.com/net/dns/resolver/forwarder.go
generated
vendored
109
vendor/tailscale.com/net/dns/resolver/forwarder.go
generated
vendored
@@ -17,6 +17,7 @@ import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -26,13 +27,17 @@ import (
|
||||
dns "golang.org/x/net/dns/dnsmessage"
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/net/dns/publicdns"
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/net/neterror"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netx"
|
||||
"tailscale.com/net/sockstats"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/nettype"
|
||||
@@ -215,18 +220,19 @@ type resolverAndDelay struct {
|
||||
|
||||
// forwarder forwards DNS packets to a number of upstream nameservers.
|
||||
type forwarder struct {
|
||||
logf logger.Logf
|
||||
netMon *netmon.Monitor // always non-nil
|
||||
linkSel ForwardLinkSelector // TODO(bradfitz): remove this when tsdial.Dialer absorbs it
|
||||
dialer *tsdial.Dialer
|
||||
health *health.Tracker // always non-nil
|
||||
logf logger.Logf
|
||||
netMon *netmon.Monitor // always non-nil
|
||||
linkSel ForwardLinkSelector // TODO(bradfitz): remove this when tsdial.Dialer absorbs it
|
||||
dialer *tsdial.Dialer
|
||||
health *health.Tracker // always non-nil
|
||||
verboseFwd bool // if true, log all DNS forwarding
|
||||
|
||||
controlKnobs *controlknobs.Knobs // or nil
|
||||
|
||||
ctx context.Context // good until Close
|
||||
ctxCancel context.CancelFunc // closes ctx
|
||||
|
||||
mu sync.Mutex // guards following
|
||||
mu syncs.Mutex // guards following
|
||||
|
||||
dohClient map[string]*http.Client // urlBase -> client
|
||||
|
||||
@@ -243,26 +249,23 @@ type forwarder struct {
|
||||
// /etc/resolv.conf is missing/corrupt, and the peerapi ExitDNS stub
|
||||
// resolver lookup.
|
||||
cloudHostFallback []resolverAndDelay
|
||||
|
||||
// missingUpstreamRecovery, if non-nil, is set called when a SERVFAIL is
|
||||
// returned due to missing upstream resolvers.
|
||||
//
|
||||
// This should attempt to properly (re)set the upstream resolvers.
|
||||
missingUpstreamRecovery func()
|
||||
}
|
||||
|
||||
func newForwarder(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkSelector, dialer *tsdial.Dialer, health *health.Tracker, knobs *controlknobs.Knobs) *forwarder {
|
||||
if !buildfeatures.HasDNS {
|
||||
return nil
|
||||
}
|
||||
if netMon == nil {
|
||||
panic("nil netMon")
|
||||
}
|
||||
f := &forwarder{
|
||||
logf: logger.WithPrefix(logf, "forward: "),
|
||||
netMon: netMon,
|
||||
linkSel: linkSel,
|
||||
dialer: dialer,
|
||||
health: health,
|
||||
controlKnobs: knobs,
|
||||
missingUpstreamRecovery: func() {},
|
||||
logf: logger.WithPrefix(logf, "forward: "),
|
||||
netMon: netMon,
|
||||
linkSel: linkSel,
|
||||
dialer: dialer,
|
||||
health: health,
|
||||
controlKnobs: knobs,
|
||||
verboseFwd: verboseDNSForward(),
|
||||
}
|
||||
f.ctx, f.ctxCancel = context.WithCancel(context.Background())
|
||||
return f
|
||||
@@ -520,15 +523,18 @@ var (
|
||||
//
|
||||
// send expects the reply to have the same txid as txidOut.
|
||||
func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDelay) (ret []byte, err error) {
|
||||
if verboseDNSForward() {
|
||||
if f.verboseFwd {
|
||||
id := forwarderCount.Add(1)
|
||||
domain, typ, _ := nameFromQuery(fq.packet)
|
||||
f.logf("forwarder.send(%q, %d, %v, %d) [%d] ...", rr.name.Addr, fq.txid, typ, len(domain), id)
|
||||
f.logf("forwarder.send(%q, %d, %v, %d) from %v [%d] ...", rr.name.Addr, fq.txid, typ, len(domain), fq.src, id)
|
||||
defer func() {
|
||||
f.logf("forwarder.send(%q, %d, %v, %d) [%d] = %v, %v", rr.name.Addr, fq.txid, typ, len(domain), id, len(ret), err)
|
||||
f.logf("forwarder.send(%q, %d, %v, %d) from %v [%d] = %v, %v", rr.name.Addr, fq.txid, typ, len(domain), fq.src, id, len(ret), err)
|
||||
}()
|
||||
}
|
||||
if strings.HasPrefix(rr.name.Addr, "http://") {
|
||||
if !buildfeatures.HasPeerAPIClient {
|
||||
return nil, feature.ErrUnavailable
|
||||
}
|
||||
return f.sendDoH(ctx, rr.name.Addr, f.dialer.PeerAPIHTTPClient(), fq.packet)
|
||||
}
|
||||
if strings.HasPrefix(rr.name.Addr, "https://") {
|
||||
@@ -739,18 +745,38 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (f *forwarder) getDialerType() dnscache.DialContextFunc {
|
||||
if f.controlKnobs != nil && f.controlKnobs.UserDialUseRoutes.Load() {
|
||||
// It is safe to use UserDial as it dials external servers without going through Tailscale
|
||||
// and closes connections on interface change in the same way as SystemDial does,
|
||||
// thus preventing DNS resolution issues when switching between WiFi and cellular,
|
||||
// but can also dial an internal DNS server on the Tailnet or via a subnet router.
|
||||
//
|
||||
// TODO(nickkhyl): Update tsdial.Dialer to reuse the bart.Table we create in net/tstun.Wrapper
|
||||
// to avoid having two bart tables in memory, especially on iOS. Once that's done,
|
||||
// we can get rid of the nodeAttr/control knob and always use UserDial for DNS.
|
||||
//
|
||||
// See https://github.com/tailscale/tailscale/issues/12027.
|
||||
var optDNSForwardUseRoutes = envknob.RegisterOptBool("TS_DEBUG_DNS_FORWARD_USE_ROUTES")
|
||||
|
||||
// ShouldUseRoutes reports whether the DNS resolver should consider routes when dialing
|
||||
// upstream nameservers via TCP.
|
||||
//
|
||||
// If true, routes should be considered ([tsdial.Dialer.UserDial]), otherwise defer
|
||||
// to the system routes ([tsdial.Dialer.SystemDial]).
|
||||
//
|
||||
// TODO(nickkhyl): Update [tsdial.Dialer] to reuse the bart.Table we create in net/tstun.Wrapper
|
||||
// to avoid having two bart tables in memory, especially on iOS. Once that's done,
|
||||
// we can get rid of the nodeAttr/control knob and always use UserDial for DNS.
|
||||
//
|
||||
// See tailscale/tailscale#12027.
|
||||
func ShouldUseRoutes(knobs *controlknobs.Knobs) bool {
|
||||
if !buildfeatures.HasDNS {
|
||||
return false
|
||||
}
|
||||
switch runtime.GOOS {
|
||||
case "android", "ios":
|
||||
// On mobile platforms with lower memory limits (e.g., 50MB on iOS),
|
||||
// this behavior is still gated by the "user-dial-routes" nodeAttr.
|
||||
return knobs != nil && knobs.UserDialUseRoutes.Load()
|
||||
default:
|
||||
// On all other platforms, it is the default behavior,
|
||||
// but it can be overridden with the "TS_DEBUG_DNS_FORWARD_USE_ROUTES" env var.
|
||||
doNotUseRoutes := optDNSForwardUseRoutes().EqualBool(false)
|
||||
return !doNotUseRoutes
|
||||
}
|
||||
}
|
||||
|
||||
func (f *forwarder) getDialerType() netx.DialFunc {
|
||||
if ShouldUseRoutes(f.controlKnobs) {
|
||||
return f.dialer.UserDial
|
||||
}
|
||||
return f.dialer.SystemDial
|
||||
@@ -878,6 +904,7 @@ type forwardQuery struct {
|
||||
txid txid
|
||||
packet []byte
|
||||
family string // "tcp" or "udp"
|
||||
src netip.AddrPort
|
||||
|
||||
// closeOnCtxDone lets send register values to Close if the
|
||||
// caller's ctx expires. This avoids send from allocating its
|
||||
@@ -943,13 +970,6 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo
|
||||
f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: ""})
|
||||
f.logf("no upstream resolvers set, returning SERVFAIL")
|
||||
|
||||
// Attempt to recompile the DNS configuration
|
||||
// If we are being asked to forward queries and we have no
|
||||
// nameservers, the network is in a bad state.
|
||||
if f.missingUpstreamRecovery != nil {
|
||||
f.missingUpstreamRecovery()
|
||||
}
|
||||
|
||||
res, err := servfailResponse(query)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -969,11 +989,12 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo
|
||||
txid: getTxID(query.bs),
|
||||
packet: query.bs,
|
||||
family: query.family,
|
||||
src: query.addr,
|
||||
closeOnCtxDone: new(closePool),
|
||||
}
|
||||
defer fq.closeOnCtxDone.Close()
|
||||
|
||||
if verboseDNSForward() {
|
||||
if f.verboseFwd {
|
||||
domainSha256 := sha256.Sum256([]byte(domain))
|
||||
domainSig := base64.RawStdEncoding.EncodeToString(domainSha256[:3])
|
||||
f.logf("request(%d, %v, %d, %s) %d...", fq.txid, typ, len(domain), domainSig, len(fq.packet))
|
||||
@@ -1018,7 +1039,7 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo
|
||||
metricDNSFwdErrorContext.Add(1)
|
||||
return fmt.Errorf("waiting to send response: %w", ctx.Err())
|
||||
case responseChan <- packet{v, query.family, query.addr}:
|
||||
if verboseDNSForward() {
|
||||
if f.verboseFwd {
|
||||
f.logf("response(%d, %v, %d) = %d, nil", fq.txid, typ, len(domain), len(v))
|
||||
}
|
||||
metricDNSFwdSuccess.Add(1)
|
||||
@@ -1048,7 +1069,7 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo
|
||||
}
|
||||
f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: strings.Join(resolverAddrs, ",")})
|
||||
case responseChan <- res:
|
||||
if verboseDNSForward() {
|
||||
if f.verboseFwd {
|
||||
f.logf("forwarder response(%d, %v, %d) = %d, %v", fq.txid, typ, len(domain), len(res.bs), firstErr)
|
||||
}
|
||||
return nil
|
||||
|
||||
34
vendor/tailscale.com/net/dns/resolver/tsdns.go
generated
vendored
34
vendor/tailscale.com/net/dns/resolver/tsdns.go
generated
vendored
@@ -25,6 +25,8 @@ import (
|
||||
dns "golang.org/x/net/dns/dnsmessage"
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/net/dns/resolvconffile"
|
||||
"tailscale.com/net/netaddr"
|
||||
@@ -212,7 +214,7 @@ type Resolver struct {
|
||||
closed chan struct{}
|
||||
|
||||
// mu guards the following fields from being updated while used.
|
||||
mu sync.Mutex
|
||||
mu syncs.Mutex
|
||||
localDomains []dnsname.FQDN
|
||||
hostToIP map[dnsname.FQDN][]netip.Addr
|
||||
ipToHost map[netip.Addr]dnsname.FQDN
|
||||
@@ -251,18 +253,12 @@ func New(logf logger.Logf, linkSel ForwardLinkSelector, dialer *tsdial.Dialer, h
|
||||
return r
|
||||
}
|
||||
|
||||
// SetMissingUpstreamRecovery sets a callback to be called upon encountering
|
||||
// a SERVFAIL due to missing upstream resolvers.
|
||||
//
|
||||
// This call should only happen before the resolver is used. It is not safe
|
||||
// for concurrent use.
|
||||
func (r *Resolver) SetMissingUpstreamRecovery(f func()) {
|
||||
r.forwarder.missingUpstreamRecovery = f
|
||||
}
|
||||
|
||||
func (r *Resolver) TestOnlySetHook(hook func(Config)) { r.saveConfigForTests = hook }
|
||||
|
||||
func (r *Resolver) SetConfig(cfg Config) error {
|
||||
if !buildfeatures.HasDNS {
|
||||
return nil
|
||||
}
|
||||
if r.saveConfigForTests != nil {
|
||||
r.saveConfigForTests(cfg)
|
||||
}
|
||||
@@ -288,6 +284,9 @@ func (r *Resolver) SetConfig(cfg Config) error {
|
||||
// Close shuts down the resolver and ensures poll goroutines have exited.
|
||||
// The Resolver cannot be used again after Close is called.
|
||||
func (r *Resolver) Close() {
|
||||
if !buildfeatures.HasDNS {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-r.closed:
|
||||
return
|
||||
@@ -305,6 +304,9 @@ func (r *Resolver) Close() {
|
||||
const dnsQueryTimeout = 10 * time.Second
|
||||
|
||||
func (r *Resolver) Query(ctx context.Context, bs []byte, family string, from netip.AddrPort) ([]byte, error) {
|
||||
if !buildfeatures.HasDNS {
|
||||
return nil, feature.ErrUnavailable
|
||||
}
|
||||
metricDNSQueryLocal.Add(1)
|
||||
select {
|
||||
case <-r.closed:
|
||||
@@ -332,6 +334,9 @@ func (r *Resolver) Query(ctx context.Context, bs []byte, family string, from net
|
||||
// GetUpstreamResolvers returns the resolvers that would be used to resolve
|
||||
// the given FQDN.
|
||||
func (r *Resolver) GetUpstreamResolvers(name dnsname.FQDN) []*dnstype.Resolver {
|
||||
if !buildfeatures.HasDNS {
|
||||
return nil
|
||||
}
|
||||
return r.forwarder.GetUpstreamResolvers(name)
|
||||
}
|
||||
|
||||
@@ -360,6 +365,9 @@ func parseExitNodeQuery(q []byte) *response {
|
||||
// and a nil error.
|
||||
// TODO: figure out if we even need an error result.
|
||||
func (r *Resolver) HandlePeerDNSQuery(ctx context.Context, q []byte, from netip.AddrPort, allowName func(name string) bool) (res []byte, err error) {
|
||||
if !buildfeatures.HasDNS {
|
||||
return nil, feature.ErrUnavailable
|
||||
}
|
||||
metricDNSExitProxyQuery.Add(1)
|
||||
ch := make(chan packet, 1)
|
||||
|
||||
@@ -436,6 +444,9 @@ var debugExitNodeDNSNetPkg = envknob.RegisterBool("TS_DEBUG_EXIT_NODE_DNS_NET_PK
|
||||
// response contains the pre-serialized response, which notably
|
||||
// includes the original question and its header.
|
||||
func handleExitNodeDNSQueryWithNetPkg(ctx context.Context, logf logger.Logf, resolver *net.Resolver, resp *response) (res []byte, err error) {
|
||||
if !buildfeatures.HasDNS {
|
||||
return nil, feature.ErrUnavailable
|
||||
}
|
||||
logf = logger.WithPrefix(logf, "exitNodeDNSQueryWithNetPkg: ")
|
||||
if resp.Question.Class != dns.ClassINET {
|
||||
return nil, errors.New("unsupported class")
|
||||
@@ -1256,6 +1267,9 @@ func (r *Resolver) respondReverse(query []byte, name dnsname.FQDN, resp *respons
|
||||
// respond returns a DNS response to query if it can be resolved locally.
|
||||
// Otherwise, it returns errNotOurName.
|
||||
func (r *Resolver) respond(query []byte) ([]byte, error) {
|
||||
if !buildfeatures.HasDNS {
|
||||
return nil, feature.ErrUnavailable
|
||||
}
|
||||
parser := dnsParserPool.Get().(*dnsParser)
|
||||
defer dnsParserPool.Put(parser)
|
||||
|
||||
|
||||
2
vendor/tailscale.com/net/dns/wsl_windows.go
generated
vendored
2
vendor/tailscale.com/net/dns/wsl_windows.go
generated
vendored
@@ -76,7 +76,7 @@ func (wm *wslManager) SetDNS(cfg OSConfig) error {
|
||||
}
|
||||
managers := make(map[string]*directManager)
|
||||
for _, distro := range distros {
|
||||
managers[distro] = newDirectManagerOnFS(wm.logf, wm.health, wslFS{
|
||||
managers[distro] = newDirectManagerOnFS(wm.logf, wm.health, nil, wslFS{
|
||||
user: "root",
|
||||
distro: distro,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user