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}