1 // Copyright 2009 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.
23 func hasSuffixFold(s, suffix string) bool {
24 return strings.HasSuffix(strings.ToLower(s), strings.ToLower(suffix))
27 func lookupLocalhost(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
31 {IP: IPv4(127, 0, 0, 1)},
35 return fn(ctx, network, host)
39 // The Lookup APIs use various sources such as local database, DNS or
40 // mDNS, and may use platform-dependent DNS stub resolver if possible.
41 // The APIs accept any of forms for a query; host name in various
42 // encodings, UTF-8 encoded net name, domain name, FQDN or absolute
43 // FQDN, but the result would be one of the forms and it depends on
46 var lookupGoogleSRVTests = []struct {
47 service, proto, name string
51 "ldap", "tcp", "google.com",
52 "google.com.", "google.com.",
55 "ldap", "tcp", "google.com.",
56 "google.com.", "google.com.",
59 // non-standard back door
61 "", "", "_ldap._tcp.google.com",
62 "google.com.", "google.com.",
65 "", "", "_ldap._tcp.google.com.",
66 "google.com.", "google.com.",
70 var backoffDuration = [...]time.Duration{time.Second, 5 * time.Second, 30 * time.Second}
72 func TestLookupGoogleSRV(t *testing.T) {
74 mustHaveExternalNetwork(t)
76 if runtime.GOOS == "ios" {
77 t.Skip("no resolv.conf on iOS")
80 if !supportsIPv4() || !*testIPv4 {
81 t.Skip("IPv4 is required")
85 for i := 0; i < len(lookupGoogleSRVTests); i++ {
86 tt := lookupGoogleSRVTests[i]
87 cname, srvs, err := LookupSRV(tt.service, tt.proto, tt.name)
89 testenv.SkipFlakyNet(t)
90 if attempts < len(backoffDuration) {
91 dur := backoffDuration[attempts]
92 t.Logf("backoff %v after failure %v\n", dur, err)
101 t.Error("got no record")
103 if !hasSuffixFold(cname, tt.cname) {
104 t.Errorf("got %s; want %s", cname, tt.cname)
106 for _, srv := range srvs {
107 if !hasSuffixFold(srv.Target, tt.target) {
108 t.Errorf("got %v; want a record containing %s", srv, tt.target)
114 var lookupGmailMXTests = []struct {
117 {"gmail.com", "google.com."},
118 {"gmail.com.", "google.com."},
121 func TestLookupGmailMX(t *testing.T) {
123 mustHaveExternalNetwork(t)
125 if runtime.GOOS == "ios" {
126 t.Skip("no resolv.conf on iOS")
129 if !supportsIPv4() || !*testIPv4 {
130 t.Skip("IPv4 is required")
134 for i := 0; i < len(lookupGmailMXTests); i++ {
135 tt := lookupGmailMXTests[i]
136 mxs, err := LookupMX(tt.name)
138 testenv.SkipFlakyNet(t)
139 if attempts < len(backoffDuration) {
140 dur := backoffDuration[attempts]
141 t.Logf("backoff %v after failure %v\n", dur, err)
150 t.Error("got no record")
152 for _, mx := range mxs {
153 if !hasSuffixFold(mx.Host, tt.host) {
154 t.Errorf("got %v; want a record containing %s", mx, tt.host)
160 var lookupGmailNSTests = []struct {
163 {"gmail.com", "google.com."},
164 {"gmail.com.", "google.com."},
167 func TestLookupGmailNS(t *testing.T) {
169 mustHaveExternalNetwork(t)
171 if runtime.GOOS == "ios" {
172 t.Skip("no resolv.conf on iOS")
175 if !supportsIPv4() || !*testIPv4 {
176 t.Skip("IPv4 is required")
180 for i := 0; i < len(lookupGmailNSTests); i++ {
181 tt := lookupGmailNSTests[i]
182 nss, err := LookupNS(tt.name)
184 testenv.SkipFlakyNet(t)
185 if attempts < len(backoffDuration) {
186 dur := backoffDuration[attempts]
187 t.Logf("backoff %v after failure %v\n", dur, err)
196 t.Error("got no record")
198 for _, ns := range nss {
199 if !hasSuffixFold(ns.Host, tt.host) {
200 t.Errorf("got %v; want a record containing %s", ns, tt.host)
206 var lookupGmailTXTTests = []struct {
207 name, txt, host string
209 {"gmail.com", "spf", "google.com"},
210 {"gmail.com.", "spf", "google.com"},
213 func TestLookupGmailTXT(t *testing.T) {
214 if runtime.GOOS == "plan9" {
215 t.Skip("skipping on plan9; see https://golang.org/issue/29722")
218 mustHaveExternalNetwork(t)
220 if runtime.GOOS == "ios" {
221 t.Skip("no resolv.conf on iOS")
224 if !supportsIPv4() || !*testIPv4 {
225 t.Skip("IPv4 is required")
229 for i := 0; i < len(lookupGmailTXTTests); i++ {
230 tt := lookupGmailTXTTests[i]
231 txts, err := LookupTXT(tt.name)
233 testenv.SkipFlakyNet(t)
234 if attempts < len(backoffDuration) {
235 dur := backoffDuration[attempts]
236 t.Logf("backoff %v after failure %v\n", dur, err)
245 t.Error("got no record")
248 for _, txt := range txts {
249 if strings.Contains(txt, tt.txt) && (strings.HasSuffix(txt, tt.host) || strings.HasSuffix(txt, tt.host+".")) {
255 t.Errorf("got %v; want a record containing %s, %s", txts, tt.txt, tt.host)
260 var lookupGooglePublicDNSAddrTests = []string{
263 "2001:4860:4860::8888",
264 "2001:4860:4860::8844",
267 func TestLookupGooglePublicDNSAddr(t *testing.T) {
268 mustHaveExternalNetwork(t)
270 if !supportsIPv4() || !supportsIPv6() || !*testIPv4 || !*testIPv6 {
271 t.Skip("both IPv4 and IPv6 are required")
274 defer dnsWaitGroup.Wait()
276 for _, ip := range lookupGooglePublicDNSAddrTests {
277 names, err := LookupAddr(ip)
282 t.Error("got no record")
284 for _, name := range names {
285 if !hasSuffixFold(name, ".google.com.") && !hasSuffixFold(name, ".google.") {
286 t.Errorf("got %q; want a record ending in .google.com. or .google.", name)
292 func TestLookupIPv6LinkLocalAddr(t *testing.T) {
293 if !supportsIPv6() || !*testIPv6 {
294 t.Skip("IPv6 is required")
297 defer dnsWaitGroup.Wait()
299 addrs, err := LookupHost("localhost")
304 for _, addr := range addrs {
305 if addr == "fe80::1%lo0" {
311 t.Skipf("not supported on %s", runtime.GOOS)
313 if _, err := LookupAddr("fe80::1%lo0"); err != nil {
318 func TestLookupIPv6LinkLocalAddrWithZone(t *testing.T) {
319 if !supportsIPv6() || !*testIPv6 {
320 t.Skip("IPv6 is required")
323 ipaddrs, err := DefaultResolver.LookupIPAddr(context.Background(), "fe80::1%lo0")
327 for _, addr := range ipaddrs {
328 if e, a := "lo0", addr.Zone; e != a {
329 t.Errorf("wrong zone: want %q, got %q", e, a)
333 addrs, err := DefaultResolver.LookupHost(context.Background(), "fe80::1%lo0")
337 for _, addr := range addrs {
338 if e, a := "fe80::1%lo0", addr; e != a {
339 t.Errorf("wrong host: want %q got %q", e, a)
344 var lookupCNAMETests = []struct {
347 {"www.iana.org", "icann.org."},
348 {"www.iana.org.", "icann.org."},
349 {"www.google.com", "google.com."},
350 {"google.com", "google.com."},
351 {"cname-to-txt.go4.org", "test-txt-record.go4.org."},
354 func TestLookupCNAME(t *testing.T) {
355 mustHaveExternalNetwork(t)
356 testenv.SkipFlakyNet(t)
358 if !supportsIPv4() || !*testIPv4 {
359 t.Skip("IPv4 is required")
362 defer dnsWaitGroup.Wait()
365 for i := 0; i < len(lookupCNAMETests); i++ {
366 tt := lookupCNAMETests[i]
367 cname, err := LookupCNAME(tt.name)
369 testenv.SkipFlakyNet(t)
370 if attempts < len(backoffDuration) {
371 dur := backoffDuration[attempts]
372 t.Logf("backoff %v after failure %v\n", dur, err)
380 if !hasSuffixFold(cname, tt.cname) {
381 t.Errorf("got %s; want a record containing %s", cname, tt.cname)
386 var lookupGoogleHostTests = []struct {
393 func TestLookupGoogleHost(t *testing.T) {
394 mustHaveExternalNetwork(t)
395 testenv.SkipFlakyNet(t)
397 if !supportsIPv4() || !*testIPv4 {
398 t.Skip("IPv4 is required")
401 defer dnsWaitGroup.Wait()
403 for _, tt := range lookupGoogleHostTests {
404 addrs, err := LookupHost(tt.name)
409 t.Error("got no record")
411 for _, addr := range addrs {
412 if ParseIP(addr) == nil {
413 t.Errorf("got %q; want a literal IP address", addr)
419 func TestLookupLongTXT(t *testing.T) {
420 testenv.SkipFlaky(t, 22857)
421 mustHaveExternalNetwork(t)
423 defer dnsWaitGroup.Wait()
425 txts, err := LookupTXT("golang.rsc.io")
431 strings.Repeat("abcdefghijklmnopqrstuvwxyABCDEFGHJIKLMNOPQRSTUVWXY", 10),
434 if !reflect.DeepEqual(txts, want) {
435 t.Fatalf("LookupTXT golang.rsc.io incorrect\nhave %q\nwant %q", txts, want)
439 var lookupGoogleIPTests = []struct {
446 func TestLookupGoogleIP(t *testing.T) {
447 mustHaveExternalNetwork(t)
448 testenv.SkipFlakyNet(t)
450 if !supportsIPv4() || !*testIPv4 {
451 t.Skip("IPv4 is required")
454 defer dnsWaitGroup.Wait()
456 for _, tt := range lookupGoogleIPTests {
457 ips, err := LookupIP(tt.name)
462 t.Error("got no record")
464 for _, ip := range ips {
465 if ip.To4() == nil && ip.To16() == nil {
466 t.Errorf("got %v; want an IP address", ip)
472 var revAddrTests = []struct {
477 {"1.2.3.4", "4.3.2.1.in-addr.arpa.", ""},
478 {"245.110.36.114", "114.36.110.245.in-addr.arpa.", ""},
479 {"::ffff:12.34.56.78", "78.56.34.12.in-addr.arpa.", ""},
480 {"::1", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", ""},
481 {"1::", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.ip6.arpa.", ""},
482 {"1234:567::89a:bcde", "e.d.c.b.a.9.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.7.6.5.0.4.3.2.1.ip6.arpa.", ""},
483 {"1234:567:fefe:bcbc:adad:9e4a:89a:bcde", "e.d.c.b.a.9.8.0.a.4.e.9.d.a.d.a.c.b.c.b.e.f.e.f.7.6.5.0.4.3.2.1.ip6.arpa.", ""},
484 {"1.2.3", "", "unrecognized address"},
485 {"1.2.3.4.5", "", "unrecognized address"},
486 {"1234:567:bcbca::89a:bcde", "", "unrecognized address"},
487 {"1234:567::bcbc:adad::89a:bcde", "", "unrecognized address"},
490 func TestReverseAddress(t *testing.T) {
491 defer dnsWaitGroup.Wait()
492 for i, tt := range revAddrTests {
493 a, err := reverseaddr(tt.Addr)
494 if len(tt.ErrPrefix) > 0 && err == nil {
495 t.Errorf("#%d: expected %q, got <nil> (error)", i, tt.ErrPrefix)
498 if len(tt.ErrPrefix) == 0 && err != nil {
499 t.Errorf("#%d: expected <nil>, got %q (error)", i, err)
501 if err != nil && err.(*DNSError).Err != tt.ErrPrefix {
502 t.Errorf("#%d: expected %q, got %q (mismatched error)", i, tt.ErrPrefix, err.(*DNSError).Err)
505 t.Errorf("#%d: expected %q, got %q (reverse address)", i, tt.Reverse, a)
510 func TestDNSFlood(t *testing.T) {
512 t.Skip("test disabled; use -dnsflood to enable")
515 defer dnsWaitGroup.Wait()
518 if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
519 // On Darwin this test consumes kernel threads much
520 // than other platforms for some reason.
521 // When we monitor the number of allocated Ms by
522 // observing on runtime.newm calls, we can see that it
523 // easily reaches the per process ceiling
524 // kern.num_threads when CGO_ENABLED=1 and
525 // GODEBUG=netdns=go.
529 const timeout = 3 * time.Second
530 ctxHalfTimeout, cancel := context.WithTimeout(context.Background(), timeout/2)
532 ctxTimeout, cancel := context.WithTimeout(context.Background(), timeout)
535 c := make(chan error, 2*N)
536 for i := 0; i < N; i++ {
537 name := fmt.Sprintf("%d.net-test.golang.org", i)
539 _, err := DefaultResolver.LookupIPAddr(ctxHalfTimeout, name)
543 _, err := DefaultResolver.LookupIPAddr(ctxTimeout, name)
548 succeeded, failed int
549 timeout, temporary, other int
552 deadline := time.After(timeout + time.Second)
553 for i := 0; i < 2*N; i++ {
556 t.Fatal("deadline exceeded")
558 switch err := err.(type) {
569 if !err.Timeout() && !err.Temporary() {
579 // A high volume of DNS queries for sub-domain of golang.org
580 // would be coordinated by authoritative or recursive server,
581 // or stub resolver which implements query-response rate
582 // limitation, so we can expect some query successes and more
583 // failures including timeout, temporary and other here.
584 // As a rule, unknown must not be shown but it might possibly
585 // happen due to issue 4856 for now.
586 t.Logf("%v succeeded, %v failed (%v timeout, %v temporary, %v other, %v unknown)", qstats.succeeded, qstats.failed, qstats.timeout, qstats.temporary, qstats.other, qstats.unknown)
589 func TestLookupDotsWithLocalSource(t *testing.T) {
590 if !supportsIPv4() || !*testIPv4 {
591 t.Skip("IPv4 is required")
594 mustHaveExternalNetwork(t)
596 defer dnsWaitGroup.Wait()
598 for i, fn := range []func() func(){forceGoDNS, forceCgoDNS} {
603 names, err := LookupAddr("127.0.0.1")
606 t.Logf("#%d: %v", i, err)
614 for i, name := range names {
615 if strings.Index(name, ".") == len(name)-1 { // "localhost" not "localhost."
616 for j := range names {
620 if names[j] == name[:len(name)-1] {
621 // It's OK if we find the name without the dot,
622 // as some systems say 127.0.0.1 localhost localhost.
626 t.Errorf("%s: got %s; want %s", mode, name, name[:len(name)-1])
627 } else if strings.Contains(name, ".") && !strings.HasSuffix(name, ".") { // "localhost.localdomain." not "localhost.localdomain"
628 t.Errorf("%s: got %s; want name ending with trailing dot", mode, name)
634 func TestLookupDotsWithRemoteSource(t *testing.T) {
635 if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
636 testenv.SkipFlaky(t, 27992)
638 mustHaveExternalNetwork(t)
639 testenv.SkipFlakyNet(t)
641 if !supportsIPv4() || !*testIPv4 {
642 t.Skip("IPv4 is required")
645 if runtime.GOOS == "ios" {
646 t.Skip("no resolv.conf on iOS")
649 defer dnsWaitGroup.Wait()
651 if fixup := forceGoDNS(); fixup != nil {
655 if fixup := forceCgoDNS(); fixup != nil {
661 func testDots(t *testing.T, mode string) {
662 names, err := LookupAddr("8.8.8.8") // Google dns server
664 t.Errorf("LookupAddr(8.8.8.8): %v (mode=%v)", err, mode)
666 for _, name := range names {
667 if !hasSuffixFold(name, ".google.com.") && !hasSuffixFold(name, ".google.") {
668 t.Errorf("LookupAddr(8.8.8.8) = %v, want names ending in .google.com or .google with trailing dot (mode=%v)", names, mode)
674 cname, err := LookupCNAME("www.mit.edu")
676 t.Errorf("LookupCNAME(www.mit.edu, mode=%v): %v", mode, err)
677 } else if !strings.HasSuffix(cname, ".") {
678 t.Errorf("LookupCNAME(www.mit.edu) = %v, want cname ending in . with trailing dot (mode=%v)", cname, mode)
681 mxs, err := LookupMX("google.com")
683 t.Errorf("LookupMX(google.com): %v (mode=%v)", err, mode)
685 for _, mx := range mxs {
686 if !hasSuffixFold(mx.Host, ".google.com.") {
687 t.Errorf("LookupMX(google.com) = %v, want names ending in .google.com. with trailing dot (mode=%v)", mxString(mxs), mode)
693 nss, err := LookupNS("google.com")
695 t.Errorf("LookupNS(google.com): %v (mode=%v)", err, mode)
697 for _, ns := range nss {
698 if !hasSuffixFold(ns.Host, ".google.com.") {
699 t.Errorf("LookupNS(google.com) = %v, want names ending in .google.com. with trailing dot (mode=%v)", nsString(nss), mode)
705 cname, srvs, err := LookupSRV("ldap", "tcp", "google.com")
707 t.Errorf("LookupSRV(ldap, tcp, google.com): %v (mode=%v)", err, mode)
709 if !hasSuffixFold(cname, ".google.com.") {
710 t.Errorf("LookupSRV(ldap, tcp, google.com) returned cname=%v, want name ending in .google.com. with trailing dot (mode=%v)", cname, mode)
712 for _, srv := range srvs {
713 if !hasSuffixFold(srv.Target, ".google.com.") {
714 t.Errorf("LookupSRV(ldap, tcp, google.com) returned addrs=%v, want names ending in .google.com. with trailing dot (mode=%v)", srvString(srvs), mode)
721 func mxString(mxs []*MX) string {
722 var buf strings.Builder
724 fmt.Fprintf(&buf, "[")
725 for _, mx := range mxs {
726 fmt.Fprintf(&buf, "%s%s:%d", sep, mx.Host, mx.Pref)
729 fmt.Fprintf(&buf, "]")
733 func nsString(nss []*NS) string {
734 var buf strings.Builder
736 fmt.Fprintf(&buf, "[")
737 for _, ns := range nss {
738 fmt.Fprintf(&buf, "%s%s", sep, ns.Host)
741 fmt.Fprintf(&buf, "]")
745 func srvString(srvs []*SRV) string {
746 var buf strings.Builder
748 fmt.Fprintf(&buf, "[")
749 for _, srv := range srvs {
750 fmt.Fprintf(&buf, "%s%s:%d:%d:%d", sep, srv.Target, srv.Port, srv.Priority, srv.Weight)
753 fmt.Fprintf(&buf, "]")
757 func TestLookupPort(t *testing.T) {
758 // See https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
760 // Please be careful about adding new test cases.
761 // There are platforms which have incomplete mappings for
762 // restricted resource access and security reasons.
770 {"tcp", "0", 0, true},
771 {"udp", "0", 0, true},
772 {"udp", "domain", 53, true},
774 {"--badnet--", "zzz", 0, false},
775 {"tcp", "--badport--", 0, false},
776 {"tcp", "-1", 0, false},
777 {"tcp", "65536", 0, false},
778 {"udp", "-1", 0, false},
779 {"udp", "65536", 0, false},
780 {"tcp", "123456789", 0, false},
782 // Issue 13610: LookupPort("tcp", "")
783 {"tcp", "", 0, true},
784 {"tcp4", "", 0, true},
785 {"tcp6", "", 0, true},
786 {"udp", "", 0, true},
787 {"udp4", "", 0, true},
788 {"udp6", "", 0, true},
791 switch runtime.GOOS {
794 t.Skipf("not supported on %s without cgo; see golang.org/issues/14576", runtime.GOOS)
797 tests = append(tests, test{"tcp", "http", 80, true})
800 for _, tt := range tests {
801 port, err := LookupPort(tt.network, tt.name)
802 if port != tt.port || (err == nil) != tt.ok {
803 t.Errorf("LookupPort(%q, %q) = %d, %v; want %d, error=%t", tt.network, tt.name, port, err, tt.port, !tt.ok)
806 if perr := parseLookupPortError(err); perr != nil {
813 // Like TestLookupPort but with minimal tests that should always pass
814 // because the answers are baked-in to the net package.
815 func TestLookupPort_Minimal(t *testing.T) {
823 {"tcp", "HTTP", 80}, // case shouldn't matter
824 {"tcp", "https", 443},
826 {"tcp", "gopher", 70},
827 {"tcp4", "http", 80},
828 {"tcp6", "http", 80},
831 for _, tt := range tests {
832 port, err := LookupPort(tt.network, tt.name)
833 if port != tt.port || err != nil {
834 t.Errorf("LookupPort(%q, %q) = %d, %v; want %d, error=nil", tt.network, tt.name, port, err, tt.port)
839 func TestLookupProtocol_Minimal(t *testing.T) {
846 {"TcP", 6}, // case shouldn't matter
853 for _, tt := range tests {
854 got, err := lookupProtocol(context.Background(), tt.name)
855 if got != tt.want || err != nil {
856 t.Errorf("LookupProtocol(%q) = %d, %v; want %d, error=nil", tt.name, got, err, tt.want)
862 func TestLookupNonLDH(t *testing.T) {
863 defer dnsWaitGroup.Wait()
865 if fixup := forceGoDNS(); fixup != nil {
869 // "LDH" stands for letters, digits, and hyphens and is the usual
870 // description of standard DNS names.
871 // This test is checking that other kinds of names are reported
872 // as not found, not reported as invalid names.
873 addrs, err := LookupHost("!!!.###.bogus..domain.")
875 t.Fatalf("lookup succeeded: %v", addrs)
877 if !strings.HasSuffix(err.Error(), errNoSuchHost.Error()) {
878 t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost)
880 if !err.(*DNSError).IsNotFound {
881 t.Fatalf("lookup error = %v, want true", err.(*DNSError).IsNotFound)
885 func TestLookupContextCancel(t *testing.T) {
886 mustHaveExternalNetwork(t)
887 testenv.SkipFlakyNet(t)
889 origTestHookLookupIP := testHookLookupIP
892 testHookLookupIP = origTestHookLookupIP
895 lookupCtx, cancelLookup := context.WithCancel(context.Background())
896 unblockLookup := make(chan struct{})
898 // Set testHookLookupIP to start a new, concurrent call to LookupIPAddr
899 // and cancel the original one, then block until the canceled call has returned
900 // (ensuring that it has performed any synchronous cleanup).
901 testHookLookupIP = func(
903 fn func(context.Context, string, string) ([]IPAddr, error),
906 ) ([]IPAddr, error) {
908 case <-unblockLookup:
910 // Start a concurrent LookupIPAddr for the same host while the caller is
911 // still blocked, and sleep a little to give it time to be deduplicated
912 // before we cancel (and unblock) the caller.
913 // (If the timing doesn't quite work out, we'll end up testing sequential
914 // calls instead of concurrent ones, but the test should still pass.)
915 t.Logf("starting concurrent LookupIPAddr")
918 defer dnsWaitGroup.Done()
919 _, err := DefaultResolver.LookupIPAddr(context.Background(), host)
924 time.Sleep(1 * time.Millisecond)
929 // If the concurrent lookup above is deduplicated to this one
930 // (as we expect to happen most of the time), it is important
931 // that the original call does not cancel the shared Context.
932 // (See https://go.dev/issue/22724.) Explicitly check for
933 // cancellation now, just in case fn itself doesn't notice it.
934 if err := ctx.Err(); err != nil {
935 t.Logf("testHookLookupIP canceled")
938 t.Logf("testHookLookupIP performing lookup")
939 return fn(ctx, network, host)
942 _, err := DefaultResolver.LookupIPAddr(lookupCtx, "google.com")
943 if dnsErr, ok := err.(*DNSError); !ok || dnsErr.Err != errCanceled.Error() {
944 t.Errorf("unexpected error from canceled, blocked LookupIPAddr: %v", err)
949 // Issue 24330: treat the nil *Resolver like a zero value. Verify nothing
950 // crashes if nil is used.
951 func TestNilResolverLookup(t *testing.T) {
952 mustHaveExternalNetwork(t)
953 var r *Resolver = nil
954 ctx := context.Background()
956 // Don't care about the results, just that nothing panics:
957 r.LookupAddr(ctx, "8.8.8.8")
958 r.LookupCNAME(ctx, "google.com")
959 r.LookupHost(ctx, "google.com")
960 r.LookupIPAddr(ctx, "google.com")
961 r.LookupIP(ctx, "ip", "google.com")
962 r.LookupMX(ctx, "gmail.com")
963 r.LookupNS(ctx, "google.com")
964 r.LookupPort(ctx, "tcp", "smtp")
965 r.LookupSRV(ctx, "service", "proto", "name")
966 r.LookupTXT(ctx, "gmail.com")
969 // TestLookupHostCancel verifies that lookup works even after many
970 // canceled lookups (see golang.org/issue/24178 for details).
971 func TestLookupHostCancel(t *testing.T) {
972 mustHaveExternalNetwork(t)
973 testenv.SkipFlakyNet(t)
974 t.Parallel() // Executes 600ms worth of sequential sleeps.
977 google = "www.google.com"
978 invalidDomain = "invalid.invalid" // RFC 2606 reserves .invalid
979 n = 600 // this needs to be larger than threadLimit size
982 _, err := LookupHost(google)
987 ctx, cancel := context.WithCancel(context.Background())
989 for i := 0; i < n; i++ {
990 addr, err := DefaultResolver.LookupHost(ctx, invalidDomain)
992 t.Fatalf("LookupHost(%q): returns %v, but should fail", invalidDomain, addr)
995 // Don't verify what the actual error is.
996 // We know that it must be non-nil because the domain is invalid,
997 // but we don't have any guarantee that LookupHost actually bothers
998 // to check for cancellation on the fast path.
999 // (For example, it could use a local cache to avoid blocking entirely.)
1001 // The lookup may deduplicate in-flight requests, so give it time to settle
1003 time.Sleep(time.Millisecond * 1)
1006 _, err = LookupHost(google)
1012 type lookupCustomResolver struct {
1018 func (lcr *lookupCustomResolver) dial() func(ctx context.Context, network, address string) (Conn, error) {
1019 return func(ctx context.Context, network, address string) (Conn, error) {
1023 return Dial(network, address)
1027 // TestConcurrentPreferGoResolversDial tests that multiple resolvers with the
1028 // PreferGo option used concurrently are all dialed properly.
1029 func TestConcurrentPreferGoResolversDial(t *testing.T) {
1030 switch runtime.GOOS {
1032 // TODO: plan9 implementation of the resolver uses the Dial function since
1033 // https://go.dev/cl/409234, this test could probably be reenabled.
1034 t.Skipf("skip on %v", runtime.GOOS)
1037 testenv.MustHaveExternalNetwork(t)
1038 testenv.SkipFlakyNet(t)
1040 defer dnsWaitGroup.Wait()
1042 resolvers := make([]*lookupCustomResolver, 2)
1043 for i := range resolvers {
1044 cs := lookupCustomResolver{Resolver: &Resolver{PreferGo: true}}
1049 var wg sync.WaitGroup
1050 wg.Add(len(resolvers))
1051 for i, resolver := range resolvers {
1052 go func(r *Resolver, index int) {
1054 _, err := r.LookupIPAddr(context.Background(), "google.com")
1056 t.Errorf("lookup failed for resolver %d: %q", index, err)
1058 }(resolver.Resolver, i)
1066 for i, resolver := range resolvers {
1067 if !resolver.dialed {
1068 t.Errorf("custom resolver %d not dialed during lookup", i)
1073 var ipVersionTests = []struct {
1090 func TestIPVersion(t *testing.T) {
1091 for _, tt := range ipVersionTests {
1092 if version := ipVersion(tt.network); version != tt.version {
1093 t.Errorf("Family for: %s. Expected: %s, Got: %s", tt.network,
1094 string(tt.version), string(version))
1099 // Issue 28600: The context that is used to lookup ips should always
1100 // preserve the values from the context that was passed into LookupIPAddr.
1101 func TestLookupIPAddrPreservesContextValues(t *testing.T) {
1102 origTestHookLookupIP := testHookLookupIP
1103 defer func() { testHookLookupIP = origTestHookLookupIP }()
1105 keyValues := []struct {
1110 {new(float64), 137},
1112 ctx := context.Background()
1113 for _, kv := range keyValues {
1114 ctx = context.WithValue(ctx, kv.key, kv.value)
1117 wantIPs := []IPAddr{
1118 {IP: IPv4(127, 0, 0, 1)},
1122 checkCtxValues := func(ctx_ context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
1123 for _, kv := range keyValues {
1124 g, w := ctx_.Value(kv.key), kv.value
1125 if !reflect.DeepEqual(g, w) {
1126 t.Errorf("Value lookup:\n\tGot: %v\n\tWant: %v", g, w)
1131 testHookLookupIP = checkCtxValues
1133 resolvers := []*Resolver{
1138 for i, resolver := range resolvers {
1139 gotIPs, err := resolver.LookupIPAddr(ctx, "golang.org")
1141 t.Errorf("Resolver #%d: unexpected error: %v", i, err)
1143 if !reflect.DeepEqual(gotIPs, wantIPs) {
1144 t.Errorf("#%d: mismatched IPAddr results\n\tGot: %v\n\tWant: %v", i, gotIPs, wantIPs)
1149 // Issue 30521: The lookup group should call the resolver for each network.
1150 func TestLookupIPAddrConcurrentCallsForNetworks(t *testing.T) {
1151 origTestHookLookupIP := testHookLookupIP
1152 defer func() { testHookLookupIP = origTestHookLookupIP }()
1154 queries := [][]string{
1155 {"udp", "golang.org"},
1156 {"udp4", "golang.org"},
1157 {"udp6", "golang.org"},
1158 {"udp", "golang.org"},
1159 {"udp", "golang.org"},
1161 results := map[[2]string][]IPAddr{
1162 {"udp", "golang.org"}: {
1163 {IP: IPv4(127, 0, 0, 1)},
1166 {"udp4", "golang.org"}: {
1167 {IP: IPv4(127, 0, 0, 1)},
1169 {"udp6", "golang.org"}: {
1174 waitCh := make(chan struct{})
1175 testHookLookupIP = func(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
1176 // We'll block until this is called one time for each different
1177 // expected result. This will ensure that the lookup group would wait
1178 // for the existing call if it was to be reused.
1179 if atomic.AddInt32(&calls, 1) == int32(len(results)) {
1185 return nil, ctx.Err()
1187 return results[[2]string{network, host}], nil
1190 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
1192 wg := sync.WaitGroup{}
1193 for _, q := range queries {
1199 gotIPs, err := DefaultResolver.lookupIPAddr(ctx, network, host)
1201 t.Errorf("lookupIPAddr(%v, %v): unexpected error: %v", network, host, err)
1203 wantIPs := results[[2]string{network, host}]
1204 if !reflect.DeepEqual(gotIPs, wantIPs) {
1205 t.Errorf("lookupIPAddr(%v, %v): mismatched IPAddr results\n\tGot: %v\n\tWant: %v", network, host, gotIPs, wantIPs)
1212 // Issue 53995: Resolver.LookupIP should return error for empty host name.
1213 func TestResolverLookupIPWithEmptyHost(t *testing.T) {
1214 _, err := DefaultResolver.LookupIP(context.Background(), "ip", "")
1216 t.Fatal("DefaultResolver.LookupIP for empty host success, want no host error")
1218 if !strings.HasSuffix(err.Error(), errNoSuchHost.Error()) {
1219 t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost)
1223 func TestWithUnexpiredValuesPreserved(t *testing.T) {
1224 ctx, cancel := context.WithCancel(context.Background())
1226 // Insert a value into it.
1227 key, value := "key-1", 2
1228 ctx = context.WithValue(ctx, key, value)
1230 // Now use the "values preserving context" like
1231 // we would for LookupIPAddr. See Issue 28600.
1232 ctx = withUnexpiredValuesPreserved(ctx)
1234 // Lookup before expiry.
1235 if g, w := ctx.Value(key), value; g != w {
1236 t.Errorf("Lookup before expiry: Got %v Want %v", g, w)
1239 // Cancel the context.
1242 // Lookup after expiry should return nil
1243 if g := ctx.Value(key); g != nil {
1244 t.Errorf("Lookup after expiry: Got %v want nil", g)
1248 // Issue 31597: don't panic on null byte in name
1249 func TestLookupNullByte(t *testing.T) {
1250 testenv.MustHaveExternalNetwork(t)
1251 testenv.SkipFlakyNet(t)
1252 LookupHost("foo\x00bar") // check that it doesn't panic; it used to on Windows
1255 func TestResolverLookupIP(t *testing.T) {
1256 testenv.MustHaveExternalNetwork(t)
1258 v4Ok := supportsIPv4() && *testIPv4
1259 v6Ok := supportsIPv6() && *testIPv6
1261 defer dnsWaitGroup.Wait()
1263 for _, impl := range []struct {
1268 {"cgo", forceCgoDNS},
1270 t.Run("implementation: "+impl.name, func(t *testing.T) {
1273 t.Skip("not supported")
1277 for _, network := range []string{"ip", "ip4", "ip6"} {
1278 t.Run("network: "+network, func(t *testing.T) {
1280 case network == "ip4" && !v4Ok:
1281 t.Skip("IPv4 is not supported")
1282 case network == "ip6" && !v6Ok:
1283 t.Skip("IPv6 is not supported")
1286 // google.com has both A and AAAA records.
1287 const host = "google.com"
1288 ips, err := DefaultResolver.LookupIP(context.Background(), network, host)
1290 testenv.SkipFlakyNet(t)
1291 t.Fatalf("DefaultResolver.LookupIP(%q, %q): failed with unexpected error: %v", network, host, err)
1294 var v4Addrs []netip.Addr
1295 var v6Addrs []netip.Addr
1296 for _, ip := range ips {
1297 if addr, ok := netip.AddrFromSlice(ip); ok {
1299 v4Addrs = append(v4Addrs, addr)
1301 v6Addrs = append(v6Addrs, addr)
1304 t.Fatalf("IP=%q is neither IPv4 nor IPv6", ip)
1308 // Check that we got the expected addresses.
1309 if network == "ip4" || network == "ip" && v4Ok {
1310 if len(v4Addrs) == 0 {
1311 t.Errorf("DefaultResolver.LookupIP(%q, %q): no IPv4 addresses", network, host)
1314 if network == "ip6" || network == "ip" && v6Ok {
1315 if len(v6Addrs) == 0 {
1316 t.Errorf("DefaultResolver.LookupIP(%q, %q): no IPv6 addresses", network, host)
1320 // Check that we didn't get any unexpected addresses.
1321 if network == "ip6" && len(v4Addrs) > 0 {
1322 t.Errorf("DefaultResolver.LookupIP(%q, %q): unexpected IPv4 addresses: %v", network, host, v4Addrs)
1324 if network == "ip4" && len(v6Addrs) > 0 {
1325 t.Errorf("DefaultResolver.LookupIP(%q, %q): unexpected IPv6 or IPv4-mapped IPv6 addresses: %v", network, host, v6Addrs)
1333 // A context timeout should still return a DNSError.
1334 func TestDNSTimeout(t *testing.T) {
1335 origTestHookLookupIP := testHookLookupIP
1336 defer func() { testHookLookupIP = origTestHookLookupIP }()
1337 defer dnsWaitGroup.Wait()
1339 timeoutHookGo := make(chan bool, 1)
1340 timeoutHook := func(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
1342 return nil, context.DeadlineExceeded
1344 testHookLookupIP = timeoutHook
1346 checkErr := func(err error) {
1349 t.Error("expected an error")
1350 } else if dnserr, ok := err.(*DNSError); !ok {
1351 t.Errorf("got error type %T, want %T", err, (*DNSError)(nil))
1352 } else if !dnserr.IsTimeout {
1353 t.Errorf("got error %#v, want IsTimeout == true", dnserr)
1354 } else if isTimeout := dnserr.Timeout(); !isTimeout {
1355 t.Errorf("got err.Timeout() == %t, want true", isTimeout)
1360 timeoutHookGo <- true
1361 _, err := LookupIP("golang.org")
1365 var err1, err2 error
1366 var wg sync.WaitGroup
1370 _, err1 = LookupIP("golang1.org")
1374 _, err2 = LookupIP("golang1.org")
1376 close(timeoutHookGo)
1381 // Double lookup with context.
1382 timeoutHookGo = make(chan bool)
1383 ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
1387 _, err1 = DefaultResolver.LookupIPAddr(ctx, "golang2.org")
1391 _, err2 = DefaultResolver.LookupIPAddr(ctx, "golang2.org")
1393 time.Sleep(10 * time.Nanosecond)
1394 close(timeoutHookGo)
1401 func TestLookupNoData(t *testing.T) {
1402 if runtime.GOOS == "plan9" {
1403 t.Skip("not supported on plan9")
1406 mustHaveExternalNetwork(t)
1408 testLookupNoData(t, "default resolver")
1411 defer forceGoDNS()()
1412 testLookupNoData(t, "forced go resolver")
1416 defer forceCgoDNS()()
1417 testLookupNoData(t, "forced cgo resolver")
1421 func testLookupNoData(t *testing.T, prefix string) {
1424 // Domain that doesn't have any A/AAAA RRs, but has different one (in this case a TXT),
1425 // so that it returns an empty response without any error codes (NXDOMAIN).
1426 _, err := LookupHost("golang.rsc.io.")
1428 t.Errorf("%v: unexpected success", prefix)
1432 var dnsErr *DNSError
1433 if errors.As(err, &dnsErr) {
1435 if !dnsErr.IsNotFound {
1437 t.Logf("%v: IsNotFound is set to false", prefix)
1440 if dnsErr.Err != errNoSuchHost.Error() {
1442 t.Logf("%v: error message is not equal to: %v", prefix, errNoSuchHost.Error())
1450 testenv.SkipFlakyNet(t)
1451 if attempts < len(backoffDuration) {
1452 dur := backoffDuration[attempts]
1453 t.Logf("%v: backoff %v after failure %v\n", prefix, dur, err)
1459 t.Errorf("%v: unexpected error: %v", prefix, err)
1464 func TestLookupPortNotFound(t *testing.T) {
1465 allResolvers(t, func(t *testing.T) {
1466 _, err := LookupPort("udp", "_-unknown-service-")
1467 var dnsErr *DNSError
1468 if !errors.As(err, &dnsErr) || !dnsErr.IsNotFound {
1469 t.Fatalf("unexpected error: %v", err)
1474 // submissions service is only available through a tcp network, see:
1475 // https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=submissions
1476 var tcpOnlyService = func() string {
1477 // plan9 does not have submissions service defined in the service database.
1478 if runtime.GOOS == "plan9" {
1481 return "submissions"
1484 func TestLookupPortDifferentNetwork(t *testing.T) {
1485 allResolvers(t, func(t *testing.T) {
1486 _, err := LookupPort("udp", tcpOnlyService)
1487 var dnsErr *DNSError
1488 if !errors.As(err, &dnsErr) || !dnsErr.IsNotFound {
1489 t.Fatalf("unexpected error: %v", err)
1494 func TestLookupPortEmptyNetworkString(t *testing.T) {
1495 allResolvers(t, func(t *testing.T) {
1496 _, err := LookupPort("", tcpOnlyService)
1498 t.Fatalf("unexpected error: %v", err)
1503 func TestLookupPortIPNetworkString(t *testing.T) {
1504 allResolvers(t, func(t *testing.T) {
1505 _, err := LookupPort("ip", tcpOnlyService)
1507 t.Fatalf("unexpected error: %v", err)
1512 func allResolvers(t *testing.T, f func(t *testing.T)) {
1513 t.Run("default resolver", f)
1514 t.Run("forced go resolver", func(t *testing.T) {
1515 if fixup := forceGoDNS(); fixup != nil {
1520 t.Run("forced cgo resolver", func(t *testing.T) {
1521 if fixup := forceCgoDNS(); fixup != nil {
1528 func TestLookupNoSuchHost(t *testing.T) {
1529 mustHaveExternalNetwork(t)
1531 const testNXDOMAIN = "invalid.invalid."
1532 const testNODATA = "_ldap._tcp.google.com."
1539 name: "LookupCNAME NXDOMAIN",
1540 query: func() error {
1541 _, err := LookupCNAME(testNXDOMAIN)
1546 name: "LookupHost NXDOMAIN",
1547 query: func() error {
1548 _, err := LookupHost(testNXDOMAIN)
1553 name: "LookupHost NODATA",
1554 query: func() error {
1555 _, err := LookupHost(testNODATA)
1560 name: "LookupMX NXDOMAIN",
1561 query: func() error {
1562 _, err := LookupMX(testNXDOMAIN)
1567 name: "LookupMX NODATA",
1568 query: func() error {
1569 _, err := LookupMX(testNODATA)
1574 name: "LookupNS NXDOMAIN",
1575 query: func() error {
1576 _, err := LookupNS(testNXDOMAIN)
1581 name: "LookupNS NODATA",
1582 query: func() error {
1583 _, err := LookupNS(testNODATA)
1588 name: "LookupSRV NXDOMAIN",
1589 query: func() error {
1590 _, _, err := LookupSRV("unknown", "tcp", testNXDOMAIN)
1595 name: "LookupTXT NXDOMAIN",
1596 query: func() error {
1597 _, err := LookupTXT(testNXDOMAIN)
1602 name: "LookupTXT NODATA",
1603 query: func() error {
1604 _, err := LookupTXT(testNODATA)
1610 for _, v := range tests {
1611 t.Run(v.name, func(t *testing.T) {
1612 allResolvers(t, func(t *testing.T) {
1617 t.Errorf("unexpected success")
1620 if dnsErr, ok := err.(*DNSError); ok {
1622 if !dnsErr.IsNotFound {
1624 t.Log("IsNotFound is set to false")
1626 if dnsErr.Err != errNoSuchHost.Error() {
1628 t.Logf("error message is not equal to: %v", errNoSuchHost.Error())
1634 testenv.SkipFlakyNet(t)
1635 if attempts < len(backoffDuration) {
1636 dur := backoffDuration[attempts]
1637 t.Logf("backoff %v after failure %v\n", dur, err)
1642 t.Errorf("unexpected error: %v", err)