...

Source file src/net/lookup_test.go

Documentation: net

     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  		{"tcp", "bad\x00port", 0, false},
   773  
   774  		// Issue 13610: LookupPort("tcp", "")
   775  		{"tcp", "", 0, true},
   776  		{"tcp4", "", 0, true},
   777  		{"tcp6", "", 0, true},
   778  		{"udp", "", 0, true},
   779  		{"udp4", "", 0, true},
   780  		{"udp6", "", 0, true},
   781  	}
   782  
   783  	switch runtime.GOOS {
   784  	case "android":
   785  		if netGoBuildTag {
   786  			t.Skipf("not supported on %s without cgo; see golang.org/issues/14576", runtime.GOOS)
   787  		}
   788  	default:
   789  		tests = append(tests, test{"tcp", "http", 80, true})
   790  	}
   791  
   792  	for _, tt := range tests {
   793  		port, err := LookupPort(tt.network, tt.name)
   794  		if port != tt.port || (err == nil) != tt.ok {
   795  			t.Errorf("LookupPort(%q, %q) = %d, %v; want %d, error=%t", tt.network, tt.name, port, err, tt.port, !tt.ok)
   796  		}
   797  		if err != nil {
   798  			if perr := parseLookupPortError(err); perr != nil {
   799  				t.Error(perr)
   800  			}
   801  		}
   802  	}
   803  }
   804  
   805  // Like TestLookupPort but with minimal tests that should always pass
   806  // because the answers are baked-in to the net package.
   807  func TestLookupPort_Minimal(t *testing.T) {
   808  	type test struct {
   809  		network string
   810  		name    string
   811  		port    int
   812  	}
   813  	var tests = []test{
   814  		{"tcp", "http", 80},
   815  		{"tcp", "HTTP", 80}, // case shouldn't matter
   816  		{"tcp", "https", 443},
   817  		{"tcp", "ssh", 22},
   818  		{"tcp", "gopher", 70},
   819  		{"tcp4", "http", 80},
   820  		{"tcp6", "http", 80},
   821  	}
   822  
   823  	for _, tt := range tests {
   824  		port, err := LookupPort(tt.network, tt.name)
   825  		if port != tt.port || err != nil {
   826  			t.Errorf("LookupPort(%q, %q) = %d, %v; want %d, error=nil", tt.network, tt.name, port, err, tt.port)
   827  		}
   828  	}
   829  }
   830  
   831  func TestLookupProtocol_Minimal(t *testing.T) {
   832  	type test struct {
   833  		name string
   834  		want int
   835  	}
   836  	var tests = []test{
   837  		{"tcp", 6},
   838  		{"TcP", 6}, // case shouldn't matter
   839  		{"icmp", 1},
   840  		{"igmp", 2},
   841  		{"udp", 17},
   842  		{"ipv6-icmp", 58},
   843  	}
   844  
   845  	for _, tt := range tests {
   846  		got, err := lookupProtocol(context.Background(), tt.name)
   847  		if got != tt.want || err != nil {
   848  			t.Errorf("LookupProtocol(%q) = %d, %v; want %d, error=nil", tt.name, got, err, tt.want)
   849  		}
   850  	}
   851  
   852  }
   853  
   854  func TestLookupNonLDH(t *testing.T) {
   855  	defer dnsWaitGroup.Wait()
   856  
   857  	if fixup := forceGoDNS(); fixup != nil {
   858  		defer fixup()
   859  	}
   860  
   861  	// "LDH" stands for letters, digits, and hyphens and is the usual
   862  	// description of standard DNS names.
   863  	// This test is checking that other kinds of names are reported
   864  	// as not found, not reported as invalid names.
   865  	addrs, err := LookupHost("!!!.###.bogus..domain.")
   866  	if err == nil {
   867  		t.Fatalf("lookup succeeded: %v", addrs)
   868  	}
   869  	if !strings.HasSuffix(err.Error(), errNoSuchHost.Error()) {
   870  		t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost)
   871  	}
   872  	if !err.(*DNSError).IsNotFound {
   873  		t.Fatalf("lookup error = %v, want true", err.(*DNSError).IsNotFound)
   874  	}
   875  }
   876  
   877  func TestLookupContextCancel(t *testing.T) {
   878  	mustHaveExternalNetwork(t)
   879  	testenv.SkipFlakyNet(t)
   880  
   881  	origTestHookLookupIP := testHookLookupIP
   882  	defer func() {
   883  		dnsWaitGroup.Wait()
   884  		testHookLookupIP = origTestHookLookupIP
   885  	}()
   886  
   887  	lookupCtx, cancelLookup := context.WithCancel(context.Background())
   888  	unblockLookup := make(chan struct{})
   889  
   890  	// Set testHookLookupIP to start a new, concurrent call to LookupIPAddr
   891  	// and cancel the original one, then block until the canceled call has returned
   892  	// (ensuring that it has performed any synchronous cleanup).
   893  	testHookLookupIP = func(
   894  		ctx context.Context,
   895  		fn func(context.Context, string, string) ([]IPAddr, error),
   896  		network string,
   897  		host string,
   898  	) ([]IPAddr, error) {
   899  		select {
   900  		case <-unblockLookup:
   901  		default:
   902  			// Start a concurrent LookupIPAddr for the same host while the caller is
   903  			// still blocked, and sleep a little to give it time to be deduplicated
   904  			// before we cancel (and unblock) the caller.
   905  			// (If the timing doesn't quite work out, we'll end up testing sequential
   906  			// calls instead of concurrent ones, but the test should still pass.)
   907  			t.Logf("starting concurrent LookupIPAddr")
   908  			dnsWaitGroup.Add(1)
   909  			go func() {
   910  				defer dnsWaitGroup.Done()
   911  				_, err := DefaultResolver.LookupIPAddr(context.Background(), host)
   912  				if err != nil {
   913  					t.Error(err)
   914  				}
   915  			}()
   916  			time.Sleep(1 * time.Millisecond)
   917  		}
   918  
   919  		cancelLookup()
   920  		<-unblockLookup
   921  		// If the concurrent lookup above is deduplicated to this one
   922  		// (as we expect to happen most of the time), it is important
   923  		// that the original call does not cancel the shared Context.
   924  		// (See https://go.dev/issue/22724.) Explicitly check for
   925  		// cancellation now, just in case fn itself doesn't notice it.
   926  		if err := ctx.Err(); err != nil {
   927  			t.Logf("testHookLookupIP canceled")
   928  			return nil, err
   929  		}
   930  		t.Logf("testHookLookupIP performing lookup")
   931  		return fn(ctx, network, host)
   932  	}
   933  
   934  	_, err := DefaultResolver.LookupIPAddr(lookupCtx, "google.com")
   935  	if dnsErr, ok := err.(*DNSError); !ok || dnsErr.Err != errCanceled.Error() {
   936  		t.Errorf("unexpected error from canceled, blocked LookupIPAddr: %v", err)
   937  	}
   938  	close(unblockLookup)
   939  }
   940  
   941  // Issue 24330: treat the nil *Resolver like a zero value. Verify nothing
   942  // crashes if nil is used.
   943  func TestNilResolverLookup(t *testing.T) {
   944  	mustHaveExternalNetwork(t)
   945  	var r *Resolver = nil
   946  	ctx := context.Background()
   947  
   948  	// Don't care about the results, just that nothing panics:
   949  	r.LookupAddr(ctx, "8.8.8.8")
   950  	r.LookupCNAME(ctx, "google.com")
   951  	r.LookupHost(ctx, "google.com")
   952  	r.LookupIPAddr(ctx, "google.com")
   953  	r.LookupIP(ctx, "ip", "google.com")
   954  	r.LookupMX(ctx, "gmail.com")
   955  	r.LookupNS(ctx, "google.com")
   956  	r.LookupPort(ctx, "tcp", "smtp")
   957  	r.LookupSRV(ctx, "service", "proto", "name")
   958  	r.LookupTXT(ctx, "gmail.com")
   959  }
   960  
   961  // TestLookupHostCancel verifies that lookup works even after many
   962  // canceled lookups (see golang.org/issue/24178 for details).
   963  func TestLookupHostCancel(t *testing.T) {
   964  	mustHaveExternalNetwork(t)
   965  	testenv.SkipFlakyNet(t)
   966  	t.Parallel() // Executes 600ms worth of sequential sleeps.
   967  
   968  	const (
   969  		google        = "www.google.com"
   970  		invalidDomain = "invalid.invalid" // RFC 2606 reserves .invalid
   971  		n             = 600               // this needs to be larger than threadLimit size
   972  	)
   973  
   974  	_, err := LookupHost(google)
   975  	if err != nil {
   976  		t.Fatal(err)
   977  	}
   978  
   979  	ctx, cancel := context.WithCancel(context.Background())
   980  	cancel()
   981  	for i := 0; i < n; i++ {
   982  		addr, err := DefaultResolver.LookupHost(ctx, invalidDomain)
   983  		if err == nil {
   984  			t.Fatalf("LookupHost(%q): returns %v, but should fail", invalidDomain, addr)
   985  		}
   986  
   987  		// Don't verify what the actual error is.
   988  		// We know that it must be non-nil because the domain is invalid,
   989  		// but we don't have any guarantee that LookupHost actually bothers
   990  		// to check for cancellation on the fast path.
   991  		// (For example, it could use a local cache to avoid blocking entirely.)
   992  
   993  		// The lookup may deduplicate in-flight requests, so give it time to settle
   994  		// in between.
   995  		time.Sleep(time.Millisecond * 1)
   996  	}
   997  
   998  	_, err = LookupHost(google)
   999  	if err != nil {
  1000  		t.Fatal(err)
  1001  	}
  1002  }
  1003  
  1004  type lookupCustomResolver struct {
  1005  	*Resolver
  1006  	mu     sync.RWMutex
  1007  	dialed bool
  1008  }
  1009  
  1010  func (lcr *lookupCustomResolver) dial() func(ctx context.Context, network, address string) (Conn, error) {
  1011  	return func(ctx context.Context, network, address string) (Conn, error) {
  1012  		lcr.mu.Lock()
  1013  		lcr.dialed = true
  1014  		lcr.mu.Unlock()
  1015  		return Dial(network, address)
  1016  	}
  1017  }
  1018  
  1019  // TestConcurrentPreferGoResolversDial tests that multiple resolvers with the
  1020  // PreferGo option used concurrently are all dialed properly.
  1021  func TestConcurrentPreferGoResolversDial(t *testing.T) {
  1022  	switch runtime.GOOS {
  1023  	case "plan9":
  1024  		// TODO: plan9 implementation of the resolver uses the Dial function since
  1025  		// https://go.dev/cl/409234, this test could probably be reenabled.
  1026  		t.Skipf("skip on %v", runtime.GOOS)
  1027  	}
  1028  
  1029  	testenv.MustHaveExternalNetwork(t)
  1030  	testenv.SkipFlakyNet(t)
  1031  
  1032  	defer dnsWaitGroup.Wait()
  1033  
  1034  	resolvers := make([]*lookupCustomResolver, 2)
  1035  	for i := range resolvers {
  1036  		cs := lookupCustomResolver{Resolver: &Resolver{PreferGo: true}}
  1037  		cs.Dial = cs.dial()
  1038  		resolvers[i] = &cs
  1039  	}
  1040  
  1041  	var wg sync.WaitGroup
  1042  	wg.Add(len(resolvers))
  1043  	for i, resolver := range resolvers {
  1044  		go func(r *Resolver, index int) {
  1045  			defer wg.Done()
  1046  			_, err := r.LookupIPAddr(context.Background(), "google.com")
  1047  			if err != nil {
  1048  				t.Errorf("lookup failed for resolver %d: %q", index, err)
  1049  			}
  1050  		}(resolver.Resolver, i)
  1051  	}
  1052  	wg.Wait()
  1053  
  1054  	if t.Failed() {
  1055  		t.FailNow()
  1056  	}
  1057  
  1058  	for i, resolver := range resolvers {
  1059  		if !resolver.dialed {
  1060  			t.Errorf("custom resolver %d not dialed during lookup", i)
  1061  		}
  1062  	}
  1063  }
  1064  
  1065  var ipVersionTests = []struct {
  1066  	network string
  1067  	version byte
  1068  }{
  1069  	{"tcp", 0},
  1070  	{"tcp4", '4'},
  1071  	{"tcp6", '6'},
  1072  	{"udp", 0},
  1073  	{"udp4", '4'},
  1074  	{"udp6", '6'},
  1075  	{"ip", 0},
  1076  	{"ip4", '4'},
  1077  	{"ip6", '6'},
  1078  	{"ip7", 0},
  1079  	{"", 0},
  1080  }
  1081  
  1082  func TestIPVersion(t *testing.T) {
  1083  	for _, tt := range ipVersionTests {
  1084  		if version := ipVersion(tt.network); version != tt.version {
  1085  			t.Errorf("Family for: %s. Expected: %s, Got: %s", tt.network,
  1086  				string(tt.version), string(version))
  1087  		}
  1088  	}
  1089  }
  1090  
  1091  // Issue 28600: The context that is used to lookup ips should always
  1092  // preserve the values from the context that was passed into LookupIPAddr.
  1093  func TestLookupIPAddrPreservesContextValues(t *testing.T) {
  1094  	origTestHookLookupIP := testHookLookupIP
  1095  	defer func() { testHookLookupIP = origTestHookLookupIP }()
  1096  
  1097  	keyValues := []struct {
  1098  		key, value any
  1099  	}{
  1100  		{"key-1", 12},
  1101  		{384, "value2"},
  1102  		{new(float64), 137},
  1103  	}
  1104  	ctx := context.Background()
  1105  	for _, kv := range keyValues {
  1106  		ctx = context.WithValue(ctx, kv.key, kv.value)
  1107  	}
  1108  
  1109  	wantIPs := []IPAddr{
  1110  		{IP: IPv4(127, 0, 0, 1)},
  1111  		{IP: IPv6loopback},
  1112  	}
  1113  
  1114  	checkCtxValues := func(ctx_ context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
  1115  		for _, kv := range keyValues {
  1116  			g, w := ctx_.Value(kv.key), kv.value
  1117  			if !reflect.DeepEqual(g, w) {
  1118  				t.Errorf("Value lookup:\n\tGot:  %v\n\tWant: %v", g, w)
  1119  			}
  1120  		}
  1121  		return wantIPs, nil
  1122  	}
  1123  	testHookLookupIP = checkCtxValues
  1124  
  1125  	resolvers := []*Resolver{
  1126  		nil,
  1127  		new(Resolver),
  1128  	}
  1129  
  1130  	for i, resolver := range resolvers {
  1131  		gotIPs, err := resolver.LookupIPAddr(ctx, "golang.org")
  1132  		if err != nil {
  1133  			t.Errorf("Resolver #%d: unexpected error: %v", i, err)
  1134  		}
  1135  		if !reflect.DeepEqual(gotIPs, wantIPs) {
  1136  			t.Errorf("#%d: mismatched IPAddr results\n\tGot: %v\n\tWant: %v", i, gotIPs, wantIPs)
  1137  		}
  1138  	}
  1139  }
  1140  
  1141  // Issue 30521: The lookup group should call the resolver for each network.
  1142  func TestLookupIPAddrConcurrentCallsForNetworks(t *testing.T) {
  1143  	origTestHookLookupIP := testHookLookupIP
  1144  	defer func() { testHookLookupIP = origTestHookLookupIP }()
  1145  
  1146  	queries := [][]string{
  1147  		{"udp", "golang.org"},
  1148  		{"udp4", "golang.org"},
  1149  		{"udp6", "golang.org"},
  1150  		{"udp", "golang.org"},
  1151  		{"udp", "golang.org"},
  1152  	}
  1153  	results := map[[2]string][]IPAddr{
  1154  		{"udp", "golang.org"}: {
  1155  			{IP: IPv4(127, 0, 0, 1)},
  1156  			{IP: IPv6loopback},
  1157  		},
  1158  		{"udp4", "golang.org"}: {
  1159  			{IP: IPv4(127, 0, 0, 1)},
  1160  		},
  1161  		{"udp6", "golang.org"}: {
  1162  			{IP: IPv6loopback},
  1163  		},
  1164  	}
  1165  	calls := int32(0)
  1166  	waitCh := make(chan struct{})
  1167  	testHookLookupIP = func(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
  1168  		// We'll block until this is called one time for each different
  1169  		// expected result. This will ensure that the lookup group would wait
  1170  		// for the existing call if it was to be reused.
  1171  		if atomic.AddInt32(&calls, 1) == int32(len(results)) {
  1172  			close(waitCh)
  1173  		}
  1174  		select {
  1175  		case <-waitCh:
  1176  		case <-ctx.Done():
  1177  			return nil, ctx.Err()
  1178  		}
  1179  		return results[[2]string{network, host}], nil
  1180  	}
  1181  
  1182  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  1183  	defer cancel()
  1184  	wg := sync.WaitGroup{}
  1185  	for _, q := range queries {
  1186  		network := q[0]
  1187  		host := q[1]
  1188  		wg.Add(1)
  1189  		go func() {
  1190  			defer wg.Done()
  1191  			gotIPs, err := DefaultResolver.lookupIPAddr(ctx, network, host)
  1192  			if err != nil {
  1193  				t.Errorf("lookupIPAddr(%v, %v): unexpected error: %v", network, host, err)
  1194  			}
  1195  			wantIPs := results[[2]string{network, host}]
  1196  			if !reflect.DeepEqual(gotIPs, wantIPs) {
  1197  				t.Errorf("lookupIPAddr(%v, %v): mismatched IPAddr results\n\tGot: %v\n\tWant: %v", network, host, gotIPs, wantIPs)
  1198  			}
  1199  		}()
  1200  	}
  1201  	wg.Wait()
  1202  }
  1203  
  1204  // Issue 53995: Resolver.LookupIP should return error for empty host name.
  1205  func TestResolverLookupIPWithEmptyHost(t *testing.T) {
  1206  	_, err := DefaultResolver.LookupIP(context.Background(), "ip", "")
  1207  	if err == nil {
  1208  		t.Fatal("DefaultResolver.LookupIP for empty host success, want no host error")
  1209  	}
  1210  	if !strings.HasSuffix(err.Error(), errNoSuchHost.Error()) {
  1211  		t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost)
  1212  	}
  1213  }
  1214  
  1215  func TestWithUnexpiredValuesPreserved(t *testing.T) {
  1216  	ctx, cancel := context.WithCancel(context.Background())
  1217  
  1218  	// Insert a value into it.
  1219  	key, value := "key-1", 2
  1220  	ctx = context.WithValue(ctx, key, value)
  1221  
  1222  	// Now use the "values preserving context" like
  1223  	// we would for LookupIPAddr. See Issue 28600.
  1224  	ctx = withUnexpiredValuesPreserved(ctx)
  1225  
  1226  	// Lookup before expiry.
  1227  	if g, w := ctx.Value(key), value; g != w {
  1228  		t.Errorf("Lookup before expiry: Got %v Want %v", g, w)
  1229  	}
  1230  
  1231  	// Cancel the context.
  1232  	cancel()
  1233  
  1234  	// Lookup after expiry should return nil
  1235  	if g := ctx.Value(key); g != nil {
  1236  		t.Errorf("Lookup after expiry: Got %v want nil", g)
  1237  	}
  1238  }
  1239  
  1240  // Issue 31597: don't panic on null byte in name
  1241  func TestLookupNullByte(t *testing.T) {
  1242  	testenv.MustHaveExternalNetwork(t)
  1243  	testenv.SkipFlakyNet(t)
  1244  	LookupHost("foo\x00bar") // check that it doesn't panic; it used to on Windows
  1245  }
  1246  
  1247  func TestResolverLookupIP(t *testing.T) {
  1248  	testenv.MustHaveExternalNetwork(t)
  1249  
  1250  	v4Ok := supportsIPv4() && *testIPv4
  1251  	v6Ok := supportsIPv6() && *testIPv6
  1252  
  1253  	defer dnsWaitGroup.Wait()
  1254  
  1255  	for _, impl := range []struct {
  1256  		name string
  1257  		fn   func() func()
  1258  	}{
  1259  		{"go", forceGoDNS},
  1260  		{"cgo", forceCgoDNS},
  1261  	} {
  1262  		t.Run("implementation: "+impl.name, func(t *testing.T) {
  1263  			fixup := impl.fn()
  1264  			if fixup == nil {
  1265  				t.Skip("not supported")
  1266  			}
  1267  			defer fixup()
  1268  
  1269  			for _, network := range []string{"ip", "ip4", "ip6"} {
  1270  				t.Run("network: "+network, func(t *testing.T) {
  1271  					switch {
  1272  					case network == "ip4" && !v4Ok:
  1273  						t.Skip("IPv4 is not supported")
  1274  					case network == "ip6" && !v6Ok:
  1275  						t.Skip("IPv6 is not supported")
  1276  					}
  1277  
  1278  					// google.com has both A and AAAA records.
  1279  					const host = "google.com"
  1280  					ips, err := DefaultResolver.LookupIP(context.Background(), network, host)
  1281  					if err != nil {
  1282  						testenv.SkipFlakyNet(t)
  1283  						t.Fatalf("DefaultResolver.LookupIP(%q, %q): failed with unexpected error: %v", network, host, err)
  1284  					}
  1285  
  1286  					var v4Addrs []netip.Addr
  1287  					var v6Addrs []netip.Addr
  1288  					for _, ip := range ips {
  1289  						if addr, ok := netip.AddrFromSlice(ip); ok {
  1290  							if addr.Is4() {
  1291  								v4Addrs = append(v4Addrs, addr)
  1292  							} else {
  1293  								v6Addrs = append(v6Addrs, addr)
  1294  							}
  1295  						} else {
  1296  							t.Fatalf("IP=%q is neither IPv4 nor IPv6", ip)
  1297  						}
  1298  					}
  1299  
  1300  					// Check that we got the expected addresses.
  1301  					if network == "ip4" || network == "ip" && v4Ok {
  1302  						if len(v4Addrs) == 0 {
  1303  							t.Errorf("DefaultResolver.LookupIP(%q, %q): no IPv4 addresses", network, host)
  1304  						}
  1305  					}
  1306  					if network == "ip6" || network == "ip" && v6Ok {
  1307  						if len(v6Addrs) == 0 {
  1308  							t.Errorf("DefaultResolver.LookupIP(%q, %q): no IPv6 addresses", network, host)
  1309  						}
  1310  					}
  1311  
  1312  					// Check that we didn't get any unexpected addresses.
  1313  					if network == "ip6" && len(v4Addrs) > 0 {
  1314  						t.Errorf("DefaultResolver.LookupIP(%q, %q): unexpected IPv4 addresses: %v", network, host, v4Addrs)
  1315  					}
  1316  					if network == "ip4" && len(v6Addrs) > 0 {
  1317  						t.Errorf("DefaultResolver.LookupIP(%q, %q): unexpected IPv6 or IPv4-mapped IPv6 addresses: %v", network, host, v6Addrs)
  1318  					}
  1319  				})
  1320  			}
  1321  		})
  1322  	}
  1323  }
  1324  
  1325  // A context timeout should still return a DNSError.
  1326  func TestDNSTimeout(t *testing.T) {
  1327  	origTestHookLookupIP := testHookLookupIP
  1328  	defer func() { testHookLookupIP = origTestHookLookupIP }()
  1329  	defer dnsWaitGroup.Wait()
  1330  
  1331  	timeoutHookGo := make(chan bool, 1)
  1332  	timeoutHook := func(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
  1333  		<-timeoutHookGo
  1334  		return nil, context.DeadlineExceeded
  1335  	}
  1336  	testHookLookupIP = timeoutHook
  1337  
  1338  	checkErr := func(err error) {
  1339  		t.Helper()
  1340  		if err == nil {
  1341  			t.Error("expected an error")
  1342  		} else if dnserr, ok := err.(*DNSError); !ok {
  1343  			t.Errorf("got error type %T, want %T", err, (*DNSError)(nil))
  1344  		} else if !dnserr.IsTimeout {
  1345  			t.Errorf("got error %#v, want IsTimeout == true", dnserr)
  1346  		} else if isTimeout := dnserr.Timeout(); !isTimeout {
  1347  			t.Errorf("got err.Timeout() == %t, want true", isTimeout)
  1348  		}
  1349  	}
  1350  
  1351  	// Single lookup.
  1352  	timeoutHookGo <- true
  1353  	_, err := LookupIP("golang.org")
  1354  	checkErr(err)
  1355  
  1356  	// Double lookup.
  1357  	var err1, err2 error
  1358  	var wg sync.WaitGroup
  1359  	wg.Add(2)
  1360  	go func() {
  1361  		defer wg.Done()
  1362  		_, err1 = LookupIP("golang1.org")
  1363  	}()
  1364  	go func() {
  1365  		defer wg.Done()
  1366  		_, err2 = LookupIP("golang1.org")
  1367  	}()
  1368  	close(timeoutHookGo)
  1369  	wg.Wait()
  1370  	checkErr(err1)
  1371  	checkErr(err2)
  1372  
  1373  	// Double lookup with context.
  1374  	timeoutHookGo = make(chan bool)
  1375  	ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
  1376  	wg.Add(2)
  1377  	go func() {
  1378  		defer wg.Done()
  1379  		_, err1 = DefaultResolver.LookupIPAddr(ctx, "golang2.org")
  1380  	}()
  1381  	go func() {
  1382  		defer wg.Done()
  1383  		_, err2 = DefaultResolver.LookupIPAddr(ctx, "golang2.org")
  1384  	}()
  1385  	time.Sleep(10 * time.Nanosecond)
  1386  	close(timeoutHookGo)
  1387  	wg.Wait()
  1388  	checkErr(err1)
  1389  	checkErr(err2)
  1390  	cancel()
  1391  }
  1392  
  1393  func TestLookupNoData(t *testing.T) {
  1394  	if runtime.GOOS == "plan9" {
  1395  		t.Skip("not supported on plan9")
  1396  	}
  1397  
  1398  	mustHaveExternalNetwork(t)
  1399  
  1400  	testLookupNoData(t, "default resolver")
  1401  
  1402  	func() {
  1403  		defer forceGoDNS()()
  1404  		testLookupNoData(t, "forced go resolver")
  1405  	}()
  1406  
  1407  	func() {
  1408  		defer forceCgoDNS()()
  1409  		testLookupNoData(t, "forced cgo resolver")
  1410  	}()
  1411  }
  1412  
  1413  func testLookupNoData(t *testing.T, prefix string) {
  1414  	attempts := 0
  1415  	for {
  1416  		// Domain that doesn't have any A/AAAA RRs, but has different one (in this case a TXT),
  1417  		// so that it returns an empty response without any error codes (NXDOMAIN).
  1418  		_, err := LookupHost("golang.rsc.io.")
  1419  		if err == nil {
  1420  			t.Errorf("%v: unexpected success", prefix)
  1421  			return
  1422  		}
  1423  
  1424  		dnsErr, ok := errors.AsType[*DNSError](err)
  1425  		if ok {
  1426  			succeeded := true
  1427  			if !dnsErr.IsNotFound {
  1428  				succeeded = false
  1429  				t.Logf("%v: IsNotFound is set to false", prefix)
  1430  			}
  1431  
  1432  			if dnsErr.Err != errNoSuchHost.Error() {
  1433  				succeeded = false
  1434  				t.Logf("%v: error message is not equal to: %v", prefix, errNoSuchHost.Error())
  1435  			}
  1436  
  1437  			if succeeded {
  1438  				return
  1439  			}
  1440  		}
  1441  
  1442  		testenv.SkipFlakyNet(t)
  1443  		if attempts < len(backoffDuration) {
  1444  			dur := backoffDuration[attempts]
  1445  			t.Logf("%v: backoff %v after failure %v\n", prefix, dur, err)
  1446  			time.Sleep(dur)
  1447  			attempts++
  1448  			continue
  1449  		}
  1450  
  1451  		t.Errorf("%v: unexpected error: %v", prefix, err)
  1452  		return
  1453  	}
  1454  }
  1455  
  1456  func TestLookupPortNotFound(t *testing.T) {
  1457  	allResolvers(t, func(t *testing.T) {
  1458  		_, err := LookupPort("udp", "_-unknown-service-")
  1459  		if dnsErr, ok := errors.AsType[*DNSError](err); !ok || !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  		if dnsErr, ok := errors.AsType[*DNSError](err); !ok || !dnsErr.IsNotFound {
  1479  			t.Fatalf("unexpected error: %v", err)
  1480  		}
  1481  	})
  1482  }
  1483  
  1484  func TestLookupPortEmptyNetworkString(t *testing.T) {
  1485  	allResolvers(t, func(t *testing.T) {
  1486  		_, err := LookupPort("", tcpOnlyService)
  1487  		if err != nil {
  1488  			t.Fatalf("unexpected error: %v", err)
  1489  		}
  1490  	})
  1491  }
  1492  
  1493  func TestLookupPortIPNetworkString(t *testing.T) {
  1494  	allResolvers(t, func(t *testing.T) {
  1495  		_, err := LookupPort("ip", tcpOnlyService)
  1496  		if err != nil {
  1497  			t.Fatalf("unexpected error: %v", err)
  1498  		}
  1499  	})
  1500  }
  1501  
  1502  func TestLookupNoSuchHost(t *testing.T) {
  1503  	mustHaveExternalNetwork(t)
  1504  
  1505  	const testNXDOMAIN = "invalid.invalid."
  1506  	const testNODATA = "_ldap._tcp.google.com."
  1507  
  1508  	tests := []struct {
  1509  		name  string
  1510  		query func() error
  1511  	}{
  1512  		{
  1513  			name: "LookupCNAME NXDOMAIN",
  1514  			query: func() error {
  1515  				_, err := LookupCNAME(testNXDOMAIN)
  1516  				return err
  1517  			},
  1518  		},
  1519  		{
  1520  			name: "LookupHost NXDOMAIN",
  1521  			query: func() error {
  1522  				_, err := LookupHost(testNXDOMAIN)
  1523  				return err
  1524  			},
  1525  		},
  1526  		{
  1527  			name: "LookupHost NODATA",
  1528  			query: func() error {
  1529  				_, err := LookupHost(testNODATA)
  1530  				return err
  1531  			},
  1532  		},
  1533  		{
  1534  			name: "LookupMX NXDOMAIN",
  1535  			query: func() error {
  1536  				_, err := LookupMX(testNXDOMAIN)
  1537  				return err
  1538  			},
  1539  		},
  1540  		{
  1541  			name: "LookupMX NODATA",
  1542  			query: func() error {
  1543  				_, err := LookupMX(testNODATA)
  1544  				return err
  1545  			},
  1546  		},
  1547  		{
  1548  			name: "LookupNS NXDOMAIN",
  1549  			query: func() error {
  1550  				_, err := LookupNS(testNXDOMAIN)
  1551  				return err
  1552  			},
  1553  		},
  1554  		{
  1555  			name: "LookupNS NODATA",
  1556  			query: func() error {
  1557  				_, err := LookupNS(testNODATA)
  1558  				return err
  1559  			},
  1560  		},
  1561  		{
  1562  			name: "LookupSRV NXDOMAIN",
  1563  			query: func() error {
  1564  				_, _, err := LookupSRV("unknown", "tcp", testNXDOMAIN)
  1565  				return err
  1566  			},
  1567  		},
  1568  		{
  1569  			name: "LookupTXT NXDOMAIN",
  1570  			query: func() error {
  1571  				_, err := LookupTXT(testNXDOMAIN)
  1572  				return err
  1573  			},
  1574  		},
  1575  		{
  1576  			name: "LookupTXT NODATA",
  1577  			query: func() error {
  1578  				_, err := LookupTXT(testNODATA)
  1579  				return err
  1580  			},
  1581  		},
  1582  	}
  1583  
  1584  	for _, v := range tests {
  1585  		t.Run(v.name, func(t *testing.T) {
  1586  			allResolvers(t, func(t *testing.T) {
  1587  				attempts := 0
  1588  				for {
  1589  					err := v.query()
  1590  					if err == nil {
  1591  						t.Errorf("unexpected success")
  1592  						return
  1593  					}
  1594  					if dnsErr, ok := err.(*DNSError); ok {
  1595  						succeeded := true
  1596  						if !dnsErr.IsNotFound {
  1597  							succeeded = false
  1598  							t.Log("IsNotFound is set to false")
  1599  						}
  1600  						if dnsErr.Err != errNoSuchHost.Error() {
  1601  							succeeded = false
  1602  							t.Logf("error message is not equal to: %v", errNoSuchHost.Error())
  1603  						}
  1604  						if succeeded {
  1605  							return
  1606  						}
  1607  					}
  1608  					testenv.SkipFlakyNet(t)
  1609  					if attempts < len(backoffDuration) {
  1610  						dur := backoffDuration[attempts]
  1611  						t.Logf("backoff %v after failure %v\n", dur, err)
  1612  						time.Sleep(dur)
  1613  						attempts++
  1614  						continue
  1615  					}
  1616  					t.Errorf("unexpected error: %v", err)
  1617  					return
  1618  				}
  1619  			})
  1620  		})
  1621  	}
  1622  }
  1623  
  1624  func TestDNSErrorUnwrap(t *testing.T) {
  1625  	if runtime.GOOS == "plan9" {
  1626  		// The Plan 9 implementation of the resolver doesn't use the Dial function yet. See https://go.dev/cl/409234
  1627  		t.Skip("skipping on plan9")
  1628  	}
  1629  	rDeadlineExcceeded := &Resolver{PreferGo: true, Dial: func(ctx context.Context, network, address string) (Conn, error) {
  1630  		return nil, context.DeadlineExceeded
  1631  	}}
  1632  	rCancelled := &Resolver{PreferGo: true, Dial: func(ctx context.Context, network, address string) (Conn, error) {
  1633  		return nil, context.Canceled
  1634  	}}
  1635  
  1636  	_, err := rDeadlineExcceeded.LookupHost(context.Background(), "test.go.dev")
  1637  	if !errors.Is(err, context.DeadlineExceeded) {
  1638  		t.Errorf("errors.Is(err, context.DeadlineExceeded) = false; want = true")
  1639  	}
  1640  
  1641  	_, err = rCancelled.LookupHost(context.Background(), "test.go.dev")
  1642  	if !errors.Is(err, context.Canceled) {
  1643  		t.Errorf("errors.Is(err, context.Canceled) = false; want = true")
  1644  	}
  1645  
  1646  	ctx, cancel := context.WithCancel(context.Background())
  1647  	cancel()
  1648  	_, err = goResolver.LookupHost(ctx, "text.go.dev")
  1649  	if !errors.Is(err, context.Canceled) {
  1650  		t.Errorf("errors.Is(err, context.Canceled) = false; want = true")
  1651  	}
  1652  }
  1653  

View as plain text