2020-07-31 20:27:09 +00:00
|
|
|
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package dns
|
|
|
|
|
|
|
|
import (
|
2021-07-20 05:24:43 +00:00
|
|
|
"bufio"
|
2022-04-14 20:27:59 +00:00
|
|
|
"context"
|
|
|
|
"errors"
|
2021-04-26 22:14:41 +00:00
|
|
|
"runtime"
|
2022-04-14 20:27:59 +00:00
|
|
|
"sync/atomic"
|
2020-07-31 20:27:09 +00:00
|
|
|
"time"
|
|
|
|
|
2021-04-03 02:34:53 +00:00
|
|
|
"inet.af/netaddr"
|
2021-11-18 23:52:21 +00:00
|
|
|
"tailscale.com/health"
|
2021-04-03 02:34:53 +00:00
|
|
|
"tailscale.com/net/dns/resolver"
|
2022-04-14 20:27:59 +00:00
|
|
|
"tailscale.com/net/packet"
|
|
|
|
"tailscale.com/net/tsaddr"
|
2021-12-01 04:39:12 +00:00
|
|
|
"tailscale.com/net/tsdial"
|
2022-04-14 20:27:59 +00:00
|
|
|
"tailscale.com/net/tstun"
|
2021-08-03 13:56:31 +00:00
|
|
|
"tailscale.com/types/dnstype"
|
2022-04-08 19:17:31 +00:00
|
|
|
"tailscale.com/types/ipproto"
|
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"
|
2021-04-03 02:34:53 +00:00
|
|
|
"tailscale.com/wgengine/monitor"
|
2020-07-31 20:27:09 +00:00
|
|
|
)
|
|
|
|
|
2022-04-14 20:27:59 +00:00
|
|
|
var (
|
|
|
|
magicDNSIP = tsaddr.TailscaleServiceIP()
|
|
|
|
magicDNSIPv6 = tsaddr.TailscaleServiceIPv6()
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
errFullQueue = errors.New("request queue full")
|
|
|
|
)
|
|
|
|
|
|
|
|
// maxActiveQueries returns the maximal number of DNS requests that be
|
|
|
|
// can running.
|
|
|
|
// If EnqueueRequest is called when this many requests are already pending,
|
|
|
|
// the request will be dropped to avoid blocking the caller.
|
|
|
|
func maxActiveQueries() int32 {
|
|
|
|
if runtime.GOOS == "ios" {
|
|
|
|
// For memory paranoia reasons on iOS, match the
|
|
|
|
// historical Tailscale 1.x..1.8 behavior for now
|
|
|
|
// (just before the 1.10 release).
|
|
|
|
return 64
|
|
|
|
}
|
|
|
|
// But for other platforms, allow more burstiness:
|
|
|
|
return 256
|
|
|
|
}
|
|
|
|
|
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
|
|
|
// 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.
|
2020-09-08 23:03:49 +00:00
|
|
|
const reconfigTimeout = time.Second
|
2020-07-31 20:27:09 +00:00
|
|
|
|
2022-04-14 20:27:59 +00:00
|
|
|
type response struct {
|
|
|
|
pkt []byte
|
2022-04-30 23:13:18 +00:00
|
|
|
to netaddr.IPPort // response destination (request source)
|
2022-04-14 20:27:59 +00:00
|
|
|
}
|
|
|
|
|
2020-07-31 20:27:09 +00:00
|
|
|
// Manager manages system DNS settings.
|
|
|
|
type Manager struct {
|
|
|
|
logf logger.Logf
|
|
|
|
|
2022-04-14 20:27:59 +00:00
|
|
|
// When netstack is not used, Manager implements magic DNS.
|
|
|
|
// In this case, responses tracks completed DNS requests
|
|
|
|
// which need a response, and NextPacket() synthesizes a
|
|
|
|
// fake IP+UDP header to finish assembling the response.
|
|
|
|
//
|
|
|
|
// TODO(tom): Rip out once all platforms use netstack.
|
|
|
|
responses chan response
|
|
|
|
activeQueriesAtomic int32
|
|
|
|
|
2021-04-03 02:34:53 +00:00
|
|
|
resolver *resolver.Resolver
|
|
|
|
os OSConfigurator
|
2020-07-31 20:27:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewManagers created a new manager from the given config.
|
2021-12-01 04:39:12 +00:00
|
|
|
func NewManager(logf logger.Logf, oscfg OSConfigurator, linkMon *monitor.Mon, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector) *Manager {
|
|
|
|
if dialer == nil {
|
|
|
|
panic("nil Dialer")
|
|
|
|
}
|
2021-04-02 06:26:52 +00:00
|
|
|
logf = logger.WithPrefix(logf, "dns: ")
|
2020-07-31 20:27:09 +00:00
|
|
|
m := &Manager{
|
2022-04-14 20:27:59 +00:00
|
|
|
logf: logf,
|
|
|
|
resolver: resolver.New(logf, linkMon, linkSel, dialer),
|
|
|
|
os: oscfg,
|
|
|
|
responses: make(chan response),
|
2020-07-31 20:27:09 +00:00
|
|
|
}
|
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)
|
|
|
|
}))
|
2021-04-07 05:00:59 +00:00
|
|
|
m.logf("OScfg: %+v", ocfg)
|
|
|
|
|
|
|
|
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 {
|
2021-11-18 23:52:21 +00:00
|
|
|
health.SetDNSOSHealth(err)
|
2021-04-07 05:00:59 +00:00
|
|
|
return err
|
2021-04-03 02:34:53 +00:00
|
|
|
}
|
2021-11-18 23:52:21 +00:00
|
|
|
health.SetDNSOSHealth(nil)
|
2021-04-07 05:00:59 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2021-08-03 13:56:31 +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
|
|
|
|
}
|
|
|
|
}
|
2021-05-17 22:18:25 +00:00
|
|
|
// Similarly, the OS always gets search paths.
|
|
|
|
ocfg.SearchDomains = cfg.SearchDomains
|
|
|
|
|
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
|
2021-08-03 13:56:31 +00:00
|
|
|
case cfg.hasDefaultIPResolversOnly():
|
2021-04-07 05:00:59 +00:00
|
|
|
// Trivial CorpDNS configuration, just override the OS
|
|
|
|
// resolver.
|
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
|
2022-01-04 21:33:08 +00:00
|
|
|
ocfg.Nameservers = []netaddr.IP{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.
|
|
|
|
isWindows := runtime.GOOS == "windows"
|
2021-05-17 22:18:25 +00:00
|
|
|
if cfg.singleResolverSet() != nil && m.os.SupportsSplitDNS() && !isWindows {
|
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
|
2022-01-04 21:33:08 +00:00
|
|
|
ocfg.Nameservers = []netaddr.IP{cfg.serviceIP()}
|
2021-04-03 02:34:53 +00:00
|
|
|
|
2021-04-07 05:00:59 +00:00
|
|
|
// If the OS can't do native split-dns, read out the underlying
|
|
|
|
// resolver config and blend it into our config.
|
|
|
|
if m.os.SupportsSplitDNS() {
|
|
|
|
ocfg.MatchDomains = cfg.matchDomains()
|
2021-04-26 22:14:41 +00:00
|
|
|
}
|
|
|
|
if !m.os.SupportsSplitDNS() || isWindows {
|
2021-04-07 07:58:02 +00:00
|
|
|
bcfg, err := m.os.GetBaseConfig()
|
|
|
|
if err != nil {
|
2021-11-18 23:52:21 +00:00
|
|
|
health.SetDNSOSHealth(err)
|
2021-05-17 22:18:25 +00:00
|
|
|
return resolver.Config{}, OSConfig{}, err
|
2021-04-07 07:58:02 +00:00
|
|
|
}
|
2021-08-03 13:56:31 +00:00
|
|
|
var defaultRoutes []dnstype.Resolver
|
|
|
|
for _, ip := range bcfg.Nameservers {
|
2022-04-19 04:58:00 +00:00
|
|
|
defaultRoutes = append(defaultRoutes, dnstype.Resolver{Addr: ip.String()})
|
2021-08-03 13:56:31 +00:00
|
|
|
}
|
|
|
|
rcfg.Routes["."] = defaultRoutes
|
2021-04-07 07:58:02 +00:00
|
|
|
ocfg.SearchDomains = append(ocfg.SearchDomains, bcfg.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
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
func toIPsOnly(resolvers []dnstype.Resolver) (ret []netaddr.IP) {
|
|
|
|
for _, r := range resolvers {
|
2022-04-19 04:58:00 +00:00
|
|
|
if ipp, ok := r.IPPort(); ok && ipp.Port() == 53 {
|
2021-08-03 13:56:31 +00:00
|
|
|
ret = append(ret, ipp.IP())
|
|
|
|
}
|
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-04-14 20:27:59 +00:00
|
|
|
// EnqueuePacket is the legacy path for handling magic DNS traffic, and is
|
|
|
|
// called with a DNS request payload.
|
|
|
|
//
|
|
|
|
// TODO(tom): Rip out once all platforms use netstack.
|
2022-04-08 19:17:31 +00:00
|
|
|
func (m *Manager) EnqueuePacket(bs []byte, proto ipproto.Proto, from, to netaddr.IPPort) error {
|
2022-04-14 20:27:59 +00:00
|
|
|
if to.Port() != 53 || proto != ipproto.UDP {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if n := atomic.AddInt32(&m.activeQueriesAtomic, 1); n > maxActiveQueries() {
|
|
|
|
atomic.AddInt32(&m.activeQueriesAtomic, -1)
|
|
|
|
metricDNSQueryErrorQueue.Add(1)
|
|
|
|
return errFullQueue
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
resp, err := m.resolver.Query(context.Background(), bs, from)
|
|
|
|
if err != nil {
|
|
|
|
atomic.AddInt32(&m.activeQueriesAtomic, -1)
|
|
|
|
m.logf("dns query: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
m.responses <- response{resp, from}
|
|
|
|
}()
|
|
|
|
return nil
|
2020-07-31 20:27:09 +00:00
|
|
|
}
|
|
|
|
|
2022-04-14 20:27:59 +00:00
|
|
|
// NextPacket is the legacy path for obtaining DNS results in response to
|
|
|
|
// magic DNS queries. It blocks until a response is available.
|
|
|
|
//
|
|
|
|
// TODO(tom): Rip out once all platforms use netstack.
|
2022-04-08 19:17:31 +00:00
|
|
|
func (m *Manager) NextPacket() ([]byte, error) {
|
2022-04-14 20:27:59 +00:00
|
|
|
resp := <-m.responses
|
|
|
|
|
|
|
|
// Unused space is needed further down the stack. To avoid extra
|
|
|
|
// allocations/copying later on, we allocate such space here.
|
|
|
|
const offset = tstun.PacketStartOffset
|
|
|
|
|
|
|
|
var buf []byte
|
|
|
|
switch {
|
2022-04-30 23:13:18 +00:00
|
|
|
case resp.to.IP().Is4():
|
2022-04-14 20:27:59 +00:00
|
|
|
h := packet.UDP4Header{
|
|
|
|
IP4Header: packet.IP4Header{
|
|
|
|
Src: magicDNSIP,
|
2022-04-30 23:13:18 +00:00
|
|
|
Dst: resp.to.IP(),
|
2022-04-14 20:27:59 +00:00
|
|
|
},
|
|
|
|
SrcPort: 53,
|
2022-04-30 23:13:18 +00:00
|
|
|
DstPort: resp.to.Port(),
|
2022-04-14 20:27:59 +00:00
|
|
|
}
|
|
|
|
hlen := h.Len()
|
|
|
|
buf = make([]byte, offset+hlen+len(resp.pkt))
|
|
|
|
copy(buf[offset+hlen:], resp.pkt)
|
|
|
|
h.Marshal(buf[offset:])
|
2022-04-30 23:13:18 +00:00
|
|
|
case resp.to.IP().Is6():
|
2022-04-14 20:27:59 +00:00
|
|
|
h := packet.UDP6Header{
|
|
|
|
IP6Header: packet.IP6Header{
|
|
|
|
Src: magicDNSIPv6,
|
2022-04-30 23:13:18 +00:00
|
|
|
Dst: resp.to.IP(),
|
2022-04-14 20:27:59 +00:00
|
|
|
},
|
|
|
|
SrcPort: 53,
|
2022-04-30 23:13:18 +00:00
|
|
|
DstPort: resp.to.Port(),
|
2022-04-14 20:27:59 +00:00
|
|
|
}
|
|
|
|
hlen := h.Len()
|
|
|
|
buf = make([]byte, offset+hlen+len(resp.pkt))
|
|
|
|
copy(buf[offset+hlen:], resp.pkt)
|
|
|
|
h.Marshal(buf[offset:])
|
|
|
|
}
|
|
|
|
|
|
|
|
atomic.AddInt32(&m.activeQueriesAtomic, -1)
|
|
|
|
return buf, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) Query(ctx context.Context, bs []byte, from netaddr.IPPort) ([]byte, error) {
|
|
|
|
if n := atomic.AddInt32(&m.activeQueriesAtomic, 1); n > maxActiveQueries() {
|
|
|
|
atomic.AddInt32(&m.activeQueriesAtomic, -1)
|
|
|
|
metricDNSQueryErrorQueue.Add(1)
|
|
|
|
return nil, errFullQueue
|
|
|
|
}
|
|
|
|
defer atomic.AddInt32(&m.activeQueriesAtomic, -1)
|
|
|
|
return m.resolver.Query(ctx, bs, from)
|
2020-07-31 20:27:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Manager) Down() error {
|
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()
|
|
|
|
}
|
|
|
|
|
2021-04-02 05:35:26 +00:00
|
|
|
// Cleanup restores the system DNS configuration to its original state
|
|
|
|
// in case the Tailscale daemon terminated without closing the router.
|
|
|
|
// No other state needs to be instantiated before this runs.
|
|
|
|
func Cleanup(logf logger.Logf, interfaceName string) {
|
2021-04-12 22:51:37 +00:00
|
|
|
oscfg, err := NewOSConfigurator(logf, interfaceName)
|
|
|
|
if err != nil {
|
|
|
|
logf("creating dns cleanup: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2021-12-01 04:39:12 +00:00
|
|
|
dns := NewManager(logf, oscfg, nil, new(tsdial.Dialer), nil)
|
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")
|
|
|
|
)
|