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

View as plain text