blob: ddab9570cf984d2bebd5604df95d9963e784d756 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Jan Schär75ea9f42024-07-29 17:01:41 +02004package forward
5
6import (
7 "fmt"
8 "slices"
9 "strings"
10 "sync"
11 "sync/atomic"
12 "testing"
13 "time"
14
15 "github.com/miekg/dns"
16
17 netDNS "source.monogon.dev/osbase/net/dns"
18 "source.monogon.dev/osbase/net/dns/test"
19 "source.monogon.dev/osbase/supervisor"
20)
21
22func rrStrings(rrs []dns.RR) []string {
23 s := make([]string, len(rrs))
24 for i, rr := range rrs {
25 s[i] = rr.String()
26 }
27 return s
28}
29
30func expectReply(t *testing.T, req *netDNS.Request, wantReply proxyReply) {
31 t.Helper()
32 if !req.Handled {
33 t.Errorf("Request was not handled")
34 }
35 if got, want := req.Reply.Truncated, wantReply.Truncated; got != want {
36 t.Errorf("Want truncated %v, got %v", want, got)
37 }
38 if got, want := req.Reply.Rcode, wantReply.Rcode; got != want {
39 t.Errorf("Want rcode %v, got %v", want, got)
40 }
41
42 wantExtra := wantReply.Extra
43 if req.Ropt != nil {
44 wantOpt := &dns.OPT{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT}}
45 wantOpt.Option = wantReply.Options
46 wantOpt.SetUDPSize(req.Ropt.UDPSize())
47 wantOpt.SetDo(req.Qopt.Do())
48 wantExtra = slices.Concat(wantExtra, []dns.RR{wantOpt})
49 }
50 checkReplySection(t, "answer", req.Reply.Answer, wantReply.Answer)
51 checkReplySection(t, "ns", req.Reply.Ns, wantReply.Ns)
52 checkReplySection(t, "extra", req.Reply.Extra, wantExtra)
53}
54
55func checkReplySection(t *testing.T, sectionName string, got []dns.RR, want []dns.RR) {
56 t.Helper()
57 gotStr := rrStrings(got)
58 wantStr := rrStrings(want)
59 if !slices.Equal(gotStr, wantStr) {
60 t.Errorf("Want %s:\n%s\nGot:\n%v", sectionName,
61 strings.Join(wantStr, "\n"), strings.Join(gotStr, "\n"))
62 }
63}
64
65type fakeTime struct {
66 time atomic.Pointer[time.Time]
67}
68
69func (f *fakeTime) now() time.Time {
70 t := f.time.Load()
71 if t != nil {
72 return *t
73 }
74 return time.Time{}
75}
76
77func (f *fakeTime) set(t time.Time) {
78 f.time.Store(&t)
79}
80
81func (f *fakeTime) add(t time.Duration) {
82 f.set(f.now().Add(t))
83}
84
85func TestUpstreams(t *testing.T) {
86 answerRecord1 := test.RR("example.com. IN A 127.0.0.1")
87 s1 := test.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
88 ret := new(dns.Msg)
89 ret.SetReply(r)
90 ret.Answer = append(ret.Answer, answerRecord1)
91 err := w.WriteMsg(ret)
92 if err != nil {
93 t.Error(err)
94 }
95 })
96 defer s1.Close()
97 answerRecord2 := test.RR("2.example.com. IN A 127.0.0.1")
98 s2 := test.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
99 ret := new(dns.Msg)
100 ret.SetReply(r)
101 ret.Answer = append(ret.Answer, answerRecord2)
102 err := w.WriteMsg(ret)
103 if err != nil {
104 t.Error(err)
105 }
106 })
107 defer s2.Close()
108
109 forward := New()
110 supervisor.TestHarness(t, forward.Run)
111
112 // If no upstreams are set, return an error.
113 req := netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
114 forward.HandleDNS(req)
115 expectReply(t, req, replyNoUpstreams)
116
117 forward.DNSServers.Set([]string{s1.Addr})
118 time.Sleep(10 * time.Millisecond)
119
120 req = netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
121 forward.HandleDNS(req)
122 expectReply(t, req, proxyReply{Answer: []dns.RR{answerRecord1}})
123
124 forward.DNSServers.Set([]string{s2.Addr})
125 time.Sleep(10 * time.Millisecond)
126
127 // New DNS server should be used.
128 req = netDNS.CreateTestRequest("2.example.com.", dns.TypeA, "udp")
129 forward.HandleDNS(req)
130 expectReply(t, req, proxyReply{Answer: []dns.RR{answerRecord2}})
131}
132
133// TestHealthcheck tests that if one of multiple upstreams is broken,
134// this upstream receives health check queries, and that client queries
135// succeed since they are retried on the good upstream.
136func TestHealthcheck(t *testing.T) {
137 var healthcheckCount atomic.Int64
138
139 sGood := test.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
140 ret := new(dns.Msg)
141 ret.SetReply(r)
142 ret.Rcode = dns.RcodeNameError
143 err := w.WriteMsg(ret)
144 if err != nil {
145 t.Error(err)
146 }
147 })
148 defer sGood.Close()
149 sBad := test.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
150 if r.Question[0] == (dns.Question{Name: ".", Qtype: dns.TypeNS, Qclass: dns.ClassINET}) {
151 healthcheckCount.Add(1)
152 }
153 w.Write([]byte("this is not a dns message"))
154 })
155 defer sBad.Close()
156
157 forward := New()
158 forward.DNSServers.Set([]string{sGood.Addr, sBad.Addr})
159 supervisor.TestHarness(t, forward.Run)
160 time.Sleep(10 * time.Millisecond)
161
162 for i := range 100 {
163 req := netDNS.CreateTestRequest(fmt.Sprintf("%v.example.com.", i), dns.TypeA, "udp")
164 forward.HandleDNS(req)
165 expectReply(t, req, proxyReply{Rcode: dns.RcodeNameError})
166 }
167
168 if healthcheckCount.Load() == 0 {
169 t.Error("Expected to see at least one healthcheck query.")
170 }
171}
172
173func TestRecursionDesired(t *testing.T) {
174 forward := New()
175
176 // If RecursionDesired is not set, refuse query.
177 req := netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
178 req.Reply.RecursionDesired = false
179 forward.HandleDNS(req)
180 expectReply(t, req, proxyReply{Rcode: dns.RcodeRefused})
181
182 // If RecursionDesired is not set and the query was redirected, send reply as is.
183 req = netDNS.CreateTestRequest("external.default.scv.cluster.local.", dns.TypeA, "udp")
184 req.Reply.RecursionDesired = false
185 req.AddCNAME("example.com.", 10)
186 req.Handled = false
187 forward.HandleDNS(req)
188 expectReply(t, req, proxyReply{
189 Answer: []dns.RR{test.RR("external.default.scv.cluster.local. 10 IN CNAME example.com.")},
190 })
191}
192
193// TestCache tests that both concurrent and sequential queries use the cache.
194func TestCache(t *testing.T) {
195 var queryCount atomic.Int64
196
197 answerRecord := test.RR("example.com. IN A 127.0.0.1")
198 s := test.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
199 queryCount.Add(1)
200 // Sleep a bit until all concurrent queries are blocked.
201 time.Sleep(10 * time.Millisecond)
202 ret := new(dns.Msg)
203 ret.SetReply(r)
204 ret.Answer = append(ret.Answer, answerRecord)
205 err := w.WriteMsg(ret)
206 if err != nil {
207 t.Error(err)
208 }
209 })
210 defer s.Close()
211
212 forward := New()
213 forward.DNSServers.Set([]string{s.Addr})
214 supervisor.TestHarness(t, forward.Run)
215 time.Sleep(10 * time.Millisecond)
216
217 wg := sync.WaitGroup{}
218 for range 3 {
219 wg.Add(1)
220 go func() {
221 req := netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
222 forward.HandleDNS(req)
223 expectReply(t, req, proxyReply{Answer: []dns.RR{answerRecord}})
224 wg.Done()
225 }()
226 }
227 wg.Wait()
228
229 req := netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
230 forward.HandleDNS(req)
231 expectReply(t, req, proxyReply{Answer: []dns.RR{answerRecord}})
232
233 // tcp query
234 req = netDNS.CreateTestRequest("example.com.", dns.TypeA, "tcp")
235 forward.HandleDNS(req)
236 expectReply(t, req, proxyReply{Answer: []dns.RR{answerRecord}})
237
238 // query without OPT
239 req = netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
240 req.Qopt = nil
241 req.Ropt = nil
242 forward.HandleDNS(req)
243 expectReply(t, req, proxyReply{Answer: []dns.RR{answerRecord}})
244
245 if got, want := queryCount.Load(), int64(1); got != want {
246 t.Errorf("Want %v queries, got %v", want, got)
247 }
248}
249
250func TestTtl(t *testing.T) {
251 var queryCount atomic.Int64
252 answer := []dns.RR{
253 test.RR("1.example.com. 3 CNAME 2.example.com."),
254 test.RR("2.example.com. 3600 IN A 127.0.0.1"),
255 }
256 answerDecrement := []dns.RR{
257 test.RR("1.example.com. 2 CNAME 2.example.com."),
258 test.RR("2.example.com. 3599 IN A 127.0.0.1"),
259 }
260 s := test.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
261 queryCount.Add(1)
262 ret := new(dns.Msg)
263 ret.SetReply(r)
264 ret.Answer = answer
265 err := w.WriteMsg(ret)
266 if err != nil {
267 t.Error(err)
268 }
269 })
270 defer s.Close()
271
272 forward := New()
273 ft := fakeTime{}
274 ft.set(time.Now())
275 forward.now = ft.now
276 forward.DNSServers.Set([]string{s.Addr})
277 supervisor.TestHarness(t, forward.Run)
278 time.Sleep(10 * time.Millisecond)
279
280 req := netDNS.CreateTestRequest("1.example.com.", dns.TypeA, "udp")
281 forward.HandleDNS(req)
282 expectReply(t, req, proxyReply{Answer: answer})
283
284 ft.add(1500 * time.Millisecond)
285
286 // TTL of cached reply should be decremented.
287 req = netDNS.CreateTestRequest("1.example.com.", dns.TypeA, "udp")
288 forward.HandleDNS(req)
289 expectReply(t, req, proxyReply{Answer: answerDecrement})
290
291 ft.add(2000 * time.Millisecond)
292
293 // Cache expired.
294 req = netDNS.CreateTestRequest("1.example.com.", dns.TypeA, "udp")
295 forward.HandleDNS(req)
296 expectReply(t, req, proxyReply{Answer: answer})
297
298 if got, want := queryCount.Load(), int64(2); got != want {
299 t.Errorf("Want %v queries, got %v", want, got)
300 }
301}
302
303// TestShuffle tests that replies from cache have shuffled RRsets.
304// In this example, only the A records should be shuffled,
305// the CNAMEs and RRSIG should stay where they are.
306func TestShuffle(t *testing.T) {
307 testAnswer := []dns.RR{
308 test.RR("1.example.com. CNAME 2.example.com."),
309 test.RR("2.example.com. CNAME 3.example.com."),
310 }
311 // A random shuffle of 20 items is extremely unlikely (1/(20!))
312 // to end up in the same order it was originally.
313 for i := range 20 {
314 testAnswer = append(testAnswer, test.RR(fmt.Sprintf("3.example.com. IN A 127.0.0.%v", i)))
315 }
316 testAnswer = append(testAnswer, test.RR("3.example.com. RRSIG A 8 2 3600 1 1 1 example.com AAAA AAAA AAAA"))
317
318 s := test.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
319 ret := new(dns.Msg)
320 ret.SetReply(r)
321 ret.Answer = testAnswer
322 err := w.WriteMsg(ret)
323 if err != nil {
324 t.Error(err)
325 }
326 })
327 defer s.Close()
328
329 forward := New()
330 forward.DNSServers.Set([]string{s.Addr})
331 supervisor.TestHarness(t, forward.Run)
332 time.Sleep(10 * time.Millisecond)
333
334 req := netDNS.CreateTestRequest("example.com.", dns.TypeA, "tcp")
335 forward.HandleDNS(req)
336 expectReply(t, req, proxyReply{Answer: testAnswer})
337
338 req = netDNS.CreateTestRequest("example.com.", dns.TypeA, "tcp")
339 forward.HandleDNS(req)
340
341 if slices.Equal(rrStrings(req.Reply.Answer), rrStrings(testAnswer)) {
342 t.Error("Expected second reply to be shuffled.")
343 }
344 slices.SortFunc(req.Reply.Answer[2:len(testAnswer)-1], func(a, b dns.RR) int {
345 return int(a.(*dns.A).A[3]) - int(b.(*dns.A).A[3])
346 })
347 expectReply(t, req, proxyReply{Answer: testAnswer})
348}
349
350func TestTruncated(t *testing.T) {
351 var queryCount atomic.Int64
352 answerRecord1 := test.RR("example.com. IN A 127.0.0.1")
353 answerRecord2 := test.RR("example.com. IN A 127.0.0.2")
354 s := test.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
355 queryCount.Add(1)
356 tcp := w.RemoteAddr().Network() == "tcp"
357 ret := new(dns.Msg)
358 ret.SetReply(r)
359 if tcp {
360 ret.Answer = append(ret.Answer, answerRecord2)
361 } else {
362 ret.Answer = append(ret.Answer, answerRecord1)
363 ret.Truncated = true
364 }
365 err := w.WriteMsg(ret)
366 if err != nil {
367 t.Error(err)
368 }
369 })
370 defer s.Close()
371
372 forward := New()
373 ft := fakeTime{}
374 ft.set(time.Now())
375 forward.now = ft.now
376 forward.DNSServers.Set([]string{s.Addr})
377 supervisor.TestHarness(t, forward.Run)
378 time.Sleep(10 * time.Millisecond)
379
380 for range 2 {
381 // Truncated replies are cached and returned for udp queries.
382 req := netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
383 forward.HandleDNS(req)
384 expectReply(t, req, proxyReply{Truncated: true, Answer: []dns.RR{answerRecord1}})
385 }
386
387 // Cached truncated replies are not used for tcp queries.
388 req := netDNS.CreateTestRequest("example.com.", dns.TypeA, "tcp")
389 forward.HandleDNS(req)
390 expectReply(t, req, proxyReply{Answer: []dns.RR{answerRecord2}})
391
392 // Subsequent udp queries get the tcp reply.
393 req = netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
394 forward.HandleDNS(req)
395 expectReply(t, req, proxyReply{Answer: []dns.RR{answerRecord2}})
396
397 ft.add(10000 * time.Second)
398
399 // After the cache expires, tcp is used.
400 req = netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
401 forward.HandleDNS(req)
402 expectReply(t, req, proxyReply{Answer: []dns.RR{answerRecord2}})
403
404 if got, want := queryCount.Load(), int64(3); got != want {
405 t.Errorf("Want %v queries, got %v", want, got)
406 }
407}
408
409type testQuery struct {
410 qtype uint16
411 dnssec bool
412 checkingDisabled bool
413}
414
415// TestQueries tests that queries which differ in relevant fields
416// result in separate upstream queries and are separately cached.
417func TestQueries(t *testing.T) {
418 var queryCount atomic.Int64
419
420 answerRecord := test.RR("example.com. IN A 127.0.0.1")
421 answerRecordAAAA := test.RR("example.com. IN AAAA ::1")
422 answerRecordRRSIG := test.RR("example.com. IN RRSIG A 8 2 3600 1 1 1 example.com AAAA AAAA AAAA")
423 answerRecordCD := test.RR("example.com. IN A 127.0.0.2")
424 s := test.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
425 queryCount.Add(1)
426 ret := new(dns.Msg)
427 ret.SetReply(r)
428 if r.Question[0].Name != "example.com." || r.Question[0].Qclass != dns.ClassINET {
429 t.Errorf("Unexpected Name or Qclass")
430 return
431 }
432 switch (testQuery{r.Question[0].Qtype, r.IsEdns0().Do(), r.CheckingDisabled}) {
433 case testQuery{dns.TypeA, false, false}:
434 ret.Answer = append(ret.Answer, answerRecord)
435 case testQuery{dns.TypeAAAA, false, false}:
436 ret.Answer = append(ret.Answer, answerRecordAAAA)
437 case testQuery{dns.TypeA, true, false}:
438 ret.Answer = append(ret.Answer, answerRecord)
439 ret.Answer = append(ret.Answer, answerRecordRRSIG)
440 case testQuery{dns.TypeA, false, true}:
441 ret.Answer = append(ret.Answer, answerRecordCD)
442 }
443 err := w.WriteMsg(ret)
444 if err != nil {
445 t.Error(err)
446 }
447 })
448 defer s.Close()
449
450 forward := New()
451 forward.DNSServers.Set([]string{s.Addr})
452 supervisor.TestHarness(t, forward.Run)
453 time.Sleep(10 * time.Millisecond)
454
455 for range 2 {
456 req := netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
457 forward.HandleDNS(req)
458 expectReply(t, req, proxyReply{Answer: []dns.RR{answerRecord}})
459
460 // different qtype
461 req = netDNS.CreateTestRequest("example.com.", dns.TypeAAAA, "udp")
462 forward.HandleDNS(req)
463 expectReply(t, req, proxyReply{Answer: []dns.RR{answerRecordAAAA}})
464
465 // DNSSEC flag
466 req = netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
467 req.Qopt.SetDo()
468 req.Ropt.SetDo()
469 forward.HandleDNS(req)
470 expectReply(t, req, proxyReply{Answer: []dns.RR{answerRecord, answerRecordRRSIG}})
471
472 // CheckingDisabled flag
473 req = netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
474 req.Reply.CheckingDisabled = true
475 forward.HandleDNS(req)
476 expectReply(t, req, proxyReply{Answer: []dns.RR{answerRecordCD}})
477 }
478
479 if got, want := queryCount.Load(), int64(4); got != want {
480 t.Errorf("Want %v queries, got %v", want, got)
481 }
482}
483
484// TestOPT tests that only certains OPT options are forwarded
485// in query and reply.
486func TestOPT(t *testing.T) {
487 s := test.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
488 wantOpt := &dns.OPT{}
489 wantOpt.Hdr.Name = "."
490 wantOpt.Hdr.Rrtype = dns.TypeOPT
491 wantOpt.SetUDPSize(r.IsEdns0().UDPSize())
492 wantOpt.Option = []dns.EDNS0{
493 &dns.EDNS0_DAU{AlgCode: []uint8{1, 4}},
494 &dns.EDNS0_DHU{AlgCode: []uint8{5}},
495 &dns.EDNS0_N3U{AlgCode: []uint8{6}},
496 }
497 if got, want := r.IsEdns0().String(), wantOpt.String(); got != want {
498 t.Errorf("Wanted opt %q, got %q", want, got)
499 }
500
501 ret := new(dns.Msg)
502 ret.SetReply(r)
503 ret.Rcode = dns.RcodeBadAlg
504 ret.SetEdns0(512, false)
505 ropt := ret.Extra[0].(*dns.OPT)
506 ropt.Option = []dns.EDNS0{
507 &dns.EDNS0_NSID{Nsid: "c0ffee"},
508 &dns.EDNS0_EDE{InfoCode: dns.ExtendedErrorCodeCensored, ExtraText: "****"},
509 &dns.EDNS0_EDE{InfoCode: dns.ExtendedErrorCodeDNSKEYMissing, ExtraText: "second problem"},
510 &dns.EDNS0_PADDING{Padding: []byte{0, 0, 0}},
511 }
512 err := w.WriteMsg(ret)
513 if err != nil {
514 t.Error(err)
515 }
516 })
517 defer s.Close()
518
519 forward := New()
520 forward.DNSServers.Set([]string{s.Addr})
521 supervisor.TestHarness(t, forward.Run)
522 time.Sleep(10 * time.Millisecond)
523
524 req := netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
525 req.Qopt.Option = []dns.EDNS0{
526 &dns.EDNS0_NSID{Nsid: ""},
527 &dns.EDNS0_EDE{InfoCode: dns.ExtendedErrorCodeDNSBogus, ExtraText: "huh?"},
528 &dns.EDNS0_DAU{AlgCode: []uint8{1, 4}},
529 &dns.EDNS0_DHU{AlgCode: []uint8{5}},
530 &dns.EDNS0_N3U{AlgCode: []uint8{6}},
531 &dns.EDNS0_PADDING{Padding: []byte{0, 0, 0}},
532 }
533 forward.HandleDNS(req)
534 expectReply(t, req, proxyReply{
535 Rcode: dns.RcodeBadAlg,
536 Options: []dns.EDNS0{
537 &dns.EDNS0_EDE{InfoCode: dns.ExtendedErrorCodeCensored, ExtraText: "****"},
538 &dns.EDNS0_EDE{InfoCode: dns.ExtendedErrorCodeDNSKEYMissing, ExtraText: "second problem"},
539 },
540 })
541}
542
543// TestBadReply tests that if the qname of the reply is not what was
544// sent in the query, the reply is rejected.
545func TestBadReply(t *testing.T) {
546 answerRecord := test.RR("1.example.com. IN A 127.0.0.1")
547 s := test.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
548 ret := new(dns.Msg)
549 ret.SetReply(r)
550 ret.Question[0].Name = "1.example.com."
551 ret.Answer = append(ret.Answer, answerRecord)
552 err := w.WriteMsg(ret)
553 if err != nil {
554 t.Error(err)
555 }
556 })
557 defer s.Close()
558
559 forward := New()
560 forward.DNSServers.Set([]string{s.Addr})
561 supervisor.TestHarness(t, forward.Run)
562 time.Sleep(10 * time.Millisecond)
563
564 req := netDNS.CreateTestRequest("example.com.", dns.TypeA, "udp")
565 forward.HandleDNS(req)
566 expectReply(t, req, replyProtocolError)
567}
568
569// TestLargeReply tests that large replies are not stored,
570// but still shared with concurrent queries.
571func TestLargeReply(t *testing.T) {
572 var queryCount atomic.Int64
573
574 var testAnswer []dns.RR
575 for i := range 100 {
576 testAnswer = append(testAnswer, test.RR(fmt.Sprintf("%v.example.com. IN A 127.0.0.1", i)))
577 }
578
579 s := test.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
580 queryCount.Add(1)
581 // Sleep a bit until all concurrent queries are blocked.
582 time.Sleep(10 * time.Millisecond)
583 ret := new(dns.Msg)
584 ret.SetReply(r)
585 ret.Answer = testAnswer
586 err := w.WriteMsg(ret)
587 if err != nil {
588 t.Error(err)
589 }
590 })
591 defer s.Close()
592
593 forward := New()
594 forward.DNSServers.Set([]string{s.Addr})
595 supervisor.TestHarness(t, forward.Run)
596 time.Sleep(10 * time.Millisecond)
597
598 wg := sync.WaitGroup{}
599 for range 2 {
600 wg.Add(1)
601 go func() {
602 req := netDNS.CreateTestRequest("example.com.", dns.TypeA, "tcp")
603 forward.HandleDNS(req)
604 expectReply(t, req, proxyReply{Answer: testAnswer})
605 wg.Done()
606 }()
607 }
608 wg.Wait()
609
610 if got, want := queryCount.Load(), int64(1); got != want {
611 t.Errorf("Want %v queries, got %v", want, got)
612 }
613
614 req := netDNS.CreateTestRequest("example.com.", dns.TypeA, "tcp")
615 forward.HandleDNS(req)
616 expectReply(t, req, proxyReply{Answer: testAnswer})
617
618 if got, want := queryCount.Load(), int64(2); got != want {
619 t.Errorf("Want %v queries, got %v", want, got)
620 }
621}