]> Cypherpunks.ru repositories - gostls13.git/blob - src/net/lookup_test.go
cmd/compile/internal/inline: score call sites exposed by inlines
[gostls13.git] / src / net / lookup_test.go
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.
4
5 package net
6
7 import (
8         "context"
9         "errors"
10         "fmt"
11         "internal/testenv"
12         "net/netip"
13         "reflect"
14         "runtime"
15         "sort"
16         "strings"
17         "sync"
18         "sync/atomic"
19         "testing"
20         "time"
21 )
22
23 func hasSuffixFold(s, suffix string) bool {
24         return strings.HasSuffix(strings.ToLower(s), strings.ToLower(suffix))
25 }
26
27 func lookupLocalhost(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
28         switch host {
29         case "localhost":
30                 return []IPAddr{
31                         {IP: IPv4(127, 0, 0, 1)},
32                         {IP: IPv6loopback},
33                 }, nil
34         default:
35                 return fn(ctx, network, host)
36         }
37 }
38
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
44 // the circumstances.
45
46 var lookupGoogleSRVTests = []struct {
47         service, proto, name string
48         cname, target        string
49 }{
50         {
51                 "ldap", "tcp", "google.com",
52                 "google.com.", "google.com.",
53         },
54         {
55                 "ldap", "tcp", "google.com.",
56                 "google.com.", "google.com.",
57         },
58
59         // non-standard back door
60         {
61                 "", "", "_ldap._tcp.google.com",
62                 "google.com.", "google.com.",
63         },
64         {
65                 "", "", "_ldap._tcp.google.com.",
66                 "google.com.", "google.com.",
67         },
68 }
69
70 var backoffDuration = [...]time.Duration{time.Second, 5 * time.Second, 30 * time.Second}
71
72 func TestLookupGoogleSRV(t *testing.T) {
73         t.Parallel()
74         mustHaveExternalNetwork(t)
75
76         if runtime.GOOS == "ios" {
77                 t.Skip("no resolv.conf on iOS")
78         }
79
80         if !supportsIPv4() || !*testIPv4 {
81                 t.Skip("IPv4 is required")
82         }
83
84         attempts := 0
85         for i := 0; i < len(lookupGoogleSRVTests); i++ {
86                 tt := lookupGoogleSRVTests[i]
87                 cname, srvs, err := LookupSRV(tt.service, tt.proto, tt.name)
88                 if err != nil {
89                         testenv.SkipFlakyNet(t)
90                         if attempts < len(backoffDuration) {
91                                 dur := backoffDuration[attempts]
92                                 t.Logf("backoff %v after failure %v\n", dur, err)
93                                 time.Sleep(dur)
94                                 attempts++
95                                 i--
96                                 continue
97                         }
98                         t.Fatal(err)
99                 }
100                 if len(srvs) == 0 {
101                         t.Error("got no record")
102                 }
103                 if !hasSuffixFold(cname, tt.cname) {
104                         t.Errorf("got %s; want %s", cname, tt.cname)
105                 }
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)
109                         }
110                 }
111         }
112 }
113
114 var lookupGmailMXTests = []struct {
115         name, host string
116 }{
117         {"gmail.com", "google.com."},
118         {"gmail.com.", "google.com."},
119 }
120
121 func TestLookupGmailMX(t *testing.T) {
122         t.Parallel()
123         mustHaveExternalNetwork(t)
124
125         if runtime.GOOS == "ios" {
126                 t.Skip("no resolv.conf on iOS")
127         }
128
129         if !supportsIPv4() || !*testIPv4 {
130                 t.Skip("IPv4 is required")
131         }
132
133         attempts := 0
134         for i := 0; i < len(lookupGmailMXTests); i++ {
135                 tt := lookupGmailMXTests[i]
136                 mxs, err := LookupMX(tt.name)
137                 if err != nil {
138                         testenv.SkipFlakyNet(t)
139                         if attempts < len(backoffDuration) {
140                                 dur := backoffDuration[attempts]
141                                 t.Logf("backoff %v after failure %v\n", dur, err)
142                                 time.Sleep(dur)
143                                 attempts++
144                                 i--
145                                 continue
146                         }
147                         t.Fatal(err)
148                 }
149                 if len(mxs) == 0 {
150                         t.Error("got no record")
151                 }
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)
155                         }
156                 }
157         }
158 }
159
160 var lookupGmailNSTests = []struct {
161         name, host string
162 }{
163         {"gmail.com", "google.com."},
164         {"gmail.com.", "google.com."},
165 }
166
167 func TestLookupGmailNS(t *testing.T) {
168         t.Parallel()
169         mustHaveExternalNetwork(t)
170
171         if runtime.GOOS == "ios" {
172                 t.Skip("no resolv.conf on iOS")
173         }
174
175         if !supportsIPv4() || !*testIPv4 {
176                 t.Skip("IPv4 is required")
177         }
178
179         attempts := 0
180         for i := 0; i < len(lookupGmailNSTests); i++ {
181                 tt := lookupGmailNSTests[i]
182                 nss, err := LookupNS(tt.name)
183                 if err != nil {
184                         testenv.SkipFlakyNet(t)
185                         if attempts < len(backoffDuration) {
186                                 dur := backoffDuration[attempts]
187                                 t.Logf("backoff %v after failure %v\n", dur, err)
188                                 time.Sleep(dur)
189                                 attempts++
190                                 i--
191                                 continue
192                         }
193                         t.Fatal(err)
194                 }
195                 if len(nss) == 0 {
196                         t.Error("got no record")
197                 }
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)
201                         }
202                 }
203         }
204 }
205
206 var lookupGmailTXTTests = []struct {
207         name, txt, host string
208 }{
209         {"gmail.com", "spf", "google.com"},
210         {"gmail.com.", "spf", "google.com"},
211 }
212
213 func TestLookupGmailTXT(t *testing.T) {
214         if runtime.GOOS == "plan9" {
215                 t.Skip("skipping on plan9; see https://golang.org/issue/29722")
216         }
217         t.Parallel()
218         mustHaveExternalNetwork(t)
219
220         if runtime.GOOS == "ios" {
221                 t.Skip("no resolv.conf on iOS")
222         }
223
224         if !supportsIPv4() || !*testIPv4 {
225                 t.Skip("IPv4 is required")
226         }
227
228         attempts := 0
229         for i := 0; i < len(lookupGmailTXTTests); i++ {
230                 tt := lookupGmailTXTTests[i]
231                 txts, err := LookupTXT(tt.name)
232                 if err != nil {
233                         testenv.SkipFlakyNet(t)
234                         if attempts < len(backoffDuration) {
235                                 dur := backoffDuration[attempts]
236                                 t.Logf("backoff %v after failure %v\n", dur, err)
237                                 time.Sleep(dur)
238                                 attempts++
239                                 i--
240                                 continue
241                         }
242                         t.Fatal(err)
243                 }
244                 if len(txts) == 0 {
245                         t.Error("got no record")
246                 }
247                 found := false
248                 for _, txt := range txts {
249                         if strings.Contains(txt, tt.txt) && (strings.HasSuffix(txt, tt.host) || strings.HasSuffix(txt, tt.host+".")) {
250                                 found = true
251                                 break
252                         }
253                 }
254                 if !found {
255                         t.Errorf("got %v; want a record containing %s, %s", txts, tt.txt, tt.host)
256                 }
257         }
258 }
259
260 var lookupGooglePublicDNSAddrTests = []string{
261         "8.8.8.8",
262         "8.8.4.4",
263         "2001:4860:4860::8888",
264         "2001:4860:4860::8844",
265 }
266
267 func TestLookupGooglePublicDNSAddr(t *testing.T) {
268         mustHaveExternalNetwork(t)
269
270         if !supportsIPv4() || !supportsIPv6() || !*testIPv4 || !*testIPv6 {
271                 t.Skip("both IPv4 and IPv6 are required")
272         }
273
274         defer dnsWaitGroup.Wait()
275
276         for _, ip := range lookupGooglePublicDNSAddrTests {
277                 names, err := LookupAddr(ip)
278                 if err != nil {
279                         t.Fatal(err)
280                 }
281                 if len(names) == 0 {
282                         t.Error("got no record")
283                 }
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)
287                         }
288                 }
289         }
290 }
291
292 func TestLookupIPv6LinkLocalAddr(t *testing.T) {
293         if !supportsIPv6() || !*testIPv6 {
294                 t.Skip("IPv6 is required")
295         }
296
297         defer dnsWaitGroup.Wait()
298
299         addrs, err := LookupHost("localhost")
300         if err != nil {
301                 t.Fatal(err)
302         }
303         found := false
304         for _, addr := range addrs {
305                 if addr == "fe80::1%lo0" {
306                         found = true
307                         break
308                 }
309         }
310         if !found {
311                 t.Skipf("not supported on %s", runtime.GOOS)
312         }
313         if _, err := LookupAddr("fe80::1%lo0"); err != nil {
314                 t.Error(err)
315         }
316 }
317
318 func TestLookupIPv6LinkLocalAddrWithZone(t *testing.T) {
319         if !supportsIPv6() || !*testIPv6 {
320                 t.Skip("IPv6 is required")
321         }
322
323         ipaddrs, err := DefaultResolver.LookupIPAddr(context.Background(), "fe80::1%lo0")
324         if err != nil {
325                 t.Error(err)
326         }
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)
330                 }
331         }
332
333         addrs, err := DefaultResolver.LookupHost(context.Background(), "fe80::1%lo0")
334         if err != nil {
335                 t.Error(err)
336         }
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)
340                 }
341         }
342 }
343
344 var lookupCNAMETests = []struct {
345         name, cname string
346 }{
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."},
352 }
353
354 func TestLookupCNAME(t *testing.T) {
355         mustHaveExternalNetwork(t)
356         testenv.SkipFlakyNet(t)
357
358         if !supportsIPv4() || !*testIPv4 {
359                 t.Skip("IPv4 is required")
360         }
361
362         defer dnsWaitGroup.Wait()
363
364         attempts := 0
365         for i := 0; i < len(lookupCNAMETests); i++ {
366                 tt := lookupCNAMETests[i]
367                 cname, err := LookupCNAME(tt.name)
368                 if err != nil {
369                         testenv.SkipFlakyNet(t)
370                         if attempts < len(backoffDuration) {
371                                 dur := backoffDuration[attempts]
372                                 t.Logf("backoff %v after failure %v\n", dur, err)
373                                 time.Sleep(dur)
374                                 attempts++
375                                 i--
376                                 continue
377                         }
378                         t.Fatal(err)
379                 }
380                 if !hasSuffixFold(cname, tt.cname) {
381                         t.Errorf("got %s; want a record containing %s", cname, tt.cname)
382                 }
383         }
384 }
385
386 var lookupGoogleHostTests = []struct {
387         name string
388 }{
389         {"google.com"},
390         {"google.com."},
391 }
392
393 func TestLookupGoogleHost(t *testing.T) {
394         mustHaveExternalNetwork(t)
395         testenv.SkipFlakyNet(t)
396
397         if !supportsIPv4() || !*testIPv4 {
398                 t.Skip("IPv4 is required")
399         }
400
401         defer dnsWaitGroup.Wait()
402
403         for _, tt := range lookupGoogleHostTests {
404                 addrs, err := LookupHost(tt.name)
405                 if err != nil {
406                         t.Fatal(err)
407                 }
408                 if len(addrs) == 0 {
409                         t.Error("got no record")
410                 }
411                 for _, addr := range addrs {
412                         if ParseIP(addr) == nil {
413                                 t.Errorf("got %q; want a literal IP address", addr)
414                         }
415                 }
416         }
417 }
418
419 func TestLookupLongTXT(t *testing.T) {
420         testenv.SkipFlaky(t, 22857)
421         mustHaveExternalNetwork(t)
422
423         defer dnsWaitGroup.Wait()
424
425         txts, err := LookupTXT("golang.rsc.io")
426         if err != nil {
427                 t.Fatal(err)
428         }
429         sort.Strings(txts)
430         want := []string{
431                 strings.Repeat("abcdefghijklmnopqrstuvwxyABCDEFGHJIKLMNOPQRSTUVWXY", 10),
432                 "gophers rule",
433         }
434         if !reflect.DeepEqual(txts, want) {
435                 t.Fatalf("LookupTXT golang.rsc.io incorrect\nhave %q\nwant %q", txts, want)
436         }
437 }
438
439 var lookupGoogleIPTests = []struct {
440         name string
441 }{
442         {"google.com"},
443         {"google.com."},
444 }
445
446 func TestLookupGoogleIP(t *testing.T) {
447         mustHaveExternalNetwork(t)
448         testenv.SkipFlakyNet(t)
449
450         if !supportsIPv4() || !*testIPv4 {
451                 t.Skip("IPv4 is required")
452         }
453
454         defer dnsWaitGroup.Wait()
455
456         for _, tt := range lookupGoogleIPTests {
457                 ips, err := LookupIP(tt.name)
458                 if err != nil {
459                         t.Fatal(err)
460                 }
461                 if len(ips) == 0 {
462                         t.Error("got no record")
463                 }
464                 for _, ip := range ips {
465                         if ip.To4() == nil && ip.To16() == nil {
466                                 t.Errorf("got %v; want an IP address", ip)
467                         }
468                 }
469         }
470 }
471
472 var revAddrTests = []struct {
473         Addr      string
474         Reverse   string
475         ErrPrefix string
476 }{
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"},
488 }
489
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)
496                         continue
497                 }
498                 if len(tt.ErrPrefix) == 0 && err != nil {
499                         t.Errorf("#%d: expected <nil>, got %q (error)", i, err)
500                 }
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)
503                 }
504                 if a != tt.Reverse {
505                         t.Errorf("#%d: expected %q, got %q (reverse address)", i, tt.Reverse, a)
506                 }
507         }
508 }
509
510 func TestDNSFlood(t *testing.T) {
511         if !*testDNSFlood {
512                 t.Skip("test disabled; use -dnsflood to enable")
513         }
514
515         defer dnsWaitGroup.Wait()
516
517         var N = 5000
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.
526                 N = 500
527         }
528
529         const timeout = 3 * time.Second
530         ctxHalfTimeout, cancel := context.WithTimeout(context.Background(), timeout/2)
531         defer cancel()
532         ctxTimeout, cancel := context.WithTimeout(context.Background(), timeout)
533         defer cancel()
534
535         c := make(chan error, 2*N)
536         for i := 0; i < N; i++ {
537                 name := fmt.Sprintf("%d.net-test.golang.org", i)
538                 go func() {
539                         _, err := DefaultResolver.LookupIPAddr(ctxHalfTimeout, name)
540                         c <- err
541                 }()
542                 go func() {
543                         _, err := DefaultResolver.LookupIPAddr(ctxTimeout, name)
544                         c <- err
545                 }()
546         }
547         qstats := struct {
548                 succeeded, failed         int
549                 timeout, temporary, other int
550                 unknown                   int
551         }{}
552         deadline := time.After(timeout + time.Second)
553         for i := 0; i < 2*N; i++ {
554                 select {
555                 case <-deadline:
556                         t.Fatal("deadline exceeded")
557                 case err := <-c:
558                         switch err := err.(type) {
559                         case nil:
560                                 qstats.succeeded++
561                         case Error:
562                                 qstats.failed++
563                                 if err.Timeout() {
564                                         qstats.timeout++
565                                 }
566                                 if err.Temporary() {
567                                         qstats.temporary++
568                                 }
569                                 if !err.Timeout() && !err.Temporary() {
570                                         qstats.other++
571                                 }
572                         default:
573                                 qstats.failed++
574                                 qstats.unknown++
575                         }
576                 }
577         }
578
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)
587 }
588
589 func TestLookupDotsWithLocalSource(t *testing.T) {
590         if !supportsIPv4() || !*testIPv4 {
591                 t.Skip("IPv4 is required")
592         }
593
594         mustHaveExternalNetwork(t)
595
596         defer dnsWaitGroup.Wait()
597
598         for i, fn := range []func() func(){forceGoDNS, forceCgoDNS} {
599                 fixup := fn()
600                 if fixup == nil {
601                         continue
602                 }
603                 names, err := LookupAddr("127.0.0.1")
604                 fixup()
605                 if err != nil {
606                         t.Logf("#%d: %v", i, err)
607                         continue
608                 }
609                 mode := "netgo"
610                 if i == 1 {
611                         mode = "netcgo"
612                 }
613         loop:
614                 for i, name := range names {
615                         if strings.Index(name, ".") == len(name)-1 { // "localhost" not "localhost."
616                                 for j := range names {
617                                         if j == i {
618                                                 continue
619                                         }
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.
623                                                 continue loop
624                                         }
625                                 }
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)
629                         }
630                 }
631         }
632 }
633
634 func TestLookupDotsWithRemoteSource(t *testing.T) {
635         if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
636                 testenv.SkipFlaky(t, 27992)
637         }
638         mustHaveExternalNetwork(t)
639         testenv.SkipFlakyNet(t)
640
641         if !supportsIPv4() || !*testIPv4 {
642                 t.Skip("IPv4 is required")
643         }
644
645         if runtime.GOOS == "ios" {
646                 t.Skip("no resolv.conf on iOS")
647         }
648
649         defer dnsWaitGroup.Wait()
650
651         if fixup := forceGoDNS(); fixup != nil {
652                 testDots(t, "go")
653                 fixup()
654         }
655         if fixup := forceCgoDNS(); fixup != nil {
656                 testDots(t, "cgo")
657                 fixup()
658         }
659 }
660
661 func testDots(t *testing.T, mode string) {
662         names, err := LookupAddr("8.8.8.8") // Google dns server
663         if err != nil {
664                 t.Errorf("LookupAddr(8.8.8.8): %v (mode=%v)", err, mode)
665         } else {
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)
669                                 break
670                         }
671                 }
672         }
673
674         cname, err := LookupCNAME("www.mit.edu")
675         if err != nil {
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)
679         }
680
681         mxs, err := LookupMX("google.com")
682         if err != nil {
683                 t.Errorf("LookupMX(google.com): %v (mode=%v)", err, mode)
684         } else {
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)
688                                 break
689                         }
690                 }
691         }
692
693         nss, err := LookupNS("google.com")
694         if err != nil {
695                 t.Errorf("LookupNS(google.com): %v (mode=%v)", err, mode)
696         } else {
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)
700                                 break
701                         }
702                 }
703         }
704
705         cname, srvs, err := LookupSRV("ldap", "tcp", "google.com")
706         if err != nil {
707                 t.Errorf("LookupSRV(ldap, tcp, google.com): %v (mode=%v)", err, mode)
708         } else {
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)
711                 }
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)
715                                 break
716                         }
717                 }
718         }
719 }
720
721 func mxString(mxs []*MX) string {
722         var buf strings.Builder
723         sep := ""
724         fmt.Fprintf(&buf, "[")
725         for _, mx := range mxs {
726                 fmt.Fprintf(&buf, "%s%s:%d", sep, mx.Host, mx.Pref)
727                 sep = " "
728         }
729         fmt.Fprintf(&buf, "]")
730         return buf.String()
731 }
732
733 func nsString(nss []*NS) string {
734         var buf strings.Builder
735         sep := ""
736         fmt.Fprintf(&buf, "[")
737         for _, ns := range nss {
738                 fmt.Fprintf(&buf, "%s%s", sep, ns.Host)
739                 sep = " "
740         }
741         fmt.Fprintf(&buf, "]")
742         return buf.String()
743 }
744
745 func srvString(srvs []*SRV) string {
746         var buf strings.Builder
747         sep := ""
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)
751                 sep = " "
752         }
753         fmt.Fprintf(&buf, "]")
754         return buf.String()
755 }
756
757 func TestLookupPort(t *testing.T) {
758         // See https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
759         //
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.
763         type test struct {
764                 network string
765                 name    string
766                 port    int
767                 ok      bool
768         }
769         var tests = []test{
770                 {"tcp", "0", 0, true},
771                 {"udp", "0", 0, true},
772                 {"udp", "domain", 53, true},
773
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},
781
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},
789         }
790
791         switch runtime.GOOS {
792         case "android":
793                 if netGoBuildTag {
794                         t.Skipf("not supported on %s without cgo; see golang.org/issues/14576", runtime.GOOS)
795                 }
796         default:
797                 tests = append(tests, test{"tcp", "http", 80, true})
798         }
799
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)
804                 }
805                 if err != nil {
806                         if perr := parseLookupPortError(err); perr != nil {
807                                 t.Error(perr)
808                         }
809                 }
810         }
811 }
812
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) {
816         type test struct {
817                 network string
818                 name    string
819                 port    int
820         }
821         var tests = []test{
822                 {"tcp", "http", 80},
823                 {"tcp", "HTTP", 80}, // case shouldn't matter
824                 {"tcp", "https", 443},
825                 {"tcp", "ssh", 22},
826                 {"tcp", "gopher", 70},
827                 {"tcp4", "http", 80},
828                 {"tcp6", "http", 80},
829         }
830
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)
835                 }
836         }
837 }
838
839 func TestLookupProtocol_Minimal(t *testing.T) {
840         type test struct {
841                 name string
842                 want int
843         }
844         var tests = []test{
845                 {"tcp", 6},
846                 {"TcP", 6}, // case shouldn't matter
847                 {"icmp", 1},
848                 {"igmp", 2},
849                 {"udp", 17},
850                 {"ipv6-icmp", 58},
851         }
852
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)
857                 }
858         }
859
860 }
861
862 func TestLookupNonLDH(t *testing.T) {
863         defer dnsWaitGroup.Wait()
864
865         if fixup := forceGoDNS(); fixup != nil {
866                 defer fixup()
867         }
868
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.")
874         if err == nil {
875                 t.Fatalf("lookup succeeded: %v", addrs)
876         }
877         if !strings.HasSuffix(err.Error(), errNoSuchHost.Error()) {
878                 t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost)
879         }
880         if !err.(*DNSError).IsNotFound {
881                 t.Fatalf("lookup error = %v, want true", err.(*DNSError).IsNotFound)
882         }
883 }
884
885 func TestLookupContextCancel(t *testing.T) {
886         mustHaveExternalNetwork(t)
887         testenv.SkipFlakyNet(t)
888
889         origTestHookLookupIP := testHookLookupIP
890         defer func() {
891                 dnsWaitGroup.Wait()
892                 testHookLookupIP = origTestHookLookupIP
893         }()
894
895         lookupCtx, cancelLookup := context.WithCancel(context.Background())
896         unblockLookup := make(chan struct{})
897
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(
902                 ctx context.Context,
903                 fn func(context.Context, string, string) ([]IPAddr, error),
904                 network string,
905                 host string,
906         ) ([]IPAddr, error) {
907                 select {
908                 case <-unblockLookup:
909                 default:
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")
916                         dnsWaitGroup.Add(1)
917                         go func() {
918                                 defer dnsWaitGroup.Done()
919                                 _, err := DefaultResolver.LookupIPAddr(context.Background(), host)
920                                 if err != nil {
921                                         t.Error(err)
922                                 }
923                         }()
924                         time.Sleep(1 * time.Millisecond)
925                 }
926
927                 cancelLookup()
928                 <-unblockLookup
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")
936                         return nil, err
937                 }
938                 t.Logf("testHookLookupIP performing lookup")
939                 return fn(ctx, network, host)
940         }
941
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)
945         }
946         close(unblockLookup)
947 }
948
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()
955
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")
967 }
968
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.
975
976         const (
977                 google        = "www.google.com"
978                 invalidDomain = "invalid.invalid" // RFC 2606 reserves .invalid
979                 n             = 600               // this needs to be larger than threadLimit size
980         )
981
982         _, err := LookupHost(google)
983         if err != nil {
984                 t.Fatal(err)
985         }
986
987         ctx, cancel := context.WithCancel(context.Background())
988         cancel()
989         for i := 0; i < n; i++ {
990                 addr, err := DefaultResolver.LookupHost(ctx, invalidDomain)
991                 if err == nil {
992                         t.Fatalf("LookupHost(%q): returns %v, but should fail", invalidDomain, addr)
993                 }
994
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.)
1000
1001                 // The lookup may deduplicate in-flight requests, so give it time to settle
1002                 // in between.
1003                 time.Sleep(time.Millisecond * 1)
1004         }
1005
1006         _, err = LookupHost(google)
1007         if err != nil {
1008                 t.Fatal(err)
1009         }
1010 }
1011
1012 type lookupCustomResolver struct {
1013         *Resolver
1014         mu     sync.RWMutex
1015         dialed bool
1016 }
1017
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) {
1020                 lcr.mu.Lock()
1021                 lcr.dialed = true
1022                 lcr.mu.Unlock()
1023                 return Dial(network, address)
1024         }
1025 }
1026
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 {
1031         case "plan9":
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)
1035         }
1036
1037         testenv.MustHaveExternalNetwork(t)
1038         testenv.SkipFlakyNet(t)
1039
1040         defer dnsWaitGroup.Wait()
1041
1042         resolvers := make([]*lookupCustomResolver, 2)
1043         for i := range resolvers {
1044                 cs := lookupCustomResolver{Resolver: &Resolver{PreferGo: true}}
1045                 cs.Dial = cs.dial()
1046                 resolvers[i] = &cs
1047         }
1048
1049         var wg sync.WaitGroup
1050         wg.Add(len(resolvers))
1051         for i, resolver := range resolvers {
1052                 go func(r *Resolver, index int) {
1053                         defer wg.Done()
1054                         _, err := r.LookupIPAddr(context.Background(), "google.com")
1055                         if err != nil {
1056                                 t.Errorf("lookup failed for resolver %d: %q", index, err)
1057                         }
1058                 }(resolver.Resolver, i)
1059         }
1060         wg.Wait()
1061
1062         if t.Failed() {
1063                 t.FailNow()
1064         }
1065
1066         for i, resolver := range resolvers {
1067                 if !resolver.dialed {
1068                         t.Errorf("custom resolver %d not dialed during lookup", i)
1069                 }
1070         }
1071 }
1072
1073 var ipVersionTests = []struct {
1074         network string
1075         version byte
1076 }{
1077         {"tcp", 0},
1078         {"tcp4", '4'},
1079         {"tcp6", '6'},
1080         {"udp", 0},
1081         {"udp4", '4'},
1082         {"udp6", '6'},
1083         {"ip", 0},
1084         {"ip4", '4'},
1085         {"ip6", '6'},
1086         {"ip7", 0},
1087         {"", 0},
1088 }
1089
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))
1095                 }
1096         }
1097 }
1098
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 }()
1104
1105         keyValues := []struct {
1106                 key, value any
1107         }{
1108                 {"key-1", 12},
1109                 {384, "value2"},
1110                 {new(float64), 137},
1111         }
1112         ctx := context.Background()
1113         for _, kv := range keyValues {
1114                 ctx = context.WithValue(ctx, kv.key, kv.value)
1115         }
1116
1117         wantIPs := []IPAddr{
1118                 {IP: IPv4(127, 0, 0, 1)},
1119                 {IP: IPv6loopback},
1120         }
1121
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)
1127                         }
1128                 }
1129                 return wantIPs, nil
1130         }
1131         testHookLookupIP = checkCtxValues
1132
1133         resolvers := []*Resolver{
1134                 nil,
1135                 new(Resolver),
1136         }
1137
1138         for i, resolver := range resolvers {
1139                 gotIPs, err := resolver.LookupIPAddr(ctx, "golang.org")
1140                 if err != nil {
1141                         t.Errorf("Resolver #%d: unexpected error: %v", i, err)
1142                 }
1143                 if !reflect.DeepEqual(gotIPs, wantIPs) {
1144                         t.Errorf("#%d: mismatched IPAddr results\n\tGot: %v\n\tWant: %v", i, gotIPs, wantIPs)
1145                 }
1146         }
1147 }
1148
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 }()
1153
1154         queries := [][]string{
1155                 {"udp", "golang.org"},
1156                 {"udp4", "golang.org"},
1157                 {"udp6", "golang.org"},
1158                 {"udp", "golang.org"},
1159                 {"udp", "golang.org"},
1160         }
1161         results := map[[2]string][]IPAddr{
1162                 {"udp", "golang.org"}: {
1163                         {IP: IPv4(127, 0, 0, 1)},
1164                         {IP: IPv6loopback},
1165                 },
1166                 {"udp4", "golang.org"}: {
1167                         {IP: IPv4(127, 0, 0, 1)},
1168                 },
1169                 {"udp6", "golang.org"}: {
1170                         {IP: IPv6loopback},
1171                 },
1172         }
1173         calls := int32(0)
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)) {
1180                         close(waitCh)
1181                 }
1182                 select {
1183                 case <-waitCh:
1184                 case <-ctx.Done():
1185                         return nil, ctx.Err()
1186                 }
1187                 return results[[2]string{network, host}], nil
1188         }
1189
1190         ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
1191         defer cancel()
1192         wg := sync.WaitGroup{}
1193         for _, q := range queries {
1194                 network := q[0]
1195                 host := q[1]
1196                 wg.Add(1)
1197                 go func() {
1198                         defer wg.Done()
1199                         gotIPs, err := DefaultResolver.lookupIPAddr(ctx, network, host)
1200                         if err != nil {
1201                                 t.Errorf("lookupIPAddr(%v, %v): unexpected error: %v", network, host, err)
1202                         }
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)
1206                         }
1207                 }()
1208         }
1209         wg.Wait()
1210 }
1211
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", "")
1215         if err == nil {
1216                 t.Fatal("DefaultResolver.LookupIP for empty host success, want no host error")
1217         }
1218         if !strings.HasSuffix(err.Error(), errNoSuchHost.Error()) {
1219                 t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost)
1220         }
1221 }
1222
1223 func TestWithUnexpiredValuesPreserved(t *testing.T) {
1224         ctx, cancel := context.WithCancel(context.Background())
1225
1226         // Insert a value into it.
1227         key, value := "key-1", 2
1228         ctx = context.WithValue(ctx, key, value)
1229
1230         // Now use the "values preserving context" like
1231         // we would for LookupIPAddr. See Issue 28600.
1232         ctx = withUnexpiredValuesPreserved(ctx)
1233
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)
1237         }
1238
1239         // Cancel the context.
1240         cancel()
1241
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)
1245         }
1246 }
1247
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
1253 }
1254
1255 func TestResolverLookupIP(t *testing.T) {
1256         testenv.MustHaveExternalNetwork(t)
1257
1258         v4Ok := supportsIPv4() && *testIPv4
1259         v6Ok := supportsIPv6() && *testIPv6
1260
1261         defer dnsWaitGroup.Wait()
1262
1263         for _, impl := range []struct {
1264                 name string
1265                 fn   func() func()
1266         }{
1267                 {"go", forceGoDNS},
1268                 {"cgo", forceCgoDNS},
1269         } {
1270                 t.Run("implementation: "+impl.name, func(t *testing.T) {
1271                         fixup := impl.fn()
1272                         if fixup == nil {
1273                                 t.Skip("not supported")
1274                         }
1275                         defer fixup()
1276
1277                         for _, network := range []string{"ip", "ip4", "ip6"} {
1278                                 t.Run("network: "+network, func(t *testing.T) {
1279                                         switch {
1280                                         case network == "ip4" && !v4Ok:
1281                                                 t.Skip("IPv4 is not supported")
1282                                         case network == "ip6" && !v6Ok:
1283                                                 t.Skip("IPv6 is not supported")
1284                                         }
1285
1286                                         // google.com has both A and AAAA records.
1287                                         const host = "google.com"
1288                                         ips, err := DefaultResolver.LookupIP(context.Background(), network, host)
1289                                         if err != nil {
1290                                                 testenv.SkipFlakyNet(t)
1291                                                 t.Fatalf("DefaultResolver.LookupIP(%q, %q): failed with unexpected error: %v", network, host, err)
1292                                         }
1293
1294                                         var v4Addrs []netip.Addr
1295                                         var v6Addrs []netip.Addr
1296                                         for _, ip := range ips {
1297                                                 if addr, ok := netip.AddrFromSlice(ip); ok {
1298                                                         if addr.Is4() {
1299                                                                 v4Addrs = append(v4Addrs, addr)
1300                                                         } else {
1301                                                                 v6Addrs = append(v6Addrs, addr)
1302                                                         }
1303                                                 } else {
1304                                                         t.Fatalf("IP=%q is neither IPv4 nor IPv6", ip)
1305                                                 }
1306                                         }
1307
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)
1312                                                 }
1313                                         }
1314                                         if network == "ip6" || network == "ip" && v6Ok {
1315                                                 if len(v6Addrs) == 0 {
1316                                                         t.Errorf("DefaultResolver.LookupIP(%q, %q): no IPv6 addresses", network, host)
1317                                                 }
1318                                         }
1319
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)
1323                                         }
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)
1326                                         }
1327                                 })
1328                         }
1329                 })
1330         }
1331 }
1332
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()
1338
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) {
1341                 <-timeoutHookGo
1342                 return nil, context.DeadlineExceeded
1343         }
1344         testHookLookupIP = timeoutHook
1345
1346         checkErr := func(err error) {
1347                 t.Helper()
1348                 if err == nil {
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)
1356                 }
1357         }
1358
1359         // Single lookup.
1360         timeoutHookGo <- true
1361         _, err := LookupIP("golang.org")
1362         checkErr(err)
1363
1364         // Double lookup.
1365         var err1, err2 error
1366         var wg sync.WaitGroup
1367         wg.Add(2)
1368         go func() {
1369                 defer wg.Done()
1370                 _, err1 = LookupIP("golang1.org")
1371         }()
1372         go func() {
1373                 defer wg.Done()
1374                 _, err2 = LookupIP("golang1.org")
1375         }()
1376         close(timeoutHookGo)
1377         wg.Wait()
1378         checkErr(err1)
1379         checkErr(err2)
1380
1381         // Double lookup with context.
1382         timeoutHookGo = make(chan bool)
1383         ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
1384         wg.Add(2)
1385         go func() {
1386                 defer wg.Done()
1387                 _, err1 = DefaultResolver.LookupIPAddr(ctx, "golang2.org")
1388         }()
1389         go func() {
1390                 defer wg.Done()
1391                 _, err2 = DefaultResolver.LookupIPAddr(ctx, "golang2.org")
1392         }()
1393         time.Sleep(10 * time.Nanosecond)
1394         close(timeoutHookGo)
1395         wg.Wait()
1396         checkErr(err1)
1397         checkErr(err2)
1398         cancel()
1399 }
1400
1401 func TestLookupNoData(t *testing.T) {
1402         if runtime.GOOS == "plan9" {
1403                 t.Skip("not supported on plan9")
1404         }
1405
1406         mustHaveExternalNetwork(t)
1407
1408         testLookupNoData(t, "default resolver")
1409
1410         func() {
1411                 defer forceGoDNS()()
1412                 testLookupNoData(t, "forced go resolver")
1413         }()
1414
1415         func() {
1416                 defer forceCgoDNS()()
1417                 testLookupNoData(t, "forced cgo resolver")
1418         }()
1419 }
1420
1421 func testLookupNoData(t *testing.T, prefix string) {
1422         attempts := 0
1423         for {
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.")
1427                 if err == nil {
1428                         t.Errorf("%v: unexpected success", prefix)
1429                         return
1430                 }
1431
1432                 var dnsErr *DNSError
1433                 if errors.As(err, &dnsErr) {
1434                         succeeded := true
1435                         if !dnsErr.IsNotFound {
1436                                 succeeded = false
1437                                 t.Logf("%v: IsNotFound is set to false", prefix)
1438                         }
1439
1440                         if dnsErr.Err != errNoSuchHost.Error() {
1441                                 succeeded = false
1442                                 t.Logf("%v: error message is not equal to: %v", prefix, errNoSuchHost.Error())
1443                         }
1444
1445                         if succeeded {
1446                                 return
1447                         }
1448                 }
1449
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)
1454                         time.Sleep(dur)
1455                         attempts++
1456                         continue
1457                 }
1458
1459                 t.Errorf("%v: unexpected error: %v", prefix, err)
1460                 return
1461         }
1462 }
1463
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)
1470                 }
1471         })
1472 }
1473
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" {
1479                 return "https"
1480         }
1481         return "submissions"
1482 }()
1483
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)
1490                 }
1491         })
1492 }
1493
1494 func TestLookupPortEmptyNetworkString(t *testing.T) {
1495         allResolvers(t, func(t *testing.T) {
1496                 _, err := LookupPort("", tcpOnlyService)
1497                 if err != nil {
1498                         t.Fatalf("unexpected error: %v", err)
1499                 }
1500         })
1501 }
1502
1503 func TestLookupPortIPNetworkString(t *testing.T) {
1504         allResolvers(t, func(t *testing.T) {
1505                 _, err := LookupPort("ip", tcpOnlyService)
1506                 if err != nil {
1507                         t.Fatalf("unexpected error: %v", err)
1508                 }
1509         })
1510 }
1511
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 {
1516                         defer fixup()
1517                         f(t)
1518                 }
1519         })
1520         t.Run("forced cgo resolver", func(t *testing.T) {
1521                 if fixup := forceCgoDNS(); fixup != nil {
1522                         defer fixup()
1523                         f(t)
1524                 }
1525         })
1526 }
1527
1528 func TestLookupNoSuchHost(t *testing.T) {
1529         mustHaveExternalNetwork(t)
1530
1531         const testNXDOMAIN = "invalid.invalid."
1532         const testNODATA = "_ldap._tcp.google.com."
1533
1534         tests := []struct {
1535                 name  string
1536                 query func() error
1537         }{
1538                 {
1539                         name: "LookupCNAME NXDOMAIN",
1540                         query: func() error {
1541                                 _, err := LookupCNAME(testNXDOMAIN)
1542                                 return err
1543                         },
1544                 },
1545                 {
1546                         name: "LookupHost NXDOMAIN",
1547                         query: func() error {
1548                                 _, err := LookupHost(testNXDOMAIN)
1549                                 return err
1550                         },
1551                 },
1552                 {
1553                         name: "LookupHost NODATA",
1554                         query: func() error {
1555                                 _, err := LookupHost(testNODATA)
1556                                 return err
1557                         },
1558                 },
1559                 {
1560                         name: "LookupMX NXDOMAIN",
1561                         query: func() error {
1562                                 _, err := LookupMX(testNXDOMAIN)
1563                                 return err
1564                         },
1565                 },
1566                 {
1567                         name: "LookupMX NODATA",
1568                         query: func() error {
1569                                 _, err := LookupMX(testNODATA)
1570                                 return err
1571                         },
1572                 },
1573                 {
1574                         name: "LookupNS NXDOMAIN",
1575                         query: func() error {
1576                                 _, err := LookupNS(testNXDOMAIN)
1577                                 return err
1578                         },
1579                 },
1580                 {
1581                         name: "LookupNS NODATA",
1582                         query: func() error {
1583                                 _, err := LookupNS(testNODATA)
1584                                 return err
1585                         },
1586                 },
1587                 {
1588                         name: "LookupSRV NXDOMAIN",
1589                         query: func() error {
1590                                 _, _, err := LookupSRV("unknown", "tcp", testNXDOMAIN)
1591                                 return err
1592                         },
1593                 },
1594                 {
1595                         name: "LookupTXT NXDOMAIN",
1596                         query: func() error {
1597                                 _, err := LookupTXT(testNXDOMAIN)
1598                                 return err
1599                         },
1600                 },
1601                 {
1602                         name: "LookupTXT NODATA",
1603                         query: func() error {
1604                                 _, err := LookupTXT(testNODATA)
1605                                 return err
1606                         },
1607                 },
1608         }
1609
1610         for _, v := range tests {
1611                 t.Run(v.name, func(t *testing.T) {
1612                         allResolvers(t, func(t *testing.T) {
1613                                 attempts := 0
1614                                 for {
1615                                         err := v.query()
1616                                         if err == nil {
1617                                                 t.Errorf("unexpected success")
1618                                                 return
1619                                         }
1620                                         if dnsErr, ok := err.(*DNSError); ok {
1621                                                 succeeded := true
1622                                                 if !dnsErr.IsNotFound {
1623                                                         succeeded = false
1624                                                         t.Log("IsNotFound is set to false")
1625                                                 }
1626                                                 if dnsErr.Err != errNoSuchHost.Error() {
1627                                                         succeeded = false
1628                                                         t.Logf("error message is not equal to: %v", errNoSuchHost.Error())
1629                                                 }
1630                                                 if succeeded {
1631                                                         return
1632                                                 }
1633                                         }
1634                                         testenv.SkipFlakyNet(t)
1635                                         if attempts < len(backoffDuration) {
1636                                                 dur := backoffDuration[attempts]
1637                                                 t.Logf("backoff %v after failure %v\n", dur, err)
1638                                                 time.Sleep(dur)
1639                                                 attempts++
1640                                                 continue
1641                                         }
1642                                         t.Errorf("unexpected error: %v", err)
1643                                         return
1644                                 }
1645                         })
1646                 })
1647         }
1648 }