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

View as plain text