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
5package net
6
7import (
8	"context"
9	"errors"
10	"fmt"
11	"internal/testenv"
12	"net/netip"
13	"reflect"
14	"runtime"
15	"slices"
16	"strings"
17	"sync"
18	"sync/atomic"
19	"testing"
20	"time"
21)
22
23var goResolver = Resolver{PreferGo: true}
24
25func hasSuffixFold(s, suffix string) bool {
26	return strings.HasSuffix(strings.ToLower(s), strings.ToLower(suffix))
27}
28
29func lookupLocalhost(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
30	switch host {
31	case "localhost":
32		return []IPAddr{
33			{IP: IPv4(127, 0, 0, 1)},
34			{IP: IPv6loopback},
35		}, nil
36	default:
37		return fn(ctx, network, host)
38	}
39}
40
41// The Lookup APIs use various sources such as local database, DNS or
42// mDNS, and may use platform-dependent DNS stub resolver if possible.
43// The APIs accept any of forms for a query; host name in various
44// encodings, UTF-8 encoded net name, domain name, FQDN or absolute
45// FQDN, but the result would be one of the forms and it depends on
46// the circumstances.
47
48var lookupGoogleSRVTests = []struct {
49	service, proto, name string
50	cname, target        string
51}{
52	{
53		"ldap", "tcp", "google.com",
54		"google.com.", "google.com.",
55	},
56	{
57		"ldap", "tcp", "google.com.",
58		"google.com.", "google.com.",
59	},
60
61	// non-standard back door
62	{
63		"", "", "_ldap._tcp.google.com",
64		"google.com.", "google.com.",
65	},
66	{
67		"", "", "_ldap._tcp.google.com.",
68		"google.com.", "google.com.",
69	},
70}
71
72var backoffDuration = [...]time.Duration{time.Second, 5 * time.Second, 30 * time.Second}
73
74func TestLookupGoogleSRV(t *testing.T) {
75	t.Parallel()
76	mustHaveExternalNetwork(t)
77
78	if runtime.GOOS == "ios" {
79		t.Skip("no resolv.conf on iOS")
80	}
81
82	if !supportsIPv4() || !*testIPv4 {
83		t.Skip("IPv4 is required")
84	}
85
86	attempts := 0
87	for i := 0; i < len(lookupGoogleSRVTests); i++ {
88		tt := lookupGoogleSRVTests[i]
89		cname, srvs, err := LookupSRV(tt.service, tt.proto, tt.name)
90		if err != nil {
91			testenv.SkipFlakyNet(t)
92			if attempts < len(backoffDuration) {
93				dur := backoffDuration[attempts]
94				t.Logf("backoff %v after failure %v\n", dur, err)
95				time.Sleep(dur)
96				attempts++
97				i--
98				continue
99			}
100			t.Fatal(err)
101		}
102		if len(srvs) == 0 {
103			t.Error("got no record")
104		}
105		if !hasSuffixFold(cname, tt.cname) {
106			t.Errorf("got %s; want %s", cname, tt.cname)
107		}
108		for _, srv := range srvs {
109			if !hasSuffixFold(srv.Target, tt.target) {
110				t.Errorf("got %v; want a record containing %s", srv, tt.target)
111			}
112		}
113	}
114}
115
116var lookupGmailMXTests = []struct {
117	name, host string
118}{
119	{"gmail.com", "google.com."},
120	{"gmail.com.", "google.com."},
121}
122
123func TestLookupGmailMX(t *testing.T) {
124	t.Parallel()
125	mustHaveExternalNetwork(t)
126
127	if runtime.GOOS == "ios" {
128		t.Skip("no resolv.conf on iOS")
129	}
130
131	if !supportsIPv4() || !*testIPv4 {
132		t.Skip("IPv4 is required")
133	}
134
135	attempts := 0
136	for i := 0; i < len(lookupGmailMXTests); i++ {
137		tt := lookupGmailMXTests[i]
138		mxs, err := LookupMX(tt.name)
139		if err != nil {
140			testenv.SkipFlakyNet(t)
141			if attempts < len(backoffDuration) {
142				dur := backoffDuration[attempts]
143				t.Logf("backoff %v after failure %v\n", dur, err)
144				time.Sleep(dur)
145				attempts++
146				i--
147				continue
148			}
149			t.Fatal(err)
150		}
151		if len(mxs) == 0 {
152			t.Error("got no record")
153		}
154		for _, mx := range mxs {
155			if !hasSuffixFold(mx.Host, tt.host) {
156				t.Errorf("got %v; want a record containing %s", mx, tt.host)
157			}
158		}
159	}
160}
161
162var lookupGmailNSTests = []struct {
163	name, host string
164}{
165	{"gmail.com", "google.com."},
166	{"gmail.com.", "google.com."},
167}
168
169func TestLookupGmailNS(t *testing.T) {
170	t.Parallel()
171	mustHaveExternalNetwork(t)
172
173	if runtime.GOOS == "ios" {
174		t.Skip("no resolv.conf on iOS")
175	}
176
177	if !supportsIPv4() || !*testIPv4 {
178		t.Skip("IPv4 is required")
179	}
180
181	attempts := 0
182	for i := 0; i < len(lookupGmailNSTests); i++ {
183		tt := lookupGmailNSTests[i]
184		nss, err := LookupNS(tt.name)
185		if err != nil {
186			testenv.SkipFlakyNet(t)
187			if attempts < len(backoffDuration) {
188				dur := backoffDuration[attempts]
189				t.Logf("backoff %v after failure %v\n", dur, err)
190				time.Sleep(dur)
191				attempts++
192				i--
193				continue
194			}
195			t.Fatal(err)
196		}
197		if len(nss) == 0 {
198			t.Error("got no record")
199		}
200		for _, ns := range nss {
201			if !hasSuffixFold(ns.Host, tt.host) {
202				t.Errorf("got %v; want a record containing %s", ns, tt.host)
203			}
204		}
205	}
206}
207
208var lookupGmailTXTTests = []struct {
209	name, txt, host string
210}{
211	{"gmail.com", "spf", "google.com"},
212	{"gmail.com.", "spf", "google.com"},
213}
214
215func TestLookupGmailTXT(t *testing.T) {
216	if runtime.GOOS == "plan9" {
217		t.Skip("skipping on plan9; see https://golang.org/issue/29722")
218	}
219	t.Parallel()
220	mustHaveExternalNetwork(t)
221
222	if runtime.GOOS == "ios" {
223		t.Skip("no resolv.conf on iOS")
224	}
225
226	if !supportsIPv4() || !*testIPv4 {
227		t.Skip("IPv4 is required")
228	}
229
230	attempts := 0
231	for i := 0; i < len(lookupGmailTXTTests); i++ {
232		tt := lookupGmailTXTTests[i]
233		txts, err := LookupTXT(tt.name)
234		if err != nil {
235			testenv.SkipFlakyNet(t)
236			if attempts < len(backoffDuration) {
237				dur := backoffDuration[attempts]
238				t.Logf("backoff %v after failure %v\n", dur, err)
239				time.Sleep(dur)
240				attempts++
241				i--
242				continue
243			}
244			t.Fatal(err)
245		}
246		if len(txts) == 0 {
247			t.Error("got no record")
248		}
249		found := false
250		for _, txt := range txts {
251			if strings.Contains(txt, tt.txt) && (strings.HasSuffix(txt, tt.host) || strings.HasSuffix(txt, tt.host+".")) {
252				found = true
253				break
254			}
255		}
256		if !found {
257			t.Errorf("got %v; want a record containing %s, %s", txts, tt.txt, tt.host)
258		}
259	}
260}
261
262var lookupGooglePublicDNSAddrTests = []string{
263	"8.8.8.8",
264	"8.8.4.4",
265	"2001:4860:4860::8888",
266	"2001:4860:4860::8844",
267}
268
269func TestLookupGooglePublicDNSAddr(t *testing.T) {
270	mustHaveExternalNetwork(t)
271
272	if !supportsIPv4() || !supportsIPv6() || !*testIPv4 || !*testIPv6 {
273		t.Skip("both IPv4 and IPv6 are required")
274	}
275
276	defer dnsWaitGroup.Wait()
277
278	for _, ip := range lookupGooglePublicDNSAddrTests {
279		names, err := LookupAddr(ip)
280		if err != nil {
281			t.Fatal(err)
282		}
283		if len(names) == 0 {
284			t.Error("got no record")
285		}
286		for _, name := range names {
287			if !hasSuffixFold(name, ".google.com.") && !hasSuffixFold(name, ".google.") {
288				t.Errorf("got %q; want a record ending in .google.com. or .google.", name)
289			}
290		}
291	}
292}
293
294func TestLookupIPv6LinkLocalAddr(t *testing.T) {
295	if !supportsIPv6() || !*testIPv6 {
296		t.Skip("IPv6 is required")
297	}
298
299	defer dnsWaitGroup.Wait()
300
301	addrs, err := LookupHost("localhost")
302	if err != nil {
303		t.Fatal(err)
304	}
305	found := false
306	for _, addr := range addrs {
307		if addr == "fe80::1%lo0" {
308			found = true
309			break
310		}
311	}
312	if !found {
313		t.Skipf("not supported on %s", runtime.GOOS)
314	}
315	if _, err := LookupAddr("fe80::1%lo0"); err != nil {
316		t.Error(err)
317	}
318}
319
320func TestLookupIPv6LinkLocalAddrWithZone(t *testing.T) {
321	if !supportsIPv6() || !*testIPv6 {
322		t.Skip("IPv6 is required")
323	}
324
325	ipaddrs, err := DefaultResolver.LookupIPAddr(context.Background(), "fe80::1%lo0")
326	if err != nil {
327		t.Error(err)
328	}
329	for _, addr := range ipaddrs {
330		if e, a := "lo0", addr.Zone; e != a {
331			t.Errorf("wrong zone: want %q, got %q", e, a)
332		}
333	}
334
335	addrs, err := DefaultResolver.LookupHost(context.Background(), "fe80::1%lo0")
336	if err != nil {
337		t.Error(err)
338	}
339	for _, addr := range addrs {
340		if e, a := "fe80::1%lo0", addr; e != a {
341			t.Errorf("wrong host: want %q got %q", e, a)
342		}
343	}
344}
345
346var lookupCNAMETests = []struct {
347	name, cname string
348}{
349	{"www.iana.org", "icann.org."},
350	{"www.iana.org.", "icann.org."},
351	{"www.google.com", "google.com."},
352	{"google.com", "google.com."},
353	{"cname-to-txt.go4.org", "test-txt-record.go4.org."},
354}
355
356func TestLookupCNAME(t *testing.T) {
357	mustHaveExternalNetwork(t)
358	testenv.SkipFlakyNet(t)
359
360	if !supportsIPv4() || !*testIPv4 {
361		t.Skip("IPv4 is required")
362	}
363
364	defer dnsWaitGroup.Wait()
365
366	attempts := 0
367	for i := 0; i < len(lookupCNAMETests); i++ {
368		tt := lookupCNAMETests[i]
369		cname, err := LookupCNAME(tt.name)
370		if err != nil {
371			testenv.SkipFlakyNet(t)
372			if attempts < len(backoffDuration) {
373				dur := backoffDuration[attempts]
374				t.Logf("backoff %v after failure %v\n", dur, err)
375				time.Sleep(dur)
376				attempts++
377				i--
378				continue
379			}
380			t.Fatal(err)
381		}
382		if !hasSuffixFold(cname, tt.cname) {
383			t.Errorf("got %s; want a record containing %s", cname, tt.cname)
384		}
385	}
386}
387
388var lookupGoogleHostTests = []struct {
389	name string
390}{
391	{"google.com"},
392	{"google.com."},
393}
394
395func TestLookupGoogleHost(t *testing.T) {
396	mustHaveExternalNetwork(t)
397	testenv.SkipFlakyNet(t)
398
399	if !supportsIPv4() || !*testIPv4 {
400		t.Skip("IPv4 is required")
401	}
402
403	defer dnsWaitGroup.Wait()
404
405	for _, tt := range lookupGoogleHostTests {
406		addrs, err := LookupHost(tt.name)
407		if err != nil {
408			t.Fatal(err)
409		}
410		if len(addrs) == 0 {
411			t.Error("got no record")
412		}
413		for _, addr := range addrs {
414			if ParseIP(addr) == nil {
415				t.Errorf("got %q; want a literal IP address", addr)
416			}
417		}
418	}
419}
420
421func TestLookupLongTXT(t *testing.T) {
422	testenv.SkipFlaky(t, 22857)
423	mustHaveExternalNetwork(t)
424
425	defer dnsWaitGroup.Wait()
426
427	txts, err := LookupTXT("golang.rsc.io")
428	if err != nil {
429		t.Fatal(err)
430	}
431	slices.Sort(txts)
432	want := []string{
433		strings.Repeat("abcdefghijklmnopqrstuvwxyABCDEFGHJIKLMNOPQRSTUVWXY", 10),
434		"gophers rule",
435	}
436	if !reflect.DeepEqual(txts, want) {
437		t.Fatalf("LookupTXT golang.rsc.io incorrect\nhave %q\nwant %q", txts, want)
438	}
439}
440
441var lookupGoogleIPTests = []struct {
442	name string
443}{
444	{"google.com"},
445	{"google.com."},
446}
447
448func TestLookupGoogleIP(t *testing.T) {
449	mustHaveExternalNetwork(t)
450	testenv.SkipFlakyNet(t)
451
452	if !supportsIPv4() || !*testIPv4 {
453		t.Skip("IPv4 is required")
454	}
455
456	defer dnsWaitGroup.Wait()
457
458	for _, tt := range lookupGoogleIPTests {
459		ips, err := LookupIP(tt.name)
460		if err != nil {
461			t.Fatal(err)
462		}
463		if len(ips) == 0 {
464			t.Error("got no record")
465		}
466		for _, ip := range ips {
467			if ip.To4() == nil && ip.To16() == nil {
468				t.Errorf("got %v; want an IP address", ip)
469			}
470		}
471	}
472}
473
474var revAddrTests = []struct {
475	Addr      string
476	Reverse   string
477	ErrPrefix string
478}{
479	{"1.2.3.4", "4.3.2.1.in-addr.arpa.", ""},
480	{"245.110.36.114", "114.36.110.245.in-addr.arpa.", ""},
481	{"::ffff:12.34.56.78", "78.56.34.12.in-addr.arpa.", ""},
482	{"::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.", ""},
483	{"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.", ""},
484	{"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.", ""},
485	{"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.", ""},
486	{"1.2.3", "", "unrecognized address"},
487	{"1.2.3.4.5", "", "unrecognized address"},
488	{"1234:567:bcbca::89a:bcde", "", "unrecognized address"},
489	{"1234:567::bcbc:adad::89a:bcde", "", "unrecognized address"},
490}
491
492func TestReverseAddress(t *testing.T) {
493	defer dnsWaitGroup.Wait()
494	for i, tt := range revAddrTests {
495		a, err := reverseaddr(tt.Addr)
496		if len(tt.ErrPrefix) > 0 && err == nil {
497			t.Errorf("#%d: expected %q, got <nil> (error)", i, tt.ErrPrefix)
498			continue
499		}
500		if len(tt.ErrPrefix) == 0 && err != nil {
501			t.Errorf("#%d: expected <nil>, got %q (error)", i, err)
502		}
503		if err != nil && err.(*DNSError).Err != tt.ErrPrefix {
504			t.Errorf("#%d: expected %q, got %q (mismatched error)", i, tt.ErrPrefix, err.(*DNSError).Err)
505		}
506		if a != tt.Reverse {
507			t.Errorf("#%d: expected %q, got %q (reverse address)", i, tt.Reverse, a)
508		}
509	}
510}
511
512func TestDNSFlood(t *testing.T) {
513	if !*testDNSFlood {
514		t.Skip("test disabled; use -dnsflood to enable")
515	}
516
517	defer dnsWaitGroup.Wait()
518
519	var N = 5000
520	if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
521		// On Darwin this test consumes kernel threads much
522		// than other platforms for some reason.
523		// When we monitor the number of allocated Ms by
524		// observing on runtime.newm calls, we can see that it
525		// easily reaches the per process ceiling
526		// kern.num_threads when CGO_ENABLED=1 and
527		// GODEBUG=netdns=go.
528		N = 500
529	}
530
531	const timeout = 3 * time.Second
532	ctxHalfTimeout, cancel := context.WithTimeout(context.Background(), timeout/2)
533	defer cancel()
534	ctxTimeout, cancel := context.WithTimeout(context.Background(), timeout)
535	defer cancel()
536
537	c := make(chan error, 2*N)
538	for i := 0; i < N; i++ {
539		name := fmt.Sprintf("%d.net-test.golang.org", i)
540		go func() {
541			_, err := DefaultResolver.LookupIPAddr(ctxHalfTimeout, name)
542			c <- err
543		}()
544		go func() {
545			_, err := DefaultResolver.LookupIPAddr(ctxTimeout, name)
546			c <- err
547		}()
548	}
549	qstats := struct {
550		succeeded, failed         int
551		timeout, temporary, other int
552		unknown                   int
553	}{}
554	deadline := time.After(timeout + time.Second)
555	for i := 0; i < 2*N; i++ {
556		select {
557		case <-deadline:
558			t.Fatal("deadline exceeded")
559		case err := <-c:
560			switch err := err.(type) {
561			case nil:
562				qstats.succeeded++
563			case Error:
564				qstats.failed++
565				if err.Timeout() {
566					qstats.timeout++
567				}
568				if err.Temporary() {
569					qstats.temporary++
570				}
571				if !err.Timeout() && !err.Temporary() {
572					qstats.other++
573				}
574			default:
575				qstats.failed++
576				qstats.unknown++
577			}
578		}
579	}
580
581	// A high volume of DNS queries for sub-domain of golang.org
582	// would be coordinated by authoritative or recursive server,
583	// or stub resolver which implements query-response rate
584	// limitation, so we can expect some query successes and more
585	// failures including timeout, temporary and other here.
586	// As a rule, unknown must not be shown but it might possibly
587	// happen due to issue 4856 for now.
588	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}
590
591func TestLookupDotsWithLocalSource(t *testing.T) {
592	if !supportsIPv4() || !*testIPv4 {
593		t.Skip("IPv4 is required")
594	}
595
596	mustHaveExternalNetwork(t)
597
598	defer dnsWaitGroup.Wait()
599
600	for i, fn := range []func() func(){forceGoDNS, forceCgoDNS} {
601		fixup := fn()
602		if fixup == nil {
603			continue
604		}
605		names, err := LookupAddr("127.0.0.1")
606		fixup()
607		if err != nil {
608			t.Logf("#%d: %v", i, err)
609			continue
610		}
611		mode := "netgo"
612		if i == 1 {
613			mode = "netcgo"
614		}
615	loop:
616		for i, name := range names {
617			if strings.Index(name, ".") == len(name)-1 { // "localhost" not "localhost."
618				for j := range names {
619					if j == i {
620						continue
621					}
622					if names[j] == name[:len(name)-1] {
623						// It's OK if we find the name without the dot,
624						// as some systems say 127.0.0.1 localhost localhost.
625						continue loop
626					}
627				}
628				t.Errorf("%s: got %s; want %s", mode, name, name[:len(name)-1])
629			} else if strings.Contains(name, ".") && !strings.HasSuffix(name, ".") { // "localhost.localdomain." not "localhost.localdomain"
630				t.Errorf("%s: got %s; want name ending with trailing dot", mode, name)
631			}
632		}
633	}
634}
635
636func TestLookupDotsWithRemoteSource(t *testing.T) {
637	if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
638		testenv.SkipFlaky(t, 27992)
639	}
640	mustHaveExternalNetwork(t)
641	testenv.SkipFlakyNet(t)
642
643	if !supportsIPv4() || !*testIPv4 {
644		t.Skip("IPv4 is required")
645	}
646
647	if runtime.GOOS == "ios" {
648		t.Skip("no resolv.conf on iOS")
649	}
650
651	defer dnsWaitGroup.Wait()
652
653	if fixup := forceGoDNS(); fixup != nil {
654		testDots(t, "go")
655		fixup()
656	}
657	if fixup := forceCgoDNS(); fixup != nil {
658		testDots(t, "cgo")
659		fixup()
660	}
661}
662
663func testDots(t *testing.T, mode string) {
664	names, err := LookupAddr("8.8.8.8") // Google dns server
665	if err != nil {
666		t.Errorf("LookupAddr(8.8.8.8): %v (mode=%v)", err, mode)
667	} else {
668		for _, name := range names {
669			if !hasSuffixFold(name, ".google.com.") && !hasSuffixFold(name, ".google.") {
670				t.Errorf("LookupAddr(8.8.8.8) = %v, want names ending in .google.com or .google with trailing dot (mode=%v)", names, mode)
671				break
672			}
673		}
674	}
675
676	cname, err := LookupCNAME("www.mit.edu")
677	if err != nil {
678		t.Errorf("LookupCNAME(www.mit.edu, mode=%v): %v", mode, err)
679	} else if !strings.HasSuffix(cname, ".") {
680		t.Errorf("LookupCNAME(www.mit.edu) = %v, want cname ending in . with trailing dot (mode=%v)", cname, mode)
681	}
682
683	mxs, err := LookupMX("google.com")
684	if err != nil {
685		t.Errorf("LookupMX(google.com): %v (mode=%v)", err, mode)
686	} else {
687		for _, mx := range mxs {
688			if !hasSuffixFold(mx.Host, ".google.com.") {
689				t.Errorf("LookupMX(google.com) = %v, want names ending in .google.com. with trailing dot (mode=%v)", mxString(mxs), mode)
690				break
691			}
692		}
693	}
694
695	nss, err := LookupNS("google.com")
696	if err != nil {
697		t.Errorf("LookupNS(google.com): %v (mode=%v)", err, mode)
698	} else {
699		for _, ns := range nss {
700			if !hasSuffixFold(ns.Host, ".google.com.") {
701				t.Errorf("LookupNS(google.com) = %v, want names ending in .google.com. with trailing dot (mode=%v)", nsString(nss), mode)
702				break
703			}
704		}
705	}
706
707	cname, srvs, err := LookupSRV("ldap", "tcp", "google.com")
708	if err != nil {
709		t.Errorf("LookupSRV(ldap, tcp, google.com): %v (mode=%v)", err, mode)
710	} else {
711		if !hasSuffixFold(cname, ".google.com.") {
712			t.Errorf("LookupSRV(ldap, tcp, google.com) returned cname=%v, want name ending in .google.com. with trailing dot (mode=%v)", cname, mode)
713		}
714		for _, srv := range srvs {
715			if !hasSuffixFold(srv.Target, ".google.com.") {
716				t.Errorf("LookupSRV(ldap, tcp, google.com) returned addrs=%v, want names ending in .google.com. with trailing dot (mode=%v)", srvString(srvs), mode)
717				break
718			}
719		}
720	}
721}
722
723func mxString(mxs []*MX) string {
724	var buf strings.Builder
725	sep := ""
726	fmt.Fprintf(&buf, "[")
727	for _, mx := range mxs {
728		fmt.Fprintf(&buf, "%s%s:%d", sep, mx.Host, mx.Pref)
729		sep = " "
730	}
731	fmt.Fprintf(&buf, "]")
732	return buf.String()
733}
734
735func nsString(nss []*NS) string {
736	var buf strings.Builder
737	sep := ""
738	fmt.Fprintf(&buf, "[")
739	for _, ns := range nss {
740		fmt.Fprintf(&buf, "%s%s", sep, ns.Host)
741		sep = " "
742	}
743	fmt.Fprintf(&buf, "]")
744	return buf.String()
745}
746
747func srvString(srvs []*SRV) string {
748	var buf strings.Builder
749	sep := ""
750	fmt.Fprintf(&buf, "[")
751	for _, srv := range srvs {
752		fmt.Fprintf(&buf, "%s%s:%d:%d:%d", sep, srv.Target, srv.Port, srv.Priority, srv.Weight)
753		sep = " "
754	}
755	fmt.Fprintf(&buf, "]")
756	return buf.String()
757}
758
759func TestLookupPort(t *testing.T) {
760	// See https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
761	//
762	// Please be careful about adding new test cases.
763	// There are platforms which have incomplete mappings for
764	// restricted resource access and security reasons.
765	type test struct {
766		network string
767		name    string
768		port    int
769		ok      bool
770	}
771	var tests = []test{
772		{"tcp", "0", 0, true},
773		{"udp", "0", 0, true},
774		{"udp", "domain", 53, true},
775
776		{"--badnet--", "zzz", 0, false},
777		{"tcp", "--badport--", 0, false},
778		{"tcp", "-1", 0, false},
779		{"tcp", "65536", 0, false},
780		{"udp", "-1", 0, false},
781		{"udp", "65536", 0, false},
782		{"tcp", "123456789", 0, false},
783
784		// Issue 13610: LookupPort("tcp", "")
785		{"tcp", "", 0, true},
786		{"tcp4", "", 0, true},
787		{"tcp6", "", 0, true},
788		{"udp", "", 0, true},
789		{"udp4", "", 0, true},
790		{"udp6", "", 0, true},
791	}
792
793	switch runtime.GOOS {
794	case "android":
795		if netGoBuildTag {
796			t.Skipf("not supported on %s without cgo; see golang.org/issues/14576", runtime.GOOS)
797		}
798	default:
799		tests = append(tests, test{"tcp", "http", 80, true})
800	}
801
802	for _, tt := range tests {
803		port, err := LookupPort(tt.network, tt.name)
804		if port != tt.port || (err == nil) != tt.ok {
805			t.Errorf("LookupPort(%q, %q) = %d, %v; want %d, error=%t", tt.network, tt.name, port, err, tt.port, !tt.ok)
806		}
807		if err != nil {
808			if perr := parseLookupPortError(err); perr != nil {
809				t.Error(perr)
810			}
811		}
812	}
813}
814
815// Like TestLookupPort but with minimal tests that should always pass
816// because the answers are baked-in to the net package.
817func TestLookupPort_Minimal(t *testing.T) {
818	type test struct {
819		network string
820		name    string
821		port    int
822	}
823	var tests = []test{
824		{"tcp", "http", 80},
825		{"tcp", "HTTP", 80}, // case shouldn't matter
826		{"tcp", "https", 443},
827		{"tcp", "ssh", 22},
828		{"tcp", "gopher", 70},
829		{"tcp4", "http", 80},
830		{"tcp6", "http", 80},
831	}
832
833	for _, tt := range tests {
834		port, err := LookupPort(tt.network, tt.name)
835		if port != tt.port || err != nil {
836			t.Errorf("LookupPort(%q, %q) = %d, %v; want %d, error=nil", tt.network, tt.name, port, err, tt.port)
837		}
838	}
839}
840
841func TestLookupProtocol_Minimal(t *testing.T) {
842	type test struct {
843		name string
844		want int
845	}
846	var tests = []test{
847		{"tcp", 6},
848		{"TcP", 6}, // case shouldn't matter
849		{"icmp", 1},
850		{"igmp", 2},
851		{"udp", 17},
852		{"ipv6-icmp", 58},
853	}
854
855	for _, tt := range tests {
856		got, err := lookupProtocol(context.Background(), tt.name)
857		if got != tt.want || err != nil {
858			t.Errorf("LookupProtocol(%q) = %d, %v; want %d, error=nil", tt.name, got, err, tt.want)
859		}
860	}
861
862}
863
864func TestLookupNonLDH(t *testing.T) {
865	defer dnsWaitGroup.Wait()
866
867	if fixup := forceGoDNS(); fixup != nil {
868		defer fixup()
869	}
870
871	// "LDH" stands for letters, digits, and hyphens and is the usual
872	// description of standard DNS names.
873	// This test is checking that other kinds of names are reported
874	// as not found, not reported as invalid names.
875	addrs, err := LookupHost("!!!.###.bogus..domain.")
876	if err == nil {
877		t.Fatalf("lookup succeeded: %v", addrs)
878	}
879	if !strings.HasSuffix(err.Error(), errNoSuchHost.Error()) {
880		t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost)
881	}
882	if !err.(*DNSError).IsNotFound {
883		t.Fatalf("lookup error = %v, want true", err.(*DNSError).IsNotFound)
884	}
885}
886
887func TestLookupContextCancel(t *testing.T) {
888	mustHaveExternalNetwork(t)
889	testenv.SkipFlakyNet(t)
890
891	origTestHookLookupIP := testHookLookupIP
892	defer func() {
893		dnsWaitGroup.Wait()
894		testHookLookupIP = origTestHookLookupIP
895	}()
896
897	lookupCtx, cancelLookup := context.WithCancel(context.Background())
898	unblockLookup := make(chan struct{})
899
900	// Set testHookLookupIP to start a new, concurrent call to LookupIPAddr
901	// and cancel the original one, then block until the canceled call has returned
902	// (ensuring that it has performed any synchronous cleanup).
903	testHookLookupIP = func(
904		ctx context.Context,
905		fn func(context.Context, string, string) ([]IPAddr, error),
906		network string,
907		host string,
908	) ([]IPAddr, error) {
909		select {
910		case <-unblockLookup:
911		default:
912			// Start a concurrent LookupIPAddr for the same host while the caller is
913			// still blocked, and sleep a little to give it time to be deduplicated
914			// before we cancel (and unblock) the caller.
915			// (If the timing doesn't quite work out, we'll end up testing sequential
916			// calls instead of concurrent ones, but the test should still pass.)
917			t.Logf("starting concurrent LookupIPAddr")
918			dnsWaitGroup.Add(1)
919			go func() {
920				defer dnsWaitGroup.Done()
921				_, err := DefaultResolver.LookupIPAddr(context.Background(), host)
922				if err != nil {
923					t.Error(err)
924				}
925			}()
926			time.Sleep(1 * time.Millisecond)
927		}
928
929		cancelLookup()
930		<-unblockLookup
931		// If the concurrent lookup above is deduplicated to this one
932		// (as we expect to happen most of the time), it is important
933		// that the original call does not cancel the shared Context.
934		// (See https://go.dev/issue/22724.) Explicitly check for
935		// cancellation now, just in case fn itself doesn't notice it.
936		if err := ctx.Err(); err != nil {
937			t.Logf("testHookLookupIP canceled")
938			return nil, err
939		}
940		t.Logf("testHookLookupIP performing lookup")
941		return fn(ctx, network, host)
942	}
943
944	_, err := DefaultResolver.LookupIPAddr(lookupCtx, "google.com")
945	if dnsErr, ok := err.(*DNSError); !ok || dnsErr.Err != errCanceled.Error() {
946		t.Errorf("unexpected error from canceled, blocked LookupIPAddr: %v", err)
947	}
948	close(unblockLookup)
949}
950
951// Issue 24330: treat the nil *Resolver like a zero value. Verify nothing
952// crashes if nil is used.
953func TestNilResolverLookup(t *testing.T) {
954	mustHaveExternalNetwork(t)
955	var r *Resolver = nil
956	ctx := context.Background()
957
958	// Don't care about the results, just that nothing panics:
959	r.LookupAddr(ctx, "8.8.8.8")
960	r.LookupCNAME(ctx, "google.com")
961	r.LookupHost(ctx, "google.com")
962	r.LookupIPAddr(ctx, "google.com")
963	r.LookupIP(ctx, "ip", "google.com")
964	r.LookupMX(ctx, "gmail.com")
965	r.LookupNS(ctx, "google.com")
966	r.LookupPort(ctx, "tcp", "smtp")
967	r.LookupSRV(ctx, "service", "proto", "name")
968	r.LookupTXT(ctx, "gmail.com")
969}
970
971// TestLookupHostCancel verifies that lookup works even after many
972// canceled lookups (see golang.org/issue/24178 for details).
973func TestLookupHostCancel(t *testing.T) {
974	mustHaveExternalNetwork(t)
975	testenv.SkipFlakyNet(t)
976	t.Parallel() // Executes 600ms worth of sequential sleeps.
977
978	const (
979		google        = "www.google.com"
980		invalidDomain = "invalid.invalid" // RFC 2606 reserves .invalid
981		n             = 600               // this needs to be larger than threadLimit size
982	)
983
984	_, err := LookupHost(google)
985	if err != nil {
986		t.Fatal(err)
987	}
988
989	ctx, cancel := context.WithCancel(context.Background())
990	cancel()
991	for i := 0; i < n; i++ {
992		addr, err := DefaultResolver.LookupHost(ctx, invalidDomain)
993		if err == nil {
994			t.Fatalf("LookupHost(%q): returns %v, but should fail", invalidDomain, addr)
995		}
996
997		// Don't verify what the actual error is.
998		// We know that it must be non-nil because the domain is invalid,
999		// but we don't have any guarantee that LookupHost actually bothers
1000		// to check for cancellation on the fast path.
1001		// (For example, it could use a local cache to avoid blocking entirely.)
1002
1003		// The lookup may deduplicate in-flight requests, so give it time to settle
1004		// in between.
1005		time.Sleep(time.Millisecond * 1)
1006	}
1007
1008	_, err = LookupHost(google)
1009	if err != nil {
1010		t.Fatal(err)
1011	}
1012}
1013
1014type lookupCustomResolver struct {
1015	*Resolver
1016	mu     sync.RWMutex
1017	dialed bool
1018}
1019
1020func (lcr *lookupCustomResolver) dial() func(ctx context.Context, network, address string) (Conn, error) {
1021	return func(ctx context.Context, network, address string) (Conn, error) {
1022		lcr.mu.Lock()
1023		lcr.dialed = true
1024		lcr.mu.Unlock()
1025		return Dial(network, address)
1026	}
1027}
1028
1029// TestConcurrentPreferGoResolversDial tests that multiple resolvers with the
1030// PreferGo option used concurrently are all dialed properly.
1031func TestConcurrentPreferGoResolversDial(t *testing.T) {
1032	switch runtime.GOOS {
1033	case "plan9":
1034		// TODO: plan9 implementation of the resolver uses the Dial function since
1035		// https://go.dev/cl/409234, this test could probably be reenabled.
1036		t.Skipf("skip on %v", runtime.GOOS)
1037	}
1038
1039	testenv.MustHaveExternalNetwork(t)
1040	testenv.SkipFlakyNet(t)
1041
1042	defer dnsWaitGroup.Wait()
1043
1044	resolvers := make([]*lookupCustomResolver, 2)
1045	for i := range resolvers {
1046		cs := lookupCustomResolver{Resolver: &Resolver{PreferGo: true}}
1047		cs.Dial = cs.dial()
1048		resolvers[i] = &cs
1049	}
1050
1051	var wg sync.WaitGroup
1052	wg.Add(len(resolvers))
1053	for i, resolver := range resolvers {
1054		go func(r *Resolver, index int) {
1055			defer wg.Done()
1056			_, err := r.LookupIPAddr(context.Background(), "google.com")
1057			if err != nil {
1058				t.Errorf("lookup failed for resolver %d: %q", index, err)
1059			}
1060		}(resolver.Resolver, i)
1061	}
1062	wg.Wait()
1063
1064	if t.Failed() {
1065		t.FailNow()
1066	}
1067
1068	for i, resolver := range resolvers {
1069		if !resolver.dialed {
1070			t.Errorf("custom resolver %d not dialed during lookup", i)
1071		}
1072	}
1073}
1074
1075var ipVersionTests = []struct {
1076	network string
1077	version byte
1078}{
1079	{"tcp", 0},
1080	{"tcp4", '4'},
1081	{"tcp6", '6'},
1082	{"udp", 0},
1083	{"udp4", '4'},
1084	{"udp6", '6'},
1085	{"ip", 0},
1086	{"ip4", '4'},
1087	{"ip6", '6'},
1088	{"ip7", 0},
1089	{"", 0},
1090}
1091
1092func TestIPVersion(t *testing.T) {
1093	for _, tt := range ipVersionTests {
1094		if version := ipVersion(tt.network); version != tt.version {
1095			t.Errorf("Family for: %s. Expected: %s, Got: %s", tt.network,
1096				string(tt.version), string(version))
1097		}
1098	}
1099}
1100
1101// Issue 28600: The context that is used to lookup ips should always
1102// preserve the values from the context that was passed into LookupIPAddr.
1103func TestLookupIPAddrPreservesContextValues(t *testing.T) {
1104	origTestHookLookupIP := testHookLookupIP
1105	defer func() { testHookLookupIP = origTestHookLookupIP }()
1106
1107	keyValues := []struct {
1108		key, value any
1109	}{
1110		{"key-1", 12},
1111		{384, "value2"},
1112		{new(float64), 137},
1113	}
1114	ctx := context.Background()
1115	for _, kv := range keyValues {
1116		ctx = context.WithValue(ctx, kv.key, kv.value)
1117	}
1118
1119	wantIPs := []IPAddr{
1120		{IP: IPv4(127, 0, 0, 1)},
1121		{IP: IPv6loopback},
1122	}
1123
1124	checkCtxValues := func(ctx_ context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
1125		for _, kv := range keyValues {
1126			g, w := ctx_.Value(kv.key), kv.value
1127			if !reflect.DeepEqual(g, w) {
1128				t.Errorf("Value lookup:\n\tGot:  %v\n\tWant: %v", g, w)
1129			}
1130		}
1131		return wantIPs, nil
1132	}
1133	testHookLookupIP = checkCtxValues
1134
1135	resolvers := []*Resolver{
1136		nil,
1137		new(Resolver),
1138	}
1139
1140	for i, resolver := range resolvers {
1141		gotIPs, err := resolver.LookupIPAddr(ctx, "golang.org")
1142		if err != nil {
1143			t.Errorf("Resolver #%d: unexpected error: %v", i, err)
1144		}
1145		if !reflect.DeepEqual(gotIPs, wantIPs) {
1146			t.Errorf("#%d: mismatched IPAddr results\n\tGot: %v\n\tWant: %v", i, gotIPs, wantIPs)
1147		}
1148	}
1149}
1150
1151// Issue 30521: The lookup group should call the resolver for each network.
1152func TestLookupIPAddrConcurrentCallsForNetworks(t *testing.T) {
1153	origTestHookLookupIP := testHookLookupIP
1154	defer func() { testHookLookupIP = origTestHookLookupIP }()
1155
1156	queries := [][]string{
1157		{"udp", "golang.org"},
1158		{"udp4", "golang.org"},
1159		{"udp6", "golang.org"},
1160		{"udp", "golang.org"},
1161		{"udp", "golang.org"},
1162	}
1163	results := map[[2]string][]IPAddr{
1164		{"udp", "golang.org"}: {
1165			{IP: IPv4(127, 0, 0, 1)},
1166			{IP: IPv6loopback},
1167		},
1168		{"udp4", "golang.org"}: {
1169			{IP: IPv4(127, 0, 0, 1)},
1170		},
1171		{"udp6", "golang.org"}: {
1172			{IP: IPv6loopback},
1173		},
1174	}
1175	calls := int32(0)
1176	waitCh := make(chan struct{})
1177	testHookLookupIP = func(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
1178		// We'll block until this is called one time for each different
1179		// expected result. This will ensure that the lookup group would wait
1180		// for the existing call if it was to be reused.
1181		if atomic.AddInt32(&calls, 1) == int32(len(results)) {
1182			close(waitCh)
1183		}
1184		select {
1185		case <-waitCh:
1186		case <-ctx.Done():
1187			return nil, ctx.Err()
1188		}
1189		return results[[2]string{network, host}], nil
1190	}
1191
1192	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
1193	defer cancel()
1194	wg := sync.WaitGroup{}
1195	for _, q := range queries {
1196		network := q[0]
1197		host := q[1]
1198		wg.Add(1)
1199		go func() {
1200			defer wg.Done()
1201			gotIPs, err := DefaultResolver.lookupIPAddr(ctx, network, host)
1202			if err != nil {
1203				t.Errorf("lookupIPAddr(%v, %v): unexpected error: %v", network, host, err)
1204			}
1205			wantIPs := results[[2]string{network, host}]
1206			if !reflect.DeepEqual(gotIPs, wantIPs) {
1207				t.Errorf("lookupIPAddr(%v, %v): mismatched IPAddr results\n\tGot: %v\n\tWant: %v", network, host, gotIPs, wantIPs)
1208			}
1209		}()
1210	}
1211	wg.Wait()
1212}
1213
1214// Issue 53995: Resolver.LookupIP should return error for empty host name.
1215func TestResolverLookupIPWithEmptyHost(t *testing.T) {
1216	_, err := DefaultResolver.LookupIP(context.Background(), "ip", "")
1217	if err == nil {
1218		t.Fatal("DefaultResolver.LookupIP for empty host success, want no host error")
1219	}
1220	if !strings.HasSuffix(err.Error(), errNoSuchHost.Error()) {
1221		t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost)
1222	}
1223}
1224
1225func TestWithUnexpiredValuesPreserved(t *testing.T) {
1226	ctx, cancel := context.WithCancel(context.Background())
1227
1228	// Insert a value into it.
1229	key, value := "key-1", 2
1230	ctx = context.WithValue(ctx, key, value)
1231
1232	// Now use the "values preserving context" like
1233	// we would for LookupIPAddr. See Issue 28600.
1234	ctx = withUnexpiredValuesPreserved(ctx)
1235
1236	// Lookup before expiry.
1237	if g, w := ctx.Value(key), value; g != w {
1238		t.Errorf("Lookup before expiry: Got %v Want %v", g, w)
1239	}
1240
1241	// Cancel the context.
1242	cancel()
1243
1244	// Lookup after expiry should return nil
1245	if g := ctx.Value(key); g != nil {
1246		t.Errorf("Lookup after expiry: Got %v want nil", g)
1247	}
1248}
1249
1250// Issue 31597: don't panic on null byte in name
1251func TestLookupNullByte(t *testing.T) {
1252	testenv.MustHaveExternalNetwork(t)
1253	testenv.SkipFlakyNet(t)
1254	LookupHost("foo\x00bar") // check that it doesn't panic; it used to on Windows
1255}
1256
1257func TestResolverLookupIP(t *testing.T) {
1258	testenv.MustHaveExternalNetwork(t)
1259
1260	v4Ok := supportsIPv4() && *testIPv4
1261	v6Ok := supportsIPv6() && *testIPv6
1262
1263	defer dnsWaitGroup.Wait()
1264
1265	for _, impl := range []struct {
1266		name string
1267		fn   func() func()
1268	}{
1269		{"go", forceGoDNS},
1270		{"cgo", forceCgoDNS},
1271	} {
1272		t.Run("implementation: "+impl.name, func(t *testing.T) {
1273			fixup := impl.fn()
1274			if fixup == nil {
1275				t.Skip("not supported")
1276			}
1277			defer fixup()
1278
1279			for _, network := range []string{"ip", "ip4", "ip6"} {
1280				t.Run("network: "+network, func(t *testing.T) {
1281					switch {
1282					case network == "ip4" && !v4Ok:
1283						t.Skip("IPv4 is not supported")
1284					case network == "ip6" && !v6Ok:
1285						t.Skip("IPv6 is not supported")
1286					}
1287
1288					// google.com has both A and AAAA records.
1289					const host = "google.com"
1290					ips, err := DefaultResolver.LookupIP(context.Background(), network, host)
1291					if err != nil {
1292						testenv.SkipFlakyNet(t)
1293						t.Fatalf("DefaultResolver.LookupIP(%q, %q): failed with unexpected error: %v", network, host, err)
1294					}
1295
1296					var v4Addrs []netip.Addr
1297					var v6Addrs []netip.Addr
1298					for _, ip := range ips {
1299						if addr, ok := netip.AddrFromSlice(ip); ok {
1300							if addr.Is4() {
1301								v4Addrs = append(v4Addrs, addr)
1302							} else {
1303								v6Addrs = append(v6Addrs, addr)
1304							}
1305						} else {
1306							t.Fatalf("IP=%q is neither IPv4 nor IPv6", ip)
1307						}
1308					}
1309
1310					// Check that we got the expected addresses.
1311					if network == "ip4" || network == "ip" && v4Ok {
1312						if len(v4Addrs) == 0 {
1313							t.Errorf("DefaultResolver.LookupIP(%q, %q): no IPv4 addresses", network, host)
1314						}
1315					}
1316					if network == "ip6" || network == "ip" && v6Ok {
1317						if len(v6Addrs) == 0 {
1318							t.Errorf("DefaultResolver.LookupIP(%q, %q): no IPv6 addresses", network, host)
1319						}
1320					}
1321
1322					// Check that we didn't get any unexpected addresses.
1323					if network == "ip6" && len(v4Addrs) > 0 {
1324						t.Errorf("DefaultResolver.LookupIP(%q, %q): unexpected IPv4 addresses: %v", network, host, v4Addrs)
1325					}
1326					if network == "ip4" && len(v6Addrs) > 0 {
1327						t.Errorf("DefaultResolver.LookupIP(%q, %q): unexpected IPv6 or IPv4-mapped IPv6 addresses: %v", network, host, v6Addrs)
1328					}
1329				})
1330			}
1331		})
1332	}
1333}
1334
1335// A context timeout should still return a DNSError.
1336func TestDNSTimeout(t *testing.T) {
1337	origTestHookLookupIP := testHookLookupIP
1338	defer func() { testHookLookupIP = origTestHookLookupIP }()
1339	defer dnsWaitGroup.Wait()
1340
1341	timeoutHookGo := make(chan bool, 1)
1342	timeoutHook := func(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
1343		<-timeoutHookGo
1344		return nil, context.DeadlineExceeded
1345	}
1346	testHookLookupIP = timeoutHook
1347
1348	checkErr := func(err error) {
1349		t.Helper()
1350		if err == nil {
1351			t.Error("expected an error")
1352		} else if dnserr, ok := err.(*DNSError); !ok {
1353			t.Errorf("got error type %T, want %T", err, (*DNSError)(nil))
1354		} else if !dnserr.IsTimeout {
1355			t.Errorf("got error %#v, want IsTimeout == true", dnserr)
1356		} else if isTimeout := dnserr.Timeout(); !isTimeout {
1357			t.Errorf("got err.Timeout() == %t, want true", isTimeout)
1358		}
1359	}
1360
1361	// Single lookup.
1362	timeoutHookGo <- true
1363	_, err := LookupIP("golang.org")
1364	checkErr(err)
1365
1366	// Double lookup.
1367	var err1, err2 error
1368	var wg sync.WaitGroup
1369	wg.Add(2)
1370	go func() {
1371		defer wg.Done()
1372		_, err1 = LookupIP("golang1.org")
1373	}()
1374	go func() {
1375		defer wg.Done()
1376		_, err2 = LookupIP("golang1.org")
1377	}()
1378	close(timeoutHookGo)
1379	wg.Wait()
1380	checkErr(err1)
1381	checkErr(err2)
1382
1383	// Double lookup with context.
1384	timeoutHookGo = make(chan bool)
1385	ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
1386	wg.Add(2)
1387	go func() {
1388		defer wg.Done()
1389		_, err1 = DefaultResolver.LookupIPAddr(ctx, "golang2.org")
1390	}()
1391	go func() {
1392		defer wg.Done()
1393		_, err2 = DefaultResolver.LookupIPAddr(ctx, "golang2.org")
1394	}()
1395	time.Sleep(10 * time.Nanosecond)
1396	close(timeoutHookGo)
1397	wg.Wait()
1398	checkErr(err1)
1399	checkErr(err2)
1400	cancel()
1401}
1402
1403func TestLookupNoData(t *testing.T) {
1404	if runtime.GOOS == "plan9" {
1405		t.Skip("not supported on plan9")
1406	}
1407
1408	mustHaveExternalNetwork(t)
1409
1410	testLookupNoData(t, "default resolver")
1411
1412	func() {
1413		defer forceGoDNS()()
1414		testLookupNoData(t, "forced go resolver")
1415	}()
1416
1417	func() {
1418		defer forceCgoDNS()()
1419		testLookupNoData(t, "forced cgo resolver")
1420	}()
1421}
1422
1423func testLookupNoData(t *testing.T, prefix string) {
1424	attempts := 0
1425	for {
1426		// Domain that doesn't have any A/AAAA RRs, but has different one (in this case a TXT),
1427		// so that it returns an empty response without any error codes (NXDOMAIN).
1428		_, err := LookupHost("golang.rsc.io.")
1429		if err == nil {
1430			t.Errorf("%v: unexpected success", prefix)
1431			return
1432		}
1433
1434		var dnsErr *DNSError
1435		if errors.As(err, &dnsErr) {
1436			succeeded := true
1437			if !dnsErr.IsNotFound {
1438				succeeded = false
1439				t.Logf("%v: IsNotFound is set to false", prefix)
1440			}
1441
1442			if dnsErr.Err != errNoSuchHost.Error() {
1443				succeeded = false
1444				t.Logf("%v: error message is not equal to: %v", prefix, errNoSuchHost.Error())
1445			}
1446
1447			if succeeded {
1448				return
1449			}
1450		}
1451
1452		testenv.SkipFlakyNet(t)
1453		if attempts < len(backoffDuration) {
1454			dur := backoffDuration[attempts]
1455			t.Logf("%v: backoff %v after failure %v\n", prefix, dur, err)
1456			time.Sleep(dur)
1457			attempts++
1458			continue
1459		}
1460
1461		t.Errorf("%v: unexpected error: %v", prefix, err)
1462		return
1463	}
1464}
1465
1466func TestLookupPortNotFound(t *testing.T) {
1467	allResolvers(t, func(t *testing.T) {
1468		_, err := LookupPort("udp", "_-unknown-service-")
1469		var dnsErr *DNSError
1470		if !errors.As(err, &dnsErr) || !dnsErr.IsNotFound {
1471			t.Fatalf("unexpected error: %v", err)
1472		}
1473	})
1474}
1475
1476// submissions service is only available through a tcp network, see:
1477// https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=submissions
1478var tcpOnlyService = func() string {
1479	// plan9 does not have submissions service defined in the service database.
1480	if runtime.GOOS == "plan9" {
1481		return "https"
1482	}
1483	return "submissions"
1484}()
1485
1486func TestLookupPortDifferentNetwork(t *testing.T) {
1487	allResolvers(t, func(t *testing.T) {
1488		_, err := LookupPort("udp", tcpOnlyService)
1489		var dnsErr *DNSError
1490		if !errors.As(err, &dnsErr) || !dnsErr.IsNotFound {
1491			t.Fatalf("unexpected error: %v", err)
1492		}
1493	})
1494}
1495
1496func TestLookupPortEmptyNetworkString(t *testing.T) {
1497	allResolvers(t, func(t *testing.T) {
1498		_, err := LookupPort("", tcpOnlyService)
1499		if err != nil {
1500			t.Fatalf("unexpected error: %v", err)
1501		}
1502	})
1503}
1504
1505func TestLookupPortIPNetworkString(t *testing.T) {
1506	allResolvers(t, func(t *testing.T) {
1507		_, err := LookupPort("ip", tcpOnlyService)
1508		if err != nil {
1509			t.Fatalf("unexpected error: %v", err)
1510		}
1511	})
1512}
1513
1514func TestLookupNoSuchHost(t *testing.T) {
1515	mustHaveExternalNetwork(t)
1516
1517	const testNXDOMAIN = "invalid.invalid."
1518	const testNODATA = "_ldap._tcp.google.com."
1519
1520	tests := []struct {
1521		name  string
1522		query func() error
1523	}{
1524		{
1525			name: "LookupCNAME NXDOMAIN",
1526			query: func() error {
1527				_, err := LookupCNAME(testNXDOMAIN)
1528				return err
1529			},
1530		},
1531		{
1532			name: "LookupHost NXDOMAIN",
1533			query: func() error {
1534				_, err := LookupHost(testNXDOMAIN)
1535				return err
1536			},
1537		},
1538		{
1539			name: "LookupHost NODATA",
1540			query: func() error {
1541				_, err := LookupHost(testNODATA)
1542				return err
1543			},
1544		},
1545		{
1546			name: "LookupMX NXDOMAIN",
1547			query: func() error {
1548				_, err := LookupMX(testNXDOMAIN)
1549				return err
1550			},
1551		},
1552		{
1553			name: "LookupMX NODATA",
1554			query: func() error {
1555				_, err := LookupMX(testNODATA)
1556				return err
1557			},
1558		},
1559		{
1560			name: "LookupNS NXDOMAIN",
1561			query: func() error {
1562				_, err := LookupNS(testNXDOMAIN)
1563				return err
1564			},
1565		},
1566		{
1567			name: "LookupNS NODATA",
1568			query: func() error {
1569				_, err := LookupNS(testNODATA)
1570				return err
1571			},
1572		},
1573		{
1574			name: "LookupSRV NXDOMAIN",
1575			query: func() error {
1576				_, _, err := LookupSRV("unknown", "tcp", testNXDOMAIN)
1577				return err
1578			},
1579		},
1580		{
1581			name: "LookupTXT NXDOMAIN",
1582			query: func() error {
1583				_, err := LookupTXT(testNXDOMAIN)
1584				return err
1585			},
1586		},
1587		{
1588			name: "LookupTXT NODATA",
1589			query: func() error {
1590				_, err := LookupTXT(testNODATA)
1591				return err
1592			},
1593		},
1594	}
1595
1596	for _, v := range tests {
1597		t.Run(v.name, func(t *testing.T) {
1598			allResolvers(t, func(t *testing.T) {
1599				attempts := 0
1600				for {
1601					err := v.query()
1602					if err == nil {
1603						t.Errorf("unexpected success")
1604						return
1605					}
1606					if dnsErr, ok := err.(*DNSError); ok {
1607						succeeded := true
1608						if !dnsErr.IsNotFound {
1609							succeeded = false
1610							t.Log("IsNotFound is set to false")
1611						}
1612						if dnsErr.Err != errNoSuchHost.Error() {
1613							succeeded = false
1614							t.Logf("error message is not equal to: %v", errNoSuchHost.Error())
1615						}
1616						if succeeded {
1617							return
1618						}
1619					}
1620					testenv.SkipFlakyNet(t)
1621					if attempts < len(backoffDuration) {
1622						dur := backoffDuration[attempts]
1623						t.Logf("backoff %v after failure %v\n", dur, err)
1624						time.Sleep(dur)
1625						attempts++
1626						continue
1627					}
1628					t.Errorf("unexpected error: %v", err)
1629					return
1630				}
1631			})
1632		})
1633	}
1634}
1635
1636func TestDNSErrorUnwrap(t *testing.T) {
1637	if runtime.GOOS == "plan9" {
1638		// The Plan 9 implementation of the resolver doesn't use the Dial function yet. See https://go.dev/cl/409234
1639		t.Skip("skipping on plan9")
1640	}
1641	rDeadlineExcceeded := &Resolver{PreferGo: true, Dial: func(ctx context.Context, network, address string) (Conn, error) {
1642		return nil, context.DeadlineExceeded
1643	}}
1644	rCancelled := &Resolver{PreferGo: true, Dial: func(ctx context.Context, network, address string) (Conn, error) {
1645		return nil, context.Canceled
1646	}}
1647
1648	_, err := rDeadlineExcceeded.LookupHost(context.Background(), "test.go.dev")
1649	if !errors.Is(err, context.DeadlineExceeded) {
1650		t.Errorf("errors.Is(err, context.DeadlineExceeded) = false; want = true")
1651	}
1652
1653	_, err = rCancelled.LookupHost(context.Background(), "test.go.dev")
1654	if !errors.Is(err, context.Canceled) {
1655		t.Errorf("errors.Is(err, context.Canceled) = false; want = true")
1656	}
1657
1658	ctx, cancel := context.WithCancel(context.Background())
1659	cancel()
1660	_, err = goResolver.LookupHost(ctx, "text.go.dev")
1661	if !errors.Is(err, context.Canceled) {
1662		t.Errorf("errors.Is(err, context.Canceled) = false; want = true")
1663	}
1664}
1665