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 dnsmessage.Resource{
2032 Header: dnsmessage.ResourceHeader{
2033 Name: dnsmessage.MustNewName("127.0.0.1."),
2034 Type: dnsmessage.TypeMX,
2035 Class: dnsmessage.ClassINET,
2036 Length: 4,
2037 },
2038 Body: &dnsmessage.MXResource{
2039 MX: dnsmessage.MustNewName("127.0.0.1."),
2040 },
2041 },
2042 dnsmessage.Resource{
2043 Header: dnsmessage.ResourceHeader{
2044 Name: dnsmessage.MustNewName("1.2.3.4.5."),
2045 Type: dnsmessage.TypeMX,
2046 Class: dnsmessage.ClassINET,
2047 Length: 4,
2048 },
2049 Body: &dnsmessage.MXResource{
2050 MX: dnsmessage.MustNewName("1.2.3.4.5."),
2051 },
2052 },
2053 dnsmessage.Resource{
2054 Header: dnsmessage.ResourceHeader{
2055 Name: dnsmessage.MustNewName("2001:4860:0:2001::68."),
2056 Type: dnsmessage.TypeMX,
2057 Class: dnsmessage.ClassINET,
2058 Length: 4,
2059 },
2060 Body: &dnsmessage.MXResource{
2061 MX: dnsmessage.MustNewName("2001:4860:0:2001::68."),
2062 },
2063 },
2064 dnsmessage.Resource{
2065 Header: dnsmessage.ResourceHeader{
2066 Name: dnsmessage.MustNewName("2001:4860:0:2001::68%zone."),
2067 Type: dnsmessage.TypeMX,
2068 Class: dnsmessage.ClassINET,
2069 Length: 4,
2070 },
2071 Body: &dnsmessage.MXResource{
2072 MX: dnsmessage.MustNewName("2001:4860:0:2001::68%zone."),
2073 },
2074 },
2075 )
2076 case dnsmessage.TypeNS:
2077 r.Answers = append(r.Answers,
2078 dnsmessage.Resource{
2079 Header: dnsmessage.ResourceHeader{
2080 Name: dnsmessage.MustNewName("<html>.golang.org."),
2081 Type: dnsmessage.TypeNS,
2082 Class: dnsmessage.ClassINET,
2083 Length: 4,
2084 },
2085 Body: &dnsmessage.NSResource{
2086 NS: dnsmessage.MustNewName("<html>.golang.org."),
2087 },
2088 },
2089 dnsmessage.Resource{
2090 Header: dnsmessage.ResourceHeader{
2091 Name: dnsmessage.MustNewName("good.golang.org."),
2092 Type: dnsmessage.TypeNS,
2093 Class: dnsmessage.ClassINET,
2094 Length: 4,
2095 },
2096 Body: &dnsmessage.NSResource{
2097 NS: dnsmessage.MustNewName("good.golang.org."),
2098 },
2099 },
2100 )
2101 case dnsmessage.TypePTR:
2102 r.Answers = append(r.Answers,
2103 dnsmessage.Resource{
2104 Header: dnsmessage.ResourceHeader{
2105 Name: dnsmessage.MustNewName("<html>.golang.org."),
2106 Type: dnsmessage.TypePTR,
2107 Class: dnsmessage.ClassINET,
2108 Length: 4,
2109 },
2110 Body: &dnsmessage.PTRResource{
2111 PTR: dnsmessage.MustNewName("<html>.golang.org."),
2112 },
2113 },
2114 dnsmessage.Resource{
2115 Header: dnsmessage.ResourceHeader{
2116 Name: dnsmessage.MustNewName("good.golang.org."),
2117 Type: dnsmessage.TypePTR,
2118 Class: dnsmessage.ClassINET,
2119 Length: 4,
2120 },
2121 Body: &dnsmessage.PTRResource{
2122 PTR: dnsmessage.MustNewName("good.golang.org."),
2123 },
2124 },
2125 )
2126 }
2127 return r, nil
2128 },
2129 }
2130
2131 r := Resolver{PreferGo: true, Dial: fake.DialContext}
2132
2133 originalDefault := DefaultResolver
2134 DefaultResolver = &r
2135 defer func() { DefaultResolver = originalDefault }()
2136
2137 defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2138 hostsFilePath = "testdata/hosts"
2139
2140 tests := []struct {
2141 name string
2142 f func(*testing.T)
2143 }{
2144 {
2145 name: "CNAME",
2146 f: func(t *testing.T) {
2147 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"}
2148 _, err := r.LookupCNAME(context.Background(), "golang.org")
2149 if err.Error() != expectedErr.Error() {
2150 t.Fatalf("unexpected error: %s", err)
2151 }
2152 _, err = LookupCNAME("golang.org")
2153 if err.Error() != expectedErr.Error() {
2154 t.Fatalf("unexpected error: %s", err)
2155 }
2156 },
2157 },
2158 {
2159 name: "SRV (bad record)",
2160 f: func(t *testing.T) {
2161 expected := []*SRV{
2162 {
2163 Target: "good.golang.org.",
2164 },
2165 }
2166 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"}
2167 _, records, err := r.LookupSRV(context.Background(), "target", "tcp", "golang.org")
2168 if err.Error() != expectedErr.Error() {
2169 t.Fatalf("unexpected error: %s", err)
2170 }
2171 if !reflect.DeepEqual(records, expected) {
2172 t.Error("Unexpected record set")
2173 }
2174 _, records, err = LookupSRV("target", "tcp", "golang.org")
2175 if err.Error() != expectedErr.Error() {
2176 t.Errorf("unexpected error: %s", err)
2177 }
2178 if !reflect.DeepEqual(records, expected) {
2179 t.Error("Unexpected record set")
2180 }
2181 },
2182 },
2183 {
2184 name: "SRV (bad header)",
2185 f: func(t *testing.T) {
2186 _, _, err := r.LookupSRV(context.Background(), "hdr", "tcp", "golang.org.")
2187 if expected := "lookup golang.org.: SRV header name is invalid"; err == nil || err.Error() != expected {
2188 t.Errorf("Resolver.LookupSRV returned unexpected error, got %q, want %q", err, expected)
2189 }
2190 _, _, err = LookupSRV("hdr", "tcp", "golang.org.")
2191 if expected := "lookup golang.org.: SRV header name is invalid"; err == nil || err.Error() != expected {
2192 t.Errorf("LookupSRV returned unexpected error, got %q, want %q", err, expected)
2193 }
2194 },
2195 },
2196 {
2197 name: "MX",
2198 f: func(t *testing.T) {
2199 expected := []string{
2200 "127.0.0.1.",
2201 "2001:4860:0:2001::68.",
2202 "good.golang.org.",
2203 }
2204 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"}
2205 records, err := r.LookupMX(context.Background(), "golang.org")
2206 if err.Error() != expectedErr.Error() {
2207 t.Fatalf("unexpected error: %s", err)
2208 }
2209
2210 hosts := func(records []*MX) []string {
2211 var got []string
2212 for _, mx := range records {
2213 got = append(got, mx.Host)
2214 }
2215 slices.Sort(got)
2216 return got
2217 }
2218
2219 got := hosts(records)
2220 if !slices.Equal(got, expected) {
2221 t.Errorf("Unexpected record set: got %v, want %v", got, expected)
2222 }
2223 records, err = LookupMX("golang.org")
2224 if err.Error() != expectedErr.Error() {
2225 t.Fatalf("unexpected error: %s", err)
2226 }
2227 got = hosts(records)
2228 if !slices.Equal(got, expected) {
2229 t.Errorf("Unexpected record set: got %v, want %v", got, expected)
2230 }
2231 },
2232 },
2233 {
2234 name: "NS",
2235 f: func(t *testing.T) {
2236 expected := []*NS{
2237 {
2238 Host: "good.golang.org.",
2239 },
2240 }
2241 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"}
2242 records, err := r.LookupNS(context.Background(), "golang.org")
2243 if err.Error() != expectedErr.Error() {
2244 t.Fatalf("unexpected error: %s", err)
2245 }
2246 if !reflect.DeepEqual(records, expected) {
2247 t.Error("Unexpected record set")
2248 }
2249 records, err = LookupNS("golang.org")
2250 if err.Error() != expectedErr.Error() {
2251 t.Fatalf("unexpected error: %s", err)
2252 }
2253 if !reflect.DeepEqual(records, expected) {
2254 t.Error("Unexpected record set")
2255 }
2256 },
2257 },
2258 {
2259 name: "Addr",
2260 f: func(t *testing.T) {
2261 expected := []string{"good.golang.org."}
2262 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "192.0.2.42"}
2263 records, err := r.LookupAddr(context.Background(), "192.0.2.42")
2264 if err.Error() != expectedErr.Error() {
2265 t.Fatalf("unexpected error: %s", err)
2266 }
2267 if !slices.Equal(records, expected) {
2268 t.Error("Unexpected record set")
2269 }
2270 records, err = LookupAddr("192.0.2.42")
2271 if err.Error() != expectedErr.Error() {
2272 t.Fatalf("unexpected error: %s", err)
2273 }
2274 if !slices.Equal(records, expected) {
2275 t.Error("Unexpected record set")
2276 }
2277 },
2278 },
2279 }
2280
2281 for _, tc := range tests {
2282 t.Run(tc.name, tc.f)
2283 }
2284
2285 }
2286
2287 func TestNullMX(t *testing.T) {
2288 fake := fakeDNSServer{
2289 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2290 r := dnsmessage.Message{
2291 Header: dnsmessage.Header{
2292 ID: q.Header.ID,
2293 Response: true,
2294 RCode: dnsmessage.RCodeSuccess,
2295 },
2296 Questions: q.Questions,
2297 Answers: []dnsmessage.Resource{
2298 {
2299 Header: dnsmessage.ResourceHeader{
2300 Name: q.Questions[0].Name,
2301 Type: dnsmessage.TypeMX,
2302 Class: dnsmessage.ClassINET,
2303 },
2304 Body: &dnsmessage.MXResource{
2305 MX: dnsmessage.MustNewName("."),
2306 },
2307 },
2308 },
2309 }
2310 return r, nil
2311 },
2312 }
2313 r := Resolver{PreferGo: true, Dial: fake.DialContext}
2314 rrset, err := r.LookupMX(context.Background(), "golang.org")
2315 if err != nil {
2316 t.Fatalf("LookupMX: %v", err)
2317 }
2318 if want := []*MX{&MX{Host: "."}}; !reflect.DeepEqual(rrset, want) {
2319 records := []string{}
2320 for _, rr := range rrset {
2321 records = append(records, fmt.Sprintf("%v", rr))
2322 }
2323 t.Errorf("records = [%v]; want [%v]", strings.Join(records, " "), want[0])
2324 }
2325 }
2326
2327 func TestRootNS(t *testing.T) {
2328
2329 fake := fakeDNSServer{
2330 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2331 r := dnsmessage.Message{
2332 Header: dnsmessage.Header{
2333 ID: q.Header.ID,
2334 Response: true,
2335 RCode: dnsmessage.RCodeSuccess,
2336 },
2337 Questions: q.Questions,
2338 Answers: []dnsmessage.Resource{
2339 {
2340 Header: dnsmessage.ResourceHeader{
2341 Name: q.Questions[0].Name,
2342 Type: dnsmessage.TypeNS,
2343 Class: dnsmessage.ClassINET,
2344 },
2345 Body: &dnsmessage.NSResource{
2346 NS: dnsmessage.MustNewName("i.root-servers.net."),
2347 },
2348 },
2349 },
2350 }
2351 return r, nil
2352 },
2353 }
2354 r := Resolver{PreferGo: true, Dial: fake.DialContext}
2355 rrset, err := r.LookupNS(context.Background(), ".")
2356 if err != nil {
2357 t.Fatalf("LookupNS: %v", err)
2358 }
2359 if want := []*NS{&NS{Host: "i.root-servers.net."}}; !reflect.DeepEqual(rrset, want) {
2360 records := []string{}
2361 for _, rr := range rrset {
2362 records = append(records, fmt.Sprintf("%v", rr))
2363 }
2364 t.Errorf("records = [%v]; want [%v]", strings.Join(records, " "), want[0])
2365 }
2366 }
2367
2368 func TestGoLookupIPCNAMEOrderHostsAliasesFilesOnlyMode(t *testing.T) {
2369 defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2370 hostsFilePath = "testdata/aliases"
2371 mode := hostLookupFiles
2372
2373 for _, v := range lookupStaticHostAliasesTest {
2374 testGoLookupIPCNAMEOrderHostsAliases(t, mode, v.lookup, absDomainName(v.res))
2375 }
2376 }
2377
2378 func TestGoLookupIPCNAMEOrderHostsAliasesFilesDNSMode(t *testing.T) {
2379 defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2380 hostsFilePath = "testdata/aliases"
2381 mode := hostLookupFilesDNS
2382
2383 for _, v := range lookupStaticHostAliasesTest {
2384 testGoLookupIPCNAMEOrderHostsAliases(t, mode, v.lookup, absDomainName(v.res))
2385 }
2386 }
2387
2388 var goLookupIPCNAMEOrderDNSFilesModeTests = []struct {
2389 lookup, res string
2390 }{
2391
2392 {"invalid.invalid", "invalid.test"},
2393 }
2394
2395 func TestGoLookupIPCNAMEOrderHostsAliasesDNSFilesMode(t *testing.T) {
2396 defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2397 hostsFilePath = "testdata/aliases"
2398 mode := hostLookupDNSFiles
2399
2400 for _, v := range goLookupIPCNAMEOrderDNSFilesModeTests {
2401 testGoLookupIPCNAMEOrderHostsAliases(t, mode, v.lookup, absDomainName(v.res))
2402 }
2403 }
2404
2405 func testGoLookupIPCNAMEOrderHostsAliases(t *testing.T, mode hostLookupOrder, lookup, lookupRes string) {
2406 fake := fakeDNSServer{
2407 rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2408 var answers []dnsmessage.Resource
2409
2410 if mode != hostLookupDNSFiles {
2411 t.Fatal("received unexpected DNS query")
2412 }
2413
2414 return dnsmessage.Message{
2415 Header: dnsmessage.Header{
2416 ID: q.Header.ID,
2417 Response: true,
2418 },
2419 Questions: []dnsmessage.Question{q.Questions[0]},
2420 Answers: answers,
2421 }, nil
2422 },
2423 }
2424
2425 r := Resolver{PreferGo: true, Dial: fake.DialContext}
2426 ins := []string{lookup, absDomainName(lookup), strings.ToLower(lookup), strings.ToUpper(lookup)}
2427 for _, in := range ins {
2428 _, res, err := r.goLookupIPCNAMEOrder(context.Background(), "ip", in, mode, nil)
2429 if err != nil {
2430 t.Errorf("expected err == nil, but got error: %v", err)
2431 }
2432 if res.String() != lookupRes {
2433 t.Errorf("goLookupIPCNAMEOrder(%v): got %v, want %v", in, res, lookupRes)
2434 }
2435 }
2436 }
2437
2438
2439
2440
2441 func TestDNSPacketSize(t *testing.T) {
2442 t.Run("enabled", func(t *testing.T) {
2443 testDNSPacketSize(t, false)
2444 })
2445 t.Run("disabled", func(t *testing.T) {
2446 testDNSPacketSize(t, true)
2447 })
2448 }
2449
2450 func testDNSPacketSize(t *testing.T, disable bool) {
2451 fake := fakeDNSServer{
2452 rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2453 if disable {
2454 if len(q.Additionals) > 0 {
2455 t.Error("unexpected additional record")
2456 }
2457 } else {
2458 if len(q.Additionals) == 0 {
2459 t.Error("missing EDNS record")
2460 } else if opt, ok := q.Additionals[0].Body.(*dnsmessage.OPTResource); !ok {
2461 t.Errorf("additional record type %T, expected OPTResource", q.Additionals[0])
2462 } else if len(opt.Options) != 0 {
2463 t.Errorf("found %d Options, expected none", len(opt.Options))
2464 } else {
2465 got := int(q.Additionals[0].Header.Class)
2466 t.Logf("EDNS packet size == %d", got)
2467 if got != maxDNSPacketSize {
2468 t.Errorf("EDNS packet size == %d, want %d", got, maxDNSPacketSize)
2469 }
2470 }
2471 }
2472
2473
2474
2475 r := dnsmessage.Message{
2476 Header: dnsmessage.Header{
2477 ID: q.Header.ID,
2478 Response: true,
2479 RCode: dnsmessage.RCodeSuccess,
2480 },
2481 Questions: q.Questions,
2482 }
2483 if q.Questions[0].Type == dnsmessage.TypeA {
2484 r.Answers = []dnsmessage.Resource{
2485 {
2486 Header: dnsmessage.ResourceHeader{
2487 Name: q.Questions[0].Name,
2488 Type: dnsmessage.TypeA,
2489 Class: dnsmessage.ClassINET,
2490 Length: 4,
2491 },
2492 Body: &dnsmessage.AResource{
2493 A: TestAddr,
2494 },
2495 },
2496 }
2497 }
2498 return r, nil
2499 },
2500 }
2501
2502 if disable {
2503 t.Setenv("GODEBUG", "netedns0=0")
2504 }
2505
2506 r := &Resolver{PreferGo: true, Dial: fake.DialContext}
2507 if _, err := r.LookupIPAddr(context.Background(), "go.dev"); err != nil {
2508 t.Errorf("lookup failed: %v", err)
2509 }
2510 }
2511
2512 func TestLongDNSNames(t *testing.T) {
2513 const longDNSsuffix = ".go.dev."
2514 const longDNSsuffixNoEndingDot = ".go.dev"
2515
2516 var longDNSPrefix = strings.Repeat("verylongdomainlabel.", 20)
2517
2518 var longDNSNamesTests = []struct {
2519 req string
2520 fail bool
2521 }{
2522 {req: longDNSPrefix[:255-len(longDNSsuffix)] + longDNSsuffix, fail: true},
2523 {req: longDNSPrefix[:254-len(longDNSsuffix)] + longDNSsuffix},
2524 {req: longDNSPrefix[:253-len(longDNSsuffix)] + longDNSsuffix},
2525
2526 {req: longDNSPrefix[:253-len(longDNSsuffixNoEndingDot)] + longDNSsuffixNoEndingDot},
2527 {req: longDNSPrefix[:254-len(longDNSsuffixNoEndingDot)] + longDNSsuffixNoEndingDot, fail: true},
2528 }
2529
2530 fake := fakeDNSServer{
2531 rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2532 r := dnsmessage.Message{
2533 Header: dnsmessage.Header{
2534 ID: q.Header.ID,
2535 Response: true,
2536 RCode: dnsmessage.RCodeSuccess,
2537 },
2538 Questions: q.Questions,
2539 Answers: []dnsmessage.Resource{
2540 {
2541 Header: dnsmessage.ResourceHeader{
2542 Name: q.Questions[0].Name,
2543 Type: q.Questions[0].Type,
2544 Class: dnsmessage.ClassINET,
2545 },
2546 },
2547 },
2548 }
2549
2550 switch q.Questions[0].Type {
2551 case dnsmessage.TypeA:
2552 r.Answers[0].Body = &dnsmessage.AResource{A: TestAddr}
2553 case dnsmessage.TypeAAAA:
2554 r.Answers[0].Body = &dnsmessage.AAAAResource{AAAA: TestAddr6}
2555 case dnsmessage.TypeTXT:
2556 r.Answers[0].Body = &dnsmessage.TXTResource{TXT: []string{"."}}
2557 case dnsmessage.TypeMX:
2558 r.Answers[0].Body = &dnsmessage.MXResource{
2559 MX: dnsmessage.MustNewName("go.dev."),
2560 }
2561 case dnsmessage.TypeNS:
2562 r.Answers[0].Body = &dnsmessage.NSResource{
2563 NS: dnsmessage.MustNewName("go.dev."),
2564 }
2565 case dnsmessage.TypeSRV:
2566 r.Answers[0].Body = &dnsmessage.SRVResource{
2567 Target: dnsmessage.MustNewName("go.dev."),
2568 }
2569 case dnsmessage.TypeCNAME:
2570 r.Answers[0].Body = &dnsmessage.CNAMEResource{
2571 CNAME: dnsmessage.MustNewName("fake.cname."),
2572 }
2573 default:
2574 panic("unknown dnsmessage type")
2575 }
2576
2577 return r, nil
2578 },
2579 }
2580
2581 r := &Resolver{PreferGo: true, Dial: fake.DialContext}
2582
2583 methodTests := []string{"CNAME", "Host", "IP", "IPAddr", "MX", "NS", "NetIP", "SRV", "TXT"}
2584 query := func(t string, req string) error {
2585 switch t {
2586 case "CNAME":
2587 _, err := r.LookupCNAME(context.Background(), req)
2588 return err
2589 case "Host":
2590 _, err := r.LookupHost(context.Background(), req)
2591 return err
2592 case "IP":
2593 _, err := r.LookupIP(context.Background(), "ip", req)
2594 return err
2595 case "IPAddr":
2596 _, err := r.LookupIPAddr(context.Background(), req)
2597 return err
2598 case "MX":
2599 _, err := r.LookupMX(context.Background(), req)
2600 return err
2601 case "NS":
2602 _, err := r.LookupNS(context.Background(), req)
2603 return err
2604 case "NetIP":
2605 _, err := r.LookupNetIP(context.Background(), "ip", req)
2606 return err
2607 case "SRV":
2608 const service = "service"
2609 const proto = "proto"
2610 req = req[len(service)+len(proto)+4:]
2611 _, _, err := r.LookupSRV(context.Background(), service, proto, req)
2612 return err
2613 case "TXT":
2614 _, err := r.LookupTXT(context.Background(), req)
2615 return err
2616 }
2617 panic("unknown query method")
2618 }
2619
2620 for i, v := range longDNSNamesTests {
2621 for _, testName := range methodTests {
2622 err := query(testName, v.req)
2623 if v.fail {
2624 if err == nil {
2625 t.Errorf("%v: Lookup%v: unexpected success", i, testName)
2626 break
2627 }
2628
2629 expectedErr := DNSError{Err: errNoSuchHost.Error(), Name: v.req, IsNotFound: true}
2630 var dnsErr *DNSError
2631 errors.As(err, &dnsErr)
2632 if dnsErr == nil || *dnsErr != expectedErr {
2633 t.Errorf("%v: Lookup%v: unexpected error: %v", i, testName, err)
2634 }
2635 break
2636 }
2637 if err != nil {
2638 t.Errorf("%v: Lookup%v: unexpected error: %v", i, testName, err)
2639 }
2640 }
2641 }
2642 }
2643
2644 func TestDNSTrustAD(t *testing.T) {
2645 fake := fakeDNSServer{
2646 rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2647 if q.Questions[0].Name.String() == "notrustad.go.dev." && q.Header.AuthenticData {
2648 t.Error("unexpected AD bit")
2649 }
2650
2651 if q.Questions[0].Name.String() == "trustad.go.dev." && !q.Header.AuthenticData {
2652 t.Error("expected AD bit")
2653 }
2654
2655 r := dnsmessage.Message{
2656 Header: dnsmessage.Header{
2657 ID: q.Header.ID,
2658 Response: true,
2659 RCode: dnsmessage.RCodeSuccess,
2660 },
2661 Questions: q.Questions,
2662 }
2663 if q.Questions[0].Type == dnsmessage.TypeA {
2664 r.Answers = []dnsmessage.Resource{
2665 {
2666 Header: dnsmessage.ResourceHeader{
2667 Name: q.Questions[0].Name,
2668 Type: dnsmessage.TypeA,
2669 Class: dnsmessage.ClassINET,
2670 Length: 4,
2671 },
2672 Body: &dnsmessage.AResource{
2673 A: TestAddr,
2674 },
2675 },
2676 }
2677 }
2678
2679 return r, nil
2680 }}
2681
2682 r := &Resolver{PreferGo: true, Dial: fake.DialContext}
2683
2684 conf, err := newResolvConfTest()
2685 if err != nil {
2686 t.Fatal(err)
2687 }
2688 defer conf.teardown()
2689
2690 err = conf.writeAndUpdate([]string{"nameserver 127.0.0.1"})
2691 if err != nil {
2692 t.Fatal(err)
2693 }
2694
2695 if _, err := r.LookupIPAddr(context.Background(), "notrustad.go.dev"); err != nil {
2696 t.Errorf("lookup failed: %v", err)
2697 }
2698
2699 err = conf.writeAndUpdate([]string{"nameserver 127.0.0.1", "options trust-ad"})
2700 if err != nil {
2701 t.Fatal(err)
2702 }
2703
2704 if _, err := r.LookupIPAddr(context.Background(), "trustad.go.dev"); err != nil {
2705 t.Errorf("lookup failed: %v", err)
2706 }
2707 }
2708
2709 func TestDNSConfigNoReload(t *testing.T) {
2710 r := &Resolver{PreferGo: true, Dial: func(ctx context.Context, network, address string) (Conn, error) {
2711 if address != "192.0.2.1:53" {
2712 return nil, errors.New("configuration unexpectedly changed")
2713 }
2714 return fakeDNSServerSuccessful.DialContext(ctx, network, address)
2715 }}
2716
2717 conf, err := newResolvConfTest()
2718 if err != nil {
2719 t.Fatal(err)
2720 }
2721 defer conf.teardown()
2722
2723 err = conf.writeAndUpdateWithLastCheckedTime([]string{"nameserver 192.0.2.1", "options no-reload"}, time.Now().Add(-time.Hour))
2724 if err != nil {
2725 t.Fatal(err)
2726 }
2727
2728 if _, err = r.LookupHost(context.Background(), "go.dev"); err != nil {
2729 t.Fatal(err)
2730 }
2731
2732 err = conf.write([]string{"nameserver 192.0.2.200"})
2733 if err != nil {
2734 t.Fatal(err)
2735 }
2736
2737 if _, err = r.LookupHost(context.Background(), "go.dev"); err != nil {
2738 t.Fatal(err)
2739 }
2740 }
2741
2742 func TestLookupOrderFilesNoSuchHost(t *testing.T) {
2743 defer func(orig string) { hostsFilePath = orig }(hostsFilePath)
2744 if runtime.GOOS != "openbsd" {
2745 defer setSystemNSS(getSystemNSS(), 0)
2746 setSystemNSS(nssStr(t, "hosts: files"), time.Hour)
2747 }
2748
2749 conf, err := newResolvConfTest()
2750 if err != nil {
2751 t.Fatal(err)
2752 }
2753 defer conf.teardown()
2754
2755 resolvConf := dnsConfig{servers: defaultNS}
2756 if runtime.GOOS == "openbsd" {
2757
2758
2759 resolvConf.err = os.ErrNotExist
2760 }
2761
2762 if !conf.forceUpdateConf(&resolvConf, time.Now().Add(time.Hour)) {
2763 t.Fatal("failed to update resolv config")
2764 }
2765
2766 tmpFile := filepath.Join(t.TempDir(), "hosts")
2767 if err := os.WriteFile(tmpFile, []byte{}, 0660); err != nil {
2768 t.Fatal(err)
2769 }
2770 hostsFilePath = tmpFile
2771
2772 const testName = "test.invalid"
2773
2774 order, _ := systemConf().hostLookupOrder(DefaultResolver, testName)
2775 if order != hostLookupFiles {
2776
2777 t.Skipf("hostLookupOrder did not return hostLookupFiles")
2778 }
2779
2780 var lookupTests = []struct {
2781 name string
2782 lookup func(name string) error
2783 }{
2784 {
2785 name: "Host",
2786 lookup: func(name string) error {
2787 _, err = DefaultResolver.LookupHost(context.Background(), name)
2788 return err
2789 },
2790 },
2791 {
2792 name: "IP",
2793 lookup: func(name string) error {
2794 _, err = DefaultResolver.LookupIP(context.Background(), "ip", name)
2795 return err
2796 },
2797 },
2798 {
2799 name: "IPAddr",
2800 lookup: func(name string) error {
2801 _, err = DefaultResolver.LookupIPAddr(context.Background(), name)
2802 return err
2803 },
2804 },
2805 {
2806 name: "NetIP",
2807 lookup: func(name string) error {
2808 _, err = DefaultResolver.LookupNetIP(context.Background(), "ip", name)
2809 return err
2810 },
2811 },
2812 }
2813
2814 for _, v := range lookupTests {
2815 err := v.lookup(testName)
2816
2817 if err == nil {
2818 t.Errorf("Lookup%v: unexpected success", v.name)
2819 continue
2820 }
2821
2822 expectedErr := DNSError{Err: errNoSuchHost.Error(), Name: testName, IsNotFound: true}
2823 var dnsErr *DNSError
2824 errors.As(err, &dnsErr)
2825 if dnsErr == nil || *dnsErr != expectedErr {
2826 t.Errorf("Lookup%v: unexpected error: %v", v.name, err)
2827 }
2828 }
2829 }
2830
2831 func TestExtendedRCode(t *testing.T) {
2832 fake := fakeDNSServer{
2833 rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
2834 fraudSuccessCode := dnsmessage.RCodeSuccess | 1<<10
2835
2836 var edns0Hdr dnsmessage.ResourceHeader
2837 edns0Hdr.SetEDNS0(maxDNSPacketSize, fraudSuccessCode, false)
2838
2839 return dnsmessage.Message{
2840 Header: dnsmessage.Header{
2841 ID: q.Header.ID,
2842 Response: true,
2843 RCode: fraudSuccessCode,
2844 },
2845 Questions: []dnsmessage.Question{q.Questions[0]},
2846 Additionals: []dnsmessage.Resource{{
2847 Header: edns0Hdr,
2848 Body: &dnsmessage.OPTResource{},
2849 }},
2850 }, nil
2851 },
2852 }
2853
2854 r := &Resolver{PreferGo: true, Dial: fake.DialContext}
2855 _, _, err := r.tryOneName(context.Background(), getSystemDNSConfig(), "go.dev.", dnsmessage.TypeA)
2856 var dnsErr *DNSError
2857 if !(errors.As(err, &dnsErr) && dnsErr.Err == errServerMisbehaving.Error()) {
2858 t.Fatalf("r.tryOneName(): unexpected error: %v", err)
2859 }
2860 }
2861
View as plain text