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
23 // Test address from 192.0.2.0/24 block, reserved by RFC 5737 for documentation.
24 const TestAddr
uint32 = 0xc0000201
26 var dnsTransportFallbackTests
= []struct {
33 // Querying "com." with qtype=255 usually makes an answer
34 // which requires more than 512 bytes.
35 {"8.8.8.8:53", "com.", dnsTypeALL
, 2, dnsRcodeSuccess
},
36 {"8.8.4.4:53", "com.", dnsTypeALL
, 4, dnsRcodeSuccess
},
39 func TestDNSTransportFallback(t
*testing
.T
) {
40 testenv
.MustHaveExternalNetwork(t
)
42 for _
, tt
:= range dnsTransportFallbackTests
{
43 ctx
, cancel
:= context
.WithCancel(context
.Background())
45 msg
, err
:= exchange(ctx
, tt
.server
, tt
.name
, tt
.qtype
, time
.Second
)
51 case tt
.rcode
, dnsRcodeServerFailure
:
53 t
.Errorf("got %v from %v; want %v", msg
.rcode
, tt
.server
, tt
.rcode
)
59 // See RFC 6761 for further information about the reserved, pseudo
61 var specialDomainNameTests
= []struct {
66 // Name resolution APIs and libraries should not recognize the
67 // followings as special.
68 {"1.0.168.192.in-addr.arpa.", dnsTypePTR
, dnsRcodeNameError
},
69 {"test.", dnsTypeALL
, dnsRcodeNameError
},
70 {"example.com.", dnsTypeALL
, dnsRcodeSuccess
},
72 // Name resolution APIs and libraries should recognize the
73 // followings as special and should not send any queries.
74 // Though, we test those names here for verifying negative
75 // answers at DNS query-response interaction level.
76 {"localhost.", dnsTypeALL
, dnsRcodeNameError
},
77 {"invalid.", dnsTypeALL
, dnsRcodeNameError
},
80 func TestSpecialDomainName(t
*testing
.T
) {
81 testenv
.MustHaveExternalNetwork(t
)
83 server
:= "8.8.8.8:53"
84 for _
, tt
:= range specialDomainNameTests
{
85 ctx
, cancel
:= context
.WithCancel(context
.Background())
87 msg
, err
:= exchange(ctx
, server
, tt
.name
, tt
.qtype
, 3*time
.Second
)
93 case tt
.rcode
, dnsRcodeServerFailure
:
95 t
.Errorf("got %v from %v; want %v", msg
.rcode
, server
, tt
.rcode
)
101 // Issue 13705: don't try to resolve onion addresses, etc
102 func TestAvoidDNSName(t
*testing
.T
) {
110 {"foo.onion.", true},
113 {"foo.ONION.", true},
115 // But do resolve *.local address; Issue 16739
116 {"foo.local.", false},
117 {"foo.local", false},
118 {"foo.LOCAL", false},
119 {"foo.LOCAL.", false},
121 {"", true}, // will be rejected earlier too
123 // Without stuff before onion/local, they're fine to
124 // use DNS. With a search path,
125 // "onion.vegegtables.com" can use DNS. Without a
126 // search path (or with a trailing dot), the queries
127 // are just kinda useless, but don't reveal anything
134 for _
, tt
:= range tests
{
135 got
:= avoidDNS(tt
.name
)
137 t
.Errorf("avoidDNS(%q) = %v; want %v", tt
.name
, got
, tt
.avoid
)
142 // Issue 13705: don't try to resolve onion addresses, etc
143 func TestLookupTorOnion(t
*testing
.T
) {
144 addrs
, err
:= goLookupIP(context
.Background(), "foo.onion")
146 t
.Errorf("unexpected addresses: %v", addrs
)
149 t
.Fatalf("lookup = %v; want nil", err
)
153 type resolvConfTest
struct {
159 func newResolvConfTest() (*resolvConfTest
, error
) {
160 dir
, err
:= ioutil
.TempDir("", "go-resolvconftest")
164 conf
:= &resolvConfTest
{
166 path
: path
.Join(dir
, "resolv.conf"),
167 resolverConfig
: &resolvConf
,
169 conf
.initOnce
.Do(conf
.init
)
173 func (conf
*resolvConfTest
) writeAndUpdate(lines
[]string) error
{
174 f
, err
:= os
.OpenFile(conf
.path
, os
.O_CREATE|os
.O_TRUNC|os
.O_WRONLY
, 0600)
178 if _
, err
:= f
.WriteString(strings
.Join(lines
, "\n")); err
!= nil {
183 if err
:= conf
.forceUpdate(conf
.path
, time
.Now().Add(time
.Hour
)); err
!= nil {
189 func (conf
*resolvConfTest
) forceUpdate(name
string, lastChecked time
.Time
) error
{
190 dnsConf
:= dnsReadConfig(name
)
192 conf
.dnsConfig
= dnsConf
194 for i
:= 0; i
< 5; i
++ {
195 if conf
.tryAcquireSema() {
196 conf
.lastChecked
= lastChecked
201 return fmt
.Errorf("tryAcquireSema for %s failed", name
)
204 func (conf
*resolvConfTest
) servers() []string {
206 servers
:= conf
.dnsConfig
.servers
211 func (conf
*resolvConfTest
) teardown() error
{
212 err
:= conf
.forceUpdate("/etc/resolv.conf", time
.Time
{})
213 os
.RemoveAll(conf
.dir
)
217 var updateResolvConfTests
= []struct {
218 name
string // query name
219 lines
[]string // resolver configuration lines
220 servers
[]string // expected name servers
224 lines
: []string{"nameserver 8.8.8.8"},
225 servers
: []string{"8.8.8.8:53"},
229 lines
: nil, // an empty resolv.conf should use defaultNS as name servers
233 name
: "www.example.com",
234 lines
: []string{"nameserver 8.8.4.4"},
235 servers
: []string{"8.8.4.4:53"},
239 func TestUpdateResolvConf(t
*testing
.T
) {
240 testenv
.MustHaveExternalNetwork(t
)
242 conf
, err
:= newResolvConfTest()
246 defer conf
.teardown()
248 for i
, tt
:= range updateResolvConfTests
{
249 if err
:= conf
.writeAndUpdate(tt
.lines
); err
!= nil {
254 var wg sync
.WaitGroup
257 for j
:= 0; j
< N
; j
++ {
258 go func(name
string) {
260 ips
, err
:= goLookupIP(context
.Background(), name
)
266 t
.Errorf("no records for %s", name
)
273 servers
:= conf
.servers()
274 if !reflect
.DeepEqual(servers
, tt
.servers
) {
275 t
.Errorf("#%d: got %v; want %v", i
, servers
, tt
.servers
)
281 var goLookupIPWithResolverConfigTests
= []struct {
283 lines
[]string // resolver configuration lines
285 a
, aaaa
bool // whether response contains A, AAAA-record
287 // no records, transport timeout
289 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j",
291 "options timeout:1 attempts:1",
292 "nameserver 255.255.255.255", // please forgive us for abuse of limited broadcast address
294 &DNSError
{Name
: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server
: "255.255.255.255:53", IsTimeout
: true},
298 // no records, non-existent domain
300 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j",
302 "options timeout:3 attempts:1",
303 "nameserver 8.8.8.8",
305 &DNSError
{Name
: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server
: "8.8.8.8:53", IsTimeout
: false},
309 // a few A records, no AAAA records
313 "nameserver 8.8.8.8",
314 "nameserver 2001:4860:4860::8888",
323 "nameserver 2001:4860:4860::8888",
324 "nameserver 8.8.8.8",
332 "search x.golang.org y.golang.org",
333 "nameserver 2001:4860:4860::8888",
334 "nameserver 8.8.8.8",
340 // no A records, a few AAAA records
344 "nameserver 2001:4860:4860::8888",
345 "nameserver 8.8.8.8",
354 "nameserver 8.8.8.8",
355 "nameserver 2001:4860:4860::8888",
363 "search x.golang.org y.golang.org",
364 "nameserver 8.8.8.8",
365 "nameserver 2001:4860:4860::8888",
371 // both A and AAAA records
373 "hostname.as112.net", // see RFC 7534
376 "nameserver 2001:4860:4860::8888",
377 "nameserver 8.8.8.8",
383 "hostname.as112.net", // see RFC 7534
385 "search x.golang.org y.golang.org",
386 "nameserver 2001:4860:4860::8888",
387 "nameserver 8.8.8.8",
394 func TestGoLookupIPWithResolverConfig(t
*testing
.T
) {
395 testenv
.MustHaveExternalNetwork(t
)
397 conf
, err
:= newResolvConfTest()
401 defer conf
.teardown()
403 for _
, tt
:= range goLookupIPWithResolverConfigTests
{
404 if err
:= conf
.writeAndUpdate(tt
.lines
); err
!= nil {
408 addrs
, err
:= goLookupIP(context
.Background(), tt
.name
)
410 // This test uses external network connectivity.
411 // We need to take care with errors on both
412 // DNS message exchange layer and DNS
413 // transport layer because goLookupIP may fail
414 // when the IP connectivity on node under test
415 // gets lost during its run.
416 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
) {
417 t
.Errorf("got %v; want %v", err
, tt
.error
)
422 t
.Errorf("no records for %s", tt
.name
)
424 if !tt
.a
&& !tt
.aaaa
&& len(addrs
) > 0 {
425 t
.Errorf("unexpected %v for %s", addrs
, tt
.name
)
427 for _
, addr
:= range addrs
{
428 if !tt
.a
&& addr
.IP
.To4() != nil {
429 t
.Errorf("got %v; must not be IPv4 address", addr
)
431 if !tt
.aaaa
&& addr
.IP
.To16() != nil && addr
.IP
.To4() == nil {
432 t
.Errorf("got %v; must not be IPv6 address", addr
)
438 // Test that goLookupIPOrder falls back to the host file when no DNS servers are available.
439 func TestGoLookupIPOrderFallbackToFile(t
*testing
.T
) {
440 testenv
.MustHaveExternalNetwork(t
)
442 // Add a config that simulates no dns servers being available.
443 conf
, err
:= newResolvConfTest()
447 if err
:= conf
.writeAndUpdate([]string{}); err
!= nil {
450 // Redirect host file lookups.
451 defer func(orig
string) { testHookHostsPath
= orig
}(testHookHostsPath
)
452 testHookHostsPath
= "testdata/hosts"
454 for _
, order
:= range []hostLookupOrder
{hostLookupFilesDNS
, hostLookupDNSFiles
} {
455 name
:= fmt
.Sprintf("order %v", order
)
457 // First ensure that we get an error when contacting a non-existent host.
458 _
, _
, err
:= goLookupIPCNAMEOrder(context
.Background(), "notarealhost", order
)
460 t
.Errorf("%s: expected error while looking up name not in hosts file", name
)
464 // Now check that we get an address when the name appears in the hosts file.
465 addrs
, _
, err
:= goLookupIPCNAMEOrder(context
.Background(), "thor", order
) // entry is in "testdata/hosts"
467 t
.Errorf("%s: expected to successfully lookup host entry", name
)
471 t
.Errorf("%s: expected exactly one result, but got %v", name
, addrs
)
474 if got
, want
:= addrs
[0].String(), "127.1.1.1"; got
!= want
{
475 t
.Errorf("%s: address doesn't match expectation. got %v, want %v", name
, got
, want
)
478 defer conf
.teardown()
482 // When using search domains, return the error encountered
483 // querying the original name instead of an error encountered
484 // querying a generated name.
485 func TestErrorForOriginalNameWhenSearching(t
*testing
.T
) {
486 const fqdn
= "doesnotexist.domain"
488 origTestHookDNSDialer
:= testHookDNSDialer
489 defer func() { testHookDNSDialer
= origTestHookDNSDialer
}()
491 conf
, err
:= newResolvConfTest()
495 defer conf
.teardown()
497 if err
:= conf
.writeAndUpdate([]string{"search servfail"}); err
!= nil {
501 d
:= &fakeDNSDialer
{}
502 testHookDNSDialer
= func() dnsDialer
{ return d
}
504 d
.rh
= func(s
string, q
*dnsMsg
, _ time
.Time
) (*dnsMsg
, error
) {
506 dnsMsgHdr
: dnsMsgHdr
{
511 switch q
.question
[0].Name
{
512 case fqdn
+ ".servfail.":
513 r
.rcode
= dnsRcodeServerFailure
515 r
.rcode
= dnsRcodeNameError
521 _
, err
= goLookupIP(context
.Background(), fqdn
)
523 t
.Fatal("expected an error")
526 want
:= &DNSError
{Name
: fqdn
, Err
: errNoSuchHost
.Error()}
527 if err
, ok
:= err
.(*DNSError
); !ok || err
.Name
!= want
.Name || err
.Err
!= want
.Err
{
528 t
.Errorf("got %v; want %v", err
, want
)
532 // Issue 15434. If a name server gives a lame referral, continue to the next.
533 func TestIgnoreLameReferrals(t
*testing
.T
) {
534 origTestHookDNSDialer
:= testHookDNSDialer
535 defer func() { testHookDNSDialer
= origTestHookDNSDialer
}()
537 conf
, err
:= newResolvConfTest()
541 defer conf
.teardown()
543 if err
:= conf
.writeAndUpdate([]string{"nameserver 192.0.2.1", // the one that will give a lame referral
544 "nameserver 192.0.2.2"}); err
!= nil {
548 d
:= &fakeDNSDialer
{}
549 testHookDNSDialer
= func() dnsDialer
{ return d
}
551 d
.rh
= func(s
string, q
*dnsMsg
, _ time
.Time
) (*dnsMsg
, error
) {
554 dnsMsgHdr
: dnsMsgHdr
{
558 question
: q
.question
,
561 if s
== "192.0.2.2:53" {
562 r
.recursion_available
= true
563 if q
.question
[0].Qtype
== dnsTypeA
{
567 Name
: q
.question
[0].Name
,
581 addrs
, err
:= goLookupIP(context
.Background(), "www.golang.org")
586 if got
:= len(addrs
); got
!= 1 {
587 t
.Fatalf("got %d addresses, want 1", got
)
590 if got
, want
:= addrs
[0].String(), "192.0.2.1"; got
!= want
{
591 t
.Fatalf("got address %v, want %v", got
, want
)
595 func BenchmarkGoLookupIP(b
*testing
.B
) {
596 testHookUninstaller
.Do(uninstallTestHooks
)
597 ctx
:= context
.Background()
599 for i
:= 0; i
< b
.N
; i
++ {
600 goLookupIP(ctx
, "www.example.com")
604 func BenchmarkGoLookupIPNoSuchHost(b
*testing
.B
) {
605 testHookUninstaller
.Do(uninstallTestHooks
)
606 ctx
:= context
.Background()
608 for i
:= 0; i
< b
.N
; i
++ {
609 goLookupIP(ctx
, "some.nonexistent")
613 func BenchmarkGoLookupIPWithBrokenNameServer(b
*testing
.B
) {
614 testHookUninstaller
.Do(uninstallTestHooks
)
616 conf
, err
:= newResolvConfTest()
620 defer conf
.teardown()
623 "nameserver 203.0.113.254", // use TEST-NET-3 block, see RFC 5737
624 "nameserver 8.8.8.8",
626 if err
:= conf
.writeAndUpdate(lines
); err
!= nil {
629 ctx
:= context
.Background()
631 for i
:= 0; i
< b
.N
; i
++ {
632 goLookupIP(ctx
, "www.example.com")
636 type fakeDNSDialer
struct {
638 rh
func(s
string, q
*dnsMsg
, t time
.Time
) (*dnsMsg
, error
)
641 func (f
*fakeDNSDialer
) dialDNS(_ context
.Context
, n
, s
string) (dnsConn
, error
) {
642 return &fakeDNSConn
{f
.rh
, s
, time
.Time
{}}, nil
645 type fakeDNSConn
struct {
646 rh
func(s
string, q
*dnsMsg
, t time
.Time
) (*dnsMsg
, error
)
651 func (f
*fakeDNSConn
) Close() error
{
655 func (f
*fakeDNSConn
) SetDeadline(t time
.Time
) error
{
660 func (f
*fakeDNSConn
) dnsRoundTrip(q
*dnsMsg
) (*dnsMsg
, error
) {
661 return f
.rh(f
.s
, q
, f
.t
)
664 // UDP round-tripper algorithm should ignore invalid DNS responses (issue 13281).
665 func TestIgnoreDNSForgeries(t
*testing
.T
) {
668 b
:= make([]byte, 512)
676 if !msg
.Unpack(b
[:n
]) {
677 t
.Error("invalid DNS query")
681 s
.Write([]byte("garbage DNS response packet"))
684 msg
.id
++ // make invalid ID
687 t
.Error("failed to pack DNS response")
692 msg
.id
-- // restore original ID
693 msg
.answer
= []dnsRR
{
696 Name
: "www.example.com.",
707 t
.Error("failed to pack DNS response")
714 dnsMsgHdr
: dnsMsgHdr
{
717 question
: []dnsQuestion
{
719 Name
: "www.example.com.",
721 Qclass
: dnsClassINET
,
726 resp
, err
:= dnsRoundTripUDP(c
, msg
)
728 t
.Fatalf("dnsRoundTripUDP failed: %v", err
)
731 if got
:= resp
.answer
[0].(*dnsRR_A
).A
; got
!= TestAddr
{
732 t
.Errorf("got address %v, want %v", got
, TestAddr
)
736 // Issue 16865. If a name server times out, continue to the next.
737 func TestRetryTimeout(t
*testing
.T
) {
738 origTestHookDNSDialer
:= testHookDNSDialer
739 defer func() { testHookDNSDialer
= origTestHookDNSDialer
}()
741 conf
, err
:= newResolvConfTest()
745 defer conf
.teardown()
747 testConf
:= []string{
748 "nameserver 192.0.2.1", // the one that will timeout
749 "nameserver 192.0.2.2",
751 if err
:= conf
.writeAndUpdate(testConf
); err
!= nil {
755 d
:= &fakeDNSDialer
{}
756 testHookDNSDialer
= func() dnsDialer
{ return d
}
758 var deadline0 time
.Time
760 d
.rh
= func(s
string, q
*dnsMsg
, deadline time
.Time
) (*dnsMsg
, error
) {
761 t
.Log(s
, q
, deadline
)
763 if deadline
.IsZero() {
764 t
.Error("zero deadline")
767 if s
== "192.0.2.1:53" {
769 time
.Sleep(10 * time
.Millisecond
)
770 return nil, errTimeout
773 if deadline
== deadline0
{
774 t
.Error("deadline didn't change")
777 return mockTXTResponse(q
), nil
780 _
, err
= LookupTXT("www.golang.org")
785 if deadline0
.IsZero() {
786 t
.Error("deadline0 still zero", deadline0
)
790 func TestRotate(t
*testing
.T
) {
791 // without rotation, always uses the first server
792 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"})
794 // with rotation, rotates through back to first
795 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"})
798 func testRotate(t
*testing
.T
, rotate
bool, nameservers
, wantServers
[]string) {
799 origTestHookDNSDialer
:= testHookDNSDialer
800 defer func() { testHookDNSDialer
= origTestHookDNSDialer
}()
802 conf
, err
:= newResolvConfTest()
806 defer conf
.teardown()
808 var confLines
[]string
809 for _
, ns
:= range nameservers
{
810 confLines
= append(confLines
, "nameserver "+ns
)
813 confLines
= append(confLines
, "options rotate")
816 if err
:= conf
.writeAndUpdate(confLines
); err
!= nil {
820 d
:= &fakeDNSDialer
{}
821 testHookDNSDialer
= func() dnsDialer
{ return d
}
823 var usedServers
[]string
824 d
.rh
= func(s
string, q
*dnsMsg
, _ time
.Time
) (*dnsMsg
, error
) {
825 usedServers
= append(usedServers
, s
)
826 return mockTXTResponse(q
), nil
829 // len(nameservers) + 1 to allow rotation to get back to start
830 for i
:= 0; i
< len(nameservers
)+1; i
++ {
831 if _
, err
:= LookupTXT("www.golang.org"); err
!= nil {
836 if !reflect
.DeepEqual(usedServers
, wantServers
) {
837 t
.Errorf("rotate=%t got used servers:\n%v\nwant:\n%v", rotate
, usedServers
, wantServers
)
841 func mockTXTResponse(q
*dnsMsg
) *dnsMsg
{
843 dnsMsgHdr
: dnsMsgHdr
{
846 recursion_available
: true,
848 question
: q
.question
,
852 Name
: q
.question
[0].Name
,