1// SPDX-License-Identifier: MIT
2
3package dnsfwd
4
5import (
6 "context"
7 "encoding/binary"
8 "errors"
9 "io"
10 "net"
11 "net/netip"
12 "os"
13 "path/filepath"
14 "reflect"
15 "strings"
16 "sync/atomic"
17 "syscall"
18 "testing"
19 "testing/synctest"
20 "time"
21)
22
23// query builds a minimal query for name with the given ID.
24func query(id uint16, name string) []byte {
25 b := make([]byte, hdrLen)
26 binary.BigEndian.PutUint16(b, id)
27 b[2] = 0x01 // RD
28 b[5] = 1 // QDCOUNT
29 for _, l := range strings.Split(name, ".") {
30 b = append(b, byte(len(l)))
31 b = append(b, l...)
32 }
33 return append(b, 0, 0, 1, 0, 1) // root, A, IN
34}
35
36// answer turns a query into a response carrying a recognisable payload.
37func answer(q []byte) []byte {
38 r := append([]byte(nil), q...)
39 r[2] |= 0x80
40 return append(r, "ANSWER"...)
41}
42
43// listenUDPAndTCP binds UDP on a free loopback port and TCP on the same port.
44// The TCP port may already be taken (outbound connections use the same
45// ephemeral range), so it picks again a few times, like Listen does.
46func listenUDPAndTCP(t *testing.T) (net.PacketConn, net.Listener) {
47 t.Helper()
48 for attempt := 0; ; attempt++ {
49 pc, err := net.ListenPacket("udp", "127.0.0.1:0")
50 if err != nil {
51 t.Skip("no loopback:", err)
52 }
53 tl, err := net.Listen("tcp", pc.LocalAddr().String())
54 if err == nil {
55 return pc, tl
56 }
57 _ = pc.Close()
58 if attempt == 49 || !errors.Is(err, syscall.EADDRINUSE) {
59 t.Fatal(err)
60 }
61 }
62}
63
64// fakeUpstream serves UDP and TCP on one port of 127.0.0.1 and returns it.
65func fakeUpstream(t *testing.T, handle func(q []byte) []byte) string {
66 t.Helper()
67 pc, tl := listenUDPAndTCP(t)
68 addr := pc.LocalAddr().String()
69 t.Cleanup(func() { _ = pc.Close(); _ = tl.Close() })
70 go func() {
71 buf := make([]byte, 4096)
72 for {
73 n, from, err := pc.ReadFrom(buf)
74 if err != nil {
75 return
76 }
77 if r := handle(append([]byte(nil), buf[:n]...)); r != nil {
78 _, _ = pc.WriteTo(r, from)
79 }
80 }
81 }()
82 go func() {
83 for {
84 c, err := tl.Accept()
85 if err != nil {
86 return
87 }
88 go func() {
89 defer func() { _ = c.Close() }()
90 var l [2]byte
91 if _, err := io.ReadFull(c, l[:]); err != nil {
92 return
93 }
94 q := make([]byte, binary.BigEndian.Uint16(l[:]))
95 if _, err := io.ReadFull(c, q); err != nil {
96 return
97 }
98 r := handle(q)
99 out := make([]byte, 2+len(r))
100 binary.BigEndian.PutUint16(out, uint16(len(r)))
101 copy(out[2:], r)
102 _, _ = c.Write(out)
103 }()
104 }
105 }()
106 return addr
107}
108
109func start(t *testing.T, ups ...string) (*Forwarder, string) {
110 t.Helper()
111 return startWith(t, nil, ups...)
112}
113
114// startWith is start with a hook to adjust the Forwarder before Listen.
115func startWith(t *testing.T, cfg func(*Forwarder), ups ...string) (*Forwarder, string) {
116 t.Helper()
117 f := New(func() []string { return ups }, 0)
118 if cfg != nil {
119 cfg(f)
120 }
121 ip := netip.MustParseAddr("127.0.0.1")
122 if err := f.Listen(context.Background(), ip); err != nil {
123 t.Fatal(err)
124 }
125 t.Cleanup(f.CloseAll)
126 a, _ := f.Addr(ip)
127 return f, a.String()
128}
129
130func TestUDPAndTCPForwarding(t *testing.T) {
131 up := fakeUpstream(t, answer)
132 _, addr := start(t, up)
133
134 c, err := net.Dial("udp", addr)
135 if err != nil {
136 t.Fatal(err)
137 }
138 defer func() { _ = c.Close() }()
139 _ = c.SetDeadline(time.Now().Add(5 * time.Second))
140 q := query(0x1234, "example.test")
141 _, _ = c.Write(q)
142 buf := make([]byte, 512)
143 n, err := c.Read(buf)
144 if err != nil {
145 t.Fatal(err)
146 }
147 if string(buf[n-6:n]) != "ANSWER" || binary.BigEndian.Uint16(buf) != 0x1234 {
148 t.Fatalf("udp answer: %x", buf[:n])
149 }
150
151 tc, err := net.Dial("tcp", addr)
152 if err != nil {
153 t.Fatal(err)
154 }
155 defer func() { _ = tc.Close() }()
156 _ = tc.SetDeadline(time.Now().Add(5 * time.Second))
157 // Two queries on one connection.
158 for _, id := range []uint16{7, 8} {
159 q := query(id, "tcp.test")
160 framed := append([]byte{byte(len(q) >> 8), byte(len(q))}, q...)
161 _, _ = tc.Write(framed)
162 var l [2]byte
163 if _, err := io.ReadFull(tc, l[:]); err != nil {
164 t.Fatal(err)
165 }
166 r := make([]byte, binary.BigEndian.Uint16(l[:]))
167 if _, err := io.ReadFull(tc, r); err != nil {
168 t.Fatal(err)
169 }
170 if binary.BigEndian.Uint16(r) != id || string(r[len(r)-6:]) != "ANSWER" {
171 t.Fatalf("tcp answer for %d: %x", id, r)
172 }
173 }
174}
175
176func TestFailoverToSecondUpstream(t *testing.T) {
177 dead := "127.0.0.1:1" // nothing listens: UDP read fails with ICMP refused
178 live := fakeUpstream(t, answer)
179 _, addr := start(t, dead, live)
180 c, _ := net.Dial("udp", addr)
181 defer func() { _ = c.Close() }()
182 _ = c.SetDeadline(time.Now().Add(8 * time.Second))
183 _, _ = c.Write(query(1, "a.test"))
184 buf := make([]byte, 512)
185 n, err := c.Read(buf)
186 if err != nil || string(buf[n-6:n]) != "ANSWER" {
187 t.Fatalf("n=%d err=%v", n, err)
188 }
189}
190
191func TestServfailWhenNoUpstreamAnswers(t *testing.T) {
192 _, addr := start(t) // no upstream at all
193 c, _ := net.Dial("udp", addr)
194 defer func() { _ = c.Close() }()
195 _ = c.SetDeadline(time.Now().Add(5 * time.Second))
196 _, _ = c.Write(query(0xbeef, "a.test"))
197 buf := make([]byte, 512)
198 n, err := c.Read(buf)
199 if err != nil {
200 t.Fatal(err)
201 }
202 if n != hdrLen || binary.BigEndian.Uint16(buf) != 0xbeef || buf[3]&0x0f != 2 || buf[2]&0x80 == 0 {
203 t.Fatalf("not a SERVFAIL: %x", buf[:n])
204 }
205}
206
207func TestShortPacketsIgnored(t *testing.T) {
208 up := fakeUpstream(t, answer)
209 _, addr := start(t, up)
210 c, _ := net.Dial("udp", addr)
211 defer func() { _ = c.Close() }()
212 _, _ = c.Write([]byte{1, 2, 3}) // dropped
213 _, _ = c.Write(query(9, "a.test"))
214 _ = c.SetDeadline(time.Now().Add(5 * time.Second))
215 buf := make([]byte, 512)
216 n, err := c.Read(buf)
217 if err != nil || binary.BigEndian.Uint16(buf) != 9 {
218 t.Fatalf("n=%d err=%v", n, err)
219 }
220}
221
222func TestListenTwiceAndClose(t *testing.T) {
223 f := New(func() []string { return nil }, 0)
224 ip := netip.MustParseAddr("127.0.0.1")
225 if err := f.Listen(context.Background(), ip); err != nil {
226 t.Skip(err)
227 }
228 if err := f.Listen(context.Background(), ip); err == nil {
229 t.Fatal("second Listen on the same address must fail")
230 }
231 a, _ := f.Addr(ip)
232 f.Close(ip)
233 f.Close(ip) // idempotent
234 if _, ok := f.Addr(ip); ok {
235 t.Fatal("still listening after Close")
236 }
237 if pc, err := net.ListenPacket("udp", a.String()); err != nil {
238 t.Fatalf("port not released: %v", err)
239 } else {
240 _ = pc.Close()
241 }
242}
243
244func TestServfailShape(t *testing.T) {
245 if servfail([]byte{1}) != nil {
246 t.Fatal("short query must give nil")
247 }
248 q := query(0xabcd, "x.test")
249 q[2] = 0x79 // opcode bits + RD + AA
250 r := servfail(q)
251 if r[0] != 0xab || r[1] != 0xcd || r[2]&0x80 == 0 || r[3] != 0x82 {
252 t.Fatalf("bad servfail %x", r)
253 }
254}
255
256func TestParseResolvConf(t *testing.T) {
257 in := `# comment
258nameserver 127.0.0.53
259nameserver 127.0.0.53
260nameserver 192.0.2.1 # trailing
261nameserver 2001:db8::1
262nameserver fe80::1%eth0
263nameserver 0.0.0.0
264nameserver not-an-ip
265search example.test
266options edns0
267nameserver
268`
269 got := ParseResolvConf(strings.NewReader(in))
270 want := []string{"127.0.0.53:53", "192.0.2.1:53", "[2001:db8::1]:53", "[fe80::1%eth0]:53"}
271 if !reflect.DeepEqual(got, want) {
272 t.Fatalf("got %v want %v", got, want)
273 }
274}
275
276func TestResolversReload(t *testing.T) {
277 p := filepath.Join(t.TempDir(), "resolv.conf")
278 if err := os.WriteFile(p, []byte("nameserver 192.0.2.1\n"), 0o644); err != nil {
279 t.Fatal(err)
280 }
281 r := NewResolvers(p)
282 if got := r.Addrs(); !reflect.DeepEqual(got, []string{"192.0.2.1:53"}) {
283 t.Fatal(got)
284 }
285 if err := os.WriteFile(p, []byte("nameserver 192.0.2.99\nnameserver 192.0.2.1\n"), 0o644); err != nil {
286 t.Fatal(err)
287 }
288 later := time.Now().Add(2 * time.Second)
289 _ = os.Chtimes(p, later, later)
290 if got := r.Addrs(); !reflect.DeepEqual(got, []string{"192.0.2.99:53", "192.0.2.1:53"}) {
291 t.Fatal(got)
292 }
293 // Unreadable file keeps the last known list.
294 _ = os.Remove(p)
295 if got := r.Addrs(); len(got) != 2 {
296 t.Fatalf("lost the last known list: %v", got)
297 }
298 if got := NewResolvers(filepath.Join(t.TempDir(), "missing")).Addrs(); len(got) != 0 {
299 t.Fatal(got)
300 }
301}
302
303// S2: a listener holds at most MaxTCPConns connections; extra ones are closed
304// at once, and a free slot is usable again after a close.
305func TestTCPConnectionCapPerListener(t *testing.T) {
306 up := fakeUpstream(t, answer)
307 _, addr := start(t, up)
308 var held []net.Conn
309 for range MaxTCPConns {
310 c, err := net.Dial("tcp", addr)
311 if err != nil {
312 t.Fatal(err)
313 }
314 held = append(held, c)
315 defer func() { _ = c.Close() }()
316 }
317 // Let the accept loop register all of them.
318 time.Sleep(200 * time.Millisecond)
319 extra, err := net.Dial("tcp", addr)
320 if err != nil {
321 t.Fatal(err)
322 }
323 defer func() { _ = extra.Close() }()
324 _ = extra.SetReadDeadline(time.Now().Add(3 * time.Second))
325 if _, err := extra.Read(make([]byte, 1)); err != io.EOF && !errors.Is(err, syscall.ECONNRESET) {
326 t.Fatalf("the connection over the cap must be closed by the server, got %v", err)
327 }
328 // Free one slot: a new connection is served.
329 _ = held[0].Close()
330 time.Sleep(200 * time.Millisecond)
331 c, err := net.Dial("tcp", addr)
332 if err != nil {
333 t.Fatal(err)
334 }
335 defer func() { _ = c.Close() }()
336 _ = c.SetDeadline(time.Now().Add(5 * time.Second))
337 q := query(3, "slot.test")
338 _, _ = c.Write(append([]byte{byte(len(q) >> 8), byte(len(q))}, q...))
339 var l [2]byte
340 if _, err := io.ReadFull(c, l[:]); err != nil {
341 t.Fatalf("a freed slot must serve again: %v", err)
342 }
343}
344
345// S2: the lifetime is absolute from accept: a client that keeps the connection
346// alive by trickling bytes still loses it.
347func TestTCPAbsoluteDeadline(t *testing.T) {
348 up := fakeUpstream(t, answer)
349 f := New(func() []string { return []string{up} }, 0)
350 f.Lifetime = 600 * time.Millisecond
351 ip := netip.MustParseAddr("127.0.0.1")
352 if err := f.Listen(context.Background(), ip); err != nil {
353 t.Skip(err)
354 }
355 t.Cleanup(f.CloseAll)
356 la, _ := f.Addr(ip)
357 addr := la.String()
358 c, err := net.Dial("tcp", addr)
359 if err != nil {
360 t.Fatal(err)
361 }
362 defer func() { _ = c.Close() }()
363 start := time.Now()
364 closed := make(chan struct{})
365 go func() {
366 _, _ = io.Copy(io.Discard, c) // returns when the server closes
367 close(closed)
368 }()
369 // Trickle one byte every 100 ms, never finishing a frame: only an idle
370 // timeout (10 s) would let this live; the absolute deadline must not.
371 go func() {
372 for i := 0; i < 40; i++ {
373 if _, err := c.Write([]byte{0}); err != nil {
374 return
375 }
376 time.Sleep(100 * time.Millisecond)
377 }
378 }()
379 select {
380 case <-closed:
381 if d := time.Since(start); d > 3*time.Second {
382 t.Fatalf("closed after %v, want about %v", d, f.Lifetime)
383 }
384 case <-time.After(5 * time.Second):
385 t.Fatal("a trickling client kept its connection past the absolute deadline")
386 }
387}
388
389// S2/S9: a saturated listener drops UDP queries instead of blocking its read
390// loop, and another listener (another guest) keeps answering.
391func TestUDPSaturationDropsAndIsolatesListeners(t *testing.T) {
392 release := make(chan struct{})
393 slow := fakeUpstream(t, func(q []byte) []byte { <-release; return answer(q) })
394 fast := fakeUpstream(t, answer)
395 var useSlow atomic.Bool
396 useSlow.Store(true)
397 f := New(func() []string {
398 if useSlow.Load() {
399 return []string{slow}
400 }
401 return []string{fast}
402 }, 0)
403 a1, a2 := netip.MustParseAddr("127.0.0.1"), netip.MustParseAddr("127.0.0.2")
404 if err := f.Listen(context.Background(), a1); err != nil {
405 t.Skip(err)
406 }
407 if err := f.Listen(context.Background(), a2); err != nil {
408 t.Skip(err)
409 }
410 t.Cleanup(f.CloseAll)
411 t.Cleanup(func() { close(release) })
412 l1, _ := f.Addr(a1)
413 flood, err := net.Dial("udp", l1.String())
414 if err != nil {
415 t.Fatal(err)
416 }
417 defer func() { _ = flood.Close() }()
418 for i := 0; i < MaxInflight*3; i++ {
419 _, _ = flood.Write(query(uint16(i), "flood.test"))
420 }
421 deadline := time.Now().Add(3 * time.Second)
422 for f.Dropped(a1) == 0 && time.Now().Before(deadline) {
423 time.Sleep(20 * time.Millisecond)
424 }
425 if f.Dropped(a1) == 0 {
426 t.Fatal("a saturated listener must drop queries")
427 }
428 // The other listener is unaffected: point the resolver at the fast upstream.
429 useSlow.Store(false)
430 l2, _ := f.Addr(a2)
431 c, err := net.Dial("udp", l2.String())
432 if err != nil {
433 t.Fatal(err)
434 }
435 defer func() { _ = c.Close() }()
436 _ = c.SetDeadline(time.Now().Add(4 * time.Second))
437 _, _ = c.Write(query(77, "other-guest.test"))
438 buf := make([]byte, 512)
439 n, err := c.Read(buf)
440 if err != nil || string(buf[n-6:n]) != "ANSWER" {
441 t.Fatalf("another listener must not be starved by the flood: n=%d err=%v", n, err)
442 }
443}
444
445// By default Listen fails fast on an address the host does not own (Docker and
446// the VM driver have configured it by then: a missing one is an error).
447func TestListenOnAMissingAddressFailsByDefault(t *testing.T) {
448 f := New(func() []string { return nil }, 0)
449 t.Cleanup(f.CloseAll)
450 ip := netip.MustParseAddr("203.0.113.77") // TEST-NET-3: never configured here
451 if err := f.Listen(context.Background(), ip); err == nil {
452 t.Fatal("listening on an address the host does not own must fail without FreeBind")
453 }
454}
455
456// With FreeBind (podman's netavark creates the bridge address only when the
457// first container starts) Listen binds an address the host does not own yet.
458func TestFreeBindListensOnAnAddressTheHostDoesNotOwnYet(t *testing.T) {
459 f := New(func() []string { return nil }, 0)
460 f.FreeBind = true
461 t.Cleanup(f.CloseAll)
462 ip := netip.MustParseAddr("203.0.113.77")
463 if err := f.Listen(context.Background(), ip); err != nil {
464 t.Fatalf("FreeBind: %v", err)
465 }
466 if _, ok := f.Addr(ip); !ok {
467 t.Fatal("no listener")
468 }
469}
470
471// queryType is query with a chosen type (and optionally EDNS0).
472func queryType(id uint16, name string, qtype uint16, edns bool) []byte {
473 q := query(id, name)
474 binary.BigEndian.PutUint16(q[len(q)-4:], qtype)
475 if edns {
476 q[11] = 1
477 q = append(q, 0, 0, 41, 0x10, 0, 0, 0, 0, 0, 0, 0)
478 }
479 return q
480}
481
482func udpExchange(t *testing.T, addr string, q []byte) []byte {
483 t.Helper()
484 c, err := net.Dial("udp", addr)
485 if err != nil {
486 t.Fatal(err)
487 }
488 defer func() { _ = c.Close() }()
489 _ = c.SetDeadline(time.Now().Add(5 * time.Second))
490 _, _ = c.Write(q)
491 buf := make([]byte, 1024)
492 n, err := c.Read(buf)
493 if err != nil {
494 t.Fatal(err)
495 }
496 return buf[:n]
497}
498
499func startOverride(t *testing.T, name string, ups ...string) (*Forwarder, string) {
500 t.Helper()
501 f := New(func() []string { return ups }, 0)
502 f.Override = name
503 ip := netip.MustParseAddr("127.0.0.1")
504 if err := f.Listen(context.Background(), ip); err != nil {
505 t.Fatal(err)
506 }
507 t.Cleanup(f.CloseAll)
508 a, _ := f.Addr(ip)
509 return f, a.String()
510}
511
512func TestOverrideAnswersAWithTheListenerAddress(t *testing.T) {
513 var asked atomic.Int64
514 up := fakeUpstream(t, func(q []byte) []byte { asked.Add(1); return answer(q) })
515 // A trailing dot in the setting is ignored; on the wire a name has none.
516 _, addr := startOverride(t, "Ermite.Example.org.", up)
517 for _, name := range []string{"ermite.example.org", "ERMITE.example.ORG", "Ermite.Example.Org"} {
518 r := udpExchange(t, addr, queryType(0x4242, name, 1, false))
519 if binary.BigEndian.Uint16(r) != 0x4242 || r[2]&0x80 == 0 || r[2]&0x01 == 0 || r[3]&0x0F != 0 {
520 t.Fatalf("%s: header %x", name, r[:hdrLen])
521 }
522 if an := binary.BigEndian.Uint16(r[6:]); an != 1 {
523 t.Fatalf("%s: %d answers", name, an)
524 }
525 if !strings.HasSuffix(string(r), "\x00\x04\x7f\x00\x00\x01") {
526 t.Fatalf("%s: rdata is not 127.0.0.1: %x", name, r)
527 }
528 }
529 if asked.Load() != 0 {
530 t.Fatalf("the override went upstream %d times", asked.Load())
531 }
532}
533
534func TestOverrideAAAAIsEmptyNoError(t *testing.T) {
535 up := fakeUpstream(t, answer)
536 _, addr := startOverride(t, "ermite.example.org", up)
537 for _, edns := range []bool{false, true} {
538 r := udpExchange(t, addr, queryType(7, "ermite.EXAMPLE.org", 28, edns))
539 if r[3]&0x0F != 0 || binary.BigEndian.Uint16(r[6:]) != 0 || r[2]&0x80 == 0 {
540 t.Fatalf("AAAA (edns %v): %x", edns, r)
541 }
542 if edns != (binary.BigEndian.Uint16(r[10:]) == 1) {
543 t.Fatalf("AAAA (edns %v): arcount %d", edns, binary.BigEndian.Uint16(r[10:]))
544 }
545 }
546}
547
548func TestOverrideForwardsEveryOtherName(t *testing.T) {
549 var asked atomic.Int64
550 up := fakeUpstream(t, func(q []byte) []byte { asked.Add(1); return answer(q) })
551 _, addr := startOverride(t, "ermite.example.org", up)
552 for _, name := range []string{"example.org", "other.example.org", "x.ermite.example.org", "ermite.example.org.evil.test", "ermite.example.or"} {
553 r := udpExchange(t, addr, query(9, name))
554 if string(r[len(r)-6:]) != "ANSWER" {
555 t.Fatalf("%s was not forwarded: %x", name, r)
556 }
557 }
558 if asked.Load() != 5 {
559 t.Fatalf("%d upstream queries, want 5", asked.Load())
560 }
561}
562
563func TestOverrideOverTCPAndDisabledByDefault(t *testing.T) {
564 up := fakeUpstream(t, answer)
565 _, addr := startOverride(t, "ermite.example.org", up)
566 tc, err := net.Dial("tcp", addr)
567 if err != nil {
568 t.Fatal(err)
569 }
570 defer func() { _ = tc.Close() }()
571 _ = tc.SetDeadline(time.Now().Add(5 * time.Second))
572 q := queryType(3, "ermite.example.org", 1, false)
573 _, _ = tc.Write(append([]byte{byte(len(q) >> 8), byte(len(q))}, q...))
574 var l [2]byte
575 if _, err := io.ReadFull(tc, l[:]); err != nil {
576 t.Fatal(err)
577 }
578 r := make([]byte, binary.BigEndian.Uint16(l[:]))
579 if _, err := io.ReadFull(tc, r); err != nil {
580 t.Fatal(err)
581 }
582 if binary.BigEndian.Uint16(r[6:]) != 1 || !strings.HasSuffix(string(r), "\x7f\x00\x00\x01") {
583 t.Fatalf("tcp: %x", r)
584 }
585
586 // Off by default: the name is forwarded like any other.
587 _, addr2 := start(t, up)
588 r2 := udpExchange(t, addr2, query(4, "ermite.example.org"))
589 if string(r2[len(r2)-6:]) != "ANSWER" {
590 t.Fatalf("not forwarded without Override: %x", r2)
591 }
592}
593
594func TestOverrideIgnoresMalformedAndNonQueries(t *testing.T) {
595 f := New(func() []string { return nil }, 0)
596 f.Override = "ermite.example.org"
597 gw := netip.MustParseAddr("10.200.0.1")
598 good := queryType(1, "ermite.example.org", 1, false)
599 if f.overrideAnswer(gw, good) == nil {
600 t.Fatal("control query not answered")
601 }
602 cut := good[:len(good)-3]
603 chaos := queryType(1, "ermite.example.org", 1, false)
604 binary.BigEndian.PutUint16(chaos[len(chaos)-2:], 3) // class CH
605 resp := append([]byte(nil), good...)
606 resp[2] |= 0x80 // QR set: not a query
607 notify := append([]byte(nil), good...)
608 notify[2] |= 4 << 3 // opcode 4
609 ptr := append(append([]byte(nil), good[:hdrLen]...), 0xC0, 0x0C, 0, 1, 0, 1)
610 for name, q := range map[string][]byte{"truncated": cut, "class CH": chaos, "response": resp, "opcode": notify, "pointer": ptr, "short": good[:5]} {
611 if f.overrideAnswer(gw, q) != nil {
612 t.Errorf("%s was answered locally", name)
613 }
614 }
615}
616
617// bigAnswer returns an answer to q padded to size bytes, with TC set if tc.
618func bigAnswer(q []byte, size int, tc bool) []byte {
619 r := answer(q)
620 if tc {
621 r[2] |= 0x02
622 }
623 for len(r) < size {
624 r = append(r, 'x')
625 }
626 return r
627}
628
629func TestLargeAnswerNonEDNSClientGetsTC(t *testing.T) {
630 up := fakeUpstream(t, func(q []byte) []byte { return bigAnswer(q, 700, false) })
631 _, addr := start(t, up)
632 q := queryType(0x3333, "storage.googleapis.com", 1, false)
633 resp := udpExchange(t, addr, q)
634 if resp[2]&0x02 == 0 {
635 t.Fatalf("TC not set: %x", resp)
636 }
637 if len(resp) != len(q) || binary.BigEndian.Uint16(resp[4:]) != 1 || binary.BigEndian.Uint16(resp[6:]) != 0 {
638 t.Fatalf("want header+question only, got %x", resp)
639 }
640 if resp[0] != q[0] || resp[1] != q[1] {
641 t.Fatal("ID changed")
642 }
643}
644
645func TestLargeAnswerEDNSClientGetsFullAnswer(t *testing.T) {
646 up := fakeUpstream(t, func(q []byte) []byte { return bigAnswer(q, 700, false) })
647 _, addr := start(t, up)
648 // queryType advertises 0x1000 (4096) bytes.
649 resp := udpExchange(t, addr, queryType(0x4444, "storage.googleapis.com", 1, true))
650 if len(resp) != 700 || resp[2]&0x02 != 0 {
651 t.Fatalf("want the full 700-byte answer, got len %d tc=%d", len(resp), resp[2]&0x02)
652 }
653}
654
655// withOPT appends an OPT record (root name, type 41) with the given class and
656// RDLEN to a query and bumps ARCOUNT.
657func withOPT(q []byte, size, rdlen uint16, body int) []byte {
658 q = append([]byte(nil), q...)
659 q[11] = 1
660 q = append(q, 0, 0, 41)
661 q = binary.BigEndian.AppendUint16(q, size)
662 q = append(q, 0, 0, 0, 0)
663 q = binary.BigEndian.AppendUint16(q, rdlen)
664 return append(q, make([]byte, body)...)
665}
666
667func TestClientUDPSize(t *testing.T) {
668 plain := query(1, "a.b")
669 bigQD := append([]byte(nil), plain[:hdrLen]...)
670 bigQD[4], bigQD[5] = 0xff, 0xff
671 bigQD = append(bigQD, 1, 'a', 0, 0, 1, 0, 1)
672 ptrQ := append([]byte(nil), plain[:hdrLen]...)
673 ptrQ = append(ptrQ, 0xc0, hdrLen, 0, 1, 0, 1) // compression pointer in question
674 tests := []struct {
675 name string
676 q []byte
677 want int
678 }{
679 {"no edns", plain, 512},
680 {"edns 1232", withOPT(plain, 1232, 0, 0), 1232},
681 {"edns below 512", withOPT(plain, 100, 0, 0), 512},
682 {"edns above cap", withOPT(plain, 65535, 0, 0), maxUDP},
683 {"edns 4096", withOPT(plain, 4096, 0, 0), 4096},
684 {"qdcount 65535 short body", bigQD, 512},
685 {"compression pointer in question", ptrQ, 512},
686 {"OPT rdlen overruns buffer", withOPT(plain, 4096, 500, 3), 4096},
687 {"truncated header", plain[:15], 512},
688 {"short", plain[:5], 512},
689 }
690 for _, tt := range tests {
691 t.Run(tt.name, func(t *testing.T) {
692 if got := clientUDPSize(tt.q); got != tt.want {
693 t.Fatalf("got %d, want %d", got, tt.want)
694 }
695 })
696 }
697}
698
699func TestTruncated(t *testing.T) {
700 q := queryType(7, "a.b", 1, false)
701 full := bigAnswer(q, 700, false)
702 full[7] = 5 // ANCOUNT
703 got := truncated(full)
704 if len(got) != len(q) || got[2]&0x02 == 0 || got[6] != 0 || got[7] != 0 || got[4] != 0 || got[5] != 1 {
705 t.Fatalf("bad truncated reply %x", got)
706 }
707 bad := append([]byte(nil), full...)
708 bad[4], bad[5] = 0xff, 0xff // QDCOUNT lies, question parse fails
709 got = truncated(bad)
710 if len(got) != hdrLen || got[2]&0x02 == 0 || got[4] != 0 || got[5] != 0 {
711 t.Fatalf("bad-question case: %x", got)
712 }
713 if truncated(full[:5]) != nil {
714 t.Fatal("short input must give nil")
715 }
716}
717
718func TestServfailAndOverrideRepliesAreNotTruncated(t *testing.T) {
719 dead := "127.0.0.1:1" // nothing listens: UDP and TCP both fail at once
720 _, addr := startOverride(t, "forge.test", dead)
721 resp := udpExchange(t, addr, queryType(9, "x.test", 1, false))
722 if len(resp) != hdrLen || resp[3]&0x0f != 2 || resp[2]&0x02 != 0 {
723 t.Fatalf("want a plain 12-byte SERVFAIL, got %x", resp)
724 }
725 resp = udpExchange(t, addr, queryType(10, "forge.test", 1, false))
726 if resp[2]&0x02 != 0 || resp[3]&0x0f != 0 || binary.BigEndian.Uint16(resp[6:]) != 1 {
727 t.Fatalf("want the override A answer, got %x", resp)
728 }
729}
730
731// fakeNet is an in-memory upstream network for Forwarder.dial. One Write on a
732// UDP conn is one datagram (net.Pipe delivers a Write to a single Read when the
733// reader's buffer is large enough). A handler returning nil never answers.
734type fakeNet struct {
735 udp, tcp func(addr string, q []byte) []byte
736 udpCalls, tcpCalls atomic.Int64
737}
738
739func (n *fakeNet) dial(ctx context.Context, network, addr string) (net.Conn, error) {
740 if err := ctx.Err(); err != nil { // like net.Dialer
741 return nil, err
742 }
743 c, s := net.Pipe()
744 switch network {
745 case "udp":
746 go func() {
747 defer func() { _ = s.Close() }()
748 buf := make([]byte, 4096)
749 for {
750 k, err := s.Read(buf)
751 if err != nil {
752 return
753 }
754 n.udpCalls.Add(1)
755 if r := n.udp(addr, append([]byte(nil), buf[:k]...)); r != nil {
756 if _, err := s.Write(r); err != nil {
757 return
758 }
759 }
760 }
761 }()
762 case "tcp":
763 go func() {
764 defer func() { _ = s.Close() }()
765 var l [2]byte
766 if _, err := io.ReadFull(s, l[:]); err != nil {
767 return
768 }
769 q := make([]byte, binary.BigEndian.Uint16(l[:]))
770 if _, err := io.ReadFull(s, q); err != nil {
771 return
772 }
773 n.tcpCalls.Add(1)
774 if r := n.tcp(addr, q); r != nil {
775 _, _ = s.Write(append(binary.BigEndian.AppendUint16(nil, uint16(len(r))), r...))
776 }
777 _, _ = io.Copy(io.Discard, s) // wait for the client to hang up
778 }()
779 default:
780 return nil, errors.New("unexpected network " + network)
781 }
782 return c, nil
783}
784
785// silent is a handler that never answers.
786func silent(string, []byte) []byte { return nil }
787
788// fakeForwarder returns a Forwarder with production timeouts whose upstreams
789// are served by n, and a listener for exchange.
790func fakeForwarder(n *fakeNet, ups ...string) (*Forwarder, *listener) {
791 f := New(func() []string { return ups }, 0)
792 f.dial = n.dial
793 return f, &listener{addr: netip.MustParseAddr("127.0.0.1")}
794}
795
796func isServfail(r []byte, id uint16) bool {
797 return len(r) == hdrLen && binary.BigEndian.Uint16(r) == id && r[3]&0x0f == 2
798}
799
800// The tests below run in a synctest bubble: the fake clock makes the real
801// production timeouts (1 s UDP, 3 s TCP, 6 s cap) instant and exact.
802
803func TestExchangeSilentUDPFallsBackToTCPAfterUDPTimeout(t *testing.T) {
804 synctest.Test(t, func(t *testing.T) {
805 n := &fakeNet{udp: silent, tcp: func(_ string, q []byte) []byte { return answer(q) }}
806 f, l := fakeForwarder(n, "up:53")
807 start := time.Now()
808 resp := f.exchange(t.Context(), l, query(1, "storage.googleapis.com"), false)
809 if !strings.HasSuffix(string(resp), "ANSWER") {
810 t.Fatalf("want the TCP answer, got %x", resp)
811 }
812 if got := time.Since(start); got != UDPAttemptTimeout {
813 t.Fatalf("answered after %v, want %v", got, UDPAttemptTimeout)
814 }
815 if n.udpCalls.Load() != 1 || n.tcpCalls.Load() != 1 {
816 t.Fatalf("udp=%d tcp=%d, want 1 and 1", n.udpCalls.Load(), n.tcpCalls.Load())
817 }
818 })
819}
820
821func TestExchangeTruncatedUDPRetriedOverTCP(t *testing.T) {
822 synctest.Test(t, func(t *testing.T) {
823 n := &fakeNet{
824 udp: func(_ string, q []byte) []byte { return bigAnswer(q, 0, true) },
825 tcp: func(_ string, q []byte) []byte { return bigAnswer(q, 700, false) },
826 }
827 f, l := fakeForwarder(n, "up:53")
828 start := time.Now()
829 resp := f.exchange(t.Context(), l, query(2, "example.com"), false)
830 if len(resp) != 700 || resp[2]&0x02 != 0 {
831 t.Fatalf("want the full TCP answer, got len %d tc=%d", len(resp), resp[2]&0x02)
832 }
833 if time.Since(start) != 0 {
834 t.Fatalf("took %v, want no waiting", time.Since(start))
835 }
836 })
837}
838
839func TestExchangeKeepsTruncatedReplyWhenTCPFails(t *testing.T) {
840 synctest.Test(t, func(t *testing.T) {
841 n := &fakeNet{udp: func(_ string, q []byte) []byte { return bigAnswer(q, 0, true) }, tcp: silent}
842 f, l := fakeForwarder(n, "up:53")
843 resp := f.exchange(t.Context(), l, query(3, "example.com"), false)
844 if len(resp) < hdrLen || resp[2]&0x02 == 0 {
845 t.Fatalf("want the TC=1 reply, got %x", resp)
846 }
847 })
848}
849
850func TestExchangeSecondUpstreamNotStarved(t *testing.T) {
851 synctest.Test(t, func(t *testing.T) {
852 n := &fakeNet{
853 udp: func(a string, q []byte) []byte {
854 if a == "dead:53" {
855 return nil
856 }
857 return answer(q)
858 },
859 tcp: func(a string, q []byte) []byte {
860 if a == "dead:53" {
861 return nil
862 }
863 return answer(q)
864 },
865 }
866 f, l := fakeForwarder(n, "dead:53", "live:53")
867 start := time.Now()
868 resp := f.exchange(t.Context(), l, query(4, "a.test"), false)
869 if !strings.HasSuffix(string(resp), "ANSWER") {
870 t.Fatalf("want the second upstream's answer, got %x", resp)
871 }
872 if got, want := time.Since(start), UDPAttemptTimeout+UpstreamTimeout; got != want {
873 t.Fatalf("took %v, want %v", got, want)
874 }
875 })
876}
877
878func TestExchangeMismatchedUpstreamIDIgnored(t *testing.T) {
879 synctest.Test(t, func(t *testing.T) {
880 wrong := func(_ string, q []byte) []byte {
881 r := answer(q)
882 r[0] ^= 0xff
883 return r
884 }
885 f, l := fakeForwarder(&fakeNet{udp: wrong, tcp: wrong}, "up:53")
886 start := time.Now()
887 resp := f.exchange(t.Context(), l, query(5, "a.test"), false)
888 if !isServfail(resp, 5) {
889 t.Fatalf("want SERVFAIL, got %x", resp)
890 }
891 // UDP keeps listening for the right ID until its timeout; the TCP
892 // reply is read once and rejected.
893 if got := time.Since(start); got != UDPAttemptTimeout {
894 t.Fatalf("took %v, want %v", got, UDPAttemptTimeout)
895 }
896 })
897}
898
899func TestExchangeCappedAtMaxExchange(t *testing.T) {
900 synctest.Test(t, func(t *testing.T) {
901 n := &fakeNet{udp: silent, tcp: silent}
902 f, l := fakeForwarder(n, "a:53", "b:53", "c:53")
903 start := time.Now()
904 resp := f.exchange(t.Context(), l, query(6, "a.test"), false)
905 if !isServfail(resp, 6) {
906 t.Fatalf("want SERVFAIL, got %x", resp)
907 }
908 if got := time.Since(start); got != MaxExchange {
909 t.Fatalf("took %v, want %v", got, MaxExchange)
910 }
911 // a: 1 s UDP + 3 s TCP; b: 1 s UDP + 1 s TCP (cut by the cap); c: skipped.
912 if n.udpCalls.Load() != 2 || n.tcpCalls.Load() != 2 {
913 t.Fatalf("udp=%d tcp=%d, want 2 and 2", n.udpCalls.Load(), n.tcpCalls.Load())
914 }
915 })
916}
917
918func TestExchangeTCPClientUsesTCPOnly(t *testing.T) {
919 synctest.Test(t, func(t *testing.T) {
920 n := &fakeNet{udp: silent, tcp: func(_ string, q []byte) []byte { return answer(q) }}
921 f, l := fakeForwarder(n, "up:53")
922 resp := f.exchange(t.Context(), l, query(7, "a.test"), true)
923 if !strings.HasSuffix(string(resp), "ANSWER") || n.udpCalls.Load() != 0 {
924 t.Fatalf("resp=%x udp=%d", resp, n.udpCalls.Load())
925 }
926 })
927}
928
929func TestExchangeCancelledReturnsNil(t *testing.T) {
930 synctest.Test(t, func(t *testing.T) {
931 f, l := fakeForwarder(&fakeNet{udp: silent, tcp: silent}, "up:53")
932 ctx, cancel := context.WithCancel(t.Context())
933 time.AfterFunc(500*time.Millisecond, cancel)
934 start := time.Now()
935 if resp := f.exchange(ctx, l, query(8, "a.test"), false); resp != nil {
936 t.Fatalf("want nil on cancellation, got %x", resp)
937 }
938 // Cancellation is noticed at the next attempt boundary: the blocked UDP
939 // read runs to its own deadline, then no TCP attempt is started.
940 if got := time.Since(start); got != UDPAttemptTimeout {
941 t.Fatalf("returned after %v, want %v", got, UDPAttemptTimeout)
942 }
943 })
944}
945
946// End to end through real client-facing sockets; the upstream is in memory.
947func TestUDPClientGetsTCPAnswerWhenUpstreamUDPSilent(t *testing.T) {
948 n := &fakeNet{udp: silent, tcp: func(_ string, q []byte) []byte { return answer(q) }}
949 _, addr := startWith(t, func(f *Forwarder) {
950 f.dial = n.dial
951 f.udpTimeout = 50 * time.Millisecond
952 }, "up:53")
953 resp := udpExchange(t, addr, query(0x1111, "storage.googleapis.com"))
954 if !strings.HasSuffix(string(resp), "ANSWER") || resp[3]&0x0f != 0 {
955 t.Fatalf("want the TCP answer, got %x", resp)
956 }
957}