2023-01-27 21:37:20 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2020-07-31 20:27:09 +00:00
|
|
|
|
|
|
|
package dns
|
|
|
|
|
|
|
|
import (
|
2021-07-20 05:24:43 +00:00
|
|
|
"bufio"
|
2022-04-14 20:27:59 +00:00
|
|
|
"context"
|
2022-05-05 23:42:45 +00:00
|
|
|
"encoding/binary"
|
2022-04-14 20:27:59 +00:00
|
|
|
"errors"
|
2022-05-05 23:42:45 +00:00
|
|
|
"io"
|
2022-05-02 16:35:32 +00:00
|
|
|
"net"
|
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
|
|
|
"net/netip"
|
2021-04-26 22:14:41 +00:00
|
|
|
"runtime"
|
2023-08-17 05:09:53 +00:00
|
|
|
"slices"
|
2023-07-28 17:39:04 +00:00
|
|
|
"strings"
|
2022-04-14 20:27:59 +00:00
|
|
|
"sync/atomic"
|
2020-07-31 20:27:09 +00:00
|
|
|
"time"
|
|
|
|
|
2023-09-07 20:27:50 +00:00
|
|
|
"tailscale.com/control/controlknobs"
|
2021-11-18 23:52:21 +00:00
|
|
|
"tailscale.com/health"
|
2021-04-03 02:34:53 +00:00
|
|
|
"tailscale.com/net/dns/resolver"
|
2023-04-18 21:26:58 +00:00
|
|
|
"tailscale.com/net/netmon"
|
2021-12-01 04:39:12 +00:00
|
|
|
"tailscale.com/net/tsdial"
|
2021-08-03 13:56:31 +00:00
|
|
|
"tailscale.com/types/dnstype"
|
2020-07-31 20:27:09 +00:00
|
|
|
"tailscale.com/types/logger"
|
2022-04-14 20:27:59 +00:00
|
|
|
"tailscale.com/util/clientmetric"
|
2021-04-09 22:24:47 +00:00
|
|
|
"tailscale.com/util/dnsname"
|
2020-07-31 20:27:09 +00:00
|
|
|
)
|
|
|
|
|
2022-04-14 20:27:59 +00:00
|
|
|
var (
|
|
|
|
errFullQueue = errors.New("request queue full")
|
|
|
|
)
|
|
|
|
|
2023-01-05 19:27:17 +00:00
|
|
|
// maxActiveQueries returns the maximal number of DNS requests that can
|
|
|
|
// be running.
|
|
|
|
const maxActiveQueries = 256
|
2022-04-14 20:27:59 +00:00
|
|
|
|
2020-09-11 18:00:39 +00:00
|
|
|
// We use file-ignore below instead of ignore because on some platforms,
|
|
|
|
// the lint exception is necessary and on others it is not,
|
|
|
|
// and plain ignore complains if the exception is unnecessary.
|
|
|
|
|
2020-07-31 20:27:09 +00:00
|
|
|
// Manager manages system DNS settings.
|
|
|
|
type Manager struct {
|
2024-04-26 17:12:46 +00:00
|
|
|
logf logger.Logf
|
|
|
|
health *health.Tracker
|
2020-07-31 20:27:09 +00:00
|
|
|
|
2022-04-14 20:27:59 +00:00
|
|
|
activeQueriesAtomic int32
|
|
|
|
|
2022-05-02 16:35:32 +00:00
|
|
|
ctx context.Context // good until Down
|
|
|
|
ctxCancel context.CancelFunc // closes ctx
|
|
|
|
|
2021-04-03 02:34:53 +00:00
|
|
|
resolver *resolver.Resolver
|
|
|
|
os OSConfigurator
|
2024-06-07 14:36:10 +00:00
|
|
|
knobs *controlknobs.Knobs // or nil
|
|
|
|
goos string // if empty, gets set to runtime.GOOS
|
2020-07-31 20:27:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewManagers created a new manager from the given config.
|
2024-06-07 14:36:10 +00:00
|
|
|
//
|
|
|
|
// knobs may be nil.
|
2024-06-04 16:41:13 +00:00
|
|
|
func NewManager(logf logger.Logf, oscfg OSConfigurator, health *health.Tracker, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector, knobs *controlknobs.Knobs, goos string) *Manager {
|
2021-12-01 04:39:12 +00:00
|
|
|
if dialer == nil {
|
|
|
|
panic("nil Dialer")
|
|
|
|
}
|
2024-04-27 05:06:20 +00:00
|
|
|
if dialer.NetMon() == nil {
|
|
|
|
panic("Dialer has nil NetMon")
|
|
|
|
}
|
2021-04-02 06:26:52 +00:00
|
|
|
logf = logger.WithPrefix(logf, "dns: ")
|
2024-06-04 16:41:13 +00:00
|
|
|
if goos == "" {
|
|
|
|
goos = runtime.GOOS
|
|
|
|
}
|
2024-06-06 22:19:33 +00:00
|
|
|
|
2020-07-31 20:27:09 +00:00
|
|
|
m := &Manager{
|
2023-01-05 19:27:17 +00:00
|
|
|
logf: logf,
|
2024-04-27 05:06:20 +00:00
|
|
|
resolver: resolver.New(logf, linkSel, dialer, knobs),
|
2023-01-05 19:27:17 +00:00
|
|
|
os: oscfg,
|
2024-04-26 17:12:46 +00:00
|
|
|
health: health,
|
2024-06-06 22:19:33 +00:00
|
|
|
knobs: knobs,
|
2024-06-04 16:41:13 +00:00
|
|
|
goos: goos,
|
2020-07-31 20:27:09 +00:00
|
|
|
}
|
2022-05-02 16:35:32 +00:00
|
|
|
m.ctx, m.ctxCancel = context.WithCancel(context.Background())
|
2021-04-03 02:40:13 +00:00
|
|
|
m.logf("using %T", m.os)
|
2020-07-31 20:27:09 +00:00
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
2021-11-23 05:45:34 +00:00
|
|
|
// Resolver returns the Manager's DNS Resolver.
|
|
|
|
func (m *Manager) Resolver() *resolver.Resolver { return m.resolver }
|
|
|
|
|
2021-04-03 02:34:53 +00:00
|
|
|
func (m *Manager) Set(cfg Config) error {
|
2021-07-20 05:24:43 +00:00
|
|
|
m.logf("Set: %v", logger.ArgWriter(func(w *bufio.Writer) {
|
|
|
|
cfg.WriteToBufioWriter(w)
|
|
|
|
}))
|
2020-07-31 20:27:09 +00:00
|
|
|
|
2021-04-07 07:58:02 +00:00
|
|
|
rcfg, ocfg, err := m.compileConfig(cfg)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-04-07 05:00:59 +00:00
|
|
|
|
2021-07-20 05:24:43 +00:00
|
|
|
m.logf("Resolvercfg: %v", logger.ArgWriter(func(w *bufio.Writer) {
|
|
|
|
rcfg.WriteToBufioWriter(w)
|
|
|
|
}))
|
2023-09-23 18:21:47 +00:00
|
|
|
m.logf("OScfg: %v", logger.ArgWriter(func(w *bufio.Writer) {
|
|
|
|
ocfg.WriteToBufioWriter(w)
|
|
|
|
}))
|
2021-04-07 05:00:59 +00:00
|
|
|
|
|
|
|
if err := m.resolver.SetConfig(rcfg); err != nil {
|
|
|
|
return err
|
2021-04-03 02:34:53 +00:00
|
|
|
}
|
2021-04-07 05:00:59 +00:00
|
|
|
if err := m.os.SetDNS(ocfg); err != nil {
|
2024-04-26 17:12:46 +00:00
|
|
|
m.health.SetDNSOSHealth(err)
|
2021-04-07 05:00:59 +00:00
|
|
|
return err
|
2021-04-03 02:34:53 +00:00
|
|
|
}
|
2024-04-26 17:12:46 +00:00
|
|
|
m.health.SetDNSOSHealth(nil)
|
2021-04-07 05:00:59 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-08-19 17:19:50 +00:00
|
|
|
// 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.
|
|
|
|
// The returned list is sorted by the first hostname in each entry.
|
|
|
|
func compileHostEntries(cfg Config) (hosts []*HostEntry) {
|
|
|
|
didLabel := make(map[string]bool, len(cfg.Hosts))
|
|
|
|
for _, sd := range cfg.SearchDomains {
|
|
|
|
for h, ips := range cfg.Hosts {
|
|
|
|
if !sd.Contains(h) || h.NumLabels() != (sd.NumLabels()+1) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
ipHosts := []string{string(h.WithTrailingDot())}
|
|
|
|
if label := dnsname.FirstLabel(string(h)); !didLabel[label] {
|
|
|
|
didLabel[label] = true
|
|
|
|
ipHosts = append(ipHosts, label)
|
|
|
|
}
|
|
|
|
for _, ip := range ips {
|
|
|
|
if cfg.OnlyIPv6 && ip.Is4() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
hosts = append(hosts, &HostEntry{
|
|
|
|
Addr: ip,
|
|
|
|
Hosts: ipHosts,
|
|
|
|
})
|
|
|
|
// Only add IPv4 or IPv6 per host, like we do in the resolver.
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-07-28 17:39:04 +00:00
|
|
|
slices.SortFunc(hosts, func(a, b *HostEntry) int {
|
|
|
|
if len(a.Hosts) == 0 && len(b.Hosts) == 0 {
|
|
|
|
return 0
|
|
|
|
} else if len(a.Hosts) == 0 {
|
|
|
|
return -1
|
|
|
|
} else if len(b.Hosts) == 0 {
|
|
|
|
return 1
|
2022-08-19 17:19:50 +00:00
|
|
|
}
|
2023-07-28 17:39:04 +00:00
|
|
|
return strings.Compare(a.Hosts[0], b.Hosts[0])
|
2022-08-19 17:19:50 +00:00
|
|
|
})
|
|
|
|
return hosts
|
|
|
|
}
|
|
|
|
|
2021-04-07 05:00:59 +00:00
|
|
|
// compileConfig converts cfg into a quad-100 resolver configuration
|
|
|
|
// and an OS-level configuration.
|
2021-05-17 22:18:25 +00:00
|
|
|
func (m *Manager) compileConfig(cfg Config) (rcfg resolver.Config, ocfg OSConfig, err error) {
|
|
|
|
// The internal resolver always gets MagicDNS hosts and
|
|
|
|
// authoritative suffixes, even if we don't propagate MagicDNS to
|
|
|
|
// the OS.
|
|
|
|
rcfg.Hosts = cfg.Hosts
|
2022-05-03 21:41:58 +00:00
|
|
|
routes := map[dnsname.FQDN][]*dnstype.Resolver{} // assigned conditionally to rcfg.Routes below.
|
2021-05-17 22:50:34 +00:00
|
|
|
for suffix, resolvers := range cfg.Routes {
|
|
|
|
if len(resolvers) == 0 {
|
|
|
|
rcfg.LocalDomains = append(rcfg.LocalDomains, suffix)
|
|
|
|
} else {
|
|
|
|
routes[suffix] = resolvers
|
|
|
|
}
|
|
|
|
}
|
2022-09-06 18:15:30 +00:00
|
|
|
|
2021-05-17 22:18:25 +00:00
|
|
|
// Similarly, the OS always gets search paths.
|
|
|
|
ocfg.SearchDomains = cfg.SearchDomains
|
2024-06-04 16:41:13 +00:00
|
|
|
if m.goos == "windows" {
|
2022-08-19 17:19:50 +00:00
|
|
|
ocfg.Hosts = compileHostEntries(cfg)
|
|
|
|
}
|
2021-05-17 22:18:25 +00:00
|
|
|
|
2021-04-07 05:00:59 +00:00
|
|
|
// Deal with trivial configs first.
|
|
|
|
switch {
|
|
|
|
case !cfg.needsOSResolver():
|
|
|
|
// 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.
|
2021-05-17 22:18:25 +00:00
|
|
|
return rcfg, ocfg, nil
|
2022-08-30 16:34:59 +00:00
|
|
|
case cfg.hasDefaultIPResolversOnly() && !cfg.hasHostsWithoutSplitDNSRoutes():
|
|
|
|
// Trivial CorpDNS configuration, just override the OS resolver.
|
|
|
|
//
|
|
|
|
// If there are hosts (ExtraRecords) that are not covered by an existing
|
|
|
|
// SplitDNS route, then we don't go into this path so that we fall into
|
|
|
|
// the next case and send the extra record hosts queries through
|
|
|
|
// 100.100.100.100 instead where we can answer them.
|
|
|
|
//
|
2021-08-03 13:56:31 +00:00
|
|
|
// TODO: for OSes that support it, pass IP:port and DoH
|
|
|
|
// addresses directly to OS.
|
|
|
|
// https://github.com/tailscale/tailscale/issues/1666
|
2021-05-17 22:18:25 +00:00
|
|
|
ocfg.Nameservers = toIPsOnly(cfg.DefaultResolvers)
|
|
|
|
return rcfg, ocfg, nil
|
2021-04-07 05:00:59 +00:00
|
|
|
case cfg.hasDefaultResolvers():
|
|
|
|
// Default resolvers plus other stuff always ends up proxying
|
|
|
|
// through quad-100.
|
2021-05-17 22:50:34 +00:00
|
|
|
rcfg.Routes = routes
|
|
|
|
rcfg.Routes["."] = cfg.DefaultResolvers
|
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
|
|
|
ocfg.Nameservers = []netip.Addr{cfg.serviceIP()}
|
2021-04-07 07:58:02 +00:00
|
|
|
return rcfg, ocfg, nil
|
2020-07-31 20:27:09 +00:00
|
|
|
}
|
|
|
|
|
2021-04-07 05:00:59 +00:00
|
|
|
// From this point on, we're figuring out split DNS
|
|
|
|
// configurations. The possible cases don't return directly any
|
|
|
|
// more, because as a final step we have to handle the case where
|
|
|
|
// the OS can't do split DNS.
|
|
|
|
|
2021-04-26 22:14:41 +00:00
|
|
|
// Workaround for
|
|
|
|
// https://github.com/tailscale/corp/issues/1662. Even though
|
|
|
|
// Windows natively supports split DNS, it only configures linux
|
|
|
|
// containers using whatever the primary is, and doesn't apply
|
|
|
|
// NRPT rules to DNS traffic coming from WSL.
|
|
|
|
//
|
|
|
|
// In order to make WSL work okay when the host Windows is using
|
|
|
|
// Tailscale, we need to set up quad-100 as a "full proxy"
|
|
|
|
// resolver, regardless of whether Windows itself can do split
|
|
|
|
// DNS. We still make Windows do split DNS itself when it can, but
|
|
|
|
// quad-100 will still have the full split configuration as well,
|
|
|
|
// and so can service WSL requests correctly.
|
|
|
|
//
|
|
|
|
// This bool is used in a couple of places below to implement this
|
|
|
|
// workaround.
|
2024-06-04 16:41:13 +00:00
|
|
|
isWindows := m.goos == "windows"
|
|
|
|
isApple := (m.goos == "darwin" || m.goos == "ios")
|
|
|
|
if len(cfg.singleResolverSet()) > 0 && m.os.SupportsSplitDNS() && !isWindows && !isApple {
|
2021-04-07 05:00:59 +00:00
|
|
|
// Split DNS configuration requested, where all split domains
|
|
|
|
// go to the same resolvers. We can let the OS do it.
|
2021-05-17 22:18:25 +00:00
|
|
|
ocfg.Nameservers = toIPsOnly(cfg.singleResolverSet())
|
|
|
|
ocfg.MatchDomains = cfg.matchDomains()
|
|
|
|
return rcfg, ocfg, nil
|
2020-07-31 20:27:09 +00:00
|
|
|
}
|
2021-04-07 05:00:59 +00:00
|
|
|
|
|
|
|
// Split DNS configuration with either multiple upstream routes,
|
|
|
|
// or routes + MagicDNS, or just MagicDNS, or on an OS that cannot
|
|
|
|
// split-DNS. Install a split config pointing at quad-100.
|
2021-05-17 22:50:34 +00:00
|
|
|
rcfg.Routes = routes
|
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
|
|
|
ocfg.Nameservers = []netip.Addr{cfg.serviceIP()}
|
2021-04-03 02:34:53 +00:00
|
|
|
|
2022-09-22 21:38:09 +00:00
|
|
|
var baseCfg *OSConfig // base config; non-nil if/when known
|
|
|
|
|
|
|
|
// Even though Apple devices can do split DNS, they don't provide a way to
|
|
|
|
// selectively answer ExtraRecords, and ignore other DNS traffic. As a
|
|
|
|
// workaround, we read the existing default resolver configuration and use
|
|
|
|
// that as the forwarder for all DNS traffic that quad-100 doesn't handle.
|
|
|
|
if isApple || !m.os.SupportsSplitDNS() {
|
2022-08-12 19:47:25 +00:00
|
|
|
// If the OS can't do native split-dns, read out the underlying
|
|
|
|
// resolver config and blend it into our config.
|
2022-09-22 21:38:09 +00:00
|
|
|
cfg, err := m.os.GetBaseConfig()
|
|
|
|
if err == nil {
|
|
|
|
baseCfg = &cfg
|
|
|
|
} else if isApple && err == ErrGetBaseConfigNotSupported {
|
|
|
|
// This is currently (2022-10-13) expected on certain iOS and macOS
|
|
|
|
// builds.
|
|
|
|
} else {
|
2024-04-26 17:12:46 +00:00
|
|
|
m.health.SetDNSOSHealth(err)
|
2021-05-17 22:18:25 +00:00
|
|
|
return resolver.Config{}, OSConfig{}, err
|
2021-04-07 07:58:02 +00:00
|
|
|
}
|
2022-09-22 21:38:09 +00:00
|
|
|
}
|
|
|
|
|
2024-06-04 16:41:13 +00:00
|
|
|
if baseCfg == nil {
|
|
|
|
// If there was no base config, then we need to fallback to SplitDNS mode.
|
2022-09-22 21:38:09 +00:00
|
|
|
ocfg.MatchDomains = cfg.matchDomains()
|
|
|
|
} else {
|
xcode/iOS: set MatchDomains when no route requires a custom DNS resolver (#10576)
Updates https://github.com/tailscale/corp/issues/15802.
On iOS exclusively, this PR adds logic to use a split DNS configuration in more cases, with the goal of improving battery life. Acting as the global DNS resolver on iOS should be avoided, as it leads to frequent wakes of IPNExtension.
We try to determine if we can have Tailscale only handle DNS queries for resources inside the tailnet, that is, all routes in the DNS configuration do not require a custom resolver (this is the case for app connectors, for instance).
If so, we set all Routes as MatchDomains. This enables a split DNS configuration which will help preserve battery life. Effectively, for the average Tailscale user who only relies on MagicDNS to resolve *.ts.net domains, this means that Tailscale DNS will only be used for those domains.
This PR doesn't affect users with Override Local DNS enabled. For these users, there should be no difference and Tailscale will continue acting as a global DNS resolver.
Signed-off-by: Andrea Gottardo <andrea@tailscale.com>
2024-05-29 19:11:02 +00:00
|
|
|
// On iOS only (for now), check if all route names point to resources inside the tailnet.
|
|
|
|
// If so, we can set those names as MatchDomains to enable a split DNS configuration
|
|
|
|
// which will help preserve battery life.
|
|
|
|
// Because on iOS MatchDomains must equal SearchDomains, we cannot do this when
|
|
|
|
// we have any Routes outside the tailnet. Otherwise when app connectors are enabled,
|
|
|
|
// a query for 'work-laptop' might lead to search domain expansion, resolving
|
|
|
|
// as 'work-laptop.aws.com' for example.
|
2024-06-04 16:41:13 +00:00
|
|
|
if m.goos == "ios" && rcfg.RoutesRequireNoCustomResolvers() {
|
2024-06-06 22:19:33 +00:00
|
|
|
if !m.disableSplitDNSOptimization() {
|
|
|
|
for r := range rcfg.Routes {
|
|
|
|
ocfg.MatchDomains = append(ocfg.MatchDomains, r)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
m.logf("iOS split DNS is disabled by nodeattr")
|
xcode/iOS: set MatchDomains when no route requires a custom DNS resolver (#10576)
Updates https://github.com/tailscale/corp/issues/15802.
On iOS exclusively, this PR adds logic to use a split DNS configuration in more cases, with the goal of improving battery life. Acting as the global DNS resolver on iOS should be avoided, as it leads to frequent wakes of IPNExtension.
We try to determine if we can have Tailscale only handle DNS queries for resources inside the tailnet, that is, all routes in the DNS configuration do not require a custom resolver (this is the case for app connectors, for instance).
If so, we set all Routes as MatchDomains. This enables a split DNS configuration which will help preserve battery life. Effectively, for the average Tailscale user who only relies on MagicDNS to resolve *.ts.net domains, this means that Tailscale DNS will only be used for those domains.
This PR doesn't affect users with Override Local DNS enabled. For these users, there should be no difference and Tailscale will continue acting as a global DNS resolver.
Signed-off-by: Andrea Gottardo <andrea@tailscale.com>
2024-05-29 19:11:02 +00:00
|
|
|
}
|
|
|
|
}
|
2022-05-03 21:41:58 +00:00
|
|
|
var defaultRoutes []*dnstype.Resolver
|
2022-09-22 21:38:09 +00:00
|
|
|
for _, ip := range baseCfg.Nameservers {
|
2022-05-03 21:41:58 +00:00
|
|
|
defaultRoutes = append(defaultRoutes, &dnstype.Resolver{Addr: ip.String()})
|
2021-08-03 13:56:31 +00:00
|
|
|
}
|
|
|
|
rcfg.Routes["."] = defaultRoutes
|
2022-09-22 21:38:09 +00:00
|
|
|
ocfg.SearchDomains = append(ocfg.SearchDomains, baseCfg.SearchDomains...)
|
2021-04-07 05:00:59 +00:00
|
|
|
}
|
|
|
|
|
2021-04-07 07:58:02 +00:00
|
|
|
return rcfg, ocfg, nil
|
2021-04-07 05:00:59 +00:00
|
|
|
}
|
|
|
|
|
2024-06-06 22:19:33 +00:00
|
|
|
func (m *Manager) disableSplitDNSOptimization() bool {
|
2024-06-07 14:36:10 +00:00
|
|
|
return m.knobs != nil && m.knobs.DisableSplitDNSWhenNoCustomResolvers.Load()
|
2024-06-06 22:19:33 +00:00
|
|
|
}
|
|
|
|
|
2021-08-03 13:56:31 +00:00
|
|
|
// toIPsOnly returns only the IP portion of dnstype.Resolver.
|
|
|
|
// Only safe to use if the resolvers slice has been cleared of
|
|
|
|
// DoH or custom-port entries with something like hasDefaultIPResolversOnly.
|
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
|
|
|
func toIPsOnly(resolvers []*dnstype.Resolver) (ret []netip.Addr) {
|
2021-08-03 13:56:31 +00:00
|
|
|
for _, r := range resolvers {
|
2022-04-19 04:58:00 +00:00
|
|
|
if ipp, ok := r.IPPort(); ok && ipp.Port() == 53 {
|
2022-07-25 03:08:42 +00:00
|
|
|
ret = append(ret, ipp.Addr())
|
2021-08-03 13:56:31 +00:00
|
|
|
}
|
2021-04-07 05:00:59 +00:00
|
|
|
}
|
|
|
|
return ret
|
2021-04-03 02:34:53 +00:00
|
|
|
}
|
2020-07-31 20:27:09 +00:00
|
|
|
|
2022-09-25 18:29:55 +00:00
|
|
|
// Query executes a DNS query received from the given address. The query is
|
2022-05-18 17:40:04 +00:00
|
|
|
// provided in bs as a wire-encoded DNS query without any transport header.
|
|
|
|
// This method is called for requests arriving over UDP and TCP.
|
2023-09-07 20:27:50 +00:00
|
|
|
//
|
|
|
|
// The "family" parameter should indicate what type of DNS query this is:
|
|
|
|
// either "tcp" or "udp".
|
|
|
|
func (m *Manager) Query(ctx context.Context, bs []byte, family string, from netip.AddrPort) ([]byte, error) {
|
2022-05-02 16:35:32 +00:00
|
|
|
select {
|
|
|
|
case <-m.ctx.Done():
|
|
|
|
return nil, net.ErrClosed
|
|
|
|
default:
|
|
|
|
// continue
|
|
|
|
}
|
|
|
|
|
2023-01-05 19:27:17 +00:00
|
|
|
if n := atomic.AddInt32(&m.activeQueriesAtomic, 1); n > maxActiveQueries {
|
2022-04-14 20:27:59 +00:00
|
|
|
atomic.AddInt32(&m.activeQueriesAtomic, -1)
|
|
|
|
metricDNSQueryErrorQueue.Add(1)
|
|
|
|
return nil, errFullQueue
|
|
|
|
}
|
|
|
|
defer atomic.AddInt32(&m.activeQueriesAtomic, -1)
|
2023-09-07 20:27:50 +00:00
|
|
|
return m.resolver.Query(ctx, bs, family, from)
|
2020-07-31 20:27:09 +00:00
|
|
|
}
|
|
|
|
|
2022-05-05 23:42:45 +00:00
|
|
|
const (
|
|
|
|
// RFC 7766 6.2 recommends connection reuse & request pipelining
|
|
|
|
// be undertaken, and the connection be closed by the server
|
|
|
|
// using an idle timeout on the order of seconds.
|
|
|
|
idleTimeoutTCP = 45 * time.Second
|
|
|
|
// The RFCs don't specify the max size of a TCP-based DNS query,
|
|
|
|
// but we want to keep this reasonable. Given payloads are typically
|
|
|
|
// much larger and all known client send a single query, I've arbitrarily
|
2022-12-20 16:42:51 +00:00
|
|
|
// chosen 4k.
|
|
|
|
maxReqSizeTCP = 4096
|
2022-05-05 23:42:45 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// dnsTCPSession services DNS requests sent over TCP.
|
|
|
|
type dnsTCPSession struct {
|
|
|
|
m *Manager
|
|
|
|
|
|
|
|
conn net.Conn
|
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
|
|
|
srcAddr netip.AddrPort
|
2022-05-05 23:42:45 +00:00
|
|
|
|
2022-06-29 21:02:23 +00:00
|
|
|
readClosing chan struct{}
|
|
|
|
responses chan []byte // DNS replies pending writing
|
2022-05-05 23:42:45 +00:00
|
|
|
|
|
|
|
ctx context.Context
|
|
|
|
closeCtx context.CancelFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *dnsTCPSession) handleWrites() {
|
|
|
|
defer s.conn.Close()
|
|
|
|
defer s.closeCtx()
|
|
|
|
|
2022-12-13 19:49:08 +00:00
|
|
|
// NOTE(andrew): we explicitly do not close the 'responses' channel
|
|
|
|
// when this function exits. If we hit an error and return, we could
|
|
|
|
// still have outstanding 'handleQuery' goroutines running, and if we
|
|
|
|
// closed this channel they'd end up trying to send on a closed channel
|
|
|
|
// when they finish.
|
|
|
|
//
|
|
|
|
// Because we call closeCtx, those goroutines will not hang since they
|
|
|
|
// select on <-s.ctx.Done() as well as s.responses.
|
|
|
|
|
2022-05-05 23:42:45 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-s.readClosing:
|
|
|
|
return // connection closed or timeout, teardown time
|
|
|
|
|
|
|
|
case resp := <-s.responses:
|
|
|
|
s.conn.SetWriteDeadline(time.Now().Add(idleTimeoutTCP))
|
|
|
|
if err := binary.Write(s.conn, binary.BigEndian, uint16(len(resp))); err != nil {
|
|
|
|
s.m.logf("tcp write (len): %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if _, err := s.conn.Write(resp); err != nil {
|
|
|
|
s.m.logf("tcp write (response): %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *dnsTCPSession) handleQuery(q []byte) {
|
2023-09-07 20:27:50 +00:00
|
|
|
resp, err := s.m.Query(s.ctx, q, "tcp", s.srcAddr)
|
2022-05-05 23:42:45 +00:00
|
|
|
if err != nil {
|
|
|
|
s.m.logf("tcp query: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-12-13 19:49:08 +00:00
|
|
|
// See note in handleWrites (above) regarding this select{}
|
2022-05-05 23:42:45 +00:00
|
|
|
select {
|
|
|
|
case <-s.ctx.Done():
|
|
|
|
case s.responses <- resp:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *dnsTCPSession) handleReads() {
|
2022-12-13 19:49:08 +00:00
|
|
|
defer s.conn.Close()
|
2022-05-05 23:42:45 +00:00
|
|
|
defer close(s.readClosing)
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-s.ctx.Done():
|
|
|
|
return
|
|
|
|
|
|
|
|
default:
|
|
|
|
s.conn.SetReadDeadline(time.Now().Add(idleTimeoutTCP))
|
|
|
|
var reqLen uint16
|
|
|
|
if err := binary.Read(s.conn, binary.BigEndian, &reqLen); err != nil {
|
|
|
|
if err == io.EOF || err == io.ErrClosedPipe {
|
|
|
|
return // connection closed nominally, we gucci
|
|
|
|
}
|
|
|
|
s.m.logf("tcp read (len): %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if int(reqLen) > maxReqSizeTCP {
|
|
|
|
s.m.logf("tcp request too large (%d > %d)", reqLen, maxReqSizeTCP)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := make([]byte, int(reqLen))
|
|
|
|
if _, err := io.ReadFull(s.conn, buf); err != nil {
|
|
|
|
s.m.logf("tcp read (payload): %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-s.ctx.Done():
|
|
|
|
return
|
|
|
|
default:
|
2022-12-13 19:49:08 +00:00
|
|
|
// NOTE: by kicking off the query handling in a
|
|
|
|
// new goroutine, it is possible that we'll
|
|
|
|
// deliver responses out-of-order. This is
|
|
|
|
// explicitly allowed by RFC7766, Section
|
|
|
|
// 6.2.1.1 ("Query Pipelining").
|
2022-05-05 23:42:45 +00:00
|
|
|
go s.handleQuery(buf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandleTCPConn implements magicDNS over TCP, taking a connection and
|
|
|
|
// servicing DNS requests sent down it.
|
all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
goimports -w .
Then delete some stuff from the net/netaddr shim package which is no
longer neeed.
Updates #5162
Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 04:14:09 +00:00
|
|
|
func (m *Manager) HandleTCPConn(conn net.Conn, srcAddr netip.AddrPort) {
|
2022-05-05 23:42:45 +00:00
|
|
|
s := dnsTCPSession{
|
2022-06-29 21:02:23 +00:00
|
|
|
m: m,
|
|
|
|
conn: conn,
|
|
|
|
srcAddr: srcAddr,
|
|
|
|
responses: make(chan []byte),
|
|
|
|
readClosing: make(chan struct{}),
|
2022-05-05 23:42:45 +00:00
|
|
|
}
|
2022-05-18 17:40:04 +00:00
|
|
|
s.ctx, s.closeCtx = context.WithCancel(m.ctx)
|
2022-05-05 23:42:45 +00:00
|
|
|
go s.handleReads()
|
|
|
|
s.handleWrites()
|
|
|
|
}
|
|
|
|
|
2020-07-31 20:27:09 +00:00
|
|
|
func (m *Manager) Down() error {
|
2022-05-02 16:35:32 +00:00
|
|
|
m.ctxCancel()
|
2021-04-03 02:34:53 +00:00
|
|
|
if err := m.os.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
m.resolver.Close()
|
|
|
|
return nil
|
2020-07-31 20:27:09 +00:00
|
|
|
}
|
2021-04-02 05:35:26 +00:00
|
|
|
|
2021-09-19 03:33:21 +00:00
|
|
|
func (m *Manager) FlushCaches() error {
|
|
|
|
return flushCaches()
|
|
|
|
}
|
|
|
|
|
2024-04-03 02:52:19 +00:00
|
|
|
// CleanUp restores the system DNS configuration to its original state
|
2021-04-02 05:35:26 +00:00
|
|
|
// in case the Tailscale daemon terminated without closing the router.
|
|
|
|
// No other state needs to be instantiated before this runs.
|
2024-04-27 05:06:20 +00:00
|
|
|
func CleanUp(logf logger.Logf, netMon *netmon.Monitor, interfaceName string) {
|
2024-04-26 17:12:46 +00:00
|
|
|
oscfg, err := NewOSConfigurator(logf, nil, interfaceName)
|
2021-04-12 22:51:37 +00:00
|
|
|
if err != nil {
|
|
|
|
logf("creating dns cleanup: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2024-04-27 05:06:20 +00:00
|
|
|
d := &tsdial.Dialer{Logf: logf}
|
|
|
|
d.SetNetMon(netMon)
|
2024-06-04 16:41:13 +00:00
|
|
|
dns := NewManager(logf, oscfg, nil, d, nil, nil, runtime.GOOS)
|
2021-04-02 05:35:26 +00:00
|
|
|
if err := dns.Down(); err != nil {
|
|
|
|
logf("dns down: %v", err)
|
|
|
|
}
|
|
|
|
}
|
2022-04-14 20:27:59 +00:00
|
|
|
|
|
|
|
var (
|
|
|
|
metricDNSQueryErrorQueue = clientmetric.NewCounter("dns_query_local_error_queue")
|
2022-05-02 16:35:32 +00:00
|
|
|
)
|