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//go:build !js
6
7package net
8
9import (
10	"bytes"
11	"context"
12	"fmt"
13	"internal/testenv"
14	"reflect"
15	"runtime"
16	"sort"
17	"strings"
18	"sync"
19	"sync/atomic"
20	"testing"
21	"time"
22)
23
24func hasSuffixFold(s, suffix string) bool {
25	return strings.HasSuffix(strings.ToLower(s), strings.ToLower(suffix))
26}
27
28func lookupLocalhost(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
29	switch host {
30	case "localhost":
31		return []IPAddr{
32			{IP: IPv4(127, 0, 0, 1)},
33			{IP: IPv6loopback},
34		}, nil
35	default:
36		return fn(ctx, network, host)
37	}
38}
39
40// The Lookup APIs use various sources such as local database, DNS or
41// mDNS, and may use platform-dependent DNS stub resolver if possible.
42// The APIs accept any of forms for a query; host name in various
43// encodings, UTF-8 encoded net name, domain name, FQDN or absolute
44// FQDN, but the result would be one of the forms and it depends on
45// the circumstances.
46
47var lookupGoogleSRVTests = []struct {
48	service, proto, name string
49	cname, target        string
50}{
51	{
52		"xmpp-server", "tcp", "google.com",
53		"google.com.", "google.com.",
54	},
55	{
56		"xmpp-server", "tcp", "google.com.",
57		"google.com.", "google.com.",
58	},
59
60	// non-standard back door
61	{
62		"", "", "_xmpp-server._tcp.google.com",
63		"google.com.", "google.com.",
64	},
65	{
66		"", "", "_xmpp-server._tcp.google.com.",
67		"google.com.", "google.com.",
68	},
69}
70
71var backoffDuration = [...]time.Duration{time.Second, 5 * time.Second, 30 * time.Second}
72
73func TestLookupGoogleSRV(t *testing.T) {
74	t.Parallel()
75	mustHaveExternalNetwork(t)
76
77	if iOS() {
78		t.Skip("no resolv.conf on iOS")
79	}
80
81	if !supportsIPv4() || !*testIPv4 {
82		t.Skip("IPv4 is required")
83	}
84
85	attempts := 0
86	for i := 0; i < len(lookupGoogleSRVTests); i++ {
87		tt := lookupGoogleSRVTests[i]
88		cname, srvs, err := LookupSRV(tt.service, tt.proto, tt.name)
89		if err != nil {
90			testenv.SkipFlakyNet(t)
91			if attempts < len(backoffDuration) {
92				dur := backoffDuration[attempts]
93				t.Logf("backoff %v after failure %v\n", dur, err)
94				time.Sleep(dur)
95				attempts++
96				i--
97				continue
98			}
99			t.Fatal(err)
100		}
101		if len(srvs) == 0 {
102			t.Error("got no record")
103		}
104		if !hasSuffixFold(cname, tt.cname) {
105			t.Errorf("got %s; want %s", cname, tt.cname)
106		}
107		for _, srv := range srvs {
108			if !hasSuffixFold(srv.Target, tt.target) {
109				t.Errorf("got %v; want a record containing %s", srv, tt.target)
110			}
111		}
112	}
113}
114
115var lookupGmailMXTests = []struct {
116	name, host string
117}{
118	{"gmail.com", "google.com."},
119	{"gmail.com.", "google.com."},
120}
121
122func TestLookupGmailMX(t *testing.T) {
123	t.Parallel()
124	mustHaveExternalNetwork(t)
125
126	if iOS() {
127		t.Skip("no resolv.conf on iOS")
128	}
129
130	if !supportsIPv4() || !*testIPv4 {
131		t.Skip("IPv4 is required")
132	}
133
134	attempts := 0
135	for i := 0; i < len(lookupGmailMXTests); i++ {
136		tt := lookupGmailMXTests[i]
137		mxs, err := LookupMX(tt.name)
138		if err != nil {
139			testenv.SkipFlakyNet(t)
140			if attempts < len(backoffDuration) {
141				dur := backoffDuration[attempts]
142				t.Logf("backoff %v after failure %v\n", dur, err)
143				time.Sleep(dur)
144				attempts++
145				i--
146				continue
147			}
148			t.Fatal(err)
149		}
150		if len(mxs) == 0 {
151			t.Error("got no record")
152		}
153		for _, mx := range mxs {
154			if !hasSuffixFold(mx.Host, tt.host) {
155				t.Errorf("got %v; want a record containing %s", mx, tt.host)
156			}
157		}
158	}
159}
160
161var lookupGmailNSTests = []struct {
162	name, host string
163}{
164	{"gmail.com", "google.com."},
165	{"gmail.com.", "google.com."},
166}
167
168func TestLookupGmailNS(t *testing.T) {
169	t.Parallel()
170	mustHaveExternalNetwork(t)
171
172	if iOS() {
173		t.Skip("no resolv.conf on iOS")
174	}
175
176	if !supportsIPv4() || !*testIPv4 {
177		t.Skip("IPv4 is required")
178	}
179
180	attempts := 0
181	for i := 0; i < len(lookupGmailNSTests); i++ {
182		tt := lookupGmailNSTests[i]
183		nss, err := LookupNS(tt.name)
184		if err != nil {
185			testenv.SkipFlakyNet(t)
186			if attempts < len(backoffDuration) {
187				dur := backoffDuration[attempts]
188				t.Logf("backoff %v after failure %v\n", dur, err)
189				time.Sleep(dur)
190				attempts++
191				i--
192				continue
193			}
194			t.Fatal(err)
195		}
196		if len(nss) == 0 {
197			t.Error("got no record")
198		}
199		for _, ns := range nss {
200			if !hasSuffixFold(ns.Host, tt.host) {
201				t.Errorf("got %v; want a record containing %s", ns, tt.host)
202			}
203		}
204	}
205}
206
207var lookupGmailTXTTests = []struct {
208	name, txt, host string
209}{
210	{"gmail.com", "spf", "google.com"},
211	{"gmail.com.", "spf", "google.com"},
212}
213
214func TestLookupGmailTXT(t *testing.T) {
215	if runtime.GOOS == "plan9" {
216		t.Skip("skipping on plan9; see https://golang.org/issue/29722")
217	}
218	t.Parallel()
219	mustHaveExternalNetwork(t)
220
221	if iOS() {
222		t.Skip("no resolv.conf on iOS")
223	}
224
225	if !supportsIPv4() || !*testIPv4 {
226		t.Skip("IPv4 is required")
227	}
228
229	attempts := 0
230	for i := 0; i < len(lookupGmailTXTTests); i++ {
231		tt := lookupGmailTXTTests[i]
232		txts, err := LookupTXT(tt.name)
233		if err != nil {
234			testenv.SkipFlakyNet(t)
235			if attempts < len(backoffDuration) {
236				dur := backoffDuration[attempts]
237				t.Logf("backoff %v after failure %v\n", dur, err)
238				time.Sleep(dur)
239				attempts++
240				i--
241				continue
242			}
243			t.Fatal(err)
244		}
245		if len(txts) == 0 {
246			t.Error("got no record")
247		}
248		found := false
249		for _, txt := range txts {
250			if strings.Contains(txt, tt.txt) && (strings.HasSuffix(txt, tt.host) || strings.HasSuffix(txt, tt.host+".")) {
251				found = true
252				break
253			}
254		}
255		if !found {
256			t.Errorf("got %v; want a record containing %s, %s", txts, tt.txt, tt.host)
257		}
258	}
259}
260
261var lookupGooglePublicDNSAddrTests = []string{
262	"8.8.8.8",
263	"8.8.4.4",
264	"2001:4860:4860::8888",
265	"2001:4860:4860::8844",
266}
267
268func TestLookupGooglePublicDNSAddr(t *testing.T) {
269	mustHaveExternalNetwork(t)
270
271	if !supportsIPv4() || !supportsIPv6() || !*testIPv4 || !*testIPv6 {
272		t.Skip("both IPv4 and IPv6 are required")
273	}
274
275	defer dnsWaitGroup.Wait()
276
277	for _, ip := range lookupGooglePublicDNSAddrTests {
278		names, err := LookupAddr(ip)
279		if err != nil {
280			t.Fatal(err)
281		}
282		if len(names) == 0 {
283			t.Error("got no record")
284		}
285		for _, name := range names {
286			if !hasSuffixFold(name, ".google.com.") && !hasSuffixFold(name, ".google.") {
287				t.Errorf("got %q; want a record ending in .google.com. or .google.", name)
288			}
289		}
290	}
291}
292
293func TestLookupIPv6LinkLocalAddr(t *testing.T) {
294	if !supportsIPv6() || !*testIPv6 {
295		t.Skip("IPv6 is required")
296	}
297
298	defer dnsWaitGroup.Wait()
299
300	addrs, err := LookupHost("localhost")
301	if err != nil {
302		t.Fatal(err)
303	}
304	found := false
305	for _, addr := range addrs {
306		if addr == "fe80::1%lo0" {
307			found = true
308			break
309		}
310	}
311	if !found {
312		t.Skipf("not supported on %s", runtime.GOOS)
313	}
314	if _, err := LookupAddr("fe80::1%lo0"); err != nil {
315		t.Error(err)
316	}
317}
318
319func TestLookupIPv6LinkLocalAddrWithZone(t *testing.T) {
320	if !supportsIPv6() || !*testIPv6 {
321		t.Skip("IPv6 is required")
322	}
323
324	ipaddrs, err := DefaultResolver.LookupIPAddr(context.Background(), "fe80::1%lo0")
325	if err != nil {
326		t.Error(err)
327	}
328	for _, addr := range ipaddrs {
329		if e, a := "lo0", addr.Zone; e != a {
330			t.Errorf("wrong zone: want %q, got %q", e, a)
331		}
332	}
333
334	addrs, err := DefaultResolver.LookupHost(context.Background(), "fe80::1%lo0")
335	if err != nil {
336		t.Error(err)
337	}
338	for _, addr := range addrs {
339		if e, a := "fe80::1%lo0", addr; e != a {
340			t.Errorf("wrong host: want %q got %q", e, a)
341		}
342	}
343}
344
345var lookupCNAMETests = []struct {
346	name, cname string
347}{
348	{"www.iana.org", "icann.org."},
349	{"www.iana.org.", "icann.org."},
350	{"www.google.com", "google.com."},
351}
352
353func TestLookupCNAME(t *testing.T) {
354	mustHaveExternalNetwork(t)
355
356	if !supportsIPv4() || !*testIPv4 {
357		t.Skip("IPv4 is required")
358	}
359
360	defer dnsWaitGroup.Wait()
361
362	attempts := 0
363	for i := 0; i < len(lookupCNAMETests); i++ {
364		tt := lookupCNAMETests[i]
365		cname, err := LookupCNAME(tt.name)
366		if err != nil {
367			testenv.SkipFlakyNet(t)
368			if attempts < len(backoffDuration) {
369				dur := backoffDuration[attempts]
370				t.Logf("backoff %v after failure %v\n", dur, err)
371				time.Sleep(dur)
372				attempts++
373				i--
374				continue
375			}
376			t.Fatal(err)
377		}
378		if !hasSuffixFold(cname, tt.cname) {
379			t.Errorf("got %s; want a record containing %s", cname, tt.cname)
380		}
381	}
382}
383
384var lookupGoogleHostTests = []struct {
385	name string
386}{
387	{"google.com"},
388	{"google.com."},
389}
390
391func TestLookupGoogleHost(t *testing.T) {
392	mustHaveExternalNetwork(t)
393
394	if !supportsIPv4() || !*testIPv4 {
395		t.Skip("IPv4 is required")
396	}
397
398	defer dnsWaitGroup.Wait()
399
400	for _, tt := range lookupGoogleHostTests {
401		addrs, err := LookupHost(tt.name)
402		if err != nil {
403			t.Fatal(err)
404		}
405		if len(addrs) == 0 {
406			t.Error("got no record")
407		}
408		for _, addr := range addrs {
409			if ParseIP(addr) == nil {
410				t.Errorf("got %q; want a literal IP address", addr)
411			}
412		}
413	}
414}
415
416func TestLookupLongTXT(t *testing.T) {
417	testenv.SkipFlaky(t, 22857)
418	mustHaveExternalNetwork(t)
419
420	defer dnsWaitGroup.Wait()
421
422	txts, err := LookupTXT("golang.rsc.io")
423	if err != nil {
424		t.Fatal(err)
425	}
426	sort.Strings(txts)
427	want := []string{
428		strings.Repeat("abcdefghijklmnopqrstuvwxyABCDEFGHJIKLMNOPQRSTUVWXY", 10),
429		"gophers rule",
430	}
431	if !reflect.DeepEqual(txts, want) {
432		t.Fatalf("LookupTXT golang.rsc.io incorrect\nhave %q\nwant %q", txts, want)
433	}
434}
435
436var lookupGoogleIPTests = []struct {
437	name string
438}{
439	{"google.com"},
440	{"google.com."},
441}
442
443func TestLookupGoogleIP(t *testing.T) {
444	mustHaveExternalNetwork(t)
445
446	if !supportsIPv4() || !*testIPv4 {
447		t.Skip("IPv4 is required")
448	}
449
450	defer dnsWaitGroup.Wait()
451
452	for _, tt := range lookupGoogleIPTests {
453		ips, err := LookupIP(tt.name)
454		if err != nil {
455			t.Fatal(err)
456		}
457		if len(ips) == 0 {
458			t.Error("got no record")
459		}
460		for _, ip := range ips {
461			if ip.To4() == nil && ip.To16() == nil {
462				t.Errorf("got %v; want an IP address", ip)
463			}
464		}
465	}
466}
467
468var revAddrTests = []struct {
469	Addr      string
470	Reverse   string
471	ErrPrefix string
472}{
473	{"1.2.3.4", "4.3.2.1.in-addr.arpa.", ""},
474	{"245.110.36.114", "114.36.110.245.in-addr.arpa.", ""},
475	{"::ffff:12.34.56.78", "78.56.34.12.in-addr.arpa.", ""},
476	{"::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.", ""},
477	{"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.", ""},
478	{"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.", ""},
479	{"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.", ""},
480	{"1.2.3", "", "unrecognized address"},
481	{"1.2.3.4.5", "", "unrecognized address"},
482	{"1234:567:bcbca::89a:bcde", "", "unrecognized address"},
483	{"1234:567::bcbc:adad::89a:bcde", "", "unrecognized address"},
484}
485
486func TestReverseAddress(t *testing.T) {
487	defer dnsWaitGroup.Wait()
488	for i, tt := range revAddrTests {
489		a, err := reverseaddr(tt.Addr)
490		if len(tt.ErrPrefix) > 0 && err == nil {
491			t.Errorf("#%d: expected %q, got <nil> (error)", i, tt.ErrPrefix)
492			continue
493		}
494		if len(tt.ErrPrefix) == 0 && err != nil {
495			t.Errorf("#%d: expected <nil>, got %q (error)", i, err)
496		}
497		if err != nil && err.(*DNSError).Err != tt.ErrPrefix {
498			t.Errorf("#%d: expected %q, got %q (mismatched error)", i, tt.ErrPrefix, err.(*DNSError).Err)
499		}
500		if a != tt.Reverse {
501			t.Errorf("#%d: expected %q, got %q (reverse address)", i, tt.Reverse, a)
502		}
503	}
504}
505
506func TestDNSFlood(t *testing.T) {
507	if !*testDNSFlood {
508		t.Skip("test disabled; use -dnsflood to enable")
509	}
510
511	defer dnsWaitGroup.Wait()
512
513	var N = 5000
514	if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
515		// On Darwin this test consumes kernel threads much
516		// than other platforms for some reason.
517		// When we monitor the number of allocated Ms by
518		// observing on runtime.newm calls, we can see that it
519		// easily reaches the per process ceiling
520		// kern.num_threads when CGO_ENABLED=1 and
521		// GODEBUG=netdns=go.
522		N = 500
523	}
524
525	const timeout = 3 * time.Second
526	ctxHalfTimeout, cancel := context.WithTimeout(context.Background(), timeout/2)
527	defer cancel()
528	ctxTimeout, cancel := context.WithTimeout(context.Background(), timeout)
529	defer cancel()
530
531	c := make(chan error, 2*N)
532	for i := 0; i < N; i++ {
533		name := fmt.Sprintf("%d.net-test.golang.org", i)
534		go func() {
535			_, err := DefaultResolver.LookupIPAddr(ctxHalfTimeout, name)
536			c <- err
537		}()
538		go func() {
539			_, err := DefaultResolver.LookupIPAddr(ctxTimeout, name)
540			c <- err
541		}()
542	}
543	qstats := struct {
544		succeeded, failed         int
545		timeout, temporary, other int
546		unknown                   int
547	}{}
548	deadline := time.After(timeout + time.Second)
549	for i := 0; i < 2*N; i++ {
550		select {
551		case <-deadline:
552			t.Fatal("deadline exceeded")
553		case err := <-c:
554			switch err := err.(type) {
555			case nil:
556				qstats.succeeded++
557			case Error:
558				qstats.failed++
559				if err.Timeout() {
560					qstats.timeout++
561				}
562				if err.Temporary() {
563					qstats.temporary++
564				}
565				if !err.Timeout() && !err.Temporary() {
566					qstats.other++
567				}
568			default:
569				qstats.failed++
570				qstats.unknown++
571			}
572		}
573	}
574
575	// A high volume of DNS queries for sub-domain of golang.org
576	// would be coordinated by authoritative or recursive server,
577	// or stub resolver which implements query-response rate
578	// limitation, so we can expect some query successes and more
579	// failures including timeout, temporary and other here.
580	// As a rule, unknown must not be shown but it might possibly
581	// happen due to issue 4856 for now.
582	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)
583}
584
585func TestLookupDotsWithLocalSource(t *testing.T) {
586	if !supportsIPv4() || !*testIPv4 {
587		t.Skip("IPv4 is required")
588	}
589
590	mustHaveExternalNetwork(t)
591
592	defer dnsWaitGroup.Wait()
593
594	for i, fn := range []func() func(){forceGoDNS, forceCgoDNS} {
595		fixup := fn()
596		if fixup == nil {
597			continue
598		}
599		names, err := LookupAddr("127.0.0.1")
600		fixup()
601		if err != nil {
602			t.Logf("#%d: %v", i, err)
603			continue
604		}
605		mode := "netgo"
606		if i == 1 {
607			mode = "netcgo"
608		}
609	loop:
610		for i, name := range names {
611			if strings.Index(name, ".") == len(name)-1 { // "localhost" not "localhost."
612				for j := range names {
613					if j == i {
614						continue
615					}
616					if names[j] == name[:len(name)-1] {
617						// It's OK if we find the name without the dot,
618						// as some systems say 127.0.0.1 localhost localhost.
619						continue loop
620					}
621				}
622				t.Errorf("%s: got %s; want %s", mode, name, name[:len(name)-1])
623			} else if strings.Contains(name, ".") && !strings.HasSuffix(name, ".") { // "localhost.localdomain." not "localhost.localdomain"
624				t.Errorf("%s: got %s; want name ending with trailing dot", mode, name)
625			}
626		}
627	}
628}
629
630func TestLookupDotsWithRemoteSource(t *testing.T) {
631	if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
632		testenv.SkipFlaky(t, 27992)
633	}
634	mustHaveExternalNetwork(t)
635
636	if !supportsIPv4() || !*testIPv4 {
637		t.Skip("IPv4 is required")
638	}
639
640	if iOS() {
641		t.Skip("no resolv.conf on iOS")
642	}
643
644	defer dnsWaitGroup.Wait()
645
646	if fixup := forceGoDNS(); fixup != nil {
647		testDots(t, "go")
648		fixup()
649	}
650	if fixup := forceCgoDNS(); fixup != nil {
651		testDots(t, "cgo")
652		fixup()
653	}
654}
655
656func testDots(t *testing.T, mode string) {
657	names, err := LookupAddr("8.8.8.8") // Google dns server
658	if err != nil {
659		testenv.SkipFlakyNet(t)
660		t.Errorf("LookupAddr(8.8.8.8): %v (mode=%v)", err, mode)
661	} else {
662		for _, name := range names {
663			if !hasSuffixFold(name, ".google.com.") && !hasSuffixFold(name, ".google.") {
664				t.Errorf("LookupAddr(8.8.8.8) = %v, want names ending in .google.com or .google with trailing dot (mode=%v)", names, mode)
665				break
666			}
667		}
668	}
669
670	cname, err := LookupCNAME("www.mit.edu")
671	if err != nil {
672		testenv.SkipFlakyNet(t)
673		t.Errorf("LookupCNAME(www.mit.edu, mode=%v): %v", mode, err)
674	} else if !strings.HasSuffix(cname, ".") {
675		t.Errorf("LookupCNAME(www.mit.edu) = %v, want cname ending in . with trailing dot (mode=%v)", cname, mode)
676	}
677
678	mxs, err := LookupMX("google.com")
679	if err != nil {
680		testenv.SkipFlakyNet(t)
681		t.Errorf("LookupMX(google.com): %v (mode=%v)", err, mode)
682	} else {
683		for _, mx := range mxs {
684			if !hasSuffixFold(mx.Host, ".google.com.") {
685				t.Errorf("LookupMX(google.com) = %v, want names ending in .google.com. with trailing dot (mode=%v)", mxString(mxs), mode)
686				break
687			}
688		}
689	}
690
691	nss, err := LookupNS("google.com")
692	if err != nil {
693		testenv.SkipFlakyNet(t)
694		t.Errorf("LookupNS(google.com): %v (mode=%v)", err, mode)
695	} else {
696		for _, ns := range nss {
697			if !hasSuffixFold(ns.Host, ".google.com.") {
698				t.Errorf("LookupNS(google.com) = %v, want names ending in .google.com. with trailing dot (mode=%v)", nsString(nss), mode)
699				break
700			}
701		}
702	}
703
704	cname, srvs, err := LookupSRV("xmpp-server", "tcp", "google.com")
705	if err != nil {
706		testenv.SkipFlakyNet(t)
707		t.Errorf("LookupSRV(xmpp-server, tcp, google.com): %v (mode=%v)", err, mode)
708	} else {
709		if !hasSuffixFold(cname, ".google.com.") {
710			t.Errorf("LookupSRV(xmpp-server, 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(xmpp-server, 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
721func mxString(mxs []*MX) string {
722	var buf bytes.Buffer
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
733func nsString(nss []*NS) string {
734	var buf bytes.Buffer
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
745func srvString(srvs []*SRV) string {
746	var buf bytes.Buffer
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
757func 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 netGo {
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.
815func 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
839func 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
862func 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
885func TestLookupContextCancel(t *testing.T) {
886	mustHaveExternalNetwork(t)
887	defer dnsWaitGroup.Wait()
888
889	ctx, ctxCancel := context.WithCancel(context.Background())
890	ctxCancel()
891	_, err := DefaultResolver.LookupIPAddr(ctx, "google.com")
892	if err.(*DNSError).Err != errCanceled.Error() {
893		testenv.SkipFlakyNet(t)
894		t.Fatal(err)
895	}
896	ctx = context.Background()
897	_, err = DefaultResolver.LookupIPAddr(ctx, "google.com")
898	if err != nil {
899		testenv.SkipFlakyNet(t)
900		t.Fatal(err)
901	}
902}
903
904// Issue 24330: treat the nil *Resolver like a zero value. Verify nothing
905// crashes if nil is used.
906func TestNilResolverLookup(t *testing.T) {
907	mustHaveExternalNetwork(t)
908	var r *Resolver = nil
909	ctx := context.Background()
910
911	// Don't care about the results, just that nothing panics:
912	r.LookupAddr(ctx, "8.8.8.8")
913	r.LookupCNAME(ctx, "google.com")
914	r.LookupHost(ctx, "google.com")
915	r.LookupIPAddr(ctx, "google.com")
916	r.LookupIP(ctx, "ip", "google.com")
917	r.LookupMX(ctx, "gmail.com")
918	r.LookupNS(ctx, "google.com")
919	r.LookupPort(ctx, "tcp", "smtp")
920	r.LookupSRV(ctx, "service", "proto", "name")
921	r.LookupTXT(ctx, "gmail.com")
922}
923
924// TestLookupHostCancel verifies that lookup works even after many
925// canceled lookups (see golang.org/issue/24178 for details).
926func TestLookupHostCancel(t *testing.T) {
927	mustHaveExternalNetwork(t)
928	t.Parallel() // Executes 600ms worth of sequential sleeps.
929
930	const (
931		google        = "www.google.com"
932		invalidDomain = "invalid.invalid" // RFC 2606 reserves .invalid
933		n             = 600               // this needs to be larger than threadLimit size
934	)
935
936	_, err := LookupHost(google)
937	if err != nil {
938		t.Fatal(err)
939	}
940
941	ctx, cancel := context.WithCancel(context.Background())
942	cancel()
943	for i := 0; i < n; i++ {
944		addr, err := DefaultResolver.LookupHost(ctx, invalidDomain)
945		if err == nil {
946			t.Fatalf("LookupHost(%q): returns %v, but should fail", invalidDomain, addr)
947		}
948
949		// Don't verify what the actual error is.
950		// We know that it must be non-nil because the domain is invalid,
951		// but we don't have any guarantee that LookupHost actually bothers
952		// to check for cancellation on the fast path.
953		// (For example, it could use a local cache to avoid blocking entirely.)
954
955		// The lookup may deduplicate in-flight requests, so give it time to settle
956		// in between.
957		time.Sleep(time.Millisecond * 1)
958	}
959
960	_, err = LookupHost(google)
961	if err != nil {
962		t.Fatal(err)
963	}
964}
965
966type lookupCustomResolver struct {
967	*Resolver
968	mu     sync.RWMutex
969	dialed bool
970}
971
972func (lcr *lookupCustomResolver) dial() func(ctx context.Context, network, address string) (Conn, error) {
973	return func(ctx context.Context, network, address string) (Conn, error) {
974		lcr.mu.Lock()
975		lcr.dialed = true
976		lcr.mu.Unlock()
977		return Dial(network, address)
978	}
979}
980
981// TestConcurrentPreferGoResolversDial tests that multiple resolvers with the
982// PreferGo option used concurrently are all dialed properly.
983func TestConcurrentPreferGoResolversDial(t *testing.T) {
984	// The windows and plan9 implementation of the resolver does not use
985	// the Dial function.
986	switch runtime.GOOS {
987	case "windows", "plan9":
988		t.Skipf("skip on %v", runtime.GOOS)
989	}
990
991	testenv.MustHaveExternalNetwork(t)
992	testenv.SkipFlakyNet(t)
993
994	defer dnsWaitGroup.Wait()
995
996	resolvers := make([]*lookupCustomResolver, 2)
997	for i := range resolvers {
998		cs := lookupCustomResolver{Resolver: &Resolver{PreferGo: true}}
999		cs.Dial = cs.dial()
1000		resolvers[i] = &cs
1001	}
1002
1003	var wg sync.WaitGroup
1004	wg.Add(len(resolvers))
1005	for i, resolver := range resolvers {
1006		go func(r *Resolver, index int) {
1007			defer wg.Done()
1008			_, err := r.LookupIPAddr(context.Background(), "google.com")
1009			if err != nil {
1010				t.Errorf("lookup failed for resolver %d: %q", index, err)
1011			}
1012		}(resolver.Resolver, i)
1013	}
1014	wg.Wait()
1015
1016	if t.Failed() {
1017		t.FailNow()
1018	}
1019
1020	for i, resolver := range resolvers {
1021		if !resolver.dialed {
1022			t.Errorf("custom resolver %d not dialed during lookup", i)
1023		}
1024	}
1025}
1026
1027var ipVersionTests = []struct {
1028	network string
1029	version byte
1030}{
1031	{"tcp", 0},
1032	{"tcp4", '4'},
1033	{"tcp6", '6'},
1034	{"udp", 0},
1035	{"udp4", '4'},
1036	{"udp6", '6'},
1037	{"ip", 0},
1038	{"ip4", '4'},
1039	{"ip6", '6'},
1040	{"ip7", 0},
1041	{"", 0},
1042}
1043
1044func TestIPVersion(t *testing.T) {
1045	for _, tt := range ipVersionTests {
1046		if version := ipVersion(tt.network); version != tt.version {
1047			t.Errorf("Family for: %s. Expected: %s, Got: %s", tt.network,
1048				string(tt.version), string(version))
1049		}
1050	}
1051}
1052
1053// Issue 28600: The context that is used to lookup ips should always
1054// preserve the values from the context that was passed into LookupIPAddr.
1055func TestLookupIPAddrPreservesContextValues(t *testing.T) {
1056	origTestHookLookupIP := testHookLookupIP
1057	defer func() { testHookLookupIP = origTestHookLookupIP }()
1058
1059	keyValues := []struct {
1060		key, value any
1061	}{
1062		{"key-1", 12},
1063		{384, "value2"},
1064		{new(float64), 137},
1065	}
1066	ctx := context.Background()
1067	for _, kv := range keyValues {
1068		ctx = context.WithValue(ctx, kv.key, kv.value)
1069	}
1070
1071	wantIPs := []IPAddr{
1072		{IP: IPv4(127, 0, 0, 1)},
1073		{IP: IPv6loopback},
1074	}
1075
1076	checkCtxValues := func(ctx_ context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
1077		for _, kv := range keyValues {
1078			g, w := ctx_.Value(kv.key), kv.value
1079			if !reflect.DeepEqual(g, w) {
1080				t.Errorf("Value lookup:\n\tGot:  %v\n\tWant: %v", g, w)
1081			}
1082		}
1083		return wantIPs, nil
1084	}
1085	testHookLookupIP = checkCtxValues
1086
1087	resolvers := []*Resolver{
1088		nil,
1089		new(Resolver),
1090	}
1091
1092	for i, resolver := range resolvers {
1093		gotIPs, err := resolver.LookupIPAddr(ctx, "golang.org")
1094		if err != nil {
1095			t.Errorf("Resolver #%d: unexpected error: %v", i, err)
1096		}
1097		if !reflect.DeepEqual(gotIPs, wantIPs) {
1098			t.Errorf("#%d: mismatched IPAddr results\n\tGot: %v\n\tWant: %v", i, gotIPs, wantIPs)
1099		}
1100	}
1101}
1102
1103// Issue 30521: The lookup group should call the resolver for each network.
1104func TestLookupIPAddrConcurrentCallsForNetworks(t *testing.T) {
1105	origTestHookLookupIP := testHookLookupIP
1106	defer func() { testHookLookupIP = origTestHookLookupIP }()
1107
1108	queries := [][]string{
1109		{"udp", "golang.org"},
1110		{"udp4", "golang.org"},
1111		{"udp6", "golang.org"},
1112		{"udp", "golang.org"},
1113		{"udp", "golang.org"},
1114	}
1115	results := map[[2]string][]IPAddr{
1116		{"udp", "golang.org"}: {
1117			{IP: IPv4(127, 0, 0, 1)},
1118			{IP: IPv6loopback},
1119		},
1120		{"udp4", "golang.org"}: {
1121			{IP: IPv4(127, 0, 0, 1)},
1122		},
1123		{"udp6", "golang.org"}: {
1124			{IP: IPv6loopback},
1125		},
1126	}
1127	calls := int32(0)
1128	waitCh := make(chan struct{})
1129	testHookLookupIP = func(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
1130		// We'll block until this is called one time for each different
1131		// expected result. This will ensure that the lookup group would wait
1132		// for the existing call if it was to be reused.
1133		if atomic.AddInt32(&calls, 1) == int32(len(results)) {
1134			close(waitCh)
1135		}
1136		select {
1137		case <-waitCh:
1138		case <-ctx.Done():
1139			return nil, ctx.Err()
1140		}
1141		return results[[2]string{network, host}], nil
1142	}
1143
1144	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
1145	defer cancel()
1146	wg := sync.WaitGroup{}
1147	for _, q := range queries {
1148		network := q[0]
1149		host := q[1]
1150		wg.Add(1)
1151		go func() {
1152			defer wg.Done()
1153			gotIPs, err := DefaultResolver.lookupIPAddr(ctx, network, host)
1154			if err != nil {
1155				t.Errorf("lookupIPAddr(%v, %v): unexpected error: %v", network, host, err)
1156			}
1157			wantIPs := results[[2]string{network, host}]
1158			if !reflect.DeepEqual(gotIPs, wantIPs) {
1159				t.Errorf("lookupIPAddr(%v, %v): mismatched IPAddr results\n\tGot: %v\n\tWant: %v", network, host, gotIPs, wantIPs)
1160			}
1161		}()
1162	}
1163	wg.Wait()
1164}
1165
1166func TestWithUnexpiredValuesPreserved(t *testing.T) {
1167	ctx, cancel := context.WithCancel(context.Background())
1168
1169	// Insert a value into it.
1170	key, value := "key-1", 2
1171	ctx = context.WithValue(ctx, key, value)
1172
1173	// Now use the "values preserving context" like
1174	// we would for LookupIPAddr. See Issue 28600.
1175	ctx = withUnexpiredValuesPreserved(ctx)
1176
1177	// Lookup before expiry.
1178	if g, w := ctx.Value(key), value; g != w {
1179		t.Errorf("Lookup before expiry: Got %v Want %v", g, w)
1180	}
1181
1182	// Cancel the context.
1183	cancel()
1184
1185	// Lookup after expiry should return nil
1186	if g := ctx.Value(key); g != nil {
1187		t.Errorf("Lookup after expiry: Got %v want nil", g)
1188	}
1189}
1190
1191// Issue 31597: don't panic on null byte in name
1192func TestLookupNullByte(t *testing.T) {
1193	testenv.MustHaveExternalNetwork(t)
1194	testenv.SkipFlakyNet(t)
1195	LookupHost("foo\x00bar") // check that it doesn't panic; it used to on Windows
1196}
1197
1198func TestResolverLookupIP(t *testing.T) {
1199	testenv.MustHaveExternalNetwork(t)
1200
1201	v4Ok := supportsIPv4() && *testIPv4
1202	v6Ok := supportsIPv6() && *testIPv6
1203
1204	defer dnsWaitGroup.Wait()
1205
1206	for _, impl := range []struct {
1207		name string
1208		fn   func() func()
1209	}{
1210		{"go", forceGoDNS},
1211		{"cgo", forceCgoDNS},
1212	} {
1213		t.Run("implementation: "+impl.name, func(t *testing.T) {
1214			fixup := impl.fn()
1215			if fixup == nil {
1216				t.Skip("not supported")
1217			}
1218			defer fixup()
1219
1220			for _, network := range []string{"ip", "ip4", "ip6"} {
1221				t.Run("network: "+network, func(t *testing.T) {
1222					switch {
1223					case network == "ip4" && !v4Ok:
1224						t.Skip("IPv4 is not supported")
1225					case network == "ip6" && !v6Ok:
1226						t.Skip("IPv6 is not supported")
1227					}
1228
1229					// google.com has both A and AAAA records.
1230					const host = "google.com"
1231					ips, err := DefaultResolver.LookupIP(context.Background(), network, host)
1232					if err != nil {
1233						testenv.SkipFlakyNet(t)
1234						t.Fatalf("DefaultResolver.LookupIP(%q, %q): failed with unexpected error: %v", network, host, err)
1235					}
1236
1237					var v4Addrs []IP
1238					var v6Addrs []IP
1239					for _, ip := range ips {
1240						switch {
1241						case ip.To4() != nil:
1242							// We need to skip the test below because To16 will
1243							// convent an IPv4 address to an IPv4-mapped IPv6
1244							// address.
1245							v4Addrs = append(v4Addrs, ip)
1246						case ip.To16() != nil:
1247							v6Addrs = append(v6Addrs, ip)
1248						default:
1249							t.Fatalf("IP=%q is neither IPv4 nor IPv6", ip)
1250						}
1251					}
1252
1253					// Check that we got the expected addresses.
1254					if network == "ip4" || network == "ip" && v4Ok {
1255						if len(v4Addrs) == 0 {
1256							t.Errorf("DefaultResolver.LookupIP(%q, %q): no IPv4 addresses", network, host)
1257						}
1258					}
1259					if network == "ip6" || network == "ip" && v6Ok {
1260						if len(v6Addrs) == 0 {
1261							t.Errorf("DefaultResolver.LookupIP(%q, %q): no IPv6 addresses", network, host)
1262						}
1263					}
1264
1265					// Check that we didn't get any unexpected addresses.
1266					if network == "ip6" && len(v4Addrs) > 0 {
1267						t.Errorf("DefaultResolver.LookupIP(%q, %q): unexpected IPv4 addresses: %v", network, host, v4Addrs)
1268					}
1269					if network == "ip4" && len(v6Addrs) > 0 {
1270						t.Errorf("DefaultResolver.LookupIP(%q, %q): unexpected IPv6 addresses: %v", network, host, v6Addrs)
1271					}
1272				})
1273			}
1274		})
1275	}
1276}
1277
1278// A context timeout should still return a DNSError.
1279func TestDNSTimeout(t *testing.T) {
1280	origTestHookLookupIP := testHookLookupIP
1281	defer func() { testHookLookupIP = origTestHookLookupIP }()
1282	defer dnsWaitGroup.Wait()
1283
1284	timeoutHookGo := make(chan bool, 1)
1285	timeoutHook := func(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
1286		<-timeoutHookGo
1287		return nil, context.DeadlineExceeded
1288	}
1289	testHookLookupIP = timeoutHook
1290
1291	checkErr := func(err error) {
1292		t.Helper()
1293		if err == nil {
1294			t.Error("expected an error")
1295		} else if dnserr, ok := err.(*DNSError); !ok {
1296			t.Errorf("got error type %T, want %T", err, (*DNSError)(nil))
1297		} else if !dnserr.IsTimeout {
1298			t.Errorf("got error %#v, want IsTimeout == true", dnserr)
1299		} else if isTimeout := dnserr.Timeout(); !isTimeout {
1300			t.Errorf("got err.Timeout() == %t, want true", isTimeout)
1301		}
1302	}
1303
1304	// Single lookup.
1305	timeoutHookGo <- true
1306	_, err := LookupIP("golang.org")
1307	checkErr(err)
1308
1309	// Double lookup.
1310	var err1, err2 error
1311	var wg sync.WaitGroup
1312	wg.Add(2)
1313	go func() {
1314		defer wg.Done()
1315		_, err1 = LookupIP("golang1.org")
1316	}()
1317	go func() {
1318		defer wg.Done()
1319		_, err2 = LookupIP("golang1.org")
1320	}()
1321	close(timeoutHookGo)
1322	wg.Wait()
1323	checkErr(err1)
1324	checkErr(err2)
1325
1326	// Double lookup with context.
1327	timeoutHookGo = make(chan bool)
1328	ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
1329	wg.Add(2)
1330	go func() {
1331		defer wg.Done()
1332		_, err1 = DefaultResolver.LookupIPAddr(ctx, "golang2.org")
1333	}()
1334	go func() {
1335		defer wg.Done()
1336		_, err2 = DefaultResolver.LookupIPAddr(ctx, "golang2.org")
1337	}()
1338	time.Sleep(10 * time.Nanosecond)
1339	close(timeoutHookGo)
1340	wg.Wait()
1341	checkErr(err1)
1342	checkErr(err2)
1343	cancel()
1344}
1345