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

View as plain text