Source file
src/net/dnsclient_unix_test.go
1
2
3
4
5
6
7 package net
8
9 import (
10 "context"
11 "errors"
12 "fmt"
13 "maps"
14 "os"
15 "path"
16 "path/filepath"
17 "reflect"
18 "runtime"
19 "slices"
20 "strings"
21 "sync"
22 "sync/atomic"
23 "testing"
24 "time"
25
26 "golang.org/x/net/dns/dnsmessage"
27 )
28
29
30 var TestAddr = [4]byte{0xc0, 0x00, 0x02, 0x01}
31
32
33 var TestAddr6 = [16]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
34
35 func mustNewName(name string) dnsmessage.Name {
36 nn, err := dnsmessage.NewName(name)
37 if err != nil {
38 panic(fmt.Sprint("creating name: ", err))
39 }
40 return nn
41 }
42
43 func mustQuestion(name string, qtype dnsmessage.Type, class dnsmessage.Class) dnsmessage.Question {
44 return dnsmessage.Question{
45 Name: mustNewName(name),
46 Type: qtype,
47 Class: class,
48 }
49 }
50
51 var dnsTransportFallbackTests = []struct {
52 server string
53 question dnsmessage.Question
54 timeout int
55 rcode dnsmessage.RCode
56 }{
57
58
59 {"8.8.8.8:53", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), 2, dnsmessage.RCodeSuccess},
60 {"8.8.4.4:53", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), 4, dnsmessage.RCodeSuccess},
61 }
62
63 func TestDNSTransportFallback(t *testing.T) {
64 fake := fakeDNSServer{
65 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
66 r := dnsmessage.Message{
67 Header: dnsmessage.Header{
68 ID: q.Header.ID,
69 Response: true,
70 RCode: dnsmessage.RCodeSuccess,
71 },
72 Questions: q.Questions,
73 }
74 if n == "udp" {
75 r.Header.Truncated = true
76 }
77 return r, nil
78 },
79 }
80 r := Resolver{PreferGo: true, Dial: fake.DialContext}
81 for _, tt := range dnsTransportFallbackTests {
82 ctx, cancel := context.WithCancel(context.Background())
83 defer cancel()
84 _, h, err := r.exchange(ctx, tt.server, tt.question, time.Second, useUDPOrTCP, false)
85 if err != nil {
86 t.Error(err)
87 continue
88 }
89 if h.RCode != tt.rcode {
90 t.Errorf("got %v from %v; want %v", h.RCode, tt.server, tt.rcode)
91 continue
92 }
93 }
94 }
95
96 func TestDNSTransportNoFallbackOnTCP(t *testing.T) {
97 fake := fakeDNSServer{
98 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
99 r := dnsmessage.Message{
100 Header: dnsmessage.Header{
101 ID: q.Header.ID,
102 Response: true,
103 RCode: dnsmessage.RCodeSuccess,
104 Truncated: true,
105 },
106 Questions: q.Questions,
107 }
108 if n == "tcp" {
109 r.Answers = []dnsmessage.Resource{
110 {
111 Header: dnsmessage.ResourceHeader{
112 Name: q.Questions[0].Name,
113 Type: dnsmessage.TypeA,
114 Class: dnsmessage.ClassINET,
115 Length: 4,
116 },
117 Body: &dnsmessage.AResource{
118 A: TestAddr,
119 },
120 },
121 }
122 }
123 return r, nil
124 },
125 }
126 r := Resolver{PreferGo: true, Dial: fake.DialContext}
127 for _, tt := range dnsTransportFallbackTests {
128 ctx, cancel := context.WithCancel(context.Background())
129 defer cancel()
130 p, h, err := r.exchange(ctx, tt.server, tt.question, time.Second, useUDPOrTCP, false)
131 if err != nil {
132 t.Error(err)
133 continue
134 }
135 if h.RCode != tt.rcode {
136 t.Errorf("got %v from %v; want %v", h.RCode, tt.server, tt.rcode)
137 continue
138 }
139 a, err := p.AllAnswers()
140 if err != nil {
141 t.Errorf("unexpected error %v getting all answers from %v", err, tt.server)
142 continue
143 }
144 if len(a) != 1 {
145 t.Errorf("got %d answers from %v; want 1", len(a), tt.server)
146 continue
147 }
148 }
149 }
150
151
152
153 var specialDomainNameTests = []struct {
154 question dnsmessage.Question
155 rcode dnsmessage.RCode
156 }{
157
158
159 {mustQuestion("1.0.168.192.in-addr.arpa.", dnsmessage.TypePTR, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
160 {mustQuestion("test.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
161 {mustQuestion("example.com.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeSuccess},
162
163
164
165
166
167 {mustQuestion("localhost.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
168 {mustQuestion("invalid.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
169 }
170
171 func TestSpecialDomainName(t *testing.T) {
172 fake := fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
173 r := dnsmessage.Message{
174 Header: dnsmessage.Header{
175 ID: q.ID,
176 Response: true,
177 },
178 Questions: q.Questions,
179 }
180
181 switch q.Questions[0].Name.String() {
182 case "example.com.":
183 r.Header.RCode = dnsmessage.RCodeSuccess
184 default:
185 r.Header.RCode = dnsmessage.RCodeNameError
186 }
187
188 return r, nil
189 }}
190 r := Resolver{PreferGo: true, Dial: fake.DialContext}
191 server := "8.8.8.8:53"
192 for _, tt := range specialDomainNameTests {
193 ctx, cancel := context.WithCancel(context.Background())
194 defer cancel()
195 _, h, err := r.exchange(ctx, server, tt.question, 3*time.Second, useUDPOrTCP, false)
196 if err != nil {
197 t.Error(err)
198 continue
199 }
200 if h.RCode != tt.rcode {
201 t.Errorf("got %v from %v; want %v", h.RCode, server, tt.rcode)
202 continue
203 }
204 }
205 }
206
207
208 func TestAvoidDNSName(t *testing.T) {
209 tests := []struct {
210 name string
211 avoid bool
212 }{
213 {"foo.com", false},
214 {"foo.com.", false},
215
216 {"foo.onion.", true},
217 {"foo.onion", true},
218 {"foo.ONION", true},
219 {"foo.ONION.", true},
220
221
222 {"foo.local.", false},
223 {"foo.local", false},
224 {"foo.LOCAL", false},
225 {"foo.LOCAL.", false},
226
227 {"", true},
228
229
230
231
232
233
234
235 {"local", false},
236 {"onion", false},
237 {"local.", false},
238 {"onion.", false},
239 }
240 for _, tt := range tests {
241 got := avoidDNS(tt.name)
242 if got != tt.avoid {
243 t.Errorf("avoidDNS(%q) = %v; want %v", tt.name, got, tt.avoid)
244 }
245 }
246 }
247
248 func TestNameListAvoidDNS(t *testing.T) {
249 c := &dnsConfig{search: []string{"go.dev.", "onion."}}
250 got := c.nameList("www")
251 if !slices.Equal(got, []string{"www.", "www.go.dev."}) {
252 t.Fatalf(`nameList("www") = %v, want "www.", "www.go.dev."`, got)
253 }
254
255 got = c.nameList("www.onion")
256 if !slices.Equal(got, []string{"www.onion.go.dev."}) {
257 t.Fatalf(`nameList("www.onion") = %v, want "www.onion.go.dev."`, got)
258 }
259 }
260
261 var fakeDNSServerSuccessful = fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
262 r := dnsmessage.Message{
263 Header: dnsmessage.Header{
264 ID: q.ID,
265 Response: true,
266 },
267 Questions: q.Questions,
268 }
269 if len(q.Questions) == 1 && q.Questions[0].Type == dnsmessage.TypeA {
270 r.Answers = []dnsmessage.Resource{
271 {
272 Header: dnsmessage.ResourceHeader{
273 Name: q.Questions[0].Name,
274 Type: dnsmessage.TypeA,
275 Class: dnsmessage.ClassINET,
276 Length: 4,
277 },
278 Body: &dnsmessage.AResource{
279 A: TestAddr,
280 },
281 },
282 }
283 }
284 return r, nil
285 }}
286
287
288 func TestLookupTorOnion(t *testing.T) {
289 defer dnsWaitGroup.Wait()
290 r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext}
291 addrs, err := r.LookupIPAddr(context.Background(), "foo.onion.")
292 if err != nil {
293 t.Fatalf("lookup = %v; want nil", err)
294 }
295 if len(addrs) > 0 {
296 t.Errorf("unexpected addresses: %v", addrs)
297 }
298 }
299
300 type resolvConfTest struct {
301 dir string
302 path string
303 *resolverConfig
304 }
305
306 func newResolvConfTest() (*resolvConfTest, error) {
307 dir, err := os.MkdirTemp("", "go-resolvconftest")
308 if err != nil {
309 return nil, err
310 }
311 conf := &resolvConfTest{
312 dir: dir,
313 path: path.Join(dir, "resolv.conf"),
314 resolverConfig: &resolvConf,
315 }
316 conf.initOnce.Do(conf.init)
317 return conf, nil
318 }
319
320 func (conf *resolvConfTest) write(lines []string) error {
321 f, err := os.OpenFile(conf.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
322 if err != nil {
323 return err
324 }
325 if _, err := f.WriteString(strings.Join(lines, "\n")); err != nil {
326 f.Close()
327 return err
328 }
329 f.Close()
330 return nil
331 }
332
333 func (conf *resolvConfTest) writeAndUpdate(lines []string) error {
334 return conf.writeAndUpdateWithLastCheckedTime(lines, time.Now().Add(time.Hour))
335 }
336
337 func (conf *resolvConfTest) writeAndUpdateWithLastCheckedTime(lines []string, lastChecked time.Time) error {
338 if err := conf.write(lines); err != nil {
339 return err
340 }
341 return conf.forceUpdate(conf.path, lastChecked)
342 }
343
344 func (conf *resolvConfTest) forceUpdate(name string, lastChecked time.Time) error {
345 dnsConf := dnsReadConfig(name)
346 if !conf.forceUpdateConf(dnsConf, lastChecked) {
347 return fmt.Errorf("tryAcquireSema for %s failed", name)
348 }
349 return nil
350 }
351
352 func (conf *resolvConfTest) forceUpdateConf(c *dnsConfig, lastChecked time.Time) bool {
353 conf.dnsConfig.Store(c)
354 for i := 0; i < 5; i++ {
355 if conf.tryAcquireSema() {
356 conf.lastChecked = lastChecked
357 conf.releaseSema()
358 return true
359 }
360 }
361 return false
362 }
363
364 func (conf *resolvConfTest) servers() []string {
365 return conf.dnsConfig.Load().servers
366 }
367
368 func (conf *resolvConfTest) teardown() error {
369 err := conf.forceUpdate("/etc/resolv.conf", time.Time{})
370 os.RemoveAll(conf.dir)
371 return err
372 }
373
374 var updateResolvConfTests = []struct {
375 name string
376 lines []string
377 servers []string
378 }{
379 {
380 name: "golang.org",
381 lines: []string{"nameserver 8.8.8.8"},
382 servers: []string{"8.8.8.8:53"},
383 },
384 {
385 name: "",
386 lines: nil,
387 servers: defaultNS,
388 },
389 {
390 name: "www.example.com",
391 lines: []string{"nameserver 8.8.4.4"},
392 servers: []string{"8.8.4.4:53"},
393 },
394 }
395
396 func TestUpdateResolvConf(t *testing.T) {
397 defer dnsWaitGroup.Wait()
398
399 r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext}
400
401 conf, err := newResolvConfTest()
402 if err != nil {
403 t.Fatal(err)
404 }
405 defer conf.teardown()
406
407 for i, tt := range updateResolvConfTests {
408 if err := conf.writeAndUpdate(tt.lines); err != nil {
409 t.Error(err)
410 continue
411 }
412 if tt.name != "" {
413 var wg sync.WaitGroup
414 const N = 10
415 wg.Add(N)
416 for j := 0; j < N; j++ {
417 go func(name string) {
418 defer wg.Done()
419 ips, err := r.LookupIPAddr(context.Background(), name)
420 if err != nil {
421 t.Error(err)
422 return
423 }
424 if len(ips) == 0 {
425 t.Errorf("no records for %s", name)
426 return
427 }
428 }(tt.name)
429 }
430 wg.Wait()
431 }
432 servers := conf.servers()
433 if !slices.Equal(servers, tt.servers) {
434 t.Errorf("#%d: got %v; want %v", i, servers, tt.servers)
435 continue
436 }
437 }
438 }
439
440 var goLookupIPWithResolverConfigTests = []struct {
441 name string
442 lines []string
443 error
444 a, aaaa bool
445 }{
446
447 {
448 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j",
449 []string{
450 "options timeout:1 attempts:1",
451 "nameserver 255.255.255.255",
452 },
453 &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "255.255.255.255:53", IsTimeout: true},
454 false, false,
455 },
456
457
458 {
459 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j",
460 []string{
461 "options timeout:3 attempts:1",
462 "nameserver 8.8.8.8",
463 },
464 &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "8.8.8.8:53", IsTimeout: false},
465 false, false,
466 },
467
468
469 {
470 "ipv4.google.com.",
471 []string{
472 "nameserver 8.8.8.8",
473 "nameserver 2001:4860:4860::8888",
474 },
475 nil,
476 true, false,
477 },
478 {
479 "ipv4.google.com",
480 []string{
481 "domain golang.org",
482 "nameserver 2001:4860:4860::8888",
483 "nameserver 8.8.8.8",
484 },
485 nil,
486 true, false,
487 },
488 {
489 "ipv4.google.com",
490 []string{
491 "search x.golang.org y.golang.org",
492 "nameserver 2001:4860:4860::8888",
493 "nameserver 8.8.8.8",
494 },
495 nil,
496 true, false,
497 },
498
499
500 {
501 "ipv6.google.com.",
502 []string{
503 "nameserver 2001:4860:4860::8888",
504 "nameserver 8.8.8.8",
505 },
506 nil,
507 false, true,
508 },
509 {
510 "ipv6.google.com",
511 []string{
512 "domain golang.org",
513 "nameserver 8.8.8.8",
514 "nameserver 2001:4860:4860::8888",
515 },
516 nil,
517 false, true,
518 },
519 {
520 "ipv6.google.com",
521 []string{
522 "search x.golang.org y.golang.org",
523 "nameserver 8.8.8.8",
524 "nameserver 2001:4860:4860::8888",
525 },
526 nil,
527 false, true,
528 },
529
530
531 {
532 "hostname.as112.net",
533 []string{
534 "domain golang.org",
535 "nameserver 2001:4860:4860::8888",
536 "nameserver 8.8.8.8",
537 },
538 nil,
539 true, true,
540 },
541 {
542 "hostname.as112.net",
543 []string{
544 "search x.golang.org y.golang.org",
545 "nameserver 2001:4860:4860::8888",
546 "nameserver 8.8.8.8",
547 },
548 nil,
549 true, true,
550 },
551 }
552
553 func TestGoLookupIPWithResolverConfig(t *testing.T) {
554 defer dnsWaitGroup.Wait()
555 fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
556 switch s {
557 case "[2001:4860:4860::8888]:53", "8.8.8.8:53":
558 break
559 default:
560 time.Sleep(10 * time.Millisecond)
561 return dnsmessage.Message{}, os.ErrDeadlineExceeded
562 }
563 r := dnsmessage.Message{
564 Header: dnsmessage.Header{
565 ID: q.ID,
566 Response: true,
567 },
568 Questions: q.Questions,
569 }
570 for _, question := range q.Questions {
571 switch question.Type {
572 case dnsmessage.TypeA:
573 switch question.Name.String() {
574 case "hostname.as112.net.":
575 break
576 case "ipv4.google.com.":
577 r.Answers = append(r.Answers, dnsmessage.Resource{
578 Header: dnsmessage.ResourceHeader{
579 Name: q.Questions[0].Name,
580 Type: dnsmessage.TypeA,
581 Class: dnsmessage.ClassINET,
582 Length: 4,
583 },
584 Body: &dnsmessage.AResource{
585 A: TestAddr,
586 },
587 })
588 default:
589
590 }
591 case dnsmessage.TypeAAAA:
592 switch question.Name.String() {
593 case "hostname.as112.net.":
594 break
595 case "ipv6.google.com.":
596 r.Answers = append(r.Answers, dnsmessage.Resource{
597 Header: dnsmessage.ResourceHeader{
598 Name: q.Questions[0].Name,
599 Type: dnsmessage.TypeAAAA,
600 Class: dnsmessage.ClassINET,
601 Length: 16,
602 },
603 Body: &dnsmessage.AAAAResource{
604 AAAA: TestAddr6,
605 },
606 })
607 }
608 }
609 }
610 return r, nil
611 }}
612 r := Resolver{PreferGo: true, Dial: fake.DialContext}
613
614 conf, err := newResolvConfTest()
615 if err != nil {
616 t.Fatal(err)
617 }
618 defer conf.teardown()
619
620 for _, tt := range goLookupIPWithResolverConfigTests {
621 if err := conf.writeAndUpdate(tt.lines); err != nil {
622 t.Error(err)
623 continue
624 }
625 addrs, err := r.LookupIPAddr(context.Background(), tt.name)
626 if err != nil {
627 if err, ok := err.(*DNSError); !ok || tt.error != nil && (err.Name != tt.error.(*DNSError).Name || err.Server != tt.error.(*DNSError).Server || err.IsTimeout != tt.error.(*DNSError).IsTimeout) {
628 t.Errorf("got %v; want %v", err, tt.error)
629 }
630 continue
631 }
632 if len(addrs) == 0 {
633 t.Errorf("no records for %s", tt.name)
634 }
635 if !tt.a && !tt.aaaa && len(addrs) > 0 {
636 t.Errorf("unexpected %v for %s", addrs, tt.name)
637 }
638 for _, addr := range addrs {
639 if !tt.a && addr.IP.To4() != nil {
640 t.Errorf("got %v; must not be IPv4 address", addr)
641 }
642 if !tt.aaaa && addr.IP.To16() != nil && addr.IP.To4() == nil {
643 t.Errorf("got %v; must not be IPv6 address", addr)
644 }
645 }
646 }
647 }
648
649
650 func TestGoLookupIPOrderFallbackToFile(t *testing.T) {
651 defer dnsWaitGroup.Wait()
652
653 fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, tm time.Time) (dnsmessage.Message, error) {
654 r := dnsmessage.Message{
655 Header: dnsmessage.Header{
656 ID: q.ID,
657 Response: true,
658 },
659 Questions: q.Questions,
660 }
661 return r, nil
662 }}
663 r := Resolver{PreferGo: true, Dial: fake.DialContext}
664
665
666 conf, err := newResolvConfTest()
667 if err != nil {
668 t.Fatal(err)
669 }
670 defer conf.teardown()
671
672 if err := conf.writeAndUpdate([]string{}); err != nil {
673 t.Fatal(err)
674 }
675
676 defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
677 hostsFilePath = "testdata/hosts"
678
679 for _, order := range []hostLookupOrder{hostLookupFilesDNS, hostLookupDNSFiles} {
680 name := fmt.Sprintf("order %v", order)
681
682 _, _, err := r.goLookupIPCNAMEOrder(context.Background(), "ip", "notarealhost", order, nil)
683 if err == nil {
684 t.Errorf("%s: expected error while looking up name not in hosts file", name)
685 continue
686 }
687
688
689 addrs, _, err := r.goLookupIPCNAMEOrder(context.Background(), "ip", "thor", order, nil)
690 if err != nil {
691 t.Errorf("%s: expected to successfully lookup host entry", name)
692 continue
693 }
694 if len(addrs) != 1 {
695 t.Errorf("%s: expected exactly one result, but got %v", name, addrs)
696 continue
697 }
698 if got, want := addrs[0].String(), "127.1.1.1"; got != want {
699 t.Errorf("%s: address doesn't match expectation. got %v, want %v", name, got, want)
700 }
701 }
702 }
703
704
705
706
707
708 func TestErrorForOriginalNameWhenSearching(t *testing.T) {
709 defer dnsWaitGroup.Wait()
710
711 const fqdn = "doesnotexist.domain"
712
713 conf, err := newResolvConfTest()
714 if err != nil {
715 t.Fatal(err)
716 }
717 defer conf.teardown()
718
719 if err := conf.writeAndUpdate([]string{"search servfail"}); err != nil {
720 t.Fatal(err)
721 }
722
723 fake := fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
724 r := dnsmessage.Message{
725 Header: dnsmessage.Header{
726 ID: q.ID,
727 Response: true,
728 },
729 Questions: q.Questions,
730 }
731
732 switch q.Questions[0].Name.String() {
733 case fqdn + ".servfail.":
734 r.Header.RCode = dnsmessage.RCodeServerFailure
735 default:
736 r.Header.RCode = dnsmessage.RCodeNameError
737 }
738
739 return r, nil
740 }}
741
742 cases := []struct {
743 strictErrors bool
744 wantErr *DNSError
745 }{
746 {true, &DNSError{Name: fqdn, Err: "server misbehaving", IsTemporary: true}},
747 {false, &DNSError{Name: fqdn, Err: errNoSuchHost.Error(), IsNotFound: true}},
748 }
749 for _, tt := range cases {
750 r := Resolver{PreferGo: true, StrictErrors: tt.strictErrors, Dial: fake.DialContext}
751 _, err = r.LookupIPAddr(context.Background(), fqdn)
752 if err == nil {
753 t.Fatal("expected an error")
754 }
755
756 want := tt.wantErr
757 if err, ok := err.(*DNSError); !ok || err.Name != want.Name || err.Err != want.Err || err.IsTemporary != want.IsTemporary {
758 t.Errorf("got %v; want %v", err, want)
759 }
760 }
761 }
762
763
764 func TestIgnoreLameReferrals(t *testing.T) {
765 defer dnsWaitGroup.Wait()
766
767 conf, err := newResolvConfTest()
768 if err != nil {
769 t.Fatal(err)
770 }
771 defer conf.teardown()
772
773 if err := conf.writeAndUpdate([]string{"nameserver 192.0.2.1",
774 "nameserver 192.0.2.2"}); err != nil {
775 t.Fatal(err)
776 }
777
778 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
779 t.Log(s, q)
780 r := dnsmessage.Message{
781 Header: dnsmessage.Header{
782 ID: q.ID,
783 Response: true,
784 },
785 Questions: q.Questions,
786 }
787
788 if s == "192.0.2.2:53" {
789 r.Header.RecursionAvailable = true
790 if q.Questions[0].Type == dnsmessage.TypeA {
791 r.Answers = []dnsmessage.Resource{
792 {
793 Header: dnsmessage.ResourceHeader{
794 Name: q.Questions[0].Name,
795 Type: dnsmessage.TypeA,
796 Class: dnsmessage.ClassINET,
797 Length: 4,
798 },
799 Body: &dnsmessage.AResource{
800 A: TestAddr,
801 },
802 },
803 }
804 }
805 } else if s == "192.0.2.1:53" {
806 if q.Questions[0].Type == dnsmessage.TypeA && strings.HasPrefix(q.Questions[0].Name.String(), "empty.com.") {
807 var edns0Hdr dnsmessage.ResourceHeader
808 edns0Hdr.SetEDNS0(maxDNSPacketSize, dnsmessage.RCodeSuccess, false)
809
810 r.Additionals = []dnsmessage.Resource{
811 {
812 Header: edns0Hdr,
813 Body: &dnsmessage.OPTResource{},
814 },
815 }
816 }
817 }
818
819 return r, nil
820 }}
821 r := Resolver{PreferGo: true, Dial: fake.DialContext}
822
823 addrs, err := r.LookupIP(context.Background(), "ip4", "www.golang.org")
824 if err != nil {
825 t.Fatal(err)
826 }
827
828 if got := len(addrs); got != 1 {
829 t.Fatalf("got %d addresses, want 1", got)
830 }
831
832 if got, want := addrs[0].String(), "192.0.2.1"; got != want {
833 t.Fatalf("got address %v, want %v", got, want)
834 }
835
836 _, err = r.LookupIP(context.Background(), "ip4", "empty.com")
837 de, ok := err.(*DNSError)
838 if !ok {
839 t.Fatalf("err = %#v; wanted a *net.DNSError", err)
840 }
841 if de.Err != errNoSuchHost.Error() {
842 t.Fatalf("Err = %#v; wanted %q", de.Err, errNoSuchHost.Error())
843 }
844 }
845
846 func BenchmarkGoLookupIP(b *testing.B) {
847 testHookUninstaller.Do(uninstallTestHooks)
848 ctx := context.Background()
849 b.ReportAllocs()
850
851 for i := 0; i < b.N; i++ {
852 goResolver.LookupIPAddr(ctx, "www.example.com")
853 }
854 }
855
856 func BenchmarkGoLookupIPNoSuchHost(b *testing.B) {
857 testHookUninstaller.Do(uninstallTestHooks)
858 ctx := context.Background()
859 b.ReportAllocs()
860
861 for i := 0; i < b.N; i++ {
862 goResolver.LookupIPAddr(ctx, "some.nonexistent")
863 }
864 }
865
866 func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) {
867 testHookUninstaller.Do(uninstallTestHooks)
868
869 conf, err := newResolvConfTest()
870 if err != nil {
871 b.Fatal(err)
872 }
873 defer conf.teardown()
874
875 lines := []string{
876 "nameserver 203.0.113.254",
877 "nameserver 8.8.8.8",
878 }
879 if err := conf.writeAndUpdate(lines); err != nil {
880 b.Fatal(err)
881 }
882 ctx := context.Background()
883 b.ReportAllocs()
884
885 for i := 0; i < b.N; i++ {
886 goResolver.LookupIPAddr(ctx, "www.example.com")
887 }
888 }
889
890 type fakeDNSServer struct {
891 rh func(n, s string, q dnsmessage.Message, t time.Time) (dnsmessage.Message, error)
892 alwaysTCP bool
893 }
894
895 func (server *fakeDNSServer) DialContext(_ context.Context, n, s string) (Conn, error) {
896 if server.alwaysTCP || n == "tcp" || n == "tcp4" || n == "tcp6" {
897 return &fakeDNSConn{tcp: true, server: server, n: n, s: s}, nil
898 }
899 return &fakeDNSPacketConn{fakeDNSConn: fakeDNSConn{tcp: false, server: server, n: n, s: s}}, nil
900 }
901
902 type fakeDNSConn struct {
903 Conn
904 tcp bool
905 server *fakeDNSServer
906 n string
907 s string
908 q dnsmessage.Message
909 t time.Time
910 buf []byte
911 }
912
913 func (f *fakeDNSConn) Close() error {
914 return nil
915 }
916
917 func (f *fakeDNSConn) Read(b []byte) (int, error) {
918 if len(f.buf) > 0 {
919 n := copy(b, f.buf)
920 f.buf = f.buf[n:]
921 return n, nil
922 }
923
924 resp, err := f.server.rh(f.n, f.s, f.q, f.t)
925 if err != nil {
926 return 0, err
927 }
928
929 bb := make([]byte, 2, 514)
930 bb, err = resp.AppendPack(bb)
931 if err != nil {
932 return 0, fmt.Errorf("cannot marshal DNS message: %v", err)
933 }
934
935 if f.tcp {
936 l := len(bb) - 2
937 bb[0] = byte(l >> 8)
938 bb[1] = byte(l)
939 f.buf = bb
940 return f.Read(b)
941 }
942
943 bb = bb[2:]
944 if len(b) < len(bb) {
945 return 0, errors.New("read would fragment DNS message")
946 }
947
948 copy(b, bb)
949 return len(bb), nil
950 }
951
952 func (f *fakeDNSConn) Write(b []byte) (int, error) {
953 if f.tcp && len(b) >= 2 {
954 b = b[2:]
955 }
956 if f.q.Unpack(b) != nil {
957 return 0, fmt.Errorf("cannot unmarshal DNS message fake %s (%d)", f.n, len(b))
958 }
959 return len(b), nil
960 }
961
962 func (f *fakeDNSConn) SetDeadline(t time.Time) error {
963 f.t = t
964 return nil
965 }
966
967 type fakeDNSPacketConn struct {
968 PacketConn
969 fakeDNSConn
970 }
971
972 func (f *fakeDNSPacketConn) SetDeadline(t time.Time) error {
973 return f.fakeDNSConn.SetDeadline(t)
974 }
975
976 func (f *fakeDNSPacketConn) Close() error {
977 return f.fakeDNSConn.Close()
978 }
979
980
981 func TestIgnoreDNSForgeries(t *testing.T) {
982 c, s := Pipe()
983 go func() {
984 b := make([]byte, maxDNSPacketSize)
985 n, err := s.Read(b)
986 if err != nil {
987 t.Error(err)
988 return
989 }
990
991 var msg dnsmessage.Message
992 if msg.Unpack(b[:n]) != nil {
993 t.Error("invalid DNS query:", err)
994 return
995 }
996
997 s.Write([]byte("garbage DNS response packet"))
998
999 msg.Header.Response = true
1000 msg.Header.ID++
1001
1002 if b, err = msg.Pack(); err != nil {
1003 t.Error("failed to pack DNS response:", err)
1004 return
1005 }
1006 s.Write(b)
1007
1008 msg.Header.ID--
1009 msg.Answers = []dnsmessage.Resource{
1010 {
1011 Header: dnsmessage.ResourceHeader{
1012 Name: mustNewName("www.example.com."),
1013 Type: dnsmessage.TypeA,
1014 Class: dnsmessage.ClassINET,
1015 Length: 4,
1016 },
1017 Body: &dnsmessage.AResource{
1018 A: TestAddr,
1019 },
1020 },
1021 }
1022
1023 b, err = msg.Pack()
1024 if err != nil {
1025 t.Error("failed to pack DNS response:", err)
1026 return
1027 }
1028 s.Write(b)
1029 }()
1030
1031 msg := dnsmessage.Message{
1032 Header: dnsmessage.Header{
1033 ID: 42,
1034 },
1035 Questions: []dnsmessage.Question{
1036 {
1037 Name: mustNewName("www.example.com."),
1038 Type: dnsmessage.TypeA,
1039 Class: dnsmessage.ClassINET,
1040 },
1041 },
1042 }
1043
1044 b, err := msg.Pack()
1045 if err != nil {
1046 t.Fatal("Pack failed:", err)
1047 }
1048
1049 p, _, err := dnsPacketRoundTrip(c, 42, msg.Questions[0], b)
1050 if err != nil {
1051 t.Fatalf("dnsPacketRoundTrip failed: %v", err)
1052 }
1053
1054 p.SkipAllQuestions()
1055 as, err := p.AllAnswers()
1056 if err != nil {
1057 t.Fatal("AllAnswers failed:", err)
1058 }
1059 if got := as[0].Body.(*dnsmessage.AResource).A; got != TestAddr {
1060 t.Errorf("got address %v, want %v", got, TestAddr)
1061 }
1062 }
1063
1064
1065 func TestRetryTimeout(t *testing.T) {
1066 defer dnsWaitGroup.Wait()
1067
1068 conf, err := newResolvConfTest()
1069 if err != nil {
1070 t.Fatal(err)
1071 }
1072 defer conf.teardown()
1073
1074 testConf := []string{
1075 "nameserver 192.0.2.1",
1076 "nameserver 192.0.2.2",
1077 }
1078 if err := conf.writeAndUpdate(testConf); err != nil {
1079 t.Fatal(err)
1080 }
1081
1082 var deadline0 time.Time
1083
1084 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1085 t.Log(s, q, deadline)
1086
1087 if deadline.IsZero() {
1088 t.Error("zero deadline")
1089 }
1090
1091 if s == "192.0.2.1:53" {
1092 deadline0 = deadline
1093 time.Sleep(10 * time.Millisecond)
1094 return dnsmessage.Message{}, os.ErrDeadlineExceeded
1095 }
1096
1097 if deadline.Equal(deadline0) {
1098 t.Error("deadline didn't change")
1099 }
1100
1101 return mockTXTResponse(q), nil
1102 }}
1103 r := &Resolver{PreferGo: true, Dial: fake.DialContext}
1104
1105 _, err = r.LookupTXT(context.Background(), "www.golang.org")
1106 if err != nil {
1107 t.Fatal(err)
1108 }
1109
1110 if deadline0.IsZero() {
1111 t.Error("deadline0 still zero", deadline0)
1112 }
1113 }
1114
1115 func TestRotate(t *testing.T) {
1116
1117 testRotate(t, false, []string{"192.0.2.1", "192.0.2.2"}, []string{"192.0.2.1:53", "192.0.2.1:53", "192.0.2.1:53"})
1118
1119
1120 testRotate(t, true, []string{"192.0.2.1", "192.0.2.2"}, []string{"192.0.2.1:53", "192.0.2.2:53", "192.0.2.1:53"})
1121 }
1122
1123 func testRotate(t *testing.T, rotate bool, nameservers, wantServers []string) {
1124 defer dnsWaitGroup.Wait()
1125
1126 conf, err := newResolvConfTest()
1127 if err != nil {
1128 t.Fatal(err)
1129 }
1130 defer conf.teardown()
1131
1132 var confLines []string
1133 for _, ns := range nameservers {
1134 confLines = append(confLines, "nameserver "+ns)
1135 }
1136 if rotate {
1137 confLines = append(confLines, "options rotate")
1138 }
1139
1140 if err := conf.writeAndUpdate(confLines); err != nil {
1141 t.Fatal(err)
1142 }
1143
1144 var usedServers []string
1145 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1146 usedServers = append(usedServers, s)
1147 return mockTXTResponse(q), nil
1148 }}
1149 r := Resolver{PreferGo: true, Dial: fake.DialContext}
1150
1151
1152 for i := 0; i < len(nameservers)+1; i++ {
1153 if _, err := r.LookupTXT(context.Background(), "www.golang.org"); err != nil {
1154 t.Fatal(err)
1155 }
1156 }
1157
1158 if !slices.Equal(usedServers, wantServers) {
1159 t.Errorf("rotate=%t got used servers:\n%v\nwant:\n%v", rotate, usedServers, wantServers)
1160 }
1161 }
1162
1163 func mockTXTResponse(q dnsmessage.Message) dnsmessage.Message {
1164 r := dnsmessage.Message{
1165 Header: dnsmessage.Header{
1166 ID: q.ID,
1167 Response: true,
1168 RecursionAvailable: true,
1169 },
1170 Questions: q.Questions,
1171 Answers: []dnsmessage.Resource{
1172 {
1173 Header: dnsmessage.ResourceHeader{
1174 Name: q.Questions[0].Name,
1175 Type: dnsmessage.TypeTXT,
1176 Class: dnsmessage.ClassINET,
1177 },
1178 Body: &dnsmessage.TXTResource{
1179 TXT: []string{"ok"},
1180 },
1181 },
1182 },
1183 }
1184
1185 return r
1186 }
1187
1188
1189
1190 func TestStrictErrorsLookupIP(t *testing.T) {
1191 defer dnsWaitGroup.Wait()
1192
1193 conf, err := newResolvConfTest()
1194 if err != nil {
1195 t.Fatal(err)
1196 }
1197 defer conf.teardown()
1198
1199 confData := []string{
1200 "nameserver 192.0.2.53",
1201 "search x.golang.org y.golang.org",
1202 }
1203 if err := conf.writeAndUpdate(confData); err != nil {
1204 t.Fatal(err)
1205 }
1206
1207 const name = "test-issue19592"
1208 const server = "192.0.2.53:53"
1209 const searchX = "test-issue19592.x.golang.org."
1210 const searchY = "test-issue19592.y.golang.org."
1211 const ip4 = "192.0.2.1"
1212 const ip6 = "2001:db8::1"
1213
1214 type resolveWhichEnum int
1215 const (
1216 resolveOK resolveWhichEnum = iota
1217 resolveOpError
1218 resolveServfail
1219 resolveTimeout
1220 )
1221
1222 makeTempError := func(err string) error {
1223 return &DNSError{
1224 Err: err,
1225 Name: name,
1226 Server: server,
1227 IsTemporary: true,
1228 }
1229 }
1230 makeTimeout := func() error {
1231 return &DNSError{
1232 Err: os.ErrDeadlineExceeded.Error(),
1233 Name: name,
1234 Server: server,
1235 IsTimeout: true,
1236 IsTemporary: true,
1237 }
1238 }
1239 makeNxDomain := func() error {
1240 return &DNSError{
1241 Err: errNoSuchHost.Error(),
1242 Name: name,
1243 Server: server,
1244 IsNotFound: true,
1245 }
1246 }
1247
1248 cases := []struct {
1249 desc string
1250 resolveWhich func(quest dnsmessage.Question) resolveWhichEnum
1251 wantStrictErr error
1252 wantLaxErr error
1253 wantIPs []string
1254 }{
1255 {
1256 desc: "No errors",
1257 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1258 return resolveOK
1259 },
1260 wantIPs: []string{ip4, ip6},
1261 },
1262 {
1263 desc: "searchX error fails in strict mode",
1264 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1265 if quest.Name.String() == searchX {
1266 return resolveTimeout
1267 }
1268 return resolveOK
1269 },
1270 wantStrictErr: makeTimeout(),
1271 wantIPs: []string{ip4, ip6},
1272 },
1273 {
1274 desc: "searchX IPv4-only timeout fails in strict mode",
1275 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1276 if quest.Name.String() == searchX && quest.Type == dnsmessage.TypeA {
1277 return resolveTimeout
1278 }
1279 return resolveOK
1280 },
1281 wantStrictErr: makeTimeout(),
1282 wantIPs: []string{ip4, ip6},
1283 },
1284 {
1285 desc: "searchX IPv6-only servfail fails in strict mode",
1286 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1287 if quest.Name.String() == searchX && quest.Type == dnsmessage.TypeAAAA {
1288 return resolveServfail
1289 }
1290 return resolveOK
1291 },
1292 wantStrictErr: makeTempError("server misbehaving"),
1293 wantIPs: []string{ip4, ip6},
1294 },
1295 {
1296 desc: "searchY error always fails",
1297 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1298 if quest.Name.String() == searchY {
1299 return resolveTimeout
1300 }
1301 return resolveOK
1302 },
1303 wantStrictErr: makeTimeout(),
1304 wantLaxErr: makeNxDomain(),
1305 },
1306 {
1307 desc: "searchY IPv4-only socket error fails in strict mode",
1308 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1309 if quest.Name.String() == searchY && quest.Type == dnsmessage.TypeA {
1310 return resolveOpError
1311 }
1312 return resolveOK
1313 },
1314 wantStrictErr: makeTempError("write: socket on fire"),
1315 wantIPs: []string{ip6},
1316 },
1317 {
1318 desc: "searchY IPv6-only timeout fails in strict mode",
1319 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1320 if quest.Name.String() == searchY && quest.Type == dnsmessage.TypeAAAA {
1321 return resolveTimeout
1322 }
1323 return resolveOK
1324 },
1325 wantStrictErr: makeTimeout(),
1326 wantIPs: []string{ip4},
1327 },
1328 }
1329
1330 for i, tt := range cases {
1331 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1332 t.Log(s, q)
1333
1334 switch tt.resolveWhich(q.Questions[0]) {
1335 case resolveOK:
1336
1337 case resolveOpError:
1338 return dnsmessage.Message{}, &OpError{Op: "write", Err: fmt.Errorf("socket on fire")}
1339 case resolveServfail:
1340 return dnsmessage.Message{
1341 Header: dnsmessage.Header{
1342 ID: q.ID,
1343 Response: true,
1344 RCode: dnsmessage.RCodeServerFailure,
1345 },
1346 Questions: q.Questions,
1347 }, nil
1348 case resolveTimeout:
1349 return dnsmessage.Message{}, os.ErrDeadlineExceeded
1350 default:
1351 t.Fatal("Impossible resolveWhich")
1352 }
1353
1354 switch q.Questions[0].Name.String() {
1355 case searchX, name + ".":
1356
1357 return dnsmessage.Message{
1358 Header: dnsmessage.Header{
1359 ID: q.ID,
1360 Response: true,
1361 RCode: dnsmessage.RCodeNameError,
1362 },
1363 Questions: q.Questions,
1364 }, nil
1365 case searchY:
1366
1367 default:
1368 return dnsmessage.Message{}, fmt.Errorf("Unexpected Name: %v", q.Questions[0].Name)
1369 }
1370
1371 r := dnsmessage.Message{
1372 Header: dnsmessage.Header{
1373 ID: q.ID,
1374 Response: true,
1375 },
1376 Questions: q.Questions,
1377 }
1378 switch q.Questions[0].Type {
1379 case dnsmessage.TypeA:
1380 r.Answers = []dnsmessage.Resource{
1381 {
1382 Header: dnsmessage.ResourceHeader{
1383 Name: q.Questions[0].Name,
1384 Type: dnsmessage.TypeA,
1385 Class: dnsmessage.ClassINET,
1386 Length: 4,
1387 },
1388 Body: &dnsmessage.AResource{
1389 A: TestAddr,
1390 },
1391 },
1392 }
1393 case dnsmessage.TypeAAAA:
1394 r.Answers = []dnsmessage.Resource{
1395 {
1396 Header: dnsmessage.ResourceHeader{
1397 Name: q.Questions[0].Name,
1398 Type: dnsmessage.TypeAAAA,
1399 Class: dnsmessage.ClassINET,
1400 Length: 16,
1401 },
1402 Body: &dnsmessage.AAAAResource{
1403 AAAA: TestAddr6,
1404 },
1405 },
1406 }
1407 default:
1408 return dnsmessage.Message{}, fmt.Errorf("Unexpected Type: %v", q.Questions[0].Type)
1409 }
1410 return r, nil
1411 }}
1412
1413 for _, strict := range []bool{true, false} {
1414 r := Resolver{PreferGo: true, StrictErrors: strict, Dial: fake.DialContext}
1415 ips, err := r.LookupIPAddr(context.Background(), name)
1416
1417 var wantErr error
1418 if strict {
1419 wantErr = tt.wantStrictErr
1420 } else {
1421 wantErr = tt.wantLaxErr
1422 }
1423 if !reflect.DeepEqual(err, wantErr) {
1424 t.Errorf("#%d (%s) strict=%v: got err %#v; want %#v", i, tt.desc, strict, err, wantErr)
1425 }
1426
1427 gotIPs := map[string]struct{}{}
1428 for _, ip := range ips {
1429 gotIPs[ip.String()] = struct{}{}
1430 }
1431 wantIPs := map[string]struct{}{}
1432 if wantErr == nil {
1433 for _, ip := range tt.wantIPs {
1434 wantIPs[ip] = struct{}{}
1435 }
1436 }
1437 if !maps.Equal(gotIPs, wantIPs) {
1438 t.Errorf("#%d (%s) strict=%v: got ips %v; want %v", i, tt.desc, strict, gotIPs, wantIPs)
1439 }
1440 }
1441 }
1442 }
1443
1444
1445
1446 func TestStrictErrorsLookupTXT(t *testing.T) {
1447 defer dnsWaitGroup.Wait()
1448
1449 conf, err := newResolvConfTest()
1450 if err != nil {
1451 t.Fatal(err)
1452 }
1453 defer conf.teardown()
1454
1455 confData := []string{
1456 "nameserver 192.0.2.53",
1457 "search x.golang.org y.golang.org",
1458 }
1459 if err := conf.writeAndUpdate(confData); err != nil {
1460 t.Fatal(err)
1461 }
1462
1463 const name = "test"
1464 const server = "192.0.2.53:53"
1465 const searchX = "test.x.golang.org."
1466 const searchY = "test.y.golang.org."
1467 const txt = "Hello World"
1468
1469 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1470 t.Log(s, q)
1471
1472 switch q.Questions[0].Name.String() {
1473 case searchX:
1474 return dnsmessage.Message{}, os.ErrDeadlineExceeded
1475 case searchY:
1476 return mockTXTResponse(q), nil
1477 default:
1478 return dnsmessage.Message{}, fmt.Errorf("Unexpected Name: %v", q.Questions[0].Name)
1479 }
1480 }}
1481
1482 for _, strict := range []bool{true, false} {
1483 r := Resolver{StrictErrors: strict, Dial: fake.DialContext}
1484 p, _, err := r.lookup(context.Background(), name, dnsmessage.TypeTXT, nil)
1485 var wantErr error
1486 var wantRRs int
1487 if strict {
1488 wantErr = &DNSError{
1489 Err: os.ErrDeadlineExceeded.Error(),
1490 Name: name,
1491 Server: server,
1492 IsTimeout: true,
1493 IsTemporary: true,
1494 }
1495 } else {
1496 wantRRs = 1
1497 }
1498 if !reflect.DeepEqual(err, wantErr) {
1499 t.Errorf("strict=%v: got err %#v; want %#v", strict, err, wantErr)
1500 }
1501 a, err := p.AllAnswers()
1502 if err != nil {
1503 a = nil
1504 }
1505 if len(a) != wantRRs {
1506 t.Errorf("strict=%v: got %v; want %v", strict, len(a), wantRRs)
1507 }
1508 }
1509 }
1510
1511
1512
1513 func TestDNSGoroutineRace(t *testing.T) {
1514 defer dnsWaitGroup.Wait()
1515
1516 fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, t time.Time) (dnsmessage.Message, error) {
1517 time.Sleep(10 * time.Microsecond)
1518 return dnsmessage.Message{}, os.ErrDeadlineExceeded
1519 }}
1520 r := Resolver{PreferGo: true, Dial: fake.DialContext}
1521
1522
1523
1524
1525 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Microsecond)
1526 defer cancel()
1527 _, err := r.LookupIPAddr(ctx, "where.are.they.now")
1528 if err == nil {
1529 t.Fatal("fake DNS lookup unexpectedly succeeded")
1530 }
1531 }
1532
1533 func lookupWithFake(fake fakeDNSServer, name string, typ dnsmessage.Type) error {
1534 r := Resolver{PreferGo: true, Dial: fake.DialContext}
1535
1536 conf := getSystemDNSConfig()
1537
1538 ctx, cancel := context.WithCancel(context.Background())
1539 defer cancel()
1540
1541 _, _, err := r.tryOneName(ctx, conf, name, typ)
1542 return err
1543 }
1544
1545
1546
1547 func TestIssue8434(t *testing.T) {
1548 err := lookupWithFake(fakeDNSServer{
1549 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1550 return dnsmessage.Message{
1551 Header: dnsmessage.Header{
1552 ID: q.ID,
1553 Response: true,
1554 RCode: dnsmessage.RCodeServerFailure,
1555 },
1556 Questions: q.Questions,
1557 }, nil
1558 },
1559 }, "golang.org.", dnsmessage.TypeALL)
1560 if err == nil {
1561 t.Fatal("expected an error")
1562 }
1563 if ne, ok := err.(Error); !ok {
1564 t.Fatalf("err = %#v; wanted something supporting net.Error", err)
1565 } else if !ne.Temporary() {
1566 t.Fatalf("Temporary = false for err = %#v; want Temporary == true", err)
1567 }
1568 if de, ok := err.(*DNSError); !ok {
1569 t.Fatalf("err = %#v; wanted a *net.DNSError", err)
1570 } else if !de.IsTemporary {
1571 t.Fatalf("IsTemporary = false for err = %#v; want IsTemporary == true", err)
1572 }
1573 }
1574
1575 func TestIssueNoSuchHostExists(t *testing.T) {
1576 err := lookupWithFake(fakeDNSServer{
1577 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1578 return dnsmessage.Message{
1579 Header: dnsmessage.Header{
1580 ID: q.ID,
1581 Response: true,
1582 RCode: dnsmessage.RCodeNameError,
1583 },
1584 Questions: q.Questions,
1585 }, nil
1586 },
1587 }, "golang.org.", dnsmessage.TypeALL)
1588 if err == nil {
1589 t.Fatal("expected an error")
1590 }
1591 if _, ok := err.(Error); !ok {
1592 t.Fatalf("err = %#v; wanted something supporting net.Error", err)
1593 }
1594 if de, ok := err.(*DNSError); !ok {
1595 t.Fatalf("err = %#v; wanted a *net.DNSError", err)
1596 } else if !de.IsNotFound {
1597 t.Fatalf("IsNotFound = false for err = %#v; want IsNotFound == true", err)
1598 }
1599 }
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610 func TestNoSuchHost(t *testing.T) {
1611 tests := []struct {
1612 name string
1613 f func(string, string, dnsmessage.Message, time.Time) (dnsmessage.Message, error)
1614 }{
1615 {
1616 "NXDOMAIN",
1617 func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1618 return dnsmessage.Message{
1619 Header: dnsmessage.Header{
1620 ID: q.ID,
1621 Response: true,
1622 RCode: dnsmessage.RCodeNameError,
1623 RecursionAvailable: false,
1624 },
1625 Questions: q.Questions,
1626 }, nil
1627 },
1628 },
1629 {
1630 "no answers",
1631 func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1632 return dnsmessage.Message{
1633 Header: dnsmessage.Header{
1634 ID: q.ID,
1635 Response: true,
1636 RCode: dnsmessage.RCodeSuccess,
1637 RecursionAvailable: false,
1638 Authoritative: true,
1639 },
1640 Questions: q.Questions,
1641 }, nil
1642 },
1643 },
1644 }
1645
1646 for _, test := range tests {
1647 t.Run(test.name, func(t *testing.T) {
1648 lookups := 0
1649 err := lookupWithFake(fakeDNSServer{
1650 rh: func(n, s string, q dnsmessage.Message, d time.Time) (dnsmessage.Message, error) {
1651 lookups++
1652 return test.f(n, s, q, d)
1653 },
1654 }, ".", dnsmessage.TypeALL)
1655
1656 if lookups != 1 {
1657 t.Errorf("got %d lookups, wanted 1", lookups)
1658 }
1659
1660 if err == nil {
1661 t.Fatal("expected an error")
1662 }
1663 de, ok := err.(*DNSError)
1664 if !ok {
1665 t.Fatalf("err = %#v; wanted a *net.DNSError", err)
1666 }
1667 if de.Err != errNoSuchHost.Error() {
1668 t.Fatalf("Err = %#v; wanted %q", de.Err, errNoSuchHost.Error())
1669 }
1670 if !de.IsNotFound {
1671 t.Fatalf("IsNotFound = %v wanted true", de.IsNotFound)
1672 }
1673 })
1674 }
1675 }
1676
1677
1678
1679 func TestDNSDialTCP(t *testing.T) {
1680 fake := fakeDNSServer{
1681 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1682 r := dnsmessage.Message{
1683 Header: dnsmessage.Header{
1684 ID: q.Header.ID,
1685 Response: true,
1686 RCode: dnsmessage.RCodeSuccess,
1687 },
1688 Questions: q.Questions,
1689 }
1690 return r, nil
1691 },
1692 alwaysTCP: true,
1693 }
1694 r := Resolver{PreferGo: true, Dial: fake.DialContext}
1695 ctx := context.Background()
1696 _, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second, useUDPOrTCP, false)
1697 if err != nil {
1698 t.Fatal("exchange failed:", err)
1699 }
1700 }
1701
1702
1703 func TestTXTRecordTwoStrings(t *testing.T) {
1704 fake := fakeDNSServer{
1705 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1706 r := dnsmessage.Message{
1707 Header: dnsmessage.Header{
1708 ID: q.Header.ID,
1709 Response: true,
1710 RCode: dnsmessage.RCodeSuccess,
1711 },
1712 Questions: q.Questions,
1713 Answers: []dnsmessage.Resource{
1714 {
1715 Header: dnsmessage.ResourceHeader{
1716 Name: q.Questions[0].Name,
1717 Type: dnsmessage.TypeA,
1718 Class: dnsmessage.ClassINET,
1719 },
1720 Body: &dnsmessage.TXTResource{
1721 TXT: []string{"string1 ", "string2"},
1722 },
1723 },
1724 {
1725 Header: dnsmessage.ResourceHeader{
1726 Name: q.Questions[0].Name,
1727 Type: dnsmessage.TypeA,
1728 Class: dnsmessage.ClassINET,
1729 },
1730 Body: &dnsmessage.TXTResource{
1731 TXT: []string{"onestring"},
1732 },
1733 },
1734 },
1735 }
1736 return r, nil
1737 },
1738 }
1739 r := Resolver{PreferGo: true, Dial: fake.DialContext}
1740 txt, err := r.lookupTXT(context.Background(), "golang.org")
1741 if err != nil {
1742 t.Fatal("LookupTXT failed:", err)
1743 }
1744 if want := 2; len(txt) != want {
1745 t.Fatalf("len(txt), got %d, want %d", len(txt), want)
1746 }
1747 if want := "string1 string2"; txt[0] != want {
1748 t.Errorf("txt[0], got %q, want %q", txt[0], want)
1749 }
1750 if want := "onestring"; txt[1] != want {
1751 t.Errorf("txt[1], got %q, want %q", txt[1], want)
1752 }
1753 }
1754
1755
1756
1757 func TestSingleRequestLookup(t *testing.T) {
1758 defer dnsWaitGroup.Wait()
1759 var (
1760 firstcalled int32
1761 ipv4 int32 = 1
1762 ipv6 int32 = 2
1763 )
1764 fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1765 r := dnsmessage.Message{
1766 Header: dnsmessage.Header{
1767 ID: q.ID,
1768 Response: true,
1769 },
1770 Questions: q.Questions,
1771 }
1772 for _, question := range q.Questions {
1773 switch question.Type {
1774 case dnsmessage.TypeA:
1775 if question.Name.String() == "slowipv4.example.net." {
1776 time.Sleep(10 * time.Millisecond)
1777 }
1778 if !atomic.CompareAndSwapInt32(&firstcalled, 0, ipv4) {
1779 t.Errorf("the A query was received after the AAAA query !")
1780 }
1781 r.Answers = append(r.Answers, dnsmessage.Resource{
1782 Header: dnsmessage.ResourceHeader{
1783 Name: q.Questions[0].Name,
1784 Type: dnsmessage.TypeA,
1785 Class: dnsmessage.ClassINET,
1786 Length: 4,
1787 },
1788 Body: &dnsmessage.AResource{
1789 A: TestAddr,
1790 },
1791 })
1792 case dnsmessage.TypeAAAA:
1793 atomic.CompareAndSwapInt32(&firstcalled, 0, ipv6)
1794 r.Answers = append(r.Answers, dnsmessage.Resource{
1795 Header: dnsmessage.ResourceHeader{
1796 Name: q.Questions[0].Name,
1797 Type: dnsmessage.TypeAAAA,
1798 Class: dnsmessage.ClassINET,
1799 Length: 16,
1800 },
1801 Body: &dnsmessage.AAAAResource{
1802 AAAA: TestAddr6,
1803 },
1804 })
1805 }
1806 }
1807 return r, nil
1808 }}
1809 r := Resolver{PreferGo: true, Dial: fake.DialContext}
1810
1811 conf, err := newResolvConfTest()
1812 if err != nil {
1813 t.Fatal(err)
1814 }
1815 defer conf.teardown()
1816 if err := conf.writeAndUpdate([]string{"options single-request"}); err != nil {
1817 t.Fatal(err)
1818 }
1819 for _, name := range []string{"hostname.example.net", "slowipv4.example.net"} {
1820 firstcalled = 0
1821 _, err := r.LookupIPAddr(context.Background(), name)
1822 if err != nil {
1823 t.Error(err)
1824 }
1825 }
1826 }
1827
1828
1829 func TestDNSUseTCP(t *testing.T) {
1830 fake := fakeDNSServer{
1831 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1832 r := dnsmessage.Message{
1833 Header: dnsmessage.Header{
1834 ID: q.Header.ID,
1835 Response: true,
1836 RCode: dnsmessage.RCodeSuccess,
1837 },
1838 Questions: q.Questions,
1839 }
1840 if n == "udp" {
1841 t.Fatal("udp protocol was used instead of tcp")
1842 }
1843 return r, nil
1844 },
1845 }
1846 r := Resolver{PreferGo: true, Dial: fake.DialContext}
1847 ctx, cancel := context.WithCancel(context.Background())
1848 defer cancel()
1849 _, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second, useTCPOnly, false)
1850 if err != nil {
1851 t.Fatal("exchange failed:", err)
1852 }
1853 }
1854
1855 func TestDNSUseTCPTruncated(t *testing.T) {
1856 fake := fakeDNSServer{
1857 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1858 r := dnsmessage.Message{
1859 Header: dnsmessage.Header{
1860 ID: q.Header.ID,
1861 Response: true,
1862 RCode: dnsmessage.RCodeSuccess,
1863 Truncated: true,
1864 },
1865 Questions: q.Questions,
1866 Answers: []dnsmessage.Resource{
1867 {
1868 Header: dnsmessage.ResourceHeader{
1869 Name: q.Questions[0].Name,
1870 Type: dnsmessage.TypeA,
1871 Class: dnsmessage.ClassINET,
1872 Length: 4,
1873 },
1874 Body: &dnsmessage.AResource{
1875 A: TestAddr,
1876 },
1877 },
1878 },
1879 }
1880 if n == "udp" {
1881 t.Fatal("udp protocol was used instead of tcp")
1882 }
1883 return r, nil
1884 },
1885 }
1886 r := Resolver{PreferGo: true, Dial: fake.DialContext}
1887 ctx, cancel := context.WithCancel(context.Background())
1888 defer cancel()
1889 p, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second, useTCPOnly, false)
1890 if err != nil {
1891 t.Fatal("exchange failed:", err)
1892 }
1893 a, err := p.AllAnswers()
1894 if err != nil {
1895 t.Fatalf("unexpected error %v getting all answers", err)
1896 }
1897 if len(a) != 1 {
1898 t.Fatalf("got %d answers; want 1", len(a))
1899 }
1900 }
1901
1902
1903 func TestPTRandNonPTR(t *testing.T) {
1904 fake := fakeDNSServer{
1905 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1906 r := dnsmessage.Message{
1907 Header: dnsmessage.Header{
1908 ID: q.Header.ID,
1909 Response: true,
1910 RCode: dnsmessage.RCodeSuccess,
1911 },
1912 Questions: q.Questions,
1913 Answers: []dnsmessage.Resource{
1914 {
1915 Header: dnsmessage.ResourceHeader{
1916 Name: q.Questions[0].Name,
1917 Type: dnsmessage.TypePTR,
1918 Class: dnsmessage.ClassINET,
1919 },
1920 Body: &dnsmessage.PTRResource{
1921 PTR: dnsmessage.MustNewName("golang.org."),
1922 },
1923 },
1924 {
1925 Header: dnsmessage.ResourceHeader{
1926 Name: q.Questions[0].Name,
1927 Type: dnsmessage.TypeTXT,
1928 Class: dnsmessage.ClassINET,
1929 },
1930 Body: &dnsmessage.TXTResource{
1931 TXT: []string{"PTR 8 6 60 ..."},
1932 },
1933 },
1934 },
1935 }
1936 return r, nil
1937 },
1938 }
1939 r := Resolver{PreferGo: true, Dial: fake.DialContext}
1940 names, err := r.lookupAddr(context.Background(), "192.0.2.123")
1941 if err != nil {
1942 t.Fatalf("LookupAddr: %v", err)
1943 }
1944 if want := []string{"golang.org."}; !slices.Equal(names, want) {
1945 t.Errorf("names = %q; want %q", names, want)
1946 }
1947 }
1948
1949 func TestCVE202133195(t *testing.T) {
1950 fake := fakeDNSServer{
1951 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1952 r := dnsmessage.Message{
1953 Header: dnsmessage.Header{
1954 ID: q.Header.ID,
1955 Response: true,
1956 RCode: dnsmessage.RCodeSuccess,
1957 RecursionAvailable: true,
1958 },
1959 Questions: q.Questions,
1960 }
1961 switch q.Questions[0].Type {
1962 case dnsmessage.TypeCNAME:
1963 r.Answers = []dnsmessage.Resource{}
1964 case dnsmessage.TypeA:
1965 r.Answers = append(r.Answers,
1966 dnsmessage.Resource{
1967 Header: dnsmessage.ResourceHeader{
1968 Name: dnsmessage.MustNewName("<html>.golang.org."),
1969 Type: dnsmessage.TypeA,
1970 Class: dnsmessage.ClassINET,
1971 Length: 4,
1972 },
1973 Body: &dnsmessage.AResource{
1974 A: TestAddr,
1975 },
1976 },
1977 )
1978 case dnsmessage.TypeSRV:
1979 n := q.Questions[0].Name
1980 if n.String() == "_hdr._tcp.golang.org." {
1981 n = dnsmessage.MustNewName("<html>.golang.org.")
1982 }
1983 r.Answers = append(r.Answers,
1984 dnsmessage.Resource{
1985 Header: dnsmessage.ResourceHeader{
1986 Name: n,
1987 Type: dnsmessage.TypeSRV,
1988 Class: dnsmessage.ClassINET,
1989 Length: 4,
1990 },
1991 Body: &dnsmessage.SRVResource{
1992 Target: dnsmessage.MustNewName("<html>.golang.org."),
1993 },
1994 },
1995 dnsmessage.Resource{
1996 Header: dnsmessage.ResourceHeader{
1997 Name: n,
1998 Type: dnsmessage.TypeSRV,
1999 Class: dnsmessage.ClassINET,
2000 Length: 4,
2001 },
2002 Body: &dnsmessage.SRVResource{
2003 Target: dnsmessage.MustNewName("good.golang.org."),
2004 },
2005 },
2006 )
2007 case dnsmessage.TypeMX:
2008 r.Answers = append(r.Answers,
2009 dnsmessage.Resource{
2010 Header: dnsmessage.ResourceHeader{
2011 Name: dnsmessage.MustNewName("<html>.golang.org."),
2012 Type: dnsmessage.TypeMX,
2013 Class: dnsmessage.ClassINET,
2014 Length: 4,
2015 },
2016 Body: &dnsmessage.MXResource{
2017 MX: dnsmessage.MustNewName("<html>.golang.org."),
2018 },
2019 },
2020 dnsmessage.Resource{
2021 Header: dnsmessage.ResourceHeader{
2022 Name: dnsmessage.MustNewName("good.golang.org."),
2023 Type: dnsmessage.TypeMX,
2024 Class: dnsmessage.ClassINET,
2025 Length: 4,
2026 },
2027 Body: &dnsmessage.MXResource{
2028 MX: dnsmessage.MustNewName("good.golang.org."),
2029 },
2030 },
2031 )
2032 case dnsmessage.TypeNS:
2033 r.Answers = append(r.Answers,
2034 dnsmessage.Resource{
2035 Header: dnsmessage.ResourceHeader{
2036 Name: dnsmessage.MustNewName("<html>.golang.org."),
2037 Type: dnsmessage.TypeNS,
2038 Class: dnsmessage.ClassINET,
2039 Length: 4,
2040 },
2041 Body: &dnsmessage.NSResource{
2042 NS: dnsmessage.MustNewName("<html>.golang.org."),
2043 },
2044 },
2045 dnsmessage.Resource{
2046 Header: dnsmessage.ResourceHeader{
2047 Name: dnsmessage.MustNewName("good.golang.org."),
2048 Type: dnsmessage.TypeNS,
2049 Class: dnsmessage.ClassINET,
2050 Length: 4,
2051 },
2052 Body: &dnsmessage.NSResource{
2053 NS: dnsmessage.MustNewName("good.golang.org."),
2054 },
2055 },
2056 )
2057 case dnsmessage.TypePTR:
2058 r.Answers = append(r.Answers,
2059 dnsmessage.Resource{
2060 Header: dnsmessage.ResourceHeader{
2061 Name: dnsmessage.MustNewName("<html>.golang.org."),
2062 Type: dnsmessage.TypePTR,
2063 Class: dnsmessage.ClassINET,
2064 Length: 4,
2065 },
2066 Body: &dnsmessage.PTRResource{
2067 PTR: dnsmessage.MustNewName("<html>.golang.org."),
2068 },
2069 },
2070 dnsmessage.Resource{
2071 Header: dnsmessage.ResourceHeader{
2072 Name: dnsmessage.MustNewName("good.golang.org."),
2073 Type: dnsmessage.TypePTR,
2074 Class: dnsmessage.ClassINET,
2075 Length: 4,
2076 },
2077 Body: &dnsmessage.PTRResource{
2078 PTR: dnsmessage.MustNewName("good.golang.org."),
2079 },
2080 },
2081 )
2082 }
2083 return r, nil
2084 },
2085 }
2086
2087 r := Resolver{PreferGo: true, Dial: fake.DialContext}
2088
2089 originalDefault := DefaultResolver
2090 DefaultResolver = &r
2091 defer func() { DefaultResolver = originalDefault }()
2092
2093 defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2094 hostsFilePath = "testdata/hosts"
2095
2096 tests := []struct {
2097 name string
2098 f func(*testing.T)
2099 }{
2100 {
2101 name: "CNAME",
2102 f: func(t *testing.T) {
2103 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"}
2104 _, err := r.LookupCNAME(context.Background(), "golang.org")
2105 if err.Error() != expectedErr.Error() {
2106 t.Fatalf("unexpected error: %s", err)
2107 }
2108 _, err = LookupCNAME("golang.org")
2109 if err.Error() != expectedErr.Error() {
2110 t.Fatalf("unexpected error: %s", err)
2111 }
2112 },
2113 },
2114 {
2115 name: "SRV (bad record)",
2116 f: func(t *testing.T) {
2117 expected := []*SRV{
2118 {
2119 Target: "good.golang.org.",
2120 },
2121 }
2122 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"}
2123 _, records, err := r.LookupSRV(context.Background(), "target", "tcp", "golang.org")
2124 if err.Error() != expectedErr.Error() {
2125 t.Fatalf("unexpected error: %s", err)
2126 }
2127 if !reflect.DeepEqual(records, expected) {
2128 t.Error("Unexpected record set")
2129 }
2130 _, records, err = LookupSRV("target", "tcp", "golang.org")
2131 if err.Error() != expectedErr.Error() {
2132 t.Errorf("unexpected error: %s", err)
2133 }
2134 if !reflect.DeepEqual(records, expected) {
2135 t.Error("Unexpected record set")
2136 }
2137 },
2138 },
2139 {
2140 name: "SRV (bad header)",
2141 f: func(t *testing.T) {
2142 _, _, err := r.LookupSRV(context.Background(), "hdr", "tcp", "golang.org.")
2143 if expected := "lookup golang.org.: SRV header name is invalid"; err == nil || err.Error() != expected {
2144 t.Errorf("Resolver.LookupSRV returned unexpected error, got %q, want %q", err, expected)
2145 }
2146 _, _, err = LookupSRV("hdr", "tcp", "golang.org.")
2147 if expected := "lookup golang.org.: SRV header name is invalid"; err == nil || err.Error() != expected {
2148 t.Errorf("LookupSRV returned unexpected error, got %q, want %q", err, expected)
2149 }
2150 },
2151 },
2152 {
2153 name: "MX",
2154 f: func(t *testing.T) {
2155 expected := []*MX{
2156 {
2157 Host: "good.golang.org.",
2158 },
2159 }
2160 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"}
2161 records, err := r.LookupMX(context.Background(), "golang.org")
2162 if err.Error() != expectedErr.Error() {
2163 t.Fatalf("unexpected error: %s", err)
2164 }
2165 if !reflect.DeepEqual(records, expected) {
2166 t.Error("Unexpected record set")
2167 }
2168 records, err = LookupMX("golang.org")
2169 if err.Error() != expectedErr.Error() {
2170 t.Fatalf("unexpected error: %s", err)
2171 }
2172 if !reflect.DeepEqual(records, expected) {
2173 t.Error("Unexpected record set")
2174 }
2175 },
2176 },
2177 {
2178 name: "NS",
2179 f: func(t *testing.T) {
2180 expected := []*NS{
2181 {
2182 Host: "good.golang.org.",
2183 },
2184 }
2185 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"}
2186 records, err := r.LookupNS(context.Background(), "golang.org")
2187 if err.Error() != expectedErr.Error() {
2188 t.Fatalf("unexpected error: %s", err)
2189 }
2190 if !reflect.DeepEqual(records, expected) {
2191 t.Error("Unexpected record set")
2192 }
2193 records, err = LookupNS("golang.org")
2194 if err.Error() != expectedErr.Error() {
2195 t.Fatalf("unexpected error: %s", err)
2196 }
2197 if !reflect.DeepEqual(records, expected) {
2198 t.Error("Unexpected record set")
2199 }
2200 },
2201 },
2202 {
2203 name: "Addr",
2204 f: func(t *testing.T) {
2205 expected := []string{"good.golang.org."}
2206 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "192.0.2.42"}
2207 records, err := r.LookupAddr(context.Background(), "192.0.2.42")
2208 if err.Error() != expectedErr.Error() {
2209 t.Fatalf("unexpected error: %s", err)
2210 }
2211 if !slices.Equal(records, expected) {
2212 t.Error("Unexpected record set")
2213 }
2214 records, err = LookupAddr("192.0.2.42")
2215 if err.Error() != expectedErr.Error() {
2216 t.Fatalf("unexpected error: %s", err)
2217 }
2218 if !slices.Equal(records, expected) {
2219 t.Error("Unexpected record set")
2220 }
2221 },
2222 },
2223 }
2224
2225 for _, tc := range tests {
2226 t.Run(tc.name, tc.f)
2227 }
2228
2229 }
2230
2231 func TestNullMX(t *testing.T) {
2232 fake := fakeDNSServer{
2233 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2234 r := dnsmessage.Message{
2235 Header: dnsmessage.Header{
2236 ID: q.Header.ID,
2237 Response: true,
2238 RCode: dnsmessage.RCodeSuccess,
2239 },
2240 Questions: q.Questions,
2241 Answers: []dnsmessage.Resource{
2242 {
2243 Header: dnsmessage.ResourceHeader{
2244 Name: q.Questions[0].Name,
2245 Type: dnsmessage.TypeMX,
2246 Class: dnsmessage.ClassINET,
2247 },
2248 Body: &dnsmessage.MXResource{
2249 MX: dnsmessage.MustNewName("."),
2250 },
2251 },
2252 },
2253 }
2254 return r, nil
2255 },
2256 }
2257 r := Resolver{PreferGo: true, Dial: fake.DialContext}
2258 rrset, err := r.LookupMX(context.Background(), "golang.org")
2259 if err != nil {
2260 t.Fatalf("LookupMX: %v", err)
2261 }
2262 if want := []*MX{&MX{Host: "."}}; !reflect.DeepEqual(rrset, want) {
2263 records := []string{}
2264 for _, rr := range rrset {
2265 records = append(records, fmt.Sprintf("%v", rr))
2266 }
2267 t.Errorf("records = [%v]; want [%v]", strings.Join(records, " "), want[0])
2268 }
2269 }
2270
2271 func TestRootNS(t *testing.T) {
2272
2273 fake := fakeDNSServer{
2274 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2275 r := dnsmessage.Message{
2276 Header: dnsmessage.Header{
2277 ID: q.Header.ID,
2278 Response: true,
2279 RCode: dnsmessage.RCodeSuccess,
2280 },
2281 Questions: q.Questions,
2282 Answers: []dnsmessage.Resource{
2283 {
2284 Header: dnsmessage.ResourceHeader{
2285 Name: q.Questions[0].Name,
2286 Type: dnsmessage.TypeNS,
2287 Class: dnsmessage.ClassINET,
2288 },
2289 Body: &dnsmessage.NSResource{
2290 NS: dnsmessage.MustNewName("i.root-servers.net."),
2291 },
2292 },
2293 },
2294 }
2295 return r, nil
2296 },
2297 }
2298 r := Resolver{PreferGo: true, Dial: fake.DialContext}
2299 rrset, err := r.LookupNS(context.Background(), ".")
2300 if err != nil {
2301 t.Fatalf("LookupNS: %v", err)
2302 }
2303 if want := []*NS{&NS{Host: "i.root-servers.net."}}; !reflect.DeepEqual(rrset, want) {
2304 records := []string{}
2305 for _, rr := range rrset {
2306 records = append(records, fmt.Sprintf("%v", rr))
2307 }
2308 t.Errorf("records = [%v]; want [%v]", strings.Join(records, " "), want[0])
2309 }
2310 }
2311
2312 func TestGoLookupIPCNAMEOrderHostsAliasesFilesOnlyMode(t *testing.T) {
2313 defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2314 hostsFilePath = "testdata/aliases"
2315 mode := hostLookupFiles
2316
2317 for _, v := range lookupStaticHostAliasesTest {
2318 testGoLookupIPCNAMEOrderHostsAliases(t, mode, v.lookup, absDomainName(v.res))
2319 }
2320 }
2321
2322 func TestGoLookupIPCNAMEOrderHostsAliasesFilesDNSMode(t *testing.T) {
2323 defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2324 hostsFilePath = "testdata/aliases"
2325 mode := hostLookupFilesDNS
2326
2327 for _, v := range lookupStaticHostAliasesTest {
2328 testGoLookupIPCNAMEOrderHostsAliases(t, mode, v.lookup, absDomainName(v.res))
2329 }
2330 }
2331
2332 var goLookupIPCNAMEOrderDNSFilesModeTests = []struct {
2333 lookup, res string
2334 }{
2335
2336 {"invalid.invalid", "invalid.test"},
2337 }
2338
2339 func TestGoLookupIPCNAMEOrderHostsAliasesDNSFilesMode(t *testing.T) {
2340 defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2341 hostsFilePath = "testdata/aliases"
2342 mode := hostLookupDNSFiles
2343
2344 for _, v := range goLookupIPCNAMEOrderDNSFilesModeTests {
2345 testGoLookupIPCNAMEOrderHostsAliases(t, mode, v.lookup, absDomainName(v.res))
2346 }
2347 }
2348
2349 func testGoLookupIPCNAMEOrderHostsAliases(t *testing.T, mode hostLookupOrder, lookup, lookupRes string) {
2350 fake := fakeDNSServer{
2351 rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2352 var answers []dnsmessage.Resource
2353
2354 if mode != hostLookupDNSFiles {
2355 t.Fatal("received unexpected DNS query")
2356 }
2357
2358 return dnsmessage.Message{
2359 Header: dnsmessage.Header{
2360 ID: q.Header.ID,
2361 Response: true,
2362 },
2363 Questions: []dnsmessage.Question{q.Questions[0]},
2364 Answers: answers,
2365 }, nil
2366 },
2367 }
2368
2369 r := Resolver{PreferGo: true, Dial: fake.DialContext}
2370 ins := []string{lookup, absDomainName(lookup), strings.ToLower(lookup), strings.ToUpper(lookup)}
2371 for _, in := range ins {
2372 _, res, err := r.goLookupIPCNAMEOrder(context.Background(), "ip", in, mode, nil)
2373 if err != nil {
2374 t.Errorf("expected err == nil, but got error: %v", err)
2375 }
2376 if res.String() != lookupRes {
2377 t.Errorf("goLookupIPCNAMEOrder(%v): got %v, want %v", in, res, lookupRes)
2378 }
2379 }
2380 }
2381
2382
2383
2384
2385 func TestDNSPacketSize(t *testing.T) {
2386 t.Run("enabled", func(t *testing.T) {
2387 testDNSPacketSize(t, false)
2388 })
2389 t.Run("disabled", func(t *testing.T) {
2390 testDNSPacketSize(t, true)
2391 })
2392 }
2393
2394 func testDNSPacketSize(t *testing.T, disable bool) {
2395 fake := fakeDNSServer{
2396 rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2397 if disable {
2398 if len(q.Additionals) > 0 {
2399 t.Error("unexpected additional record")
2400 }
2401 } else {
2402 if len(q.Additionals) == 0 {
2403 t.Error("missing EDNS record")
2404 } else if opt, ok := q.Additionals[0].Body.(*dnsmessage.OPTResource); !ok {
2405 t.Errorf("additional record type %T, expected OPTResource", q.Additionals[0])
2406 } else if len(opt.Options) != 0 {
2407 t.Errorf("found %d Options, expected none", len(opt.Options))
2408 } else {
2409 got := int(q.Additionals[0].Header.Class)
2410 t.Logf("EDNS packet size == %d", got)
2411 if got != maxDNSPacketSize {
2412 t.Errorf("EDNS packet size == %d, want %d", got, maxDNSPacketSize)
2413 }
2414 }
2415 }
2416
2417
2418
2419 r := dnsmessage.Message{
2420 Header: dnsmessage.Header{
2421 ID: q.Header.ID,
2422 Response: true,
2423 RCode: dnsmessage.RCodeSuccess,
2424 },
2425 Questions: q.Questions,
2426 }
2427 if q.Questions[0].Type == dnsmessage.TypeA {
2428 r.Answers = []dnsmessage.Resource{
2429 {
2430 Header: dnsmessage.ResourceHeader{
2431 Name: q.Questions[0].Name,
2432 Type: dnsmessage.TypeA,
2433 Class: dnsmessage.ClassINET,
2434 Length: 4,
2435 },
2436 Body: &dnsmessage.AResource{
2437 A: TestAddr,
2438 },
2439 },
2440 }
2441 }
2442 return r, nil
2443 },
2444 }
2445
2446 if disable {
2447 t.Setenv("GODEBUG", "netedns0=0")
2448 }
2449
2450 r := &Resolver{PreferGo: true, Dial: fake.DialContext}
2451 if _, err := r.LookupIPAddr(context.Background(), "go.dev"); err != nil {
2452 t.Errorf("lookup failed: %v", err)
2453 }
2454 }
2455
2456 func TestLongDNSNames(t *testing.T) {
2457 const longDNSsuffix = ".go.dev."
2458 const longDNSsuffixNoEndingDot = ".go.dev"
2459
2460 var longDNSPrefix = strings.Repeat("verylongdomainlabel.", 20)
2461
2462 var longDNSNamesTests = []struct {
2463 req string
2464 fail bool
2465 }{
2466 {req: longDNSPrefix[:255-len(longDNSsuffix)] + longDNSsuffix, fail: true},
2467 {req: longDNSPrefix[:254-len(longDNSsuffix)] + longDNSsuffix},
2468 {req: longDNSPrefix[:253-len(longDNSsuffix)] + longDNSsuffix},
2469
2470 {req: longDNSPrefix[:253-len(longDNSsuffixNoEndingDot)] + longDNSsuffixNoEndingDot},
2471 {req: longDNSPrefix[:254-len(longDNSsuffixNoEndingDot)] + longDNSsuffixNoEndingDot, fail: true},
2472 }
2473
2474 fake := fakeDNSServer{
2475 rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2476 r := dnsmessage.Message{
2477 Header: dnsmessage.Header{
2478 ID: q.Header.ID,
2479 Response: true,
2480 RCode: dnsmessage.RCodeSuccess,
2481 },
2482 Questions: q.Questions,
2483 Answers: []dnsmessage.Resource{
2484 {
2485 Header: dnsmessage.ResourceHeader{
2486 Name: q.Questions[0].Name,
2487 Type: q.Questions[0].Type,
2488 Class: dnsmessage.ClassINET,
2489 },
2490 },
2491 },
2492 }
2493
2494 switch q.Questions[0].Type {
2495 case dnsmessage.TypeA:
2496 r.Answers[0].Body = &dnsmessage.AResource{A: TestAddr}
2497 case dnsmessage.TypeAAAA:
2498 r.Answers[0].Body = &dnsmessage.AAAAResource{AAAA: TestAddr6}
2499 case dnsmessage.TypeTXT:
2500 r.Answers[0].Body = &dnsmessage.TXTResource{TXT: []string{"."}}
2501 case dnsmessage.TypeMX:
2502 r.Answers[0].Body = &dnsmessage.MXResource{
2503 MX: dnsmessage.MustNewName("go.dev."),
2504 }
2505 case dnsmessage.TypeNS:
2506 r.Answers[0].Body = &dnsmessage.NSResource{
2507 NS: dnsmessage.MustNewName("go.dev."),
2508 }
2509 case dnsmessage.TypeSRV:
2510 r.Answers[0].Body = &dnsmessage.SRVResource{
2511 Target: dnsmessage.MustNewName("go.dev."),
2512 }
2513 case dnsmessage.TypeCNAME:
2514 r.Answers[0].Body = &dnsmessage.CNAMEResource{
2515 CNAME: dnsmessage.MustNewName("fake.cname."),
2516 }
2517 default:
2518 panic("unknown dnsmessage type")
2519 }
2520
2521 return r, nil
2522 },
2523 }
2524
2525 r := &Resolver{PreferGo: true, Dial: fake.DialContext}
2526
2527 methodTests := []string{"CNAME", "Host", "IP", "IPAddr", "MX", "NS", "NetIP", "SRV", "TXT"}
2528 query := func(t string, req string) error {
2529 switch t {
2530 case "CNAME":
2531 _, err := r.LookupCNAME(context.Background(), req)
2532 return err
2533 case "Host":
2534 _, err := r.LookupHost(context.Background(), req)
2535 return err
2536 case "IP":
2537 _, err := r.LookupIP(context.Background(), "ip", req)
2538 return err
2539 case "IPAddr":
2540 _, err := r.LookupIPAddr(context.Background(), req)
2541 return err
2542 case "MX":
2543 _, err := r.LookupMX(context.Background(), req)
2544 return err
2545 case "NS":
2546 _, err := r.LookupNS(context.Background(), req)
2547 return err
2548 case "NetIP":
2549 _, err := r.LookupNetIP(context.Background(), "ip", req)
2550 return err
2551 case "SRV":
2552 const service = "service"
2553 const proto = "proto"
2554 req = req[len(service)+len(proto)+4:]
2555 _, _, err := r.LookupSRV(context.Background(), service, proto, req)
2556 return err
2557 case "TXT":
2558 _, err := r.LookupTXT(context.Background(), req)
2559 return err
2560 }
2561 panic("unknown query method")
2562 }
2563
2564 for i, v := range longDNSNamesTests {
2565 for _, testName := range methodTests {
2566 err := query(testName, v.req)
2567 if v.fail {
2568 if err == nil {
2569 t.Errorf("%v: Lookup%v: unexpected success", i, testName)
2570 break
2571 }
2572
2573 expectedErr := DNSError{Err: errNoSuchHost.Error(), Name: v.req, IsNotFound: true}
2574 var dnsErr *DNSError
2575 errors.As(err, &dnsErr)
2576 if dnsErr == nil || *dnsErr != expectedErr {
2577 t.Errorf("%v: Lookup%v: unexpected error: %v", i, testName, err)
2578 }
2579 break
2580 }
2581 if err != nil {
2582 t.Errorf("%v: Lookup%v: unexpected error: %v", i, testName, err)
2583 }
2584 }
2585 }
2586 }
2587
2588 func TestDNSTrustAD(t *testing.T) {
2589 fake := fakeDNSServer{
2590 rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2591 if q.Questions[0].Name.String() == "notrustad.go.dev." && q.Header.AuthenticData {
2592 t.Error("unexpected AD bit")
2593 }
2594
2595 if q.Questions[0].Name.String() == "trustad.go.dev." && !q.Header.AuthenticData {
2596 t.Error("expected AD bit")
2597 }
2598
2599 r := dnsmessage.Message{
2600 Header: dnsmessage.Header{
2601 ID: q.Header.ID,
2602 Response: true,
2603 RCode: dnsmessage.RCodeSuccess,
2604 },
2605 Questions: q.Questions,
2606 }
2607 if q.Questions[0].Type == dnsmessage.TypeA {
2608 r.Answers = []dnsmessage.Resource{
2609 {
2610 Header: dnsmessage.ResourceHeader{
2611 Name: q.Questions[0].Name,
2612 Type: dnsmessage.TypeA,
2613 Class: dnsmessage.ClassINET,
2614 Length: 4,
2615 },
2616 Body: &dnsmessage.AResource{
2617 A: TestAddr,
2618 },
2619 },
2620 }
2621 }
2622
2623 return r, nil
2624 }}
2625
2626 r := &Resolver{PreferGo: true, Dial: fake.DialContext}
2627
2628 conf, err := newResolvConfTest()
2629 if err != nil {
2630 t.Fatal(err)
2631 }
2632 defer conf.teardown()
2633
2634 err = conf.writeAndUpdate([]string{"nameserver 127.0.0.1"})
2635 if err != nil {
2636 t.Fatal(err)
2637 }
2638
2639 if _, err := r.LookupIPAddr(context.Background(), "notrustad.go.dev"); err != nil {
2640 t.Errorf("lookup failed: %v", err)
2641 }
2642
2643 err = conf.writeAndUpdate([]string{"nameserver 127.0.0.1", "options trust-ad"})
2644 if err != nil {
2645 t.Fatal(err)
2646 }
2647
2648 if _, err := r.LookupIPAddr(context.Background(), "trustad.go.dev"); err != nil {
2649 t.Errorf("lookup failed: %v", err)
2650 }
2651 }
2652
2653 func TestDNSConfigNoReload(t *testing.T) {
2654 r := &Resolver{PreferGo: true, Dial: func(ctx context.Context, network, address string) (Conn, error) {
2655 if address != "192.0.2.1:53" {
2656 return nil, errors.New("configuration unexpectedly changed")
2657 }
2658 return fakeDNSServerSuccessful.DialContext(ctx, network, address)
2659 }}
2660
2661 conf, err := newResolvConfTest()
2662 if err != nil {
2663 t.Fatal(err)
2664 }
2665 defer conf.teardown()
2666
2667 err = conf.writeAndUpdateWithLastCheckedTime([]string{"nameserver 192.0.2.1", "options no-reload"}, time.Now().Add(-time.Hour))
2668 if err != nil {
2669 t.Fatal(err)
2670 }
2671
2672 if _, err = r.LookupHost(context.Background(), "go.dev"); err != nil {
2673 t.Fatal(err)
2674 }
2675
2676 err = conf.write([]string{"nameserver 192.0.2.200"})
2677 if err != nil {
2678 t.Fatal(err)
2679 }
2680
2681 if _, err = r.LookupHost(context.Background(), "go.dev"); err != nil {
2682 t.Fatal(err)
2683 }
2684 }
2685
2686 func TestLookupOrderFilesNoSuchHost(t *testing.T) {
2687 defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2688 if runtime.GOOS != "openbsd" {
2689 defer setSystemNSS(getSystemNSS(), 0)
2690 setSystemNSS(nssStr(t, "hosts: files"), time.Hour)
2691 }
2692
2693 conf, err := newResolvConfTest()
2694 if err != nil {
2695 t.Fatal(err)
2696 }
2697 defer conf.teardown()
2698
2699 resolvConf := dnsConfig{servers: defaultNS}
2700 if runtime.GOOS == "openbsd" {
2701
2702
2703 resolvConf.err = os.ErrNotExist
2704 }
2705
2706 if !conf.forceUpdateConf(&resolvConf, time.Now().Add(time.Hour)) {
2707 t.Fatal("failed to update resolv config")
2708 }
2709
2710 tmpFile := filepath.Join(t.TempDir(), "hosts")
2711 if err := os.WriteFile(tmpFile, []byte{}, 0660); err != nil {
2712 t.Fatal(err)
2713 }
2714 hostsFilePath = tmpFile
2715
2716 const testName = "test.invalid"
2717
2718 order, _ := systemConf().hostLookupOrder(DefaultResolver, testName)
2719 if order != hostLookupFiles {
2720
2721 t.Skipf("hostLookupOrder did not return hostLookupFiles")
2722 }
2723
2724 var lookupTests = []struct {
2725 name string
2726 lookup func(name string) error
2727 }{
2728 {
2729 name: "Host",
2730 lookup: func(name string) error {
2731 _, err = DefaultResolver.LookupHost(context.Background(), name)
2732 return err
2733 },
2734 },
2735 {
2736 name: "IP",
2737 lookup: func(name string) error {
2738 _, err = DefaultResolver.LookupIP(context.Background(), "ip", name)
2739 return err
2740 },
2741 },
2742 {
2743 name: "IPAddr",
2744 lookup: func(name string) error {
2745 _, err = DefaultResolver.LookupIPAddr(context.Background(), name)
2746 return err
2747 },
2748 },
2749 {
2750 name: "NetIP",
2751 lookup: func(name string) error {
2752 _, err = DefaultResolver.LookupNetIP(context.Background(), "ip", name)
2753 return err
2754 },
2755 },
2756 }
2757
2758 for _, v := range lookupTests {
2759 err := v.lookup(testName)
2760
2761 if err == nil {
2762 t.Errorf("Lookup%v: unexpected success", v.name)
2763 continue
2764 }
2765
2766 expectedErr := DNSError{Err: errNoSuchHost.Error(), Name: testName, IsNotFound: true}
2767 var dnsErr *DNSError
2768 errors.As(err, &dnsErr)
2769 if dnsErr == nil || *dnsErr != expectedErr {
2770 t.Errorf("Lookup%v: unexpected error: %v", v.name, err)
2771 }
2772 }
2773 }
2774
2775 func TestExtendedRCode(t *testing.T) {
2776 fake := fakeDNSServer{
2777 rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2778 fraudSuccessCode := dnsmessage.RCodeSuccess | 1<<10
2779
2780 var edns0Hdr dnsmessage.ResourceHeader
2781 edns0Hdr.SetEDNS0(maxDNSPacketSize, fraudSuccessCode, false)
2782
2783 return dnsmessage.Message{
2784 Header: dnsmessage.Header{
2785 ID: q.Header.ID,
2786 Response: true,
2787 RCode: fraudSuccessCode,
2788 },
2789 Questions: []dnsmessage.Question{q.Questions[0]},
2790 Additionals: []dnsmessage.Resource{{
2791 Header: edns0Hdr,
2792 Body: &dnsmessage.OPTResource{},
2793 }},
2794 }, nil
2795 },
2796 }
2797
2798 r := &Resolver{PreferGo: true, Dial: fake.DialContext}
2799 _, _, err := r.tryOneName(context.Background(), getSystemDNSConfig(), "go.dev.", dnsmessage.TypeA)
2800 var dnsErr *DNSError
2801 if !(errors.As(err, &dnsErr) && dnsErr.Err == errServerMisbehaving.Error()) {
2802 t.Fatalf("r.tryOneName(): unexpected error: %v", err)
2803 }
2804 }
2805
View as plain text