Plain non-EDNS UDP A queries for names with large answers (storage.googleapis.com, 610 bytes uncompressed) got SERVFAIL after the 3 s upstream timeout. glibc guests then only got IPv6 addresses, and IPv6 is denied in sandboxes, so Playwright browser downloads failed with EAFNOSUPPORT (vev-vt CI, browser job). EDNS and TCP queries worked.
UDP attempt of 1 s, then TCP on the same upstream when UDP fails, times out or is truncated; each upstream gets its own budget, one exchange is capped at 6 s
a reply larger than the client's UDP size (512 without EDNS, else its OPT size) becomes TC=1 so the client retries over TCP
unexported dial hook; synctest tests with the real timeouts; loopback fakes retry port collisions (the package was flaky on main)
Root cause of the lost UDP answer upstream is inferred (large plain-UDP replies lost on the host path); TCP fallback covers it.
- Source address and destination checks run before any "established" accept. A guest cannot forge another's flow.
80
80
- The DNS forwarder allows 32 in-flight queries per listener (extra UDP is dropped), 16 TCP connections, and 30 s per TCP connection.
81
+
- Upstream queries use UDP for 1 s, then TCP on the same resolver (also when UDP is truncated). One query takes at most 6 s. A UDP answer larger than the client accepts (512 bytes without EDNS) is replaced by an empty reply with TC=1, so the client retries over TCP.
81
82
- Golden rulesets: `internal/netpolicy/testdata`.
82
83
83
84
Host side effects:
@@ -78,6 +78,7 @@ From a sandbox:
78
78
79
- Source address and destination checks run before any "established" accept. A guest cannot forge another's flow.
79
- Source address and destination checks run before any "established" accept. A guest cannot forge another's flow.
80
- The DNS forwarder allows 32 in-flight queries per listener (extra UDP is dropped), 16 TCP connections, and 30 s per TCP connection.
80
- The DNS forwarder allows 32 in-flight queries per listener (extra UDP is dropped), 16 TCP connections, and 30 s per TCP connection.
81
+
- Upstream queries use UDP for 1 s, then TCP on the same resolver (also when UDP is truncated). One query takes at most 6 s. A UDP answer larger than the client accepts (512 bytes without EDNS) is replaced by an empty reply with TC=1, so the client retries over TCP.
81
- Golden rulesets: `internal/netpolicy/testdata`.
82
- Golden rulesets: `internal/netpolicy/testdata`.
82
83
83
Host side effects:
84
Host side effects:
Minternal/dnsfwd/dnsfwd.go+140-20
@@ -39,8 +39,15 @@ import (
39
39
const (
40
40
// TCPLifetime is the absolute lifetime of a TCP connection, set at accept.
41
41
TCPLifetime = 30 * time.Second
42
-
// UpstreamTimeout is the deadline of one upstream attempt.
42
+
// UpstreamTimeout is the deadline of one upstream TCP attempt.
43
43
UpstreamTimeout = 3 * time.Second
44
+
// UDPAttemptTimeout is the deadline of one upstream UDP attempt. It is short
45
+
// on purpose: glibc guests use "timeout:2 attempts:2", so the reply (or its
46
+
// TC=1 / TCP fallback) must reach them well inside the first 2 s window.
47
+
UDPAttemptTimeout = time.Second
48
+
// MaxExchange caps one whole exchange over all upstreams and transports, so
49
+
// a long upstream list cannot hold an in-flight slot for long.
50
+
MaxExchange = 6 * time.Second
44
51
// TCPIdleTimeout closes TCP client connections that stay silent.
45
52
TCPIdleTimeout = 10 * time.Second
46
53
@@ -53,6 +60,9 @@ const (
53
60
54
61
maxUDP = 4096 // largest datagram relayed (EDNS0 sizes up to 4096)
55
62
hdrLen = 12
63
+
64
+
// plainUDPSize is the largest UDP reply for a client without EDNS (RFC 1035).
65
+
plainUDPSize = 512
56
66
)
57
67
58
68
// Forwarder relays DNS queries. The zero value is not usable; use New.
@@ -61,6 +71,11 @@ type Forwarder struct {
61
71
port int
62
72
// Lifetime overrides TCPLifetime; set before Listen (tests).
63
73
Lifetime time.Duration
74
+
// Upstream deadlines, set to the constants above by New; tests shorten
75
+
// them before Listen.
76
+
udpTimeout, tcpTimeout, maxExchange time.Duration
77
+
// dial opens upstream connections; tests replace it with in-memory fakes.