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 "time"
20)
21
22// query builds a minimal query for name with the given ID.
23func query(id uint16, name string) []byte {
24 b := make([]byte, hdrLen)
25 binary.BigEndian.PutUint16(b, id)
26 b[2] = 0x01 // RD
27 b[5] = 1 // QDCOUNT
28 for _, l := range strings.Split(name, ".") {
29 b = append(b, byte(len(l)))
30 b = append(b, l...)
31 }
32 return append(b, 0, 0, 1, 0, 1) // root, A, IN
33}
34
35// answer turns a query into a response carrying a recognisable payload.
36func answer(q []byte) []byte {
37 r := append([]byte(nil), q...)
38 r[2] |= 0x80
39 return append(r, "ANSWER"...)
40}
41
42// fakeUpstream serves UDP and TCP on one port of 127.0.0.1 and returns it.
43func fakeUpstream(t *testing.T, handle func(q []byte) []byte) string {
44 t.Helper()
45 pc, err := net.ListenPacket("udp", "127.0.0.1:0")
46 if err != nil {
47 t.Skip("no loopback:", err)
48 }
49 addr := pc.LocalAddr().String()
50 tl, err := net.Listen("tcp", addr)
51 if err != nil {
52 t.Fatal(err)
53 }
54 t.Cleanup(func() { _ = pc.Close(); _ = tl.Close() })
55 go func() {
56 buf := make([]byte, 4096)
57 for {
58 n, from, err := pc.ReadFrom(buf)
59 if err != nil {
60 return
61 }
62 if r := handle(append([]byte(nil), buf[:n]...)); r != nil {
63 _, _ = pc.WriteTo(r, from)
64 }
65 }
66 }()
67 go func() {
68 for {
69 c, err := tl.Accept()
70 if err != nil {
71 return
72 }
73 go func() {
74 defer func() { _ = c.Close() }()
75 var l [2]byte
76 if _, err := io.ReadFull(c, l[:]); err != nil {
77 return
78 }
79 q := make([]byte, binary.BigEndian.Uint16(l[:]))
80 if _, err := io.ReadFull(c, q); err != nil {
81 return
82 }
83 r := handle(q)
84 out := make([]byte, 2+len(r))
85 binary.BigEndian.PutUint16(out, uint16(len(r)))
86 copy(out[2:], r)
87 _, _ = c.Write(out)
88 }()
89 }
90 }()
91 return addr
92}
93
94func start(t *testing.T, ups ...string) (*Forwarder, string) {
95 t.Helper()
96 f := New(func() []string { return ups }, 0)
97 ip := netip.MustParseAddr("127.0.0.1")
98 if err := f.Listen(context.Background(), ip); err != nil {
99 t.Fatal(err)
100 }
101 t.Cleanup(f.CloseAll)
102 a, _ := f.Addr(ip)
103 return f, a.String()
104}
105
106func TestUDPAndTCPForwarding(t *testing.T) {
107 up := fakeUpstream(t, answer)
108 _, addr := start(t, up)
109
110 c, err := net.Dial("udp", addr)
111 if err != nil {
112 t.Fatal(err)
113 }
114 defer func() { _ = c.Close() }()
115 _ = c.SetDeadline(time.Now().Add(5 * time.Second))
116 q := query(0x1234, "example.test")
117 _, _ = c.Write(q)
118 buf := make([]byte, 512)
119 n, err := c.Read(buf)
120 if err != nil {
121 t.Fatal(err)
122 }
123 if string(buf[n-6:n]) != "ANSWER" || binary.BigEndian.Uint16(buf) != 0x1234 {
124 t.Fatalf("udp answer: %x", buf[:n])
125 }
126
127 tc, err := net.Dial("tcp", addr)
128 if err != nil {
129 t.Fatal(err)
130 }
131 defer func() { _ = tc.Close() }()
132 _ = tc.SetDeadline(time.Now().Add(5 * time.Second))
133 // Two queries on one connection.
134 for _, id := range []uint16{7, 8} {
135 q := query(id, "tcp.test")
136 framed := append([]byte{byte(len(q) >> 8), byte(len(q))}, q...)
137 _, _ = tc.Write(framed)
138 var l [2]byte
139 if _, err := io.ReadFull(tc, l[:]); err != nil {
140 t.Fatal(err)
141 }
142 r := make([]byte, binary.BigEndian.Uint16(l[:]))
143 if _, err := io.ReadFull(tc, r); err != nil {
144 t.Fatal(err)
145 }
146 if binary.BigEndian.Uint16(r) != id || string(r[len(r)-6:]) != "ANSWER" {
147 t.Fatalf("tcp answer for %d: %x", id, r)
148 }
149 }
150}
151
152func TestFailoverToSecondUpstream(t *testing.T) {
153 dead := "127.0.0.1:1" // nothing listens: UDP read fails with ICMP refused
154 live := fakeUpstream(t, answer)
155 _, addr := start(t, dead, live)
156 c, _ := net.Dial("udp", addr)
157 defer func() { _ = c.Close() }()
158 _ = c.SetDeadline(time.Now().Add(8 * time.Second))
159 _, _ = c.Write(query(1, "a.test"))
160 buf := make([]byte, 512)
161 n, err := c.Read(buf)
162 if err != nil || string(buf[n-6:n]) != "ANSWER" {
163 t.Fatalf("n=%d err=%v", n, err)
164 }
165}
166
167func TestServfailWhenNoUpstreamAnswers(t *testing.T) {
168 _, addr := start(t) // no upstream at all
169 c, _ := net.Dial("udp", addr)
170 defer func() { _ = c.Close() }()
171 _ = c.SetDeadline(time.Now().Add(5 * time.Second))
172 _, _ = c.Write(query(0xbeef, "a.test"))
173 buf := make([]byte, 512)
174 n, err := c.Read(buf)
175 if err != nil {
176 t.Fatal(err)
177 }
178 if n != hdrLen || binary.BigEndian.Uint16(buf) != 0xbeef || buf[3]&0x0f != 2 || buf[2]&0x80 == 0 {
179 t.Fatalf("not a SERVFAIL: %x", buf[:n])
180 }
181}
182
183func TestMismatchedUpstreamIDIgnored(t *testing.T) {
184 // Upstream answers with a wrong ID: treated as no answer, client gets SERVFAIL.
185 up := fakeUpstream(t, func(q []byte) []byte {
186 r := answer(q)
187 r[0] ^= 0xff
188 return r
189 })
190 f, addr := start(t, up)
191 _ = f
192 c, _ := net.Dial("udp", addr)
193 defer func() { _ = c.Close() }()
194 _ = c.SetDeadline(time.Now().Add(2*UpstreamTimeout + 2*time.Second))
195 _, _ = c.Write(query(5, "a.test"))
196 buf := make([]byte, 512)
197 n, err := c.Read(buf)
198 if err != nil {
199 t.Fatal(err)
200 }
201 if buf[3]&0x0f != 2 {
202 t.Fatalf("want SERVFAIL, got %x", buf[:n])
203 }
204}
205
206func TestShortPacketsIgnored(t *testing.T) {
207 up := fakeUpstream(t, answer)
208 _, addr := start(t, up)
209 c, _ := net.Dial("udp", addr)
210 defer func() { _ = c.Close() }()
211 _, _ = c.Write([]byte{1, 2, 3}) // dropped
212 _, _ = c.Write(query(9, "a.test"))
213 _ = c.SetDeadline(time.Now().Add(5 * time.Second))
214 buf := make([]byte, 512)
215 n, err := c.Read(buf)
216 if err != nil || binary.BigEndian.Uint16(buf) != 9 {
217 t.Fatalf("n=%d err=%v", n, err)
218 }
219}
220
221func TestListenTwiceAndClose(t *testing.T) {
222 f := New(func() []string { return nil }, 0)
223 ip := netip.MustParseAddr("127.0.0.1")
224 if err := f.Listen(context.Background(), ip); err != nil {
225 t.Skip(err)
226 }
227 if err := f.Listen(context.Background(), ip); err == nil {
228 t.Fatal("second Listen on the same address must fail")
229 }
230 a, _ := f.Addr(ip)
231 f.Close(ip)
232 f.Close(ip) // idempotent
233 if _, ok := f.Addr(ip); ok {
234 t.Fatal("still listening after Close")
235 }
236 if pc, err := net.ListenPacket("udp", a.String()); err != nil {
237 t.Fatalf("port not released: %v", err)
238 } else {
239 _ = pc.Close()
240 }
241}
242
243func TestServfailShape(t *testing.T) {
244 if servfail([]byte{1}) != nil {
245 t.Fatal("short query must give nil")
246 }
247 q := query(0xabcd, "x.test")
248 q[2] = 0x79 // opcode bits + RD + AA
249 r := servfail(q)
250 if r[0] != 0xab || r[1] != 0xcd || r[2]&0x80 == 0 || r[3] != 0x82 {
251 t.Fatalf("bad servfail %x", r)
252 }
253}
254
255func TestParseResolvConf(t *testing.T) {
256 in := `# comment
257nameserver 127.0.0.53
258nameserver 127.0.0.53
259nameserver 192.0.2.1 # trailing
260nameserver 2001:db8::1
261nameserver fe80::1%eth0
262nameserver 0.0.0.0
263nameserver not-an-ip
264search example.test
265options edns0
266nameserver
267`
268 got := ParseResolvConf(strings.NewReader(in))
269 want := []string{"127.0.0.53:53", "192.0.2.1:53", "[2001:db8::1]:53", "[fe80::1%eth0]:53"}
270 if !reflect.DeepEqual(got, want) {
271 t.Fatalf("got %v want %v", got, want)
272 }
273}
274
275func TestResolversReload(t *testing.T) {
276 p := filepath.Join(t.TempDir(), "resolv.conf")
277 if err := os.WriteFile(p, []byte("nameserver 192.0.2.1\n"), 0o644); err != nil {
278 t.Fatal(err)
279 }
280 r := NewResolvers(p)
281 if got := r.Addrs(); !reflect.DeepEqual(got, []string{"192.0.2.1:53"}) {
282 t.Fatal(got)
283 }
284 if err := os.WriteFile(p, []byte("nameserver 192.0.2.99\nnameserver 192.0.2.1\n"), 0o644); err != nil {
285 t.Fatal(err)
286 }
287 later := time.Now().Add(2 * time.Second)
288 _ = os.Chtimes(p, later, later)
289 if got := r.Addrs(); !reflect.DeepEqual(got, []string{"192.0.2.99:53", "192.0.2.1:53"}) {
290 t.Fatal(got)
291 }
292 // Unreadable file keeps the last known list.
293 _ = os.Remove(p)
294 if got := r.Addrs(); len(got) != 2 {
295 t.Fatalf("lost the last known list: %v", got)
296 }
297 if got := NewResolvers(filepath.Join(t.TempDir(), "missing")).Addrs(); len(got) != 0 {
298 t.Fatal(got)
299 }
300}
301
302// S2: a listener holds at most MaxTCPConns connections; extra ones are closed
303// at once, and a free slot is usable again after a close.
304func TestTCPConnectionCapPerListener(t *testing.T) {
305 up := fakeUpstream(t, answer)
306 _, addr := start(t, up)
307 var held []net.Conn
308 for range MaxTCPConns {
309 c, err := net.Dial("tcp", addr)
310 if err != nil {
311 t.Fatal(err)
312 }
313 held = append(held, c)
314 defer func() { _ = c.Close() }()
315 }
316 // Let the accept loop register all of them.
317 time.Sleep(200 * time.Millisecond)
318 extra, err := net.Dial("tcp", addr)
319 if err != nil {
320 t.Fatal(err)
321 }
322 defer func() { _ = extra.Close() }()
323 _ = extra.SetReadDeadline(time.Now().Add(3 * time.Second))
324 if _, err := extra.Read(make([]byte, 1)); err != io.EOF && !errors.Is(err, syscall.ECONNRESET) {
325 t.Fatalf("the connection over the cap must be closed by the server, got %v", err)
326 }
327 // Free one slot: a new connection is served.
328 _ = held[0].Close()
329 time.Sleep(200 * time.Millisecond)
330 c, err := net.Dial("tcp", addr)
331 if err != nil {
332 t.Fatal(err)
333 }
334 defer func() { _ = c.Close() }()
335 _ = c.SetDeadline(time.Now().Add(5 * time.Second))
336 q := query(3, "slot.test")
337 _, _ = c.Write(append([]byte{byte(len(q) >> 8), byte(len(q))}, q...))
338 var l [2]byte
339 if _, err := io.ReadFull(c, l[:]); err != nil {
340 t.Fatalf("a freed slot must serve again: %v", err)
341 }
342}
343
344// S2: the lifetime is absolute from accept: a client that keeps the connection
345// alive by trickling bytes still loses it.
346func TestTCPAbsoluteDeadline(t *testing.T) {
347 up := fakeUpstream(t, answer)
348 f := New(func() []string { return []string{up} }, 0)
349 f.Lifetime = 600 * time.Millisecond
350 ip := netip.MustParseAddr("127.0.0.1")
351 if err := f.Listen(context.Background(), ip); err != nil {
352 t.Skip(err)
353 }
354 t.Cleanup(f.CloseAll)
355 la, _ := f.Addr(ip)
356 addr := la.String()
357 c, err := net.Dial("tcp", addr)
358 if err != nil {
359 t.Fatal(err)
360 }
361 defer func() { _ = c.Close() }()
362 start := time.Now()
363 closed := make(chan struct{})
364 go func() {
365 _, _ = io.Copy(io.Discard, c) // returns when the server closes
366 close(closed)
367 }()
368 // Trickle one byte every 100 ms, never finishing a frame: only an idle
369 // timeout (10 s) would let this live; the absolute deadline must not.
370 go func() {
371 for i := 0; i < 40; i++ {
372 if _, err := c.Write([]byte{0}); err != nil {
373 return
374 }
375 time.Sleep(100 * time.Millisecond)
376 }
377 }()
378 select {
379 case <-closed:
380 if d := time.Since(start); d > 3*time.Second {
381 t.Fatalf("closed after %v, want about %v", d, f.Lifetime)
382 }
383 case <-time.After(5 * time.Second):
384 t.Fatal("a trickling client kept its connection past the absolute deadline")
385 }
386}
387
388// S2/S9: a saturated listener drops UDP queries instead of blocking its read
389// loop, and another listener (another guest) keeps answering.
390func TestUDPSaturationDropsAndIsolatesListeners(t *testing.T) {
391 release := make(chan struct{})
392 slow := fakeUpstream(t, func(q []byte) []byte { <-release; return answer(q) })
393 fast := fakeUpstream(t, answer)
394 var useSlow atomic.Bool
395 useSlow.Store(true)
396 f := New(func() []string {
397 if useSlow.Load() {
398 return []string{slow}
399 }
400 return []string{fast}
401 }, 0)
402 a1, a2 := netip.MustParseAddr("127.0.0.1"), netip.MustParseAddr("127.0.0.2")
403 if err := f.Listen(context.Background(), a1); err != nil {
404 t.Skip(err)
405 }
406 if err := f.Listen(context.Background(), a2); err != nil {
407 t.Skip(err)
408 }
409 t.Cleanup(f.CloseAll)
410 t.Cleanup(func() { close(release) })
411 l1, _ := f.Addr(a1)
412 flood, err := net.Dial("udp", l1.String())
413 if err != nil {
414 t.Fatal(err)
415 }
416 defer func() { _ = flood.Close() }()
417 for i := 0; i < MaxInflight*3; i++ {
418 _, _ = flood.Write(query(uint16(i), "flood.test"))
419 }
420 deadline := time.Now().Add(3 * time.Second)
421 for f.Dropped(a1) == 0 && time.Now().Before(deadline) {
422 time.Sleep(20 * time.Millisecond)
423 }
424 if f.Dropped(a1) == 0 {
425 t.Fatal("a saturated listener must drop queries")
426 }
427 // The other listener is unaffected: point the resolver at the fast upstream.
428 useSlow.Store(false)
429 l2, _ := f.Addr(a2)
430 c, err := net.Dial("udp", l2.String())
431 if err != nil {
432 t.Fatal(err)
433 }
434 defer func() { _ = c.Close() }()
435 _ = c.SetDeadline(time.Now().Add(4 * time.Second))
436 _, _ = c.Write(query(77, "other-guest.test"))
437 buf := make([]byte, 512)
438 n, err := c.Read(buf)
439 if err != nil || string(buf[n-6:n]) != "ANSWER" {
440 t.Fatalf("another listener must not be starved by the flood: n=%d err=%v", n, err)
441 }
442}
443
444// By default Listen fails fast on an address the host does not own (Docker and
445// the VM driver have configured it by then: a missing one is an error).
446func TestListenOnAMissingAddressFailsByDefault(t *testing.T) {
447 f := New(func() []string { return nil }, 0)
448 t.Cleanup(f.CloseAll)
449 ip := netip.MustParseAddr("203.0.113.77") // TEST-NET-3: never configured here
450 if err := f.Listen(context.Background(), ip); err == nil {
451 t.Fatal("listening on an address the host does not own must fail without FreeBind")
452 }
453}
454
455// With FreeBind (podman's netavark creates the bridge address only when the
456// first container starts) Listen binds an address the host does not own yet.
457func TestFreeBindListensOnAnAddressTheHostDoesNotOwnYet(t *testing.T) {
458 f := New(func() []string { return nil }, 0)
459 f.FreeBind = true
460 t.Cleanup(f.CloseAll)
461 ip := netip.MustParseAddr("203.0.113.77")
462 if err := f.Listen(context.Background(), ip); err != nil {
463 t.Fatalf("FreeBind: %v", err)
464 }
465 if _, ok := f.Addr(ip); !ok {
466 t.Fatal("no listener")
467 }
468}
469
470// queryType is query with a chosen type (and optionally EDNS0).
471func queryType(id uint16, name string, qtype uint16, edns bool) []byte {
472 q := query(id, name)
473 binary.BigEndian.PutUint16(q[len(q)-4:], qtype)
474 if edns {
475 q[11] = 1
476 q = append(q, 0, 0, 41, 0x10, 0, 0, 0, 0, 0, 0, 0)
477 }
478 return q
479}
480
481func udpExchange(t *testing.T, addr string, q []byte) []byte {
482 t.Helper()
483 c, err := net.Dial("udp", addr)
484 if err != nil {
485 t.Fatal(err)
486 }
487 defer func() { _ = c.Close() }()
488 _ = c.SetDeadline(time.Now().Add(5 * time.Second))
489 _, _ = c.Write(q)
490 buf := make([]byte, 1024)
491 n, err := c.Read(buf)
492 if err != nil {
493 t.Fatal(err)
494 }
495 return buf[:n]
496}
497
498func startOverride(t *testing.T, name string, ups ...string) (*Forwarder, string) {
499 t.Helper()
500 f := New(func() []string { return ups }, 0)
501 f.Override = name
502 ip := netip.MustParseAddr("127.0.0.1")
503 if err := f.Listen(context.Background(), ip); err != nil {
504 t.Fatal(err)
505 }
506 t.Cleanup(f.CloseAll)
507 a, _ := f.Addr(ip)
508 return f, a.String()
509}
510
511func TestOverrideAnswersAWithTheListenerAddress(t *testing.T) {
512 var asked atomic.Int64
513 up := fakeUpstream(t, func(q []byte) []byte { asked.Add(1); return answer(q) })
514 // A trailing dot in the setting is ignored; on the wire a name has none.
515 _, addr := startOverride(t, "Ermite.Example.org.", up)
516 for _, name := range []string{"ermite.example.org", "ERMITE.example.ORG", "Ermite.Example.Org"} {
517 r := udpExchange(t, addr, queryType(0x4242, name, 1, false))
518 if binary.BigEndian.Uint16(r) != 0x4242 || r[2]&0x80 == 0 || r[2]&0x01 == 0 || r[3]&0x0F != 0 {
519 t.Fatalf("%s: header %x", name, r[:hdrLen])
520 }
521 if an := binary.BigEndian.Uint16(r[6:]); an != 1 {
522 t.Fatalf("%s: %d answers", name, an)
523 }
524 if !strings.HasSuffix(string(r), "\x00\x04\x7f\x00\x00\x01") {
525 t.Fatalf("%s: rdata is not 127.0.0.1: %x", name, r)
526 }
527 }
528 if asked.Load() != 0 {
529 t.Fatalf("the override went upstream %d times", asked.Load())
530 }
531}
532
533func TestOverrideAAAAIsEmptyNoError(t *testing.T) {
534 up := fakeUpstream(t, answer)
535 _, addr := startOverride(t, "ermite.example.org", up)
536 for _, edns := range []bool{false, true} {
537 r := udpExchange(t, addr, queryType(7, "ermite.EXAMPLE.org", 28, edns))
538 if r[3]&0x0F != 0 || binary.BigEndian.Uint16(r[6:]) != 0 || r[2]&0x80 == 0 {
539 t.Fatalf("AAAA (edns %v): %x", edns, r)
540 }
541 if edns != (binary.BigEndian.Uint16(r[10:]) == 1) {
542 t.Fatalf("AAAA (edns %v): arcount %d", edns, binary.BigEndian.Uint16(r[10:]))
543 }
544 }
545}
546
547func TestOverrideForwardsEveryOtherName(t *testing.T) {
548 var asked atomic.Int64
549 up := fakeUpstream(t, func(q []byte) []byte { asked.Add(1); return answer(q) })
550 _, addr := startOverride(t, "ermite.example.org", up)
551 for _, name := range []string{"example.org", "other.example.org", "x.ermite.example.org", "ermite.example.org.evil.test", "ermite.example.or"} {
552 r := udpExchange(t, addr, query(9, name))
553 if string(r[len(r)-6:]) != "ANSWER" {
554 t.Fatalf("%s was not forwarded: %x", name, r)
555 }
556 }
557 if asked.Load() != 5 {
558 t.Fatalf("%d upstream queries, want 5", asked.Load())
559 }
560}
561
562func TestOverrideOverTCPAndDisabledByDefault(t *testing.T) {
563 up := fakeUpstream(t, answer)
564 _, addr := startOverride(t, "ermite.example.org", up)
565 tc, err := net.Dial("tcp", addr)
566 if err != nil {
567 t.Fatal(err)
568 }
569 defer func() { _ = tc.Close() }()
570 _ = tc.SetDeadline(time.Now().Add(5 * time.Second))
571 q := queryType(3, "ermite.example.org", 1, false)
572 _, _ = tc.Write(append([]byte{byte(len(q) >> 8), byte(len(q))}, q...))
573 var l [2]byte
574 if _, err := io.ReadFull(tc, l[:]); err != nil {
575 t.Fatal(err)
576 }
577 r := make([]byte, binary.BigEndian.Uint16(l[:]))
578 if _, err := io.ReadFull(tc, r); err != nil {
579 t.Fatal(err)
580 }
581 if binary.BigEndian.Uint16(r[6:]) != 1 || !strings.HasSuffix(string(r), "\x7f\x00\x00\x01") {
582 t.Fatalf("tcp: %x", r)
583 }
584
585 // Off by default: the name is forwarded like any other.
586 _, addr2 := start(t, up)
587 r2 := udpExchange(t, addr2, query(4, "ermite.example.org"))
588 if string(r2[len(r2)-6:]) != "ANSWER" {
589 t.Fatalf("not forwarded without Override: %x", r2)
590 }
591}
592
593func TestOverrideIgnoresMalformedAndNonQueries(t *testing.T) {
594 f := New(func() []string { return nil }, 0)
595 f.Override = "ermite.example.org"
596 gw := netip.MustParseAddr("10.200.0.1")
597 good := queryType(1, "ermite.example.org", 1, false)
598 if f.overrideAnswer(gw, good) == nil {
599 t.Fatal("control query not answered")
600 }
601 cut := good[:len(good)-3]
602 chaos := queryType(1, "ermite.example.org", 1, false)
603 binary.BigEndian.PutUint16(chaos[len(chaos)-2:], 3) // class CH
604 resp := append([]byte(nil), good...)
605 resp[2] |= 0x80 // QR set: not a query
606 notify := append([]byte(nil), good...)
607 notify[2] |= 4 << 3 // opcode 4
608 ptr := append(append([]byte(nil), good[:hdrLen]...), 0xC0, 0x0C, 0, 1, 0, 1)
609 for name, q := range map[string][]byte{"truncated": cut, "class CH": chaos, "response": resp, "opcode": notify, "pointer": ptr, "short": good[:5]} {
610 if f.overrideAnswer(gw, q) != nil {
611 t.Errorf("%s was answered locally", name)
612 }
613 }
614}