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