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 5package net 6 7import ( 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 23var goResolver = Resolver{PreferGo: true} 24 25func hasSuffixFold(s, suffix string) bool { 26 return strings.HasSuffix(strings.ToLower(s), strings.ToLower(suffix)) 27} 28 29func 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 48var 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 72var backoffDuration = [...]time.Duration{time.Second, 5 * time.Second, 30 * time.Second} 73 74func 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 116var lookupGmailMXTests = []struct { 117 name, host string 118}{ 119 {"gmail.com", "google.com."}, 120 {"gmail.com.", "google.com."}, 121} 122 123func 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 162var lookupGmailNSTests = []struct { 163 name, host string 164}{ 165 {"gmail.com", "google.com."}, 166 {"gmail.com.", "google.com."}, 167} 168 169func 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 208var lookupGmailTXTTests = []struct { 209 name, txt, host string 210}{ 211 {"gmail.com", "spf", "google.com"}, 212 {"gmail.com.", "spf", "google.com"}, 213} 214 215func 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 262var lookupGooglePublicDNSAddrTests = []string{ 263 "8.8.8.8", 264 "8.8.4.4", 265 "2001:4860:4860::8888", 266 "2001:4860:4860::8844", 267} 268 269func 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 294func 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 320func 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 346var 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 356func 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 388var lookupGoogleHostTests = []struct { 389 name string 390}{ 391 {"google.com"}, 392 {"google.com."}, 393} 394 395func 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 421func 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 441var lookupGoogleIPTests = []struct { 442 name string 443}{ 444 {"google.com"}, 445 {"google.com."}, 446} 447 448func 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 474var 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 492func 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 512func 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 591func 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 636func 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 663func 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 723func 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 735func 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 747func 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 759func 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. 817func 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 841func 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 864func 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 887func 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. 953func 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). 973func 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 1014type lookupCustomResolver struct { 1015 *Resolver 1016 mu sync.RWMutex 1017 dialed bool 1018} 1019 1020func (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. 1031func 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 1075var 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 1092func 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. 1103func 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. 1152func 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. 1215func 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 1225func 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 1251func 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 1257func 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. 1336func 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 1403func 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 1423func 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 1466func 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 1478var 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 1486func 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 1496func 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 1505func 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 1514func 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 1636func 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