2023-01-27 21:37:20 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2020-08-19 19:39:25 +00:00
|
|
|
|
2021-04-01 04:54:38 +00:00
|
|
|
package resolver
|
2020-08-19 19:39:25 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2020-09-23 20:21:52 +00:00
|
|
|
"context"
|
2024-08-08 20:41:08 +00:00
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/base64"
|
2020-08-19 19:39:25 +00:00
|
|
|
"encoding/binary"
|
|
|
|
"errors"
|
2021-07-15 16:11:12 +00:00
|
|
|
"fmt"
|
2021-06-23 04:53:43 +00:00
|
|
|
"io"
|
2020-08-19 19:39:25 +00:00
|
|
|
"net"
|
2021-07-15 16:11:12 +00:00
|
|
|
"net/http"
|
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"
|
2022-04-18 19:50:26 +00:00
|
|
|
"net/url"
|
2021-07-26 03:40:37 +00:00
|
|
|
"sort"
|
2021-07-15 16:11:12 +00:00
|
|
|
"strings"
|
2020-08-19 19:39:25 +00:00
|
|
|
"sync"
|
2023-10-03 20:26:38 +00:00
|
|
|
"sync/atomic"
|
2020-08-19 19:39:25 +00:00
|
|
|
"time"
|
|
|
|
|
2021-04-02 02:31:55 +00:00
|
|
|
dns "golang.org/x/net/dns/dnsmessage"
|
2023-09-07 20:27:50 +00:00
|
|
|
"tailscale.com/control/controlknobs"
|
2022-04-19 04:58:00 +00:00
|
|
|
"tailscale.com/envknob"
|
2024-07-29 17:48:46 +00:00
|
|
|
"tailscale.com/health"
|
2022-04-14 21:15:54 +00:00
|
|
|
"tailscale.com/net/dns/publicdns"
|
2022-04-18 19:50:26 +00:00
|
|
|
"tailscale.com/net/dnscache"
|
2022-01-03 18:49:56 +00:00
|
|
|
"tailscale.com/net/neterror"
|
2023-04-18 21:26:58 +00:00
|
|
|
"tailscale.com/net/netmon"
|
2023-02-03 20:07:58 +00:00
|
|
|
"tailscale.com/net/sockstats"
|
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-08-19 19:39:25 +00:00
|
|
|
"tailscale.com/types/logger"
|
2022-07-25 03:08:42 +00:00
|
|
|
"tailscale.com/types/nettype"
|
ipn/ipnlocal, net/dns*, util/cloudenv: specialize DNS config on Google Cloud
This does three things:
* If you're on GCP, it adds a *.internal DNS split route to the
metadata server, so we never break GCP DNS names. This lets people
have some Tailscale nodes on GCP and some not (e.g. laptops at home)
without having to add a Tailnet-wide *.internal DNS route.
If you already have such a route, though, it won't overwrite it.
* If the 100.100.100.100 DNS forwarder has nowhere to forward to,
it forwards it to the GCP metadata IP, which forwards to 8.8.8.8.
This means there are never errNoUpstreams ("upstream nameservers not set")
errors on GCP due to e.g. mangled /etc/resolv.conf (GCP default VMs
don't have systemd-resolved, so it's likely a DNS supremacy fight)
* makes the DNS fallback mechanism use the GCP metadata IP as a
fallback before our hosted HTTP-based fallbacks
I created a default GCP VM from their web wizard. It has no
systemd-resolved.
I then made its /etc/resolv.conf be empty and deleted its GCP
hostnames in /etc/hosts.
I then logged in to a tailnet with no global DNS settings.
With this, tailscaled writes /etc/resolv.conf (direct mode, as no
systemd-resolved) and sets it to 100.100.100.100, which then has
regular DNS via the metadata IP and *.internal DNS via the metadata IP
as well. If the tailnet configures explicit DNS servers, those are used
instead, except for *.internal.
This also adds a new util/cloudenv package based on version/distro
where the cloud type is only detected once. We'll likely expand it in
the future for other clouds, doing variants of this change for other
popular cloud environments.
Fixes #4911
RELNOTES=Google Cloud DNS improvements
Change-Id: I19f3c2075983669b2b2c0f29a548da8de373c7cf
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-06-29 20:19:34 +00:00
|
|
|
"tailscale.com/util/cloudenv"
|
2021-04-02 02:31:55 +00:00
|
|
|
"tailscale.com/util/dnsname"
|
2023-10-03 20:26:38 +00:00
|
|
|
"tailscale.com/util/race"
|
2022-09-10 15:43:18 +00:00
|
|
|
"tailscale.com/version"
|
2020-08-19 19:39:25 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// headerBytes is the number of bytes in a DNS message header.
|
|
|
|
const headerBytes = 12
|
|
|
|
|
2022-04-22 22:01:55 +00:00
|
|
|
// dnsFlagTruncated is set in the flags word when the packet is truncated.
|
|
|
|
const dnsFlagTruncated = 0x200
|
|
|
|
|
|
|
|
// truncatedFlagSet returns true if the DNS packet signals that it has
|
|
|
|
// been truncated. False is also returned if the packet was too small
|
|
|
|
// to be valid.
|
|
|
|
func truncatedFlagSet(pkt []byte) bool {
|
|
|
|
if len(pkt) < headerBytes {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return (binary.BigEndian.Uint16(pkt[2:4]) & dnsFlagTruncated) != 0
|
|
|
|
}
|
|
|
|
|
2020-08-19 19:39:25 +00:00
|
|
|
const (
|
2021-07-15 16:11:12 +00:00
|
|
|
// dohTransportTimeout is how long to keep idle HTTP
|
|
|
|
// connections open to DNS-over-HTTPs servers. This is pretty
|
|
|
|
// arbitrary.
|
|
|
|
dohTransportTimeout = 30 * time.Second
|
2021-07-26 03:40:37 +00:00
|
|
|
|
2022-04-19 04:58:00 +00:00
|
|
|
// dohTransportTimeout is how much of a head start to give a DoH query
|
|
|
|
// that was upgraded from a well-known public DNS provider's IP before
|
|
|
|
// normal UDP mode is attempted as a fallback.
|
|
|
|
dohHeadStart = 500 * time.Millisecond
|
|
|
|
|
2021-07-26 03:40:37 +00:00
|
|
|
// wellKnownHostBackupDelay is how long to artificially delay upstream
|
|
|
|
// DNS queries to the "fallback" DNS server IP for a known provider
|
|
|
|
// (e.g. how long to wait to query Google's 8.8.4.4 after 8.8.8.8).
|
|
|
|
wellKnownHostBackupDelay = 200 * time.Millisecond
|
2023-09-07 20:27:50 +00:00
|
|
|
|
2023-10-03 20:26:38 +00:00
|
|
|
// udpRaceTimeout is the timeout after which we will start a DNS query
|
|
|
|
// over TCP while waiting for the UDP query to complete.
|
|
|
|
udpRaceTimeout = 2 * time.Second
|
|
|
|
|
2023-09-07 20:27:50 +00:00
|
|
|
// tcpQueryTimeout is the timeout for a DNS query performed over TCP.
|
|
|
|
// It matches the default 5sec timeout of the 'dig' utility.
|
|
|
|
tcpQueryTimeout = 5 * time.Second
|
2020-08-19 19:39:25 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// txid identifies a DNS transaction.
|
|
|
|
//
|
|
|
|
// As the standard DNS Request ID is only 16 bits, we extend it:
|
|
|
|
// the lower 32 bits are the zero-extended bits of the DNS Request ID;
|
|
|
|
// the upper 32 bits are the CRC32 checksum of the first question in the request.
|
|
|
|
// This makes probability of txid collision negligible.
|
|
|
|
type txid uint64
|
|
|
|
|
|
|
|
// getTxID computes the txid of the given DNS packet.
|
|
|
|
func getTxID(packet []byte) txid {
|
|
|
|
if len(packet) < headerBytes {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
dnsid := binary.BigEndian.Uint16(packet[0:2])
|
2021-08-06 15:46:33 +00:00
|
|
|
// Previously, we hashed the question and combined it with the original txid
|
|
|
|
// which was useful when concurrent queries were multiplexed on a single
|
|
|
|
// local source port. We encountered some situations where the DNS server
|
|
|
|
// canonicalizes the question in the response (uppercase converted to
|
|
|
|
// lowercase in this case), which resulted in responses that we couldn't
|
|
|
|
// match to the original request due to hash mismatches.
|
|
|
|
return txid(dnsid)
|
2020-08-19 19:39:25 +00:00
|
|
|
}
|
|
|
|
|
2021-09-19 00:34:33 +00:00
|
|
|
func getRCode(packet []byte) dns.RCode {
|
|
|
|
if len(packet) < headerBytes {
|
|
|
|
// treat invalid packets as a refusal
|
|
|
|
return dns.RCode(5)
|
|
|
|
}
|
|
|
|
// get bottom 4 bits of 3rd byte
|
|
|
|
return dns.RCode(packet[3] & 0x0F)
|
|
|
|
}
|
|
|
|
|
2021-06-24 14:36:23 +00:00
|
|
|
// clampEDNSSize attempts to limit the maximum EDNS response size. This is not
|
|
|
|
// an exhaustive solution, instead only easy cases are currently handled in the
|
|
|
|
// interest of speed and reduced complexity. Only OPT records at the very end of
|
|
|
|
// the message with no option codes are addressed.
|
|
|
|
// TODO: handle more situations if we discover that they happen often
|
|
|
|
func clampEDNSSize(packet []byte, maxSize uint16) {
|
|
|
|
// optFixedBytes is the size of an OPT record with no option codes.
|
|
|
|
const optFixedBytes = 11
|
|
|
|
const edns0Version = 0
|
|
|
|
|
|
|
|
if len(packet) < headerBytes+optFixedBytes {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
arCount := binary.BigEndian.Uint16(packet[10:12])
|
|
|
|
if arCount == 0 {
|
|
|
|
// OPT shows up in an AR, so there must be no OPT
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-07-27 23:23:07 +00:00
|
|
|
// https://datatracker.ietf.org/doc/html/rfc6891#section-6.1.2
|
2021-06-24 14:36:23 +00:00
|
|
|
opt := packet[len(packet)-optFixedBytes:]
|
|
|
|
|
|
|
|
if opt[0] != 0 {
|
|
|
|
// OPT NAME must be 0 (root domain)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if dns.Type(binary.BigEndian.Uint16(opt[1:3])) != dns.TypeOPT {
|
|
|
|
// Not an OPT record
|
|
|
|
return
|
|
|
|
}
|
|
|
|
requestedSize := binary.BigEndian.Uint16(opt[3:5])
|
|
|
|
// Ignore extended RCODE in opt[5]
|
|
|
|
if opt[6] != edns0Version {
|
|
|
|
// Be conservative and don't touch unknown versions.
|
|
|
|
return
|
|
|
|
}
|
2021-07-27 23:23:07 +00:00
|
|
|
// Ignore flags in opt[6:9]
|
|
|
|
if binary.BigEndian.Uint16(opt[9:11]) != 0 {
|
2021-06-24 14:36:23 +00:00
|
|
|
// RDLEN must be 0 (no variable length data). We're at the end of the
|
|
|
|
// packet so this should be 0 anyway)..
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if requestedSize <= maxSize {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clamp the maximum size
|
|
|
|
binary.BigEndian.PutUint16(opt[3:5], maxSize)
|
|
|
|
}
|
|
|
|
|
2024-07-29 17:48:46 +00:00
|
|
|
// dnsForwarderFailing should be raised when the forwarder is unable to reach the
|
|
|
|
// upstream resolvers. This is a high severity warning as it results in "no internet".
|
|
|
|
// This warning must be cleared when the forwarder is working again.
|
|
|
|
//
|
|
|
|
// We allow for 5 second grace period to ensure this is not raised for spurious errors
|
|
|
|
// under the assumption that DNS queries are relatively frequent and a subsequent
|
|
|
|
// successful query will clear any one-off errors.
|
|
|
|
var dnsForwarderFailing = health.Register(&health.Warnable{
|
|
|
|
Code: "dns-forward-failing",
|
|
|
|
Title: "DNS unavailable",
|
2024-08-16 15:12:06 +00:00
|
|
|
Severity: health.SeverityMedium,
|
2024-07-29 17:48:46 +00:00
|
|
|
DependsOn: []*health.Warnable{health.NetworkStatusWarnable},
|
|
|
|
Text: health.StaticMessage("Tailscale can't reach the configured DNS servers. Internet connectivity may be affected."),
|
|
|
|
ImpactsConnectivity: true,
|
2024-08-29 18:43:38 +00:00
|
|
|
TimeToVisible: 15 * time.Second,
|
2024-07-29 17:48:46 +00:00
|
|
|
})
|
|
|
|
|
2021-04-02 02:31:55 +00:00
|
|
|
type route struct {
|
2021-06-23 04:53:43 +00:00
|
|
|
Suffix dnsname.FQDN
|
2021-07-26 03:40:37 +00:00
|
|
|
Resolvers []resolverAndDelay
|
|
|
|
}
|
|
|
|
|
|
|
|
// resolverAndDelay is an upstream DNS resolver and a delay for how
|
|
|
|
// long to wait before querying it.
|
|
|
|
type resolverAndDelay struct {
|
2021-08-03 13:56:31 +00:00
|
|
|
// name is the upstream resolver.
|
2022-05-03 21:41:58 +00:00
|
|
|
name *dnstype.Resolver
|
2021-07-26 03:40:37 +00:00
|
|
|
|
|
|
|
// startDelay is an amount to delay this resolver at
|
|
|
|
// start. It's used when, say, there are four Google or
|
|
|
|
// Cloudflare DNS IPs (two IPv4 + two IPv6) and we don't want
|
|
|
|
// to race all four at once.
|
|
|
|
startDelay time.Duration
|
2021-04-02 02:31:55 +00:00
|
|
|
}
|
|
|
|
|
2020-08-19 19:39:25 +00:00
|
|
|
// forwarder forwards DNS packets to a number of upstream nameservers.
|
|
|
|
type forwarder struct {
|
2021-06-23 04:53:43 +00:00
|
|
|
logf logger.Logf
|
2024-04-27 05:06:20 +00:00
|
|
|
netMon *netmon.Monitor // always non-nil
|
2022-09-25 18:29:55 +00:00
|
|
|
linkSel ForwardLinkSelector // TODO(bradfitz): remove this when tsdial.Dialer absorbs it
|
2021-12-01 04:39:12 +00:00
|
|
|
dialer *tsdial.Dialer
|
2024-07-29 17:48:46 +00:00
|
|
|
health *health.Tracker // always non-nil
|
2021-06-23 04:53:43 +00:00
|
|
|
|
2023-09-07 20:27:50 +00:00
|
|
|
controlKnobs *controlknobs.Knobs // or nil
|
|
|
|
|
2021-06-23 04:53:43 +00:00
|
|
|
ctx context.Context // good until Close
|
|
|
|
ctxCancel context.CancelFunc // closes ctx
|
2020-08-19 19:39:25 +00:00
|
|
|
|
2021-06-23 04:53:43 +00:00
|
|
|
mu sync.Mutex // guards following
|
|
|
|
|
2021-08-03 13:56:31 +00:00
|
|
|
dohClient map[string]*http.Client // urlBase -> client
|
2021-07-15 16:11:12 +00:00
|
|
|
|
2021-06-23 04:53:43 +00:00
|
|
|
// routes are per-suffix resolvers to use, with
|
|
|
|
// the most specific routes first.
|
|
|
|
routes []route
|
2022-06-30 02:32:41 +00:00
|
|
|
// cloudHostFallback are last resort resolvers to use if no per-suffix
|
|
|
|
// resolver matches. These are only populated on cloud hosts where the
|
|
|
|
// platform provides a well-known recursive resolver.
|
|
|
|
//
|
|
|
|
// That is, if we're running on GCP or AWS where there's always a well-known
|
|
|
|
// IP of a recursive resolver, return that rather than having callers return
|
2022-08-09 14:33:45 +00:00
|
|
|
// SERVFAIL. This fixes both normal 100.100.100.100 resolution when
|
2022-06-30 02:32:41 +00:00
|
|
|
// /etc/resolv.conf is missing/corrupt, and the peerapi ExitDNS stub
|
|
|
|
// resolver lookup.
|
|
|
|
cloudHostFallback []resolverAndDelay
|
2024-06-25 18:56:13 +00:00
|
|
|
|
2024-06-25 19:21:32 +00:00
|
|
|
// missingUpstreamRecovery, if non-nil, is set called when a SERVFAIL is
|
|
|
|
// returned due to missing upstream resolvers.
|
|
|
|
//
|
2024-06-25 18:56:13 +00:00
|
|
|
// This should attempt to properly (re)set the upstream resolvers.
|
|
|
|
missingUpstreamRecovery func()
|
2020-08-19 19:39:25 +00:00
|
|
|
}
|
|
|
|
|
2024-07-29 17:48:46 +00:00
|
|
|
func newForwarder(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkSelector, dialer *tsdial.Dialer, health *health.Tracker, knobs *controlknobs.Knobs) *forwarder {
|
2024-04-27 05:06:20 +00:00
|
|
|
if netMon == nil {
|
|
|
|
panic("nil netMon")
|
|
|
|
}
|
2021-06-23 04:53:43 +00:00
|
|
|
f := &forwarder{
|
2024-07-02 18:22:13 +00:00
|
|
|
logf: logger.WithPrefix(logf, "forward: "),
|
|
|
|
netMon: netMon,
|
|
|
|
linkSel: linkSel,
|
|
|
|
dialer: dialer,
|
2024-07-29 17:48:46 +00:00
|
|
|
health: health,
|
2024-07-02 18:22:13 +00:00
|
|
|
controlKnobs: knobs,
|
|
|
|
missingUpstreamRecovery: func() {},
|
2020-08-19 19:39:25 +00:00
|
|
|
}
|
2021-06-23 04:53:43 +00:00
|
|
|
f.ctx, f.ctxCancel = context.WithCancel(context.Background())
|
|
|
|
return f
|
2020-08-19 19:39:25 +00:00
|
|
|
}
|
|
|
|
|
2021-06-23 04:53:43 +00:00
|
|
|
func (f *forwarder) Close() error {
|
|
|
|
f.ctxCancel()
|
|
|
|
return nil
|
2021-03-12 16:39:43 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 04:58:00 +00:00
|
|
|
// resolversWithDelays maps from a set of DNS server names to a slice of a type
|
|
|
|
// that included a startDelay, upgrading any well-known DoH (DNS-over-HTTP)
|
|
|
|
// servers in the process, insert a DoH lookup first before UDP fallbacks.
|
2022-05-03 21:41:58 +00:00
|
|
|
func resolversWithDelays(resolvers []*dnstype.Resolver) []resolverAndDelay {
|
2022-04-19 04:58:00 +00:00
|
|
|
rr := make([]resolverAndDelay, 0, len(resolvers)+2)
|
2021-07-26 03:40:37 +00:00
|
|
|
|
2022-09-06 18:15:30 +00:00
|
|
|
type dohState uint8
|
|
|
|
const addedDoH = dohState(1)
|
|
|
|
const addedDoHAndDontAddUDP = dohState(2)
|
|
|
|
|
2022-04-19 04:58:00 +00:00
|
|
|
// Add the known DoH ones first, starting immediately.
|
2022-09-06 18:15:30 +00:00
|
|
|
didDoH := map[string]dohState{}
|
2021-08-03 13:56:31 +00:00
|
|
|
for _, r := range resolvers {
|
2022-04-19 04:58:00 +00:00
|
|
|
ipp, ok := r.IPPort()
|
|
|
|
if !ok {
|
|
|
|
continue
|
2021-07-26 03:40:37 +00:00
|
|
|
}
|
2022-09-06 18:15:30 +00:00
|
|
|
dohBase, dohOnly, ok := publicdns.DoHEndpointFromIP(ipp.Addr())
|
|
|
|
if !ok || didDoH[dohBase] != 0 {
|
2022-04-19 04:58:00 +00:00
|
|
|
continue
|
|
|
|
}
|
2022-09-06 18:15:30 +00:00
|
|
|
if dohOnly {
|
|
|
|
didDoH[dohBase] = addedDoHAndDontAddUDP
|
|
|
|
} else {
|
|
|
|
didDoH[dohBase] = addedDoH
|
|
|
|
}
|
2022-05-03 21:41:58 +00:00
|
|
|
rr = append(rr, resolverAndDelay{name: &dnstype.Resolver{Addr: dohBase}})
|
2021-07-26 03:40:37 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 04:58:00 +00:00
|
|
|
type hostAndFam struct {
|
|
|
|
host string // some arbitrary string representing DNS host (currently the DoH base)
|
|
|
|
bits uint8 // either 32 or 128 for IPv4 vs IPv6s address family
|
|
|
|
}
|
2021-07-26 03:40:37 +00:00
|
|
|
done := map[hostAndFam]int{}
|
2022-04-19 04:58:00 +00:00
|
|
|
for _, r := range resolvers {
|
|
|
|
ipp, ok := r.IPPort()
|
|
|
|
if !ok {
|
|
|
|
// Pass non-IP ones through unchanged, without delay.
|
|
|
|
// (e.g. DNS-over-ExitDNS when using an exit node)
|
|
|
|
rr = append(rr, resolverAndDelay{name: r})
|
|
|
|
continue
|
|
|
|
}
|
2022-07-25 03:08:42 +00:00
|
|
|
ip := ipp.Addr()
|
2021-07-26 03:40:37 +00:00
|
|
|
var startDelay time.Duration
|
2022-09-06 18:15:30 +00:00
|
|
|
if host, _, ok := publicdns.DoHEndpointFromIP(ip); ok {
|
|
|
|
if didDoH[host] == addedDoHAndDontAddUDP {
|
|
|
|
continue
|
|
|
|
}
|
2022-04-19 04:58:00 +00:00
|
|
|
// We already did the DoH query early. These
|
2022-09-06 18:15:30 +00:00
|
|
|
// are for normal dns53 UDP queries.
|
2022-04-19 04:58:00 +00:00
|
|
|
startDelay = dohHeadStart
|
2022-07-25 03:08:42 +00:00
|
|
|
key := hostAndFam{host, uint8(ip.BitLen())}
|
2022-04-19 04:58:00 +00:00
|
|
|
if done[key] > 0 {
|
|
|
|
startDelay += wellKnownHostBackupDelay
|
2021-07-26 03:40:37 +00:00
|
|
|
}
|
2022-04-19 04:58:00 +00:00
|
|
|
done[key]++
|
2021-07-26 03:40:37 +00:00
|
|
|
}
|
2022-04-19 04:58:00 +00:00
|
|
|
rr = append(rr, resolverAndDelay{
|
2021-08-03 13:56:31 +00:00
|
|
|
name: r,
|
2021-07-26 03:40:37 +00:00
|
|
|
startDelay: startDelay,
|
2022-04-19 04:58:00 +00:00
|
|
|
})
|
2021-07-26 03:40:37 +00:00
|
|
|
}
|
|
|
|
return rr
|
|
|
|
}
|
|
|
|
|
2022-06-30 02:32:41 +00:00
|
|
|
var (
|
|
|
|
cloudResolversOnce sync.Once
|
|
|
|
cloudResolversLazy []resolverAndDelay
|
|
|
|
)
|
|
|
|
|
|
|
|
func cloudResolvers() []resolverAndDelay {
|
|
|
|
cloudResolversOnce.Do(func() {
|
|
|
|
if ip := cloudenv.Get().ResolverIP(); ip != "" {
|
|
|
|
cloudResolver := []*dnstype.Resolver{{Addr: ip}}
|
|
|
|
cloudResolversLazy = resolversWithDelays(cloudResolver)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return cloudResolversLazy
|
|
|
|
}
|
|
|
|
|
2021-07-26 03:40:37 +00:00
|
|
|
// setRoutes sets the routes to use for DNS forwarding. It's called by
|
|
|
|
// Resolver.SetConfig on reconfig.
|
|
|
|
//
|
|
|
|
// The memory referenced by routesBySuffix should not be modified.
|
2022-05-03 21:41:58 +00:00
|
|
|
func (f *forwarder) setRoutes(routesBySuffix map[dnsname.FQDN][]*dnstype.Resolver) {
|
2021-07-26 03:40:37 +00:00
|
|
|
routes := make([]route, 0, len(routesBySuffix))
|
2022-06-30 02:32:41 +00:00
|
|
|
|
|
|
|
cloudHostFallback := cloudResolvers()
|
2021-08-03 13:56:31 +00:00
|
|
|
for suffix, rs := range routesBySuffix {
|
2022-06-30 02:32:41 +00:00
|
|
|
if suffix == "." && len(rs) == 0 && len(cloudHostFallback) > 0 {
|
|
|
|
routes = append(routes, route{
|
|
|
|
Suffix: suffix,
|
|
|
|
Resolvers: cloudHostFallback,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
routes = append(routes, route{
|
|
|
|
Suffix: suffix,
|
|
|
|
Resolvers: resolversWithDelays(rs),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if cloudenv.Get().HasInternalTLD() && len(cloudHostFallback) > 0 {
|
|
|
|
if _, ok := routesBySuffix["internal."]; !ok {
|
|
|
|
routes = append(routes, route{
|
|
|
|
Suffix: "internal.",
|
|
|
|
Resolvers: cloudHostFallback,
|
|
|
|
})
|
|
|
|
}
|
2021-07-26 03:40:37 +00:00
|
|
|
}
|
2022-06-30 02:32:41 +00:00
|
|
|
|
2021-07-26 03:40:37 +00:00
|
|
|
// Sort from longest prefix to shortest.
|
|
|
|
sort.Slice(routes, func(i, j int) bool {
|
|
|
|
return routes[i].Suffix.NumLabels() > routes[j].Suffix.NumLabels()
|
|
|
|
})
|
|
|
|
|
2020-08-19 19:39:25 +00:00
|
|
|
f.mu.Lock()
|
2021-06-23 04:53:43 +00:00
|
|
|
defer f.mu.Unlock()
|
2021-04-02 02:31:55 +00:00
|
|
|
f.routes = routes
|
2022-06-30 02:32:41 +00:00
|
|
|
f.cloudHostFallback = cloudHostFallback
|
2020-08-19 19:39:25 +00:00
|
|
|
}
|
|
|
|
|
2022-07-25 03:08:42 +00:00
|
|
|
var stdNetPacketListener nettype.PacketListenerWithNetIP = nettype.MakePacketListenerWithNetIP(new(net.ListenConfig))
|
2020-08-19 19:39:25 +00:00
|
|
|
|
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 (f *forwarder) packetListener(ip netip.Addr) (nettype.PacketListenerWithNetIP, error) {
|
2021-06-23 04:53:43 +00:00
|
|
|
if f.linkSel == nil || initListenConfig == nil {
|
|
|
|
return stdNetPacketListener, nil
|
|
|
|
}
|
|
|
|
linkName := f.linkSel.PickLink(ip)
|
|
|
|
if linkName == "" {
|
|
|
|
return stdNetPacketListener, nil
|
|
|
|
}
|
|
|
|
lc := new(net.ListenConfig)
|
2023-04-18 21:26:58 +00:00
|
|
|
if err := initListenConfig(lc, f.netMon, linkName); err != nil {
|
2021-06-23 04:53:43 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2022-07-25 03:08:42 +00:00
|
|
|
return nettype.MakePacketListenerWithNetIP(lc), nil
|
2021-06-23 04:53:43 +00:00
|
|
|
}
|
2020-08-19 19:39:25 +00:00
|
|
|
|
2022-04-18 19:50:26 +00:00
|
|
|
// getKnownDoHClientForProvider returns an HTTP client for a specific DoH
|
|
|
|
// provider named by its DoH base URL (like "https://dns.google/dns-query").
|
|
|
|
//
|
|
|
|
// The returned client race/Happy Eyeballs dials all IPs for urlBase (usually
|
|
|
|
// 4), as statically known by the publicdns package.
|
|
|
|
func (f *forwarder) getKnownDoHClientForProvider(urlBase string) (c *http.Client, ok bool) {
|
2021-07-15 16:11:12 +00:00
|
|
|
f.mu.Lock()
|
|
|
|
defer f.mu.Unlock()
|
2021-08-03 13:56:31 +00:00
|
|
|
if c, ok := f.dohClient[urlBase]; ok {
|
2022-04-18 19:50:26 +00:00
|
|
|
return c, true
|
2021-07-15 16:11:12 +00:00
|
|
|
}
|
2022-09-06 18:15:30 +00:00
|
|
|
allIPs := publicdns.DoHIPsOfBase(urlBase)
|
2022-04-18 19:50:26 +00:00
|
|
|
if len(allIPs) == 0 {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
dohURL, err := url.Parse(urlBase)
|
|
|
|
if err != nil {
|
|
|
|
return nil, false
|
2021-07-15 16:11:12 +00:00
|
|
|
}
|
2024-05-02 23:33:13 +00:00
|
|
|
|
|
|
|
dialer := dnscache.Dialer(f.getDialerType(), &dnscache.Resolver{
|
2022-04-18 19:50:26 +00:00
|
|
|
SingleHost: dohURL.Hostname(),
|
|
|
|
SingleHostStaticResult: allIPs,
|
2023-03-12 14:58:11 +00:00
|
|
|
Logf: f.logf,
|
2022-04-18 19:50:26 +00:00
|
|
|
})
|
2021-07-15 16:11:12 +00:00
|
|
|
c = &http.Client{
|
|
|
|
Transport: &http.Transport{
|
2022-08-23 01:53:03 +00:00
|
|
|
ForceAttemptHTTP2: true,
|
2022-09-06 18:15:30 +00:00
|
|
|
IdleConnTimeout: dohTransportTimeout,
|
2024-02-24 06:51:17 +00:00
|
|
|
// On mobile platforms TCP KeepAlive is disabled in the dialer,
|
|
|
|
// ensure that we timeout if the connection appears to be hung.
|
|
|
|
ResponseHeaderTimeout: 10 * time.Second,
|
2021-07-15 16:11:12 +00:00
|
|
|
DialContext: func(ctx context.Context, netw, addr string) (net.Conn, error) {
|
|
|
|
if !strings.HasPrefix(netw, "tcp") {
|
|
|
|
return nil, fmt.Errorf("unexpected network %q", netw)
|
|
|
|
}
|
2022-04-18 19:50:26 +00:00
|
|
|
return dialer(ctx, netw, addr)
|
2021-07-15 16:11:12 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2022-04-18 19:50:26 +00:00
|
|
|
if f.dohClient == nil {
|
|
|
|
f.dohClient = map[string]*http.Client{}
|
|
|
|
}
|
2021-08-03 13:56:31 +00:00
|
|
|
f.dohClient[urlBase] = c
|
2022-04-18 19:50:26 +00:00
|
|
|
return c, true
|
2021-07-15 16:11:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const dohType = "application/dns-message"
|
|
|
|
|
|
|
|
func (f *forwarder) sendDoH(ctx context.Context, urlBase string, c *http.Client, packet []byte) ([]byte, error) {
|
2023-04-13 01:23:22 +00:00
|
|
|
ctx = sockstats.WithSockStats(ctx, sockstats.LabelDNSForwarderDoH, f.logf)
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdDoH.Add(1)
|
2021-07-15 16:11:12 +00:00
|
|
|
req, err := http.NewRequestWithContext(ctx, "POST", urlBase, bytes.NewReader(packet))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.Header.Set("Content-Type", dohType)
|
2022-09-10 15:43:18 +00:00
|
|
|
req.Header.Set("Accept", dohType)
|
2023-02-11 06:20:36 +00:00
|
|
|
req.Header.Set("User-Agent", "tailscaled/"+version.Long())
|
2021-07-15 16:11:12 +00:00
|
|
|
|
|
|
|
hres, err := c.Do(req)
|
|
|
|
if err != nil {
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdDoHErrorTransport.Add(1)
|
2021-07-15 16:11:12 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer hres.Body.Close()
|
|
|
|
if hres.StatusCode != 200 {
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdDoHErrorStatus.Add(1)
|
2021-07-15 16:11:12 +00:00
|
|
|
return nil, errors.New(hres.Status)
|
|
|
|
}
|
|
|
|
if ct := hres.Header.Get("Content-Type"); ct != dohType {
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdDoHErrorCT.Add(1)
|
2021-07-15 16:11:12 +00:00
|
|
|
return nil, fmt.Errorf("unexpected response Content-Type %q", ct)
|
|
|
|
}
|
2022-09-15 12:06:59 +00:00
|
|
|
res, err := io.ReadAll(hres.Body)
|
2021-11-26 22:43:38 +00:00
|
|
|
if err != nil {
|
|
|
|
metricDNSFwdDoHErrorBody.Add(1)
|
|
|
|
}
|
2022-04-22 22:01:55 +00:00
|
|
|
if truncatedFlagSet(res) {
|
|
|
|
metricDNSFwdTruncated.Add(1)
|
|
|
|
}
|
2021-11-26 22:43:38 +00:00
|
|
|
return res, err
|
2021-07-15 16:11:12 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 20:27:50 +00:00
|
|
|
var (
|
|
|
|
verboseDNSForward = envknob.RegisterBool("TS_DEBUG_DNS_FORWARD_SEND")
|
|
|
|
skipTCPRetry = envknob.RegisterBool("TS_DNS_FORWARD_SKIP_TCP_RETRY")
|
2024-01-05 15:33:09 +00:00
|
|
|
|
|
|
|
// For correlating log messages in the send() function; only used when
|
|
|
|
// verboseDNSForward() is true.
|
|
|
|
forwarderCount atomic.Uint64
|
2023-09-07 20:27:50 +00:00
|
|
|
)
|
2022-04-19 04:58:00 +00:00
|
|
|
|
2021-06-23 04:53:43 +00:00
|
|
|
// send sends packet to dst. It is best effort.
|
|
|
|
//
|
|
|
|
// send expects the reply to have the same txid as txidOut.
|
2022-04-19 04:58:00 +00:00
|
|
|
func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDelay) (ret []byte, err error) {
|
2022-09-14 19:49:39 +00:00
|
|
|
if verboseDNSForward() {
|
2024-01-05 15:33:09 +00:00
|
|
|
id := forwarderCount.Add(1)
|
2024-08-08 20:41:08 +00:00
|
|
|
domain, typ, _ := nameFromQuery(fq.packet)
|
|
|
|
f.logf("forwarder.send(%q, %d, %v, %d) [%d] ...", rr.name.Addr, fq.txid, typ, len(domain), id)
|
2022-04-19 04:58:00 +00:00
|
|
|
defer func() {
|
2024-08-08 20:41:08 +00:00
|
|
|
f.logf("forwarder.send(%q, %d, %v, %d) [%d] = %v, %v", rr.name.Addr, fq.txid, typ, len(domain), id, len(ret), err)
|
2022-04-19 04:58:00 +00:00
|
|
|
}()
|
|
|
|
}
|
2021-08-03 13:56:31 +00:00
|
|
|
if strings.HasPrefix(rr.name.Addr, "http://") {
|
2021-12-01 04:39:12 +00:00
|
|
|
return f.sendDoH(ctx, rr.name.Addr, f.dialer.PeerAPIHTTPClient(), fq.packet)
|
2021-08-03 13:56:31 +00:00
|
|
|
}
|
|
|
|
if strings.HasPrefix(rr.name.Addr, "https://") {
|
2022-04-19 04:58:00 +00:00
|
|
|
// Only known DoH providers are supported currently. Specifically, we
|
|
|
|
// only support DoH providers where we can TCP connect to them on port
|
|
|
|
// 443 at the same IP address they serve normal UDP DNS from (1.1.1.1,
|
2022-09-25 18:29:55 +00:00
|
|
|
// 8.8.8.8, 9.9.9.9, etc.) That's why OpenDNS and custom DoH providers
|
2022-04-19 04:58:00 +00:00
|
|
|
// aren't currently supported. There's no backup DNS resolution path for
|
|
|
|
// them.
|
|
|
|
urlBase := rr.name.Addr
|
|
|
|
if hc, ok := f.getKnownDoHClientForProvider(urlBase); ok {
|
|
|
|
return f.sendDoH(ctx, urlBase, hc, fq.packet)
|
|
|
|
}
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdErrorType.Add(1)
|
2022-04-19 04:58:00 +00:00
|
|
|
return nil, fmt.Errorf("arbitrary https:// resolvers not supported yet")
|
2021-08-03 13:56:31 +00:00
|
|
|
}
|
|
|
|
if strings.HasPrefix(rr.name.Addr, "tls://") {
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdErrorType.Add(1)
|
2021-08-03 13:56:31 +00:00
|
|
|
return nil, fmt.Errorf("tls:// resolvers not supported yet")
|
|
|
|
}
|
2022-04-22 22:01:55 +00:00
|
|
|
|
2023-10-03 20:26:38 +00:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
isUDPQuery := fq.family == "udp"
|
|
|
|
skipTCP := skipTCPRetry() || (f.controlKnobs != nil && f.controlKnobs.DisableDNSForwarderTCPRetries.Load())
|
|
|
|
|
|
|
|
// Print logs about retries if this was because of a truncated response.
|
|
|
|
var explicitRetry atomic.Bool // true if truncated UDP response retried
|
|
|
|
defer func() {
|
|
|
|
if !explicitRetry.Load() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
f.logf("forwarder.send(%q): successfully retried via TCP", rr.name.Addr)
|
|
|
|
} else {
|
|
|
|
f.logf("forwarder.send(%q): could not retry via TCP: %v", rr.name.Addr, err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
firstUDP := func(ctx context.Context) ([]byte, error) {
|
|
|
|
resp, err := f.sendUDP(ctx, fq, rr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !truncatedFlagSet(resp) {
|
|
|
|
// Successful, non-truncated response; no retry.
|
|
|
|
return resp, nil
|
|
|
|
}
|
2023-09-07 20:27:50 +00:00
|
|
|
|
|
|
|
// If this is a UDP query, return it regardless of whether the
|
|
|
|
// response is truncated or not; the client can retry
|
|
|
|
// communicating with tailscaled over TCP. There's no point
|
|
|
|
// falling back to TCP for a truncated query if we can't return
|
|
|
|
// the results to the client.
|
2023-10-03 20:26:38 +00:00
|
|
|
if isUDPQuery {
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if skipTCP {
|
|
|
|
// Envknob or control knob disabled the TCP retry behaviour;
|
|
|
|
// just return what we have.
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is a TCP query from the client, and the UDP response
|
|
|
|
// from the upstream DNS server is truncated; map this to an
|
|
|
|
// error to cause our retry helper to immediately kick off the
|
|
|
|
// TCP retry.
|
|
|
|
explicitRetry.Store(true)
|
|
|
|
return nil, truncatedResponseError{resp}
|
|
|
|
}
|
|
|
|
thenTCP := func(ctx context.Context) ([]byte, error) {
|
|
|
|
// If we're skipping the TCP fallback, then wait until the
|
|
|
|
// context is canceled and return that error (i.e. not
|
|
|
|
// returning anything).
|
|
|
|
if skipTCP {
|
|
|
|
<-ctx.Done()
|
|
|
|
return nil, ctx.Err()
|
|
|
|
}
|
|
|
|
|
|
|
|
return f.sendTCP(ctx, fq, rr)
|
2023-09-07 20:27:50 +00:00
|
|
|
}
|
2023-10-03 20:26:38 +00:00
|
|
|
|
|
|
|
// If the input query is TCP, then don't have a timeout between
|
|
|
|
// starting UDP and TCP.
|
|
|
|
timeout := udpRaceTimeout
|
|
|
|
if !isUDPQuery {
|
|
|
|
timeout = 0
|
2023-09-07 20:27:50 +00:00
|
|
|
}
|
|
|
|
|
2023-10-03 20:26:38 +00:00
|
|
|
// Kick off the race between the UDP and TCP queries.
|
2024-04-27 05:06:20 +00:00
|
|
|
rh := race.New(timeout, firstUDP, thenTCP)
|
2023-10-03 20:26:38 +00:00
|
|
|
resp, err := rh.Start(ctx)
|
|
|
|
if err == nil {
|
|
|
|
return resp, nil
|
2023-09-07 20:27:50 +00:00
|
|
|
}
|
|
|
|
|
2023-10-03 20:26:38 +00:00
|
|
|
// If we got a truncated UDP response, return that instead of an error.
|
|
|
|
var trErr truncatedResponseError
|
|
|
|
if errors.As(err, &trErr) {
|
|
|
|
return trErr.res, nil
|
2023-09-07 20:27:50 +00:00
|
|
|
}
|
2023-10-03 20:26:38 +00:00
|
|
|
return nil, err
|
2022-04-22 22:01:55 +00:00
|
|
|
}
|
|
|
|
|
2023-10-03 20:26:38 +00:00
|
|
|
type truncatedResponseError struct {
|
|
|
|
res []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tr truncatedResponseError) Error() string { return "response truncated" }
|
|
|
|
|
2022-06-15 23:19:05 +00:00
|
|
|
var errServerFailure = errors.New("response code indicates server issue")
|
2023-09-07 20:27:50 +00:00
|
|
|
var errTxIDMismatch = errors.New("txid doesn't match")
|
2022-06-15 23:19:05 +00:00
|
|
|
|
2022-04-22 22:01:55 +00:00
|
|
|
func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAndDelay) (ret []byte, err error) {
|
2022-04-19 04:58:00 +00:00
|
|
|
ipp, ok := rr.name.IPPort()
|
|
|
|
if !ok {
|
|
|
|
metricDNSFwdErrorType.Add(1)
|
|
|
|
return nil, fmt.Errorf("unrecognized resolver type %q", rr.name.Addr)
|
2021-07-15 16:11:12 +00:00
|
|
|
}
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdUDP.Add(1)
|
2023-04-13 01:23:22 +00:00
|
|
|
ctx = sockstats.WithSockStats(ctx, sockstats.LabelDNSForwarderUDP, f.logf)
|
2022-04-22 22:01:55 +00:00
|
|
|
|
2022-07-25 03:08:42 +00:00
|
|
|
ln, err := f.packetListener(ipp.Addr())
|
2021-06-23 04:53:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-07-25 03:08:42 +00:00
|
|
|
|
|
|
|
// Specify the exact UDP family to work around https://github.com/golang/go/issues/52264
|
|
|
|
udpFam := "udp4"
|
|
|
|
if ipp.Addr().Is6() {
|
|
|
|
udpFam = "udp6"
|
|
|
|
}
|
|
|
|
conn, err := ln.ListenPacket(ctx, udpFam, ":0")
|
2021-06-23 04:53:43 +00:00
|
|
|
if err != nil {
|
|
|
|
f.logf("ListenPacket failed: %v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer conn.Close()
|
2020-08-19 19:39:25 +00:00
|
|
|
|
2021-07-25 22:43:49 +00:00
|
|
|
fq.closeOnCtxDone.Add(conn)
|
|
|
|
defer fq.closeOnCtxDone.Remove(conn)
|
2021-06-07 21:16:07 +00:00
|
|
|
|
2022-07-25 03:08:42 +00:00
|
|
|
if _, err := conn.WriteToUDPAddrPort(fq.packet, ipp); err != nil {
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdUDPErrorWrite.Add(1)
|
2021-06-23 04:53:43 +00:00
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
return nil, err
|
2021-06-07 21:16:07 +00:00
|
|
|
}
|
2021-06-23 04:53:43 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdUDPWrote.Add(1)
|
2021-06-07 21:16:07 +00:00
|
|
|
|
2021-06-23 04:53:43 +00:00
|
|
|
// The 1 extra byte is to detect packet truncation.
|
|
|
|
out := make([]byte, maxResponseBytes+1)
|
2023-04-15 20:08:16 +00:00
|
|
|
n, _, err := conn.ReadFromUDPAddrPort(out)
|
2021-06-23 04:53:43 +00:00
|
|
|
if err != nil {
|
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
return nil, err
|
2020-08-19 19:39:25 +00:00
|
|
|
}
|
2022-01-03 18:49:56 +00:00
|
|
|
if neterror.PacketWasTruncated(err) {
|
2021-06-23 04:53:43 +00:00
|
|
|
err = nil
|
|
|
|
} else {
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdUDPErrorRead.Add(1)
|
2021-06-23 04:53:43 +00:00
|
|
|
return nil, err
|
2020-08-19 19:39:25 +00:00
|
|
|
}
|
|
|
|
}
|
2021-06-23 04:53:43 +00:00
|
|
|
truncated := n > maxResponseBytes
|
|
|
|
if truncated {
|
|
|
|
n = maxResponseBytes
|
|
|
|
}
|
|
|
|
if n < headerBytes {
|
|
|
|
f.logf("recv: packet too small (%d bytes)", n)
|
|
|
|
}
|
|
|
|
out = out[:n]
|
|
|
|
txid := getTxID(out)
|
2021-07-25 22:43:49 +00:00
|
|
|
if txid != fq.txid {
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdUDPErrorTxID.Add(1)
|
2023-09-07 20:27:50 +00:00
|
|
|
return nil, errTxIDMismatch
|
2021-06-23 04:53:43 +00:00
|
|
|
}
|
2021-09-19 00:34:33 +00:00
|
|
|
rcode := getRCode(out)
|
|
|
|
// don't forward transient errors back to the client when the server fails
|
|
|
|
if rcode == dns.RCodeServerFailure {
|
|
|
|
f.logf("recv: response code indicating server failure: %d", rcode)
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdUDPErrorServer.Add(1)
|
2022-06-15 23:19:05 +00:00
|
|
|
return nil, errServerFailure
|
2021-09-19 00:34:33 +00:00
|
|
|
}
|
2020-08-19 19:39:25 +00:00
|
|
|
|
2021-06-23 04:53:43 +00:00
|
|
|
if truncated {
|
2022-04-22 22:01:55 +00:00
|
|
|
// Set the truncated bit if it wasn't already.
|
2021-06-23 04:53:43 +00:00
|
|
|
flags := binary.BigEndian.Uint16(out[2:4])
|
|
|
|
flags |= dnsFlagTruncated
|
|
|
|
binary.BigEndian.PutUint16(out[2:4], flags)
|
|
|
|
|
|
|
|
// TODO(#2067): Remove any incomplete records? RFC 1035 section 6.2
|
|
|
|
// states that truncation should head drop so that the authority
|
|
|
|
// section can be preserved if possible. However, the UDP read with
|
|
|
|
// a too-small buffer has already dropped the end, so that's the
|
|
|
|
// best we can do.
|
|
|
|
}
|
2020-08-19 19:39:25 +00:00
|
|
|
|
2022-04-22 22:01:55 +00:00
|
|
|
if truncatedFlagSet(out) {
|
|
|
|
metricDNSFwdTruncated.Add(1)
|
|
|
|
}
|
|
|
|
|
2021-06-24 14:36:23 +00:00
|
|
|
clampEDNSSize(out, maxResponseBytes)
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdUDPSuccess.Add(1)
|
2021-06-23 04:53:43 +00:00
|
|
|
return out, nil
|
|
|
|
}
|
2020-08-19 19:39:25 +00:00
|
|
|
|
2024-05-02 23:33:13 +00:00
|
|
|
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.
|
|
|
|
return f.dialer.UserDial
|
|
|
|
}
|
|
|
|
return f.dialer.SystemDial
|
|
|
|
}
|
|
|
|
|
2023-09-07 20:27:50 +00:00
|
|
|
func (f *forwarder) sendTCP(ctx context.Context, fq *forwardQuery, rr resolverAndDelay) (ret []byte, err error) {
|
|
|
|
ipp, ok := rr.name.IPPort()
|
|
|
|
if !ok {
|
|
|
|
metricDNSFwdErrorType.Add(1)
|
|
|
|
return nil, fmt.Errorf("unrecognized resolver type %q", rr.name.Addr)
|
|
|
|
}
|
|
|
|
metricDNSFwdTCP.Add(1)
|
|
|
|
ctx = sockstats.WithSockStats(ctx, sockstats.LabelDNSForwarderTCP, f.logf)
|
|
|
|
|
|
|
|
// Specify the exact family to work around https://github.com/golang/go/issues/52264
|
|
|
|
tcpFam := "tcp4"
|
|
|
|
if ipp.Addr().Is6() {
|
|
|
|
tcpFam = "tcp6"
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, tcpQueryTimeout)
|
|
|
|
defer cancel()
|
|
|
|
|
2024-05-02 23:33:13 +00:00
|
|
|
conn, err := f.getDialerType()(ctx, tcpFam, ipp.String())
|
2023-09-07 20:27:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
|
|
|
|
fq.closeOnCtxDone.Add(conn)
|
|
|
|
defer fq.closeOnCtxDone.Remove(conn)
|
|
|
|
|
|
|
|
ctxOrErr := func(err2 error) ([]byte, error) {
|
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return nil, err2
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write the query to the server.
|
|
|
|
query := make([]byte, len(fq.packet)+2)
|
|
|
|
binary.BigEndian.PutUint16(query, uint16(len(fq.packet)))
|
|
|
|
copy(query[2:], fq.packet)
|
|
|
|
if _, err := conn.Write(query); err != nil {
|
|
|
|
metricDNSFwdTCPErrorWrite.Add(1)
|
|
|
|
return ctxOrErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
metricDNSFwdTCPWrote.Add(1)
|
|
|
|
|
|
|
|
// Read the header length back from the server
|
|
|
|
var length uint16
|
|
|
|
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
|
|
|
|
metricDNSFwdTCPErrorRead.Add(1)
|
|
|
|
return ctxOrErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now read the response
|
|
|
|
out := make([]byte, length)
|
|
|
|
n, err := io.ReadFull(conn, out)
|
|
|
|
if err != nil {
|
|
|
|
metricDNSFwdTCPErrorRead.Add(1)
|
|
|
|
return ctxOrErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if n < int(length) {
|
|
|
|
f.logf("sendTCP: packet too small (%d bytes)", n)
|
|
|
|
return nil, io.ErrUnexpectedEOF
|
|
|
|
}
|
|
|
|
out = out[:n]
|
|
|
|
txid := getTxID(out)
|
|
|
|
if txid != fq.txid {
|
|
|
|
metricDNSFwdTCPErrorTxID.Add(1)
|
|
|
|
return nil, errTxIDMismatch
|
|
|
|
}
|
|
|
|
|
|
|
|
rcode := getRCode(out)
|
|
|
|
|
|
|
|
// don't forward transient errors back to the client when the server fails
|
|
|
|
if rcode == dns.RCodeServerFailure {
|
|
|
|
f.logf("sendTCP: response code indicating server failure: %d", rcode)
|
|
|
|
metricDNSFwdTCPErrorServer.Add(1)
|
|
|
|
return nil, errServerFailure
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(andrew): do we need to do this?
|
|
|
|
//clampEDNSSize(out, maxResponseBytes)
|
|
|
|
metricDNSFwdTCPSuccess.Add(1)
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
2021-06-23 04:53:43 +00:00
|
|
|
// resolvers returns the resolvers to use for domain.
|
2021-07-26 03:40:37 +00:00
|
|
|
func (f *forwarder) resolvers(domain dnsname.FQDN) []resolverAndDelay {
|
2021-06-23 04:53:43 +00:00
|
|
|
f.mu.Lock()
|
|
|
|
routes := f.routes
|
2022-06-30 02:32:41 +00:00
|
|
|
cloudHostFallback := f.cloudHostFallback
|
2021-06-23 04:53:43 +00:00
|
|
|
f.mu.Unlock()
|
|
|
|
for _, route := range routes {
|
|
|
|
if route.Suffix == "." || route.Suffix.Contains(domain) {
|
2022-06-30 02:32:41 +00:00
|
|
|
return route.Resolvers
|
2020-08-19 19:39:25 +00:00
|
|
|
}
|
|
|
|
}
|
2022-06-30 02:32:41 +00:00
|
|
|
return cloudHostFallback // or nil if no fallback
|
2020-08-19 19:39:25 +00:00
|
|
|
}
|
|
|
|
|
2024-09-24 20:18:45 +00:00
|
|
|
// GetUpstreamResolvers returns the resolvers that would be used to resolve
|
|
|
|
// the given FQDN.
|
|
|
|
func (f *forwarder) GetUpstreamResolvers(name dnsname.FQDN) []*dnstype.Resolver {
|
|
|
|
resolvers := f.resolvers(name)
|
|
|
|
upstreamResolvers := make([]*dnstype.Resolver, 0, len(resolvers))
|
|
|
|
for _, r := range resolvers {
|
|
|
|
upstreamResolvers = append(upstreamResolvers, r.name)
|
|
|
|
}
|
|
|
|
return upstreamResolvers
|
|
|
|
}
|
|
|
|
|
2021-07-25 22:43:49 +00:00
|
|
|
// forwardQuery is information and state about a forwarded DNS query that's
|
|
|
|
// being sent to 1 or more upstreams.
|
|
|
|
//
|
|
|
|
// In the case of racing against multiple equivalent upstreams
|
|
|
|
// (e.g. Google or CloudFlare's 4 DNS IPs: 2 IPv4 + 2 IPv6), this type
|
|
|
|
// handles racing them more intelligently than just blasting away 4
|
|
|
|
// queries at once.
|
|
|
|
type forwardQuery struct {
|
|
|
|
txid txid
|
|
|
|
packet []byte
|
2023-09-07 20:27:50 +00:00
|
|
|
family string // "tcp" or "udp"
|
2021-07-25 22:43:49 +00:00
|
|
|
|
|
|
|
// closeOnCtxDone lets send register values to Close if the
|
|
|
|
// caller's ctx expires. This avoids send from allocating its
|
|
|
|
// own waiting goroutine to interrupt the ReadFrom, as memory
|
|
|
|
// is tight on iOS and we want the number of pending DNS
|
|
|
|
// lookups to be bursty without too much associated
|
|
|
|
// goroutine/memory cost.
|
|
|
|
closeOnCtxDone *closePool
|
|
|
|
|
|
|
|
// TODO(bradfitz): add race delay state:
|
|
|
|
// mu sync.Mutex
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
|
2021-12-18 23:11:01 +00:00
|
|
|
// forwardWithDestChan forwards the query to all upstream nameservers
|
|
|
|
// and waits for the first response.
|
2021-11-23 17:58:34 +00:00
|
|
|
//
|
|
|
|
// It either sends to responseChan and returns nil, or returns a
|
|
|
|
// non-nil error (without sending to the channel).
|
|
|
|
//
|
2021-11-29 22:18:09 +00:00
|
|
|
// If resolvers is non-empty, it's used explicitly (notably, for exit
|
|
|
|
// node DNS proxy queries), otherwise f.resolvers is used.
|
|
|
|
func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, responseChan chan<- packet, resolvers ...resolverAndDelay) error {
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwd.Add(1)
|
2024-08-08 20:41:08 +00:00
|
|
|
domain, typ, err := nameFromQuery(query.bs)
|
2021-04-02 02:31:55 +00:00
|
|
|
if err != nil {
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdErrorName.Add(1)
|
2021-04-02 02:31:55 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-04-19 17:58:52 +00:00
|
|
|
// Guarantee that the ctx we use below is done when this function returns.
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
defer cancel()
|
|
|
|
|
2021-10-06 22:01:48 +00:00
|
|
|
// Drop DNS service discovery spam, primarily for battery life
|
2021-10-08 12:57:16 +00:00
|
|
|
// on mobile. Things like Spotify on iOS generate this traffic,
|
|
|
|
// when browsing for LAN devices. But even when filtering this
|
|
|
|
// out, playing on Sonos still works.
|
|
|
|
if hasRDNSBonjourPrefix(domain) {
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdDropBonjour.Add(1)
|
2021-12-18 23:37:40 +00:00
|
|
|
res, err := nxDomainResponse(query)
|
|
|
|
if err != nil {
|
|
|
|
f.logf("error parsing bonjour query: %v", err)
|
2022-08-09 20:29:34 +00:00
|
|
|
// Returning an error will cause an internal retry, there is
|
|
|
|
// nothing we can do if parsing failed. Just drop the packet.
|
2021-12-18 23:37:40 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
2024-05-02 16:28:38 +00:00
|
|
|
return fmt.Errorf("waiting to send NXDOMAIN: %w", ctx.Err())
|
2021-12-18 23:37:40 +00:00
|
|
|
case responseChan <- res:
|
|
|
|
return nil
|
|
|
|
}
|
2021-10-06 22:01:48 +00:00
|
|
|
}
|
|
|
|
|
2022-08-04 04:31:40 +00:00
|
|
|
if fl := fwdLogAtomic.Load(); fl != nil {
|
2021-12-21 21:52:50 +00:00
|
|
|
fl.addName(string(domain))
|
|
|
|
}
|
|
|
|
|
2021-06-24 14:36:23 +00:00
|
|
|
clampEDNSSize(query.bs, maxResponseBytes)
|
2020-08-19 19:39:25 +00:00
|
|
|
|
2021-11-23 17:58:34 +00:00
|
|
|
if len(resolvers) == 0 {
|
2021-11-29 22:18:09 +00:00
|
|
|
resolvers = f.resolvers(domain)
|
|
|
|
if len(resolvers) == 0 {
|
|
|
|
metricDNSFwdErrorNoUpstream.Add(1)
|
2024-07-29 17:48:46 +00:00
|
|
|
f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: ""})
|
2022-08-09 14:33:45 +00:00
|
|
|
f.logf("no upstream resolvers set, returning SERVFAIL")
|
2024-06-12 19:45:13 +00:00
|
|
|
|
2024-06-25 18:56:13 +00:00
|
|
|
// 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.
|
2024-06-26 02:58:45 +00:00
|
|
|
if f.missingUpstreamRecovery != nil {
|
|
|
|
f.missingUpstreamRecovery()
|
|
|
|
}
|
2024-06-12 19:45:13 +00:00
|
|
|
|
2022-08-09 14:33:45 +00:00
|
|
|
res, err := servfailResponse(query)
|
|
|
|
if err != nil {
|
|
|
|
f.logf("building servfail response: %v", err)
|
2022-08-09 20:29:34 +00:00
|
|
|
// Returning an error will cause an internal retry, there is
|
|
|
|
// nothing we can do if parsing failed. Just drop the packet.
|
2022-08-09 14:33:45 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
2024-05-02 16:28:38 +00:00
|
|
|
return fmt.Errorf("waiting to send SERVFAIL: %w", ctx.Err())
|
2022-08-09 14:33:45 +00:00
|
|
|
case responseChan <- res:
|
|
|
|
return nil
|
|
|
|
}
|
2024-07-29 17:48:46 +00:00
|
|
|
} else {
|
|
|
|
f.health.SetHealthy(dnsForwarderFailing)
|
2021-11-29 22:18:09 +00:00
|
|
|
}
|
2020-08-19 19:39:25 +00:00
|
|
|
}
|
2021-04-02 02:31:55 +00:00
|
|
|
|
2021-07-25 22:43:49 +00:00
|
|
|
fq := &forwardQuery{
|
|
|
|
txid: getTxID(query.bs),
|
|
|
|
packet: query.bs,
|
2023-09-07 20:27:50 +00:00
|
|
|
family: query.family,
|
2021-07-25 22:43:49 +00:00
|
|
|
closeOnCtxDone: new(closePool),
|
|
|
|
}
|
|
|
|
defer fq.closeOnCtxDone.Close()
|
2021-06-23 04:53:43 +00:00
|
|
|
|
2024-08-08 20:41:08 +00:00
|
|
|
if verboseDNSForward() {
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2022-04-19 17:58:52 +00:00
|
|
|
resc := make(chan []byte, 1) // it's fine buffered or not
|
|
|
|
errc := make(chan error, 1) // it's fine buffered or not too
|
2021-08-03 13:56:31 +00:00
|
|
|
for i := range resolvers {
|
|
|
|
go func(rr *resolverAndDelay) {
|
2021-07-26 03:40:37 +00:00
|
|
|
if rr.startDelay > 0 {
|
|
|
|
timer := time.NewTimer(rr.startDelay)
|
|
|
|
select {
|
|
|
|
case <-timer.C:
|
|
|
|
case <-ctx.Done():
|
|
|
|
timer.Stop()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2021-08-03 13:56:31 +00:00
|
|
|
resb, err := f.send(ctx, fq, *rr)
|
2021-06-23 04:53:43 +00:00
|
|
|
if err != nil {
|
2024-05-02 16:28:38 +00:00
|
|
|
err = fmt.Errorf("resolving using %q: %w", rr.name.Addr, err)
|
2022-04-19 17:58:52 +00:00
|
|
|
select {
|
|
|
|
case errc <- err:
|
|
|
|
case <-ctx.Done():
|
2021-06-23 04:53:43 +00:00
|
|
|
}
|
|
|
|
return
|
2020-09-23 20:21:52 +00:00
|
|
|
}
|
2021-06-23 04:53:43 +00:00
|
|
|
select {
|
|
|
|
case resc <- resb:
|
2022-04-19 17:58:52 +00:00
|
|
|
case <-ctx.Done():
|
2021-06-23 04:53:43 +00:00
|
|
|
}
|
2021-08-03 13:56:31 +00:00
|
|
|
}(&resolvers[i])
|
2020-09-23 20:21:52 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 17:58:52 +00:00
|
|
|
var firstErr error
|
|
|
|
var numErr int
|
|
|
|
for {
|
2021-06-23 04:53:43 +00:00
|
|
|
select {
|
2022-04-19 17:58:52 +00:00
|
|
|
case v := <-resc:
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
metricDNSFwdErrorContext.Add(1)
|
2024-05-02 16:28:38 +00:00
|
|
|
return fmt.Errorf("waiting to send response: %w", ctx.Err())
|
2023-09-07 20:27:50 +00:00
|
|
|
case responseChan <- packet{v, query.family, query.addr}:
|
2024-08-08 20:41:08 +00:00
|
|
|
if verboseDNSForward() {
|
|
|
|
f.logf("response(%d, %v, %d) = %d, nil", fq.txid, typ, len(domain), len(v))
|
|
|
|
}
|
2022-04-19 17:58:52 +00:00
|
|
|
metricDNSFwdSuccess.Add(1)
|
2024-07-29 17:48:46 +00:00
|
|
|
f.health.SetHealthy(dnsForwarderFailing)
|
2022-04-19 17:58:52 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
case err := <-errc:
|
|
|
|
if firstErr == nil {
|
|
|
|
firstErr = err
|
|
|
|
}
|
|
|
|
numErr++
|
|
|
|
if numErr == len(resolvers) {
|
2023-10-03 20:26:38 +00:00
|
|
|
if errors.Is(firstErr, errServerFailure) {
|
2022-06-15 23:19:05 +00:00
|
|
|
res, err := servfailResponse(query)
|
|
|
|
if err != nil {
|
|
|
|
f.logf("building servfail response: %v", err)
|
|
|
|
return firstErr
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
metricDNSFwdErrorContext.Add(1)
|
|
|
|
metricDNSFwdErrorContextGotError.Add(1)
|
2024-07-29 17:48:46 +00:00
|
|
|
var resolverAddrs []string
|
|
|
|
for _, rr := range resolvers {
|
|
|
|
resolverAddrs = append(resolverAddrs, rr.name.Addr)
|
|
|
|
}
|
|
|
|
f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: strings.Join(resolverAddrs, ",")})
|
2022-06-15 23:19:05 +00:00
|
|
|
case responseChan <- res:
|
2024-08-08 20:41:08 +00:00
|
|
|
if verboseDNSForward() {
|
|
|
|
f.logf("forwarder response(%d, %v, %d) = %d, %v", fq.txid, typ, len(domain), len(res.bs), firstErr)
|
|
|
|
}
|
2022-06-15 23:19:05 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-19 17:58:52 +00:00
|
|
|
return firstErr
|
|
|
|
}
|
2021-06-23 04:53:43 +00:00
|
|
|
case <-ctx.Done():
|
2021-11-26 22:43:38 +00:00
|
|
|
metricDNSFwdErrorContext.Add(1)
|
2022-04-19 17:58:52 +00:00
|
|
|
if firstErr != nil {
|
|
|
|
metricDNSFwdErrorContextGotError.Add(1)
|
|
|
|
return firstErr
|
|
|
|
}
|
2024-05-02 16:28:38 +00:00
|
|
|
|
|
|
|
// If we haven't got an error or a successful response,
|
|
|
|
// include all resolvers in the error message so we can
|
|
|
|
// at least see what what servers we're trying to
|
|
|
|
// query.
|
|
|
|
var resolverAddrs []string
|
|
|
|
for _, rr := range resolvers {
|
|
|
|
resolverAddrs = append(resolverAddrs, rr.name.Addr)
|
|
|
|
}
|
2024-07-29 17:48:46 +00:00
|
|
|
f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: strings.Join(resolverAddrs, ",")})
|
2024-05-02 16:28:38 +00:00
|
|
|
return fmt.Errorf("waiting for response or error from %v: %w", resolverAddrs, ctx.Err())
|
2020-09-23 20:21:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-18 21:26:58 +00:00
|
|
|
var initListenConfig func(_ *net.ListenConfig, _ *netmon.Monitor, tunName string) error
|
2021-04-02 02:31:55 +00:00
|
|
|
|
|
|
|
// nameFromQuery extracts the normalized query name from bs.
|
2024-08-08 20:41:08 +00:00
|
|
|
func nameFromQuery(bs []byte) (dnsname.FQDN, dns.Type, error) {
|
2021-04-02 02:31:55 +00:00
|
|
|
var parser dns.Parser
|
|
|
|
|
|
|
|
hdr, err := parser.Start(bs)
|
|
|
|
if err != nil {
|
2024-08-08 20:41:08 +00:00
|
|
|
return "", 0, err
|
2021-04-02 02:31:55 +00:00
|
|
|
}
|
|
|
|
if hdr.Response {
|
2024-08-08 20:41:08 +00:00
|
|
|
return "", 0, errNotQuery
|
2021-04-02 02:31:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
q, err := parser.Question()
|
|
|
|
if err != nil {
|
2024-08-08 20:41:08 +00:00
|
|
|
return "", 0, err
|
2021-04-02 02:31:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
n := q.Name.Data[:q.Name.Length]
|
2024-08-08 20:41:08 +00:00
|
|
|
fqdn, err := dnsname.ToFQDN(rawNameToLower(n))
|
|
|
|
if err != nil {
|
|
|
|
return "", 0, err
|
|
|
|
}
|
|
|
|
return fqdn, q.Type, nil
|
2021-04-02 02:31:55 +00:00
|
|
|
}
|
2021-06-23 04:53:43 +00:00
|
|
|
|
2021-12-18 23:37:40 +00:00
|
|
|
// nxDomainResponse returns an NXDomain DNS reply for the provided request.
|
|
|
|
func nxDomainResponse(req packet) (res packet, err error) {
|
|
|
|
p := dnsParserPool.Get().(*dnsParser)
|
|
|
|
defer dnsParserPool.Put(p)
|
|
|
|
|
|
|
|
if err := p.parseQuery(req.bs); err != nil {
|
|
|
|
return packet{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
h := p.Header
|
|
|
|
h.Response = true
|
|
|
|
h.RecursionAvailable = h.RecursionDesired
|
|
|
|
h.RCode = dns.RCodeNameError
|
|
|
|
b := dns.NewBuilder(nil, h)
|
|
|
|
// TODO(bradfitz): should we add an SOA record in the Authority
|
|
|
|
// section too? (for the nxdomain negative caching TTL)
|
|
|
|
// For which zone? Does iOS care?
|
net/dns/resolver: fix dns-sd NXDOMAIN responses from quad-100
mdnsResponder at least as of macOS Sequoia does not find NXDOMAIN
responses to these dns-sd PTR queries acceptable unless they include the
question section in the response. This was found debugging #13511, once
we turned on additional diagnostic reporting from mdnsResponder we
witnessed:
```
Received unacceptable 12-byte response from 100.100.100.100 over UDP via utun6/27 -- id: 0x7F41 (32577), flags: 0x8183 (R/Query, RD, RA, NXDomain), counts: 0/0/0/0,
```
If the response includes a question section, the resposnes are
acceptable, e.g.:
```
Received acceptable 59-byte response from 8.8.8.8 over UDP via en0/17 -- id: 0x2E55 (11861), flags: 0x8183 (R/Query, RD, RA, NXDomain), counts: 1/0/0/0,
```
This may be contributing to an issue under diagnosis in #13511 wherein
some combination of conditions results in mdnsResponder no longer
answering DNS queries correctly to applications on the system for
extended periods of time (multiple minutes), while dig against quad-100
provides correct responses for those same domains. If additional debug
logging is enabled in mdnsResponder we see it reporting:
```
Penalizing server 100.100.100.100 for 60 seconds
```
It is also possible that the reason that macOS & iOS never "stopped
spamming" these queries is that they have never been replied to with
acceptable responses. It is not clear if this special case handling of
dns-sd PTR queries was ever beneficial, and given this evidence may have
always been harmful. If we subsequently observe that the queries settle
down now that they have acceptable responses, we should remove these
special cases - making upstream queries very occasionally isn't a lot of
battery, so we should be better off having to maintain less special
cases and avoid bugs of this class.
Updates #2442
Updates #3025
Updates #3363
Updates #3594
Updates #13511
Signed-off-by: James Tucker <james@tailscale.com>
2024-09-19 00:57:26 +00:00
|
|
|
b.StartQuestions()
|
|
|
|
b.Question(p.Question)
|
2021-12-18 23:37:40 +00:00
|
|
|
res.bs, err = b.Finish()
|
|
|
|
res.addr = req.addr
|
|
|
|
return res, err
|
|
|
|
}
|
|
|
|
|
2022-06-15 23:19:05 +00:00
|
|
|
// servfailResponse returns a SERVFAIL error reply for the provided request.
|
|
|
|
func servfailResponse(req packet) (res packet, err error) {
|
|
|
|
p := dnsParserPool.Get().(*dnsParser)
|
|
|
|
defer dnsParserPool.Put(p)
|
|
|
|
|
|
|
|
if err := p.parseQuery(req.bs); err != nil {
|
|
|
|
return packet{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
h := p.Header
|
|
|
|
h.Response = true
|
|
|
|
h.Authoritative = true
|
|
|
|
h.RCode = dns.RCodeServerFailure
|
|
|
|
b := dns.NewBuilder(nil, h)
|
|
|
|
b.StartQuestions()
|
|
|
|
b.Question(p.Question)
|
|
|
|
res.bs, err = b.Finish()
|
|
|
|
res.addr = req.addr
|
|
|
|
return res, err
|
|
|
|
}
|
|
|
|
|
2021-06-23 04:53:43 +00:00
|
|
|
// closePool is a dynamic set of io.Closers to close as a group.
|
|
|
|
// It's intended to be Closed at most once.
|
|
|
|
//
|
|
|
|
// The zero value is ready for use.
|
|
|
|
type closePool struct {
|
|
|
|
mu sync.Mutex
|
|
|
|
m map[io.Closer]bool
|
|
|
|
closed bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *closePool) Add(c io.Closer) {
|
|
|
|
p.mu.Lock()
|
|
|
|
defer p.mu.Unlock()
|
|
|
|
if p.closed {
|
|
|
|
c.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if p.m == nil {
|
|
|
|
p.m = map[io.Closer]bool{}
|
|
|
|
}
|
|
|
|
p.m[c] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *closePool) Remove(c io.Closer) {
|
|
|
|
p.mu.Lock()
|
|
|
|
defer p.mu.Unlock()
|
|
|
|
if p.closed {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
delete(p.m, c)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *closePool) Close() error {
|
|
|
|
p.mu.Lock()
|
|
|
|
defer p.mu.Unlock()
|
|
|
|
if p.closed {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
p.closed = true
|
|
|
|
for c := range p.m {
|
|
|
|
c.Close()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|