libgo: update to Go 1.11
[official-gcc.git] / libgo / go / net / dnsclient_unix_test.go
blobf1bb09d161e4b16a601e47e95c2c3a2000e9f99c
1 // Copyright 2013 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
7 package net
9 import (
10 "context"
11 "errors"
12 "fmt"
13 "internal/poll"
14 "io/ioutil"
15 "os"
16 "path"
17 "reflect"
18 "strings"
19 "sync"
20 "testing"
21 "time"
23 "golang_org/x/net/dns/dnsmessage"
26 var goResolver = Resolver{PreferGo: true}
28 // Test address from 192.0.2.0/24 block, reserved by RFC 5737 for documentation.
29 var TestAddr = [4]byte{0xc0, 0x00, 0x02, 0x01}
31 // Test address from 2001:db8::/32 block, reserved by RFC 3849 for documentation.
32 var VarTestAddr6 = [16]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
34 func mustNewName(name string) dnsmessage.Name {
35 nn, err := dnsmessage.NewName(name)
36 if err != nil {
37 panic(fmt.Sprint("creating name: ", err))
39 return nn
42 func mustQuestion(name string, qtype dnsmessage.Type, class dnsmessage.Class) dnsmessage.Question {
43 return dnsmessage.Question{
44 Name: mustNewName(name),
45 Type: qtype,
46 Class: class,
50 var dnsTransportFallbackTests = []struct {
51 server string
52 question dnsmessage.Question
53 timeout int
54 rcode dnsmessage.RCode
56 // Querying "com." with qtype=255 usually makes an answer
57 // which requires more than 512 bytes.
58 {"8.8.8.8:53", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), 2, dnsmessage.RCodeSuccess},
59 {"8.8.4.4:53", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), 4, dnsmessage.RCodeSuccess},
62 func TestDNSTransportFallback(t *testing.T) {
63 fake := fakeDNSServer{
64 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
65 r := dnsmessage.Message{
66 Header: dnsmessage.Header{
67 ID: q.Header.ID,
68 Response: true,
69 RCode: dnsmessage.RCodeSuccess,
71 Questions: q.Questions,
73 if n == "udp" {
74 r.Header.Truncated = true
76 return r, nil
79 r := Resolver{PreferGo: true, Dial: fake.DialContext}
80 for _, tt := range dnsTransportFallbackTests {
81 ctx, cancel := context.WithCancel(context.Background())
82 defer cancel()
83 _, h, err := r.exchange(ctx, tt.server, tt.question, time.Second)
84 if err != nil {
85 t.Error(err)
86 continue
88 if h.RCode != tt.rcode {
89 t.Errorf("got %v from %v; want %v", h.RCode, tt.server, tt.rcode)
90 continue
95 // See RFC 6761 for further information about the reserved, pseudo
96 // domain names.
97 var specialDomainNameTests = []struct {
98 question dnsmessage.Question
99 rcode dnsmessage.RCode
101 // Name resolution APIs and libraries should not recognize the
102 // followings as special.
103 {mustQuestion("1.0.168.192.in-addr.arpa.", dnsmessage.TypePTR, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
104 {mustQuestion("test.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
105 {mustQuestion("example.com.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeSuccess},
107 // Name resolution APIs and libraries should recognize the
108 // followings as special and should not send any queries.
109 // Though, we test those names here for verifying negative
110 // answers at DNS query-response interaction level.
111 {mustQuestion("localhost.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
112 {mustQuestion("invalid.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError},
115 func TestSpecialDomainName(t *testing.T) {
116 fake := fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
117 r := dnsmessage.Message{
118 Header: dnsmessage.Header{
119 ID: q.ID,
120 Response: true,
122 Questions: q.Questions,
125 switch q.Questions[0].Name.String() {
126 case "example.com.":
127 r.Header.RCode = dnsmessage.RCodeSuccess
128 default:
129 r.Header.RCode = dnsmessage.RCodeNameError
132 return r, nil
134 r := Resolver{PreferGo: true, Dial: fake.DialContext}
135 server := "8.8.8.8:53"
136 for _, tt := range specialDomainNameTests {
137 ctx, cancel := context.WithCancel(context.Background())
138 defer cancel()
139 _, h, err := r.exchange(ctx, server, tt.question, 3*time.Second)
140 if err != nil {
141 t.Error(err)
142 continue
144 if h.RCode != tt.rcode {
145 t.Errorf("got %v from %v; want %v", h.RCode, server, tt.rcode)
146 continue
151 // Issue 13705: don't try to resolve onion addresses, etc
152 func TestAvoidDNSName(t *testing.T) {
153 tests := []struct {
154 name string
155 avoid bool
157 {"foo.com", false},
158 {"foo.com.", false},
160 {"foo.onion.", true},
161 {"foo.onion", true},
162 {"foo.ONION", true},
163 {"foo.ONION.", true},
165 // But do resolve *.local address; Issue 16739
166 {"foo.local.", false},
167 {"foo.local", false},
168 {"foo.LOCAL", false},
169 {"foo.LOCAL.", false},
171 {"", true}, // will be rejected earlier too
173 // Without stuff before onion/local, they're fine to
174 // use DNS. With a search path,
175 // "onion.vegegtables.com" can use DNS. Without a
176 // search path (or with a trailing dot), the queries
177 // are just kinda useless, but don't reveal anything
178 // private.
179 {"local", false},
180 {"onion", false},
181 {"local.", false},
182 {"onion.", false},
184 for _, tt := range tests {
185 got := avoidDNS(tt.name)
186 if got != tt.avoid {
187 t.Errorf("avoidDNS(%q) = %v; want %v", tt.name, got, tt.avoid)
192 var fakeDNSServerSuccessful = fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
193 r := dnsmessage.Message{
194 Header: dnsmessage.Header{
195 ID: q.ID,
196 Response: true,
198 Questions: q.Questions,
200 if len(q.Questions) == 1 && q.Questions[0].Type == dnsmessage.TypeA {
201 r.Answers = []dnsmessage.Resource{
203 Header: dnsmessage.ResourceHeader{
204 Name: q.Questions[0].Name,
205 Type: dnsmessage.TypeA,
206 Class: dnsmessage.ClassINET,
207 Length: 4,
209 Body: &dnsmessage.AResource{
210 A: TestAddr,
215 return r, nil
218 // Issue 13705: don't try to resolve onion addresses, etc
219 func TestLookupTorOnion(t *testing.T) {
220 defer dnsWaitGroup.Wait()
221 r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext}
222 addrs, err := r.LookupIPAddr(context.Background(), "foo.onion")
223 if err != nil {
224 t.Fatalf("lookup = %v; want nil", err)
226 if len(addrs) > 0 {
227 t.Errorf("unexpected addresses: %v", addrs)
231 type resolvConfTest struct {
232 dir string
233 path string
234 *resolverConfig
237 func newResolvConfTest() (*resolvConfTest, error) {
238 dir, err := ioutil.TempDir("", "go-resolvconftest")
239 if err != nil {
240 return nil, err
242 conf := &resolvConfTest{
243 dir: dir,
244 path: path.Join(dir, "resolv.conf"),
245 resolverConfig: &resolvConf,
247 conf.initOnce.Do(conf.init)
248 return conf, nil
251 func (conf *resolvConfTest) writeAndUpdate(lines []string) error {
252 f, err := os.OpenFile(conf.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
253 if err != nil {
254 return err
256 if _, err := f.WriteString(strings.Join(lines, "\n")); err != nil {
257 f.Close()
258 return err
260 f.Close()
261 if err := conf.forceUpdate(conf.path, time.Now().Add(time.Hour)); err != nil {
262 return err
264 return nil
267 func (conf *resolvConfTest) forceUpdate(name string, lastChecked time.Time) error {
268 dnsConf := dnsReadConfig(name)
269 conf.mu.Lock()
270 conf.dnsConfig = dnsConf
271 conf.mu.Unlock()
272 for i := 0; i < 5; i++ {
273 if conf.tryAcquireSema() {
274 conf.lastChecked = lastChecked
275 conf.releaseSema()
276 return nil
279 return fmt.Errorf("tryAcquireSema for %s failed", name)
282 func (conf *resolvConfTest) servers() []string {
283 conf.mu.RLock()
284 servers := conf.dnsConfig.servers
285 conf.mu.RUnlock()
286 return servers
289 func (conf *resolvConfTest) teardown() error {
290 err := conf.forceUpdate("/etc/resolv.conf", time.Time{})
291 os.RemoveAll(conf.dir)
292 return err
295 var updateResolvConfTests = []struct {
296 name string // query name
297 lines []string // resolver configuration lines
298 servers []string // expected name servers
301 name: "golang.org",
302 lines: []string{"nameserver 8.8.8.8"},
303 servers: []string{"8.8.8.8:53"},
306 name: "",
307 lines: nil, // an empty resolv.conf should use defaultNS as name servers
308 servers: defaultNS,
311 name: "www.example.com",
312 lines: []string{"nameserver 8.8.4.4"},
313 servers: []string{"8.8.4.4:53"},
317 func TestUpdateResolvConf(t *testing.T) {
318 defer dnsWaitGroup.Wait()
320 r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext}
322 conf, err := newResolvConfTest()
323 if err != nil {
324 t.Fatal(err)
326 defer conf.teardown()
328 for i, tt := range updateResolvConfTests {
329 if err := conf.writeAndUpdate(tt.lines); err != nil {
330 t.Error(err)
331 continue
333 if tt.name != "" {
334 var wg sync.WaitGroup
335 const N = 10
336 wg.Add(N)
337 for j := 0; j < N; j++ {
338 go func(name string) {
339 defer wg.Done()
340 ips, err := r.LookupIPAddr(context.Background(), name)
341 if err != nil {
342 t.Error(err)
343 return
345 if len(ips) == 0 {
346 t.Errorf("no records for %s", name)
347 return
349 }(tt.name)
351 wg.Wait()
353 servers := conf.servers()
354 if !reflect.DeepEqual(servers, tt.servers) {
355 t.Errorf("#%d: got %v; want %v", i, servers, tt.servers)
356 continue
361 var goLookupIPWithResolverConfigTests = []struct {
362 name string
363 lines []string // resolver configuration lines
364 error
365 a, aaaa bool // whether response contains A, AAAA-record
367 // no records, transport timeout
369 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j",
370 []string{
371 "options timeout:1 attempts:1",
372 "nameserver 255.255.255.255", // please forgive us for abuse of limited broadcast address
374 &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "255.255.255.255:53", IsTimeout: true},
375 false, false,
378 // no records, non-existent domain
380 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j",
381 []string{
382 "options timeout:3 attempts:1",
383 "nameserver 8.8.8.8",
385 &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "8.8.8.8:53", IsTimeout: false},
386 false, false,
389 // a few A records, no AAAA records
391 "ipv4.google.com.",
392 []string{
393 "nameserver 8.8.8.8",
394 "nameserver 2001:4860:4860::8888",
396 nil,
397 true, false,
400 "ipv4.google.com",
401 []string{
402 "domain golang.org",
403 "nameserver 2001:4860:4860::8888",
404 "nameserver 8.8.8.8",
406 nil,
407 true, false,
410 "ipv4.google.com",
411 []string{
412 "search x.golang.org y.golang.org",
413 "nameserver 2001:4860:4860::8888",
414 "nameserver 8.8.8.8",
416 nil,
417 true, false,
420 // no A records, a few AAAA records
422 "ipv6.google.com.",
423 []string{
424 "nameserver 2001:4860:4860::8888",
425 "nameserver 8.8.8.8",
427 nil,
428 false, true,
431 "ipv6.google.com",
432 []string{
433 "domain golang.org",
434 "nameserver 8.8.8.8",
435 "nameserver 2001:4860:4860::8888",
437 nil,
438 false, true,
441 "ipv6.google.com",
442 []string{
443 "search x.golang.org y.golang.org",
444 "nameserver 8.8.8.8",
445 "nameserver 2001:4860:4860::8888",
447 nil,
448 false, true,
451 // both A and AAAA records
453 "hostname.as112.net", // see RFC 7534
454 []string{
455 "domain golang.org",
456 "nameserver 2001:4860:4860::8888",
457 "nameserver 8.8.8.8",
459 nil,
460 true, true,
463 "hostname.as112.net", // see RFC 7534
464 []string{
465 "search x.golang.org y.golang.org",
466 "nameserver 2001:4860:4860::8888",
467 "nameserver 8.8.8.8",
469 nil,
470 true, true,
474 func TestGoLookupIPWithResolverConfig(t *testing.T) {
475 defer dnsWaitGroup.Wait()
476 fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
477 switch s {
478 case "[2001:4860:4860::8888]:53", "8.8.8.8:53":
479 break
480 default:
481 time.Sleep(10 * time.Millisecond)
482 return dnsmessage.Message{}, poll.ErrTimeout
484 r := dnsmessage.Message{
485 Header: dnsmessage.Header{
486 ID: q.ID,
487 Response: true,
489 Questions: q.Questions,
491 for _, question := range q.Questions {
492 switch question.Type {
493 case dnsmessage.TypeA:
494 switch question.Name.String() {
495 case "hostname.as112.net.":
496 break
497 case "ipv4.google.com.":
498 r.Answers = append(r.Answers, dnsmessage.Resource{
499 Header: dnsmessage.ResourceHeader{
500 Name: q.Questions[0].Name,
501 Type: dnsmessage.TypeA,
502 Class: dnsmessage.ClassINET,
503 Length: 4,
505 Body: &dnsmessage.AResource{
506 A: TestAddr,
509 default:
512 case dnsmessage.TypeAAAA:
513 switch question.Name.String() {
514 case "hostname.as112.net.":
515 break
516 case "ipv6.google.com.":
517 r.Answers = append(r.Answers, dnsmessage.Resource{
518 Header: dnsmessage.ResourceHeader{
519 Name: q.Questions[0].Name,
520 Type: dnsmessage.TypeAAAA,
521 Class: dnsmessage.ClassINET,
522 Length: 16,
524 Body: &dnsmessage.AAAAResource{
525 AAAA: VarTestAddr6,
531 return r, nil
533 r := Resolver{PreferGo: true, Dial: fake.DialContext}
535 conf, err := newResolvConfTest()
536 if err != nil {
537 t.Fatal(err)
539 defer conf.teardown()
541 for _, tt := range goLookupIPWithResolverConfigTests {
542 if err := conf.writeAndUpdate(tt.lines); err != nil {
543 t.Error(err)
544 continue
546 addrs, err := r.LookupIPAddr(context.Background(), tt.name)
547 if err != nil {
548 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) {
549 t.Errorf("got %v; want %v", err, tt.error)
551 continue
553 if len(addrs) == 0 {
554 t.Errorf("no records for %s", tt.name)
556 if !tt.a && !tt.aaaa && len(addrs) > 0 {
557 t.Errorf("unexpected %v for %s", addrs, tt.name)
559 for _, addr := range addrs {
560 if !tt.a && addr.IP.To4() != nil {
561 t.Errorf("got %v; must not be IPv4 address", addr)
563 if !tt.aaaa && addr.IP.To16() != nil && addr.IP.To4() == nil {
564 t.Errorf("got %v; must not be IPv6 address", addr)
570 // Test that goLookupIPOrder falls back to the host file when no DNS servers are available.
571 func TestGoLookupIPOrderFallbackToFile(t *testing.T) {
572 defer dnsWaitGroup.Wait()
574 fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, tm time.Time) (dnsmessage.Message, error) {
575 r := dnsmessage.Message{
576 Header: dnsmessage.Header{
577 ID: q.ID,
578 Response: true,
580 Questions: q.Questions,
582 return r, nil
584 r := Resolver{PreferGo: true, Dial: fake.DialContext}
586 // Add a config that simulates no dns servers being available.
587 conf, err := newResolvConfTest()
588 if err != nil {
589 t.Fatal(err)
591 if err := conf.writeAndUpdate([]string{}); err != nil {
592 t.Fatal(err)
594 // Redirect host file lookups.
595 defer func(orig string) { testHookHostsPath = orig }(testHookHostsPath)
596 testHookHostsPath = "testdata/hosts"
598 for _, order := range []hostLookupOrder{hostLookupFilesDNS, hostLookupDNSFiles} {
599 name := fmt.Sprintf("order %v", order)
601 // First ensure that we get an error when contacting a non-existent host.
602 _, _, err := r.goLookupIPCNAMEOrder(context.Background(), "notarealhost", order)
603 if err == nil {
604 t.Errorf("%s: expected error while looking up name not in hosts file", name)
605 continue
608 // Now check that we get an address when the name appears in the hosts file.
609 addrs, _, err := r.goLookupIPCNAMEOrder(context.Background(), "thor", order) // entry is in "testdata/hosts"
610 if err != nil {
611 t.Errorf("%s: expected to successfully lookup host entry", name)
612 continue
614 if len(addrs) != 1 {
615 t.Errorf("%s: expected exactly one result, but got %v", name, addrs)
616 continue
618 if got, want := addrs[0].String(), "127.1.1.1"; got != want {
619 t.Errorf("%s: address doesn't match expectation. got %v, want %v", name, got, want)
622 defer conf.teardown()
625 // Issue 12712.
626 // When using search domains, return the error encountered
627 // querying the original name instead of an error encountered
628 // querying a generated name.
629 func TestErrorForOriginalNameWhenSearching(t *testing.T) {
630 defer dnsWaitGroup.Wait()
632 const fqdn = "doesnotexist.domain"
634 conf, err := newResolvConfTest()
635 if err != nil {
636 t.Fatal(err)
638 defer conf.teardown()
640 if err := conf.writeAndUpdate([]string{"search servfail"}); err != nil {
641 t.Fatal(err)
644 fake := fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
645 r := dnsmessage.Message{
646 Header: dnsmessage.Header{
647 ID: q.ID,
648 Response: true,
650 Questions: q.Questions,
653 switch q.Questions[0].Name.String() {
654 case fqdn + ".servfail.":
655 r.Header.RCode = dnsmessage.RCodeServerFailure
656 default:
657 r.Header.RCode = dnsmessage.RCodeNameError
660 return r, nil
663 cases := []struct {
664 strictErrors bool
665 wantErr *DNSError
667 {true, &DNSError{Name: fqdn, Err: "server misbehaving", IsTemporary: true}},
668 {false, &DNSError{Name: fqdn, Err: errNoSuchHost.Error()}},
670 for _, tt := range cases {
671 r := Resolver{PreferGo: true, StrictErrors: tt.strictErrors, Dial: fake.DialContext}
672 _, err = r.LookupIPAddr(context.Background(), fqdn)
673 if err == nil {
674 t.Fatal("expected an error")
677 want := tt.wantErr
678 if err, ok := err.(*DNSError); !ok || err.Name != want.Name || err.Err != want.Err || err.IsTemporary != want.IsTemporary {
679 t.Errorf("got %v; want %v", err, want)
684 // Issue 15434. If a name server gives a lame referral, continue to the next.
685 func TestIgnoreLameReferrals(t *testing.T) {
686 defer dnsWaitGroup.Wait()
688 conf, err := newResolvConfTest()
689 if err != nil {
690 t.Fatal(err)
692 defer conf.teardown()
694 if err := conf.writeAndUpdate([]string{"nameserver 192.0.2.1", // the one that will give a lame referral
695 "nameserver 192.0.2.2"}); err != nil {
696 t.Fatal(err)
699 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
700 t.Log(s, q)
701 r := dnsmessage.Message{
702 Header: dnsmessage.Header{
703 ID: q.ID,
704 Response: true,
706 Questions: q.Questions,
709 if s == "192.0.2.2:53" {
710 r.Header.RecursionAvailable = true
711 if q.Questions[0].Type == dnsmessage.TypeA {
712 r.Answers = []dnsmessage.Resource{
714 Header: dnsmessage.ResourceHeader{
715 Name: q.Questions[0].Name,
716 Type: dnsmessage.TypeA,
717 Class: dnsmessage.ClassINET,
718 Length: 4,
720 Body: &dnsmessage.AResource{
721 A: TestAddr,
728 return r, nil
730 r := Resolver{PreferGo: true, Dial: fake.DialContext}
732 addrs, err := r.LookupIPAddr(context.Background(), "www.golang.org")
733 if err != nil {
734 t.Fatal(err)
737 if got := len(addrs); got != 1 {
738 t.Fatalf("got %d addresses, want 1", got)
741 if got, want := addrs[0].String(), "192.0.2.1"; got != want {
742 t.Fatalf("got address %v, want %v", got, want)
746 func BenchmarkGoLookupIP(b *testing.B) {
747 testHookUninstaller.Do(uninstallTestHooks)
748 ctx := context.Background()
749 b.ReportAllocs()
751 for i := 0; i < b.N; i++ {
752 goResolver.LookupIPAddr(ctx, "www.example.com")
756 func BenchmarkGoLookupIPNoSuchHost(b *testing.B) {
757 testHookUninstaller.Do(uninstallTestHooks)
758 ctx := context.Background()
759 b.ReportAllocs()
761 for i := 0; i < b.N; i++ {
762 goResolver.LookupIPAddr(ctx, "some.nonexistent")
766 func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) {
767 testHookUninstaller.Do(uninstallTestHooks)
769 conf, err := newResolvConfTest()
770 if err != nil {
771 b.Fatal(err)
773 defer conf.teardown()
775 lines := []string{
776 "nameserver 203.0.113.254", // use TEST-NET-3 block, see RFC 5737
777 "nameserver 8.8.8.8",
779 if err := conf.writeAndUpdate(lines); err != nil {
780 b.Fatal(err)
782 ctx := context.Background()
783 b.ReportAllocs()
785 for i := 0; i < b.N; i++ {
786 goResolver.LookupIPAddr(ctx, "www.example.com")
790 type fakeDNSServer struct {
791 rh func(n, s string, q dnsmessage.Message, t time.Time) (dnsmessage.Message, error)
792 alwaysTCP bool
795 func (server *fakeDNSServer) DialContext(_ context.Context, n, s string) (Conn, error) {
796 if server.alwaysTCP || n == "tcp" || n == "tcp4" || n == "tcp6" {
797 return &fakeDNSConn{tcp: true, server: server, n: n, s: s}, nil
799 return &fakeDNSPacketConn{fakeDNSConn: fakeDNSConn{tcp: false, server: server, n: n, s: s}}, nil
802 type fakeDNSConn struct {
803 Conn
804 tcp bool
805 server *fakeDNSServer
806 n string
807 s string
808 q dnsmessage.Message
809 t time.Time
810 buf []byte
813 func (f *fakeDNSConn) Close() error {
814 return nil
817 func (f *fakeDNSConn) Read(b []byte) (int, error) {
818 if len(f.buf) > 0 {
819 n := copy(b, f.buf)
820 f.buf = f.buf[n:]
821 return n, nil
824 resp, err := f.server.rh(f.n, f.s, f.q, f.t)
825 if err != nil {
826 return 0, err
829 bb := make([]byte, 2, 514)
830 bb, err = resp.AppendPack(bb)
831 if err != nil {
832 return 0, fmt.Errorf("cannot marshal DNS message: %v", err)
835 if f.tcp {
836 l := len(bb) - 2
837 bb[0] = byte(l >> 8)
838 bb[1] = byte(l)
839 f.buf = bb
840 return f.Read(b)
843 bb = bb[2:]
844 if len(b) < len(bb) {
845 return 0, errors.New("read would fragment DNS message")
848 copy(b, bb)
849 return len(bb), nil
852 func (f *fakeDNSConn) Write(b []byte) (int, error) {
853 if f.tcp && len(b) >= 2 {
854 b = b[2:]
856 if f.q.Unpack(b) != nil {
857 return 0, fmt.Errorf("cannot unmarshal DNS message fake %s (%d)", f.n, len(b))
859 return len(b), nil
862 func (f *fakeDNSConn) SetDeadline(t time.Time) error {
863 f.t = t
864 return nil
867 type fakeDNSPacketConn struct {
868 PacketConn
869 fakeDNSConn
872 func (f *fakeDNSPacketConn) SetDeadline(t time.Time) error {
873 return f.fakeDNSConn.SetDeadline(t)
876 func (f *fakeDNSPacketConn) Close() error {
877 return f.fakeDNSConn.Close()
880 // UDP round-tripper algorithm should ignore invalid DNS responses (issue 13281).
881 func TestIgnoreDNSForgeries(t *testing.T) {
882 c, s := Pipe()
883 go func() {
884 b := make([]byte, 512)
885 n, err := s.Read(b)
886 if err != nil {
887 t.Error(err)
888 return
891 var msg dnsmessage.Message
892 if msg.Unpack(b[:n]) != nil {
893 t.Error("invalid DNS query:", err)
894 return
897 s.Write([]byte("garbage DNS response packet"))
899 msg.Header.Response = true
900 msg.Header.ID++ // make invalid ID
902 if b, err = msg.Pack(); err != nil {
903 t.Error("failed to pack DNS response:", err)
904 return
906 s.Write(b)
908 msg.Header.ID-- // restore original ID
909 msg.Answers = []dnsmessage.Resource{
911 Header: dnsmessage.ResourceHeader{
912 Name: mustNewName("www.example.com."),
913 Type: dnsmessage.TypeA,
914 Class: dnsmessage.ClassINET,
915 Length: 4,
917 Body: &dnsmessage.AResource{
918 A: TestAddr,
923 b, err = msg.Pack()
924 if err != nil {
925 t.Error("failed to pack DNS response:", err)
926 return
928 s.Write(b)
931 msg := dnsmessage.Message{
932 Header: dnsmessage.Header{
933 ID: 42,
935 Questions: []dnsmessage.Question{
937 Name: mustNewName("www.example.com."),
938 Type: dnsmessage.TypeA,
939 Class: dnsmessage.ClassINET,
944 b, err := msg.Pack()
945 if err != nil {
946 t.Fatal("Pack failed:", err)
949 p, _, err := dnsPacketRoundTrip(c, 42, msg.Questions[0], b)
950 if err != nil {
951 t.Fatalf("dnsPacketRoundTrip failed: %v", err)
954 p.SkipAllQuestions()
955 as, err := p.AllAnswers()
956 if err != nil {
957 t.Fatal("AllAnswers failed:", err)
959 if got := as[0].Body.(*dnsmessage.AResource).A; got != TestAddr {
960 t.Errorf("got address %v, want %v", got, TestAddr)
964 // Issue 16865. If a name server times out, continue to the next.
965 func TestRetryTimeout(t *testing.T) {
966 defer dnsWaitGroup.Wait()
968 conf, err := newResolvConfTest()
969 if err != nil {
970 t.Fatal(err)
972 defer conf.teardown()
974 testConf := []string{
975 "nameserver 192.0.2.1", // the one that will timeout
976 "nameserver 192.0.2.2",
978 if err := conf.writeAndUpdate(testConf); err != nil {
979 t.Fatal(err)
982 var deadline0 time.Time
984 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
985 t.Log(s, q, deadline)
987 if deadline.IsZero() {
988 t.Error("zero deadline")
991 if s == "192.0.2.1:53" {
992 deadline0 = deadline
993 time.Sleep(10 * time.Millisecond)
994 return dnsmessage.Message{}, poll.ErrTimeout
997 if deadline.Equal(deadline0) {
998 t.Error("deadline didn't change")
1001 return mockTXTResponse(q), nil
1003 r := &Resolver{PreferGo: true, Dial: fake.DialContext}
1005 _, err = r.LookupTXT(context.Background(), "www.golang.org")
1006 if err != nil {
1007 t.Fatal(err)
1010 if deadline0.IsZero() {
1011 t.Error("deadline0 still zero", deadline0)
1015 func TestRotate(t *testing.T) {
1016 // without rotation, always uses the first server
1017 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"})
1019 // with rotation, rotates through back to first
1020 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"})
1023 func testRotate(t *testing.T, rotate bool, nameservers, wantServers []string) {
1024 defer dnsWaitGroup.Wait()
1026 conf, err := newResolvConfTest()
1027 if err != nil {
1028 t.Fatal(err)
1030 defer conf.teardown()
1032 var confLines []string
1033 for _, ns := range nameservers {
1034 confLines = append(confLines, "nameserver "+ns)
1036 if rotate {
1037 confLines = append(confLines, "options rotate")
1040 if err := conf.writeAndUpdate(confLines); err != nil {
1041 t.Fatal(err)
1044 var usedServers []string
1045 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1046 usedServers = append(usedServers, s)
1047 return mockTXTResponse(q), nil
1049 r := Resolver{PreferGo: true, Dial: fake.DialContext}
1051 // len(nameservers) + 1 to allow rotation to get back to start
1052 for i := 0; i < len(nameservers)+1; i++ {
1053 if _, err := r.LookupTXT(context.Background(), "www.golang.org"); err != nil {
1054 t.Fatal(err)
1058 if !reflect.DeepEqual(usedServers, wantServers) {
1059 t.Errorf("rotate=%t got used servers:\n%v\nwant:\n%v", rotate, usedServers, wantServers)
1063 func mockTXTResponse(q dnsmessage.Message) dnsmessage.Message {
1064 r := dnsmessage.Message{
1065 Header: dnsmessage.Header{
1066 ID: q.ID,
1067 Response: true,
1068 RecursionAvailable: true,
1070 Questions: q.Questions,
1071 Answers: []dnsmessage.Resource{
1073 Header: dnsmessage.ResourceHeader{
1074 Name: q.Questions[0].Name,
1075 Type: dnsmessage.TypeTXT,
1076 Class: dnsmessage.ClassINET,
1078 Body: &dnsmessage.TXTResource{
1079 TXT: []string{"ok"},
1085 return r
1088 // Issue 17448. With StrictErrors enabled, temporary errors should make
1089 // LookupIP fail rather than return a partial result.
1090 func TestStrictErrorsLookupIP(t *testing.T) {
1091 defer dnsWaitGroup.Wait()
1093 conf, err := newResolvConfTest()
1094 if err != nil {
1095 t.Fatal(err)
1097 defer conf.teardown()
1099 confData := []string{
1100 "nameserver 192.0.2.53",
1101 "search x.golang.org y.golang.org",
1103 if err := conf.writeAndUpdate(confData); err != nil {
1104 t.Fatal(err)
1107 const name = "test-issue19592"
1108 const server = "192.0.2.53:53"
1109 const searchX = "test-issue19592.x.golang.org."
1110 const searchY = "test-issue19592.y.golang.org."
1111 const ip4 = "192.0.2.1"
1112 const ip6 = "2001:db8::1"
1114 type resolveWhichEnum int
1115 const (
1116 resolveOK resolveWhichEnum = iota
1117 resolveOpError
1118 resolveServfail
1119 resolveTimeout
1122 makeTempError := func(err string) error {
1123 return &DNSError{
1124 Err: err,
1125 Name: name,
1126 Server: server,
1127 IsTemporary: true,
1130 makeTimeout := func() error {
1131 return &DNSError{
1132 Err: poll.ErrTimeout.Error(),
1133 Name: name,
1134 Server: server,
1135 IsTimeout: true,
1138 makeNxDomain := func() error {
1139 return &DNSError{
1140 Err: errNoSuchHost.Error(),
1141 Name: name,
1142 Server: server,
1146 cases := []struct {
1147 desc string
1148 resolveWhich func(quest dnsmessage.Question) resolveWhichEnum
1149 wantStrictErr error
1150 wantLaxErr error
1151 wantIPs []string
1154 desc: "No errors",
1155 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1156 return resolveOK
1158 wantIPs: []string{ip4, ip6},
1161 desc: "searchX error fails in strict mode",
1162 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1163 if quest.Name.String() == searchX {
1164 return resolveTimeout
1166 return resolveOK
1168 wantStrictErr: makeTimeout(),
1169 wantIPs: []string{ip4, ip6},
1172 desc: "searchX IPv4-only timeout fails in strict mode",
1173 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1174 if quest.Name.String() == searchX && quest.Type == dnsmessage.TypeA {
1175 return resolveTimeout
1177 return resolveOK
1179 wantStrictErr: makeTimeout(),
1180 wantIPs: []string{ip4, ip6},
1183 desc: "searchX IPv6-only servfail fails in strict mode",
1184 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1185 if quest.Name.String() == searchX && quest.Type == dnsmessage.TypeAAAA {
1186 return resolveServfail
1188 return resolveOK
1190 wantStrictErr: makeTempError("server misbehaving"),
1191 wantIPs: []string{ip4, ip6},
1194 desc: "searchY error always fails",
1195 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1196 if quest.Name.String() == searchY {
1197 return resolveTimeout
1199 return resolveOK
1201 wantStrictErr: makeTimeout(),
1202 wantLaxErr: makeNxDomain(), // This one reaches the "test." FQDN.
1205 desc: "searchY IPv4-only socket error fails in strict mode",
1206 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1207 if quest.Name.String() == searchY && quest.Type == dnsmessage.TypeA {
1208 return resolveOpError
1210 return resolveOK
1212 wantStrictErr: makeTempError("write: socket on fire"),
1213 wantIPs: []string{ip6},
1216 desc: "searchY IPv6-only timeout fails in strict mode",
1217 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum {
1218 if quest.Name.String() == searchY && quest.Type == dnsmessage.TypeAAAA {
1219 return resolveTimeout
1221 return resolveOK
1223 wantStrictErr: makeTimeout(),
1224 wantIPs: []string{ip4},
1228 for i, tt := range cases {
1229 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1230 t.Log(s, q)
1232 switch tt.resolveWhich(q.Questions[0]) {
1233 case resolveOK:
1234 // Handle below.
1235 case resolveOpError:
1236 return dnsmessage.Message{}, &OpError{Op: "write", Err: fmt.Errorf("socket on fire")}
1237 case resolveServfail:
1238 return dnsmessage.Message{
1239 Header: dnsmessage.Header{
1240 ID: q.ID,
1241 Response: true,
1242 RCode: dnsmessage.RCodeServerFailure,
1244 Questions: q.Questions,
1245 }, nil
1246 case resolveTimeout:
1247 return dnsmessage.Message{}, poll.ErrTimeout
1248 default:
1249 t.Fatal("Impossible resolveWhich")
1252 switch q.Questions[0].Name.String() {
1253 case searchX, name + ".":
1254 // Return NXDOMAIN to utilize the search list.
1255 return dnsmessage.Message{
1256 Header: dnsmessage.Header{
1257 ID: q.ID,
1258 Response: true,
1259 RCode: dnsmessage.RCodeNameError,
1261 Questions: q.Questions,
1262 }, nil
1263 case searchY:
1264 // Return records below.
1265 default:
1266 return dnsmessage.Message{}, fmt.Errorf("Unexpected Name: %v", q.Questions[0].Name)
1269 r := dnsmessage.Message{
1270 Header: dnsmessage.Header{
1271 ID: q.ID,
1272 Response: true,
1274 Questions: q.Questions,
1276 switch q.Questions[0].Type {
1277 case dnsmessage.TypeA:
1278 r.Answers = []dnsmessage.Resource{
1280 Header: dnsmessage.ResourceHeader{
1281 Name: q.Questions[0].Name,
1282 Type: dnsmessage.TypeA,
1283 Class: dnsmessage.ClassINET,
1284 Length: 4,
1286 Body: &dnsmessage.AResource{
1287 A: TestAddr,
1291 case dnsmessage.TypeAAAA:
1292 r.Answers = []dnsmessage.Resource{
1294 Header: dnsmessage.ResourceHeader{
1295 Name: q.Questions[0].Name,
1296 Type: dnsmessage.TypeAAAA,
1297 Class: dnsmessage.ClassINET,
1298 Length: 16,
1300 Body: &dnsmessage.AAAAResource{
1301 AAAA: VarTestAddr6,
1305 default:
1306 return dnsmessage.Message{}, fmt.Errorf("Unexpected Type: %v", q.Questions[0].Type)
1308 return r, nil
1311 for _, strict := range []bool{true, false} {
1312 r := Resolver{PreferGo: true, StrictErrors: strict, Dial: fake.DialContext}
1313 ips, err := r.LookupIPAddr(context.Background(), name)
1315 var wantErr error
1316 if strict {
1317 wantErr = tt.wantStrictErr
1318 } else {
1319 wantErr = tt.wantLaxErr
1321 if !reflect.DeepEqual(err, wantErr) {
1322 t.Errorf("#%d (%s) strict=%v: got err %#v; want %#v", i, tt.desc, strict, err, wantErr)
1325 gotIPs := map[string]struct{}{}
1326 for _, ip := range ips {
1327 gotIPs[ip.String()] = struct{}{}
1329 wantIPs := map[string]struct{}{}
1330 if wantErr == nil {
1331 for _, ip := range tt.wantIPs {
1332 wantIPs[ip] = struct{}{}
1335 if !reflect.DeepEqual(gotIPs, wantIPs) {
1336 t.Errorf("#%d (%s) strict=%v: got ips %v; want %v", i, tt.desc, strict, gotIPs, wantIPs)
1342 // Issue 17448. With StrictErrors enabled, temporary errors should make
1343 // LookupTXT stop walking the search list.
1344 func TestStrictErrorsLookupTXT(t *testing.T) {
1345 defer dnsWaitGroup.Wait()
1347 conf, err := newResolvConfTest()
1348 if err != nil {
1349 t.Fatal(err)
1351 defer conf.teardown()
1353 confData := []string{
1354 "nameserver 192.0.2.53",
1355 "search x.golang.org y.golang.org",
1357 if err := conf.writeAndUpdate(confData); err != nil {
1358 t.Fatal(err)
1361 const name = "test"
1362 const server = "192.0.2.53:53"
1363 const searchX = "test.x.golang.org."
1364 const searchY = "test.y.golang.org."
1365 const txt = "Hello World"
1367 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) {
1368 t.Log(s, q)
1370 switch q.Questions[0].Name.String() {
1371 case searchX:
1372 return dnsmessage.Message{}, poll.ErrTimeout
1373 case searchY:
1374 return mockTXTResponse(q), nil
1375 default:
1376 return dnsmessage.Message{}, fmt.Errorf("Unexpected Name: %v", q.Questions[0].Name)
1380 for _, strict := range []bool{true, false} {
1381 r := Resolver{StrictErrors: strict, Dial: fake.DialContext}
1382 p, _, err := r.lookup(context.Background(), name, dnsmessage.TypeTXT)
1383 var wantErr error
1384 var wantRRs int
1385 if strict {
1386 wantErr = &DNSError{
1387 Err: poll.ErrTimeout.Error(),
1388 Name: name,
1389 Server: server,
1390 IsTimeout: true,
1392 } else {
1393 wantRRs = 1
1395 if !reflect.DeepEqual(err, wantErr) {
1396 t.Errorf("strict=%v: got err %#v; want %#v", strict, err, wantErr)
1398 a, err := p.AllAnswers()
1399 if err != nil {
1400 a = nil
1402 if len(a) != wantRRs {
1403 t.Errorf("strict=%v: got %v; want %v", strict, len(a), wantRRs)
1408 // Test for a race between uninstalling the test hooks and closing a
1409 // socket connection. This used to fail when testing with -race.
1410 func TestDNSGoroutineRace(t *testing.T) {
1411 defer dnsWaitGroup.Wait()
1413 fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, t time.Time) (dnsmessage.Message, error) {
1414 time.Sleep(10 * time.Microsecond)
1415 return dnsmessage.Message{}, poll.ErrTimeout
1417 r := Resolver{PreferGo: true, Dial: fake.DialContext}
1419 // The timeout here is less than the timeout used by the server,
1420 // so the goroutine started to query the (fake) server will hang
1421 // around after this test is done if we don't call dnsWaitGroup.Wait.
1422 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Microsecond)
1423 defer cancel()
1424 _, err := r.LookupIPAddr(ctx, "where.are.they.now")
1425 if err == nil {
1426 t.Fatal("fake DNS lookup unexpectedly succeeded")
1430 // Issue 8434: verify that Temporary returns true on an error when rcode
1431 // is SERVFAIL
1432 func TestIssue8434(t *testing.T) {
1433 msg := dnsmessage.Message{
1434 Header: dnsmessage.Header{
1435 RCode: dnsmessage.RCodeServerFailure,
1438 b, err := msg.Pack()
1439 if err != nil {
1440 t.Fatal("Pack failed:", err)
1442 var p dnsmessage.Parser
1443 h, err := p.Start(b)
1444 if err != nil {
1445 t.Fatal("Start failed:", err)
1447 if err := p.SkipAllQuestions(); err != nil {
1448 t.Fatal("SkipAllQuestions failed:", err)
1451 err = checkHeader(&p, h, "golang.org", "foo:53")
1452 if err == nil {
1453 t.Fatal("expected an error")
1455 if ne, ok := err.(Error); !ok {
1456 t.Fatalf("err = %#v; wanted something supporting net.Error", err)
1457 } else if !ne.Temporary() {
1458 t.Fatalf("Temporary = false for err = %#v; want Temporary == true", err)
1460 if de, ok := err.(*DNSError); !ok {
1461 t.Fatalf("err = %#v; wanted a *net.DNSError", err)
1462 } else if !de.IsTemporary {
1463 t.Fatalf("IsTemporary = false for err = %#v; want IsTemporary == true", err)
1467 // Issue 12778: verify that NXDOMAIN without RA bit errors as
1468 // "no such host" and not "server misbehaving"
1470 // Issue 25336: verify that NXDOMAIN errors fail fast.
1471 func TestIssue12778(t *testing.T) {
1472 lookups := 0
1473 fake := fakeDNSServer{
1474 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1475 lookups++
1476 return dnsmessage.Message{
1477 Header: dnsmessage.Header{
1478 ID: q.ID,
1479 Response: true,
1480 RCode: dnsmessage.RCodeNameError,
1481 RecursionAvailable: false,
1483 Questions: q.Questions,
1484 }, nil
1487 r := Resolver{PreferGo: true, Dial: fake.DialContext}
1489 resolvConf.mu.RLock()
1490 conf := resolvConf.dnsConfig
1491 resolvConf.mu.RUnlock()
1493 ctx, cancel := context.WithCancel(context.Background())
1494 defer cancel()
1496 _, _, err := r.tryOneName(ctx, conf, ".", dnsmessage.TypeALL)
1498 if lookups != 1 {
1499 t.Errorf("got %d lookups, wanted 1", lookups)
1502 if err == nil {
1503 t.Fatal("expected an error")
1505 de, ok := err.(*DNSError)
1506 if !ok {
1507 t.Fatalf("err = %#v; wanted a *net.DNSError", err)
1509 if de.Err != errNoSuchHost.Error() {
1510 t.Fatalf("Err = %#v; wanted %q", de.Err, errNoSuchHost.Error())
1514 // Issue 26573: verify that Conns that don't implement PacketConn are treated
1515 // as streams even when udp was requested.
1516 func TestDNSDialTCP(t *testing.T) {
1517 fake := fakeDNSServer{
1518 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) {
1519 r := dnsmessage.Message{
1520 Header: dnsmessage.Header{
1521 ID: q.Header.ID,
1522 Response: true,
1523 RCode: dnsmessage.RCodeSuccess,
1525 Questions: q.Questions,
1527 return r, nil
1529 alwaysTCP: true,
1531 r := Resolver{PreferGo: true, Dial: fake.DialContext}
1532 ctx := context.Background()
1533 _, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second)
1534 if err != nil {
1535 t.Fatal("exhange failed:", err)