github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/net/lookup_test.go (about) 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 //go:build !js 6 7 package net 8 9 import ( 10 "context" 11 "fmt" 12 "internal/testenv" 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 "xmpp-server", "tcp", "google.com", 52 "google.com.", "google.com.", 53 }, 54 { 55 "xmpp-server", "tcp", "google.com.", 56 "google.com.", "google.com.", 57 }, 58 59 // non-standard back door 60 { 61 "", "", "_xmpp-server._tcp.google.com", 62 "google.com.", "google.com.", 63 }, 64 { 65 "", "", "_xmpp-server._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 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 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 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 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 } 351 352 func TestLookupCNAME(t *testing.T) { 353 mustHaveExternalNetwork(t) 354 testenv.SkipFlakyNet(t) 355 356 if !supportsIPv4() || !*testIPv4 { 357 t.Skip("IPv4 is required") 358 } 359 360 defer dnsWaitGroup.Wait() 361 362 attempts := 0 363 for i := 0; i < len(lookupCNAMETests); i++ { 364 tt := lookupCNAMETests[i] 365 cname, err := LookupCNAME(tt.name) 366 if err != nil { 367 testenv.SkipFlakyNet(t) 368 if attempts < len(backoffDuration) { 369 dur := backoffDuration[attempts] 370 t.Logf("backoff %v after failure %v\n", dur, err) 371 time.Sleep(dur) 372 attempts++ 373 i-- 374 continue 375 } 376 t.Fatal(err) 377 } 378 if !hasSuffixFold(cname, tt.cname) { 379 t.Errorf("got %s; want a record containing %s", cname, tt.cname) 380 } 381 } 382 } 383 384 var lookupGoogleHostTests = []struct { 385 name string 386 }{ 387 {"google.com"}, 388 {"google.com."}, 389 } 390 391 func TestLookupGoogleHost(t *testing.T) { 392 mustHaveExternalNetwork(t) 393 testenv.SkipFlakyNet(t) 394 395 if !supportsIPv4() || !*testIPv4 { 396 t.Skip("IPv4 is required") 397 } 398 399 defer dnsWaitGroup.Wait() 400 401 for _, tt := range lookupGoogleHostTests { 402 addrs, err := LookupHost(tt.name) 403 if err != nil { 404 t.Fatal(err) 405 } 406 if len(addrs) == 0 { 407 t.Error("got no record") 408 } 409 for _, addr := range addrs { 410 if ParseIP(addr) == nil { 411 t.Errorf("got %q; want a literal IP address", addr) 412 } 413 } 414 } 415 } 416 417 func TestLookupLongTXT(t *testing.T) { 418 testenv.SkipFlaky(t, 22857) 419 mustHaveExternalNetwork(t) 420 421 defer dnsWaitGroup.Wait() 422 423 txts, err := LookupTXT("golang.rsc.io") 424 if err != nil { 425 t.Fatal(err) 426 } 427 sort.Strings(txts) 428 want := []string{ 429 strings.Repeat("abcdefghijklmnopqrstuvwxyABCDEFGHJIKLMNOPQRSTUVWXY", 10), 430 "gophers rule", 431 } 432 if !reflect.DeepEqual(txts, want) { 433 t.Fatalf("LookupTXT golang.rsc.io incorrect\nhave %q\nwant %q", txts, want) 434 } 435 } 436 437 var lookupGoogleIPTests = []struct { 438 name string 439 }{ 440 {"google.com"}, 441 {"google.com."}, 442 } 443 444 func TestLookupGoogleIP(t *testing.T) { 445 mustHaveExternalNetwork(t) 446 testenv.SkipFlakyNet(t) 447 448 if !supportsIPv4() || !*testIPv4 { 449 t.Skip("IPv4 is required") 450 } 451 452 defer dnsWaitGroup.Wait() 453 454 for _, tt := range lookupGoogleIPTests { 455 ips, err := LookupIP(tt.name) 456 if err != nil { 457 t.Fatal(err) 458 } 459 if len(ips) == 0 { 460 t.Error("got no record") 461 } 462 for _, ip := range ips { 463 if ip.To4() == nil && ip.To16() == nil { 464 t.Errorf("got %v; want an IP address", ip) 465 } 466 } 467 } 468 } 469 470 var revAddrTests = []struct { 471 Addr string 472 Reverse string 473 ErrPrefix string 474 }{ 475 {"1.2.3.4", "4.3.2.1.in-addr.arpa.", ""}, 476 {"245.110.36.114", "114.36.110.245.in-addr.arpa.", ""}, 477 {"::ffff:12.34.56.78", "78.56.34.12.in-addr.arpa.", ""}, 478 {"::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.", ""}, 479 {"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.", ""}, 480 {"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.", ""}, 481 {"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.", ""}, 482 {"1.2.3", "", "unrecognized address"}, 483 {"1.2.3.4.5", "", "unrecognized address"}, 484 {"1234:567:bcbca::89a:bcde", "", "unrecognized address"}, 485 {"1234:567::bcbc:adad::89a:bcde", "", "unrecognized address"}, 486 } 487 488 func TestReverseAddress(t *testing.T) { 489 defer dnsWaitGroup.Wait() 490 for i, tt := range revAddrTests { 491 a, err := reverseaddr(tt.Addr) 492 if len(tt.ErrPrefix) > 0 && err == nil { 493 t.Errorf("#%d: expected %q, got <nil> (error)", i, tt.ErrPrefix) 494 continue 495 } 496 if len(tt.ErrPrefix) == 0 && err != nil { 497 t.Errorf("#%d: expected <nil>, got %q (error)", i, err) 498 } 499 if err != nil && err.(*DNSError).Err != tt.ErrPrefix { 500 t.Errorf("#%d: expected %q, got %q (mismatched error)", i, tt.ErrPrefix, err.(*DNSError).Err) 501 } 502 if a != tt.Reverse { 503 t.Errorf("#%d: expected %q, got %q (reverse address)", i, tt.Reverse, a) 504 } 505 } 506 } 507 508 func TestDNSFlood(t *testing.T) { 509 if !*testDNSFlood { 510 t.Skip("test disabled; use -dnsflood to enable") 511 } 512 513 defer dnsWaitGroup.Wait() 514 515 var N = 5000 516 if runtime.GOOS == "darwin" || runtime.GOOS == "ios" { 517 // On Darwin this test consumes kernel threads much 518 // than other platforms for some reason. 519 // When we monitor the number of allocated Ms by 520 // observing on runtime.newm calls, we can see that it 521 // easily reaches the per process ceiling 522 // kern.num_threads when CGO_ENABLED=1 and 523 // GODEBUG=netdns=go. 524 N = 500 525 } 526 527 const timeout = 3 * time.Second 528 ctxHalfTimeout, cancel := context.WithTimeout(context.Background(), timeout/2) 529 defer cancel() 530 ctxTimeout, cancel := context.WithTimeout(context.Background(), timeout) 531 defer cancel() 532 533 c := make(chan error, 2*N) 534 for i := 0; i < N; i++ { 535 name := fmt.Sprintf("%d.net-test.golang.org", i) 536 go func() { 537 _, err := DefaultResolver.LookupIPAddr(ctxHalfTimeout, name) 538 c <- err 539 }() 540 go func() { 541 _, err := DefaultResolver.LookupIPAddr(ctxTimeout, name) 542 c <- err 543 }() 544 } 545 qstats := struct { 546 succeeded, failed int 547 timeout, temporary, other int 548 unknown int 549 }{} 550 deadline := time.After(timeout + time.Second) 551 for i := 0; i < 2*N; i++ { 552 select { 553 case <-deadline: 554 t.Fatal("deadline exceeded") 555 case err := <-c: 556 switch err := err.(type) { 557 case nil: 558 qstats.succeeded++ 559 case Error: 560 qstats.failed++ 561 if err.Timeout() { 562 qstats.timeout++ 563 } 564 if err.Temporary() { 565 qstats.temporary++ 566 } 567 if !err.Timeout() && !err.Temporary() { 568 qstats.other++ 569 } 570 default: 571 qstats.failed++ 572 qstats.unknown++ 573 } 574 } 575 } 576 577 // A high volume of DNS queries for sub-domain of golang.org 578 // would be coordinated by authoritative or recursive server, 579 // or stub resolver which implements query-response rate 580 // limitation, so we can expect some query successes and more 581 // failures including timeout, temporary and other here. 582 // As a rule, unknown must not be shown but it might possibly 583 // happen due to issue 4856 for now. 584 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) 585 } 586 587 func TestLookupDotsWithLocalSource(t *testing.T) { 588 if !supportsIPv4() || !*testIPv4 { 589 t.Skip("IPv4 is required") 590 } 591 592 mustHaveExternalNetwork(t) 593 594 defer dnsWaitGroup.Wait() 595 596 for i, fn := range []func() func(){forceGoDNS, forceCgoDNS} { 597 fixup := fn() 598 if fixup == nil { 599 continue 600 } 601 names, err := LookupAddr("127.0.0.1") 602 fixup() 603 if err != nil { 604 t.Logf("#%d: %v", i, err) 605 continue 606 } 607 mode := "netgo" 608 if i == 1 { 609 mode = "netcgo" 610 } 611 loop: 612 for i, name := range names { 613 if strings.Index(name, ".") == len(name)-1 { // "localhost" not "localhost." 614 for j := range names { 615 if j == i { 616 continue 617 } 618 if names[j] == name[:len(name)-1] { 619 // It's OK if we find the name without the dot, 620 // as some systems say 127.0.0.1 localhost localhost. 621 continue loop 622 } 623 } 624 t.Errorf("%s: got %s; want %s", mode, name, name[:len(name)-1]) 625 } else if strings.Contains(name, ".") && !strings.HasSuffix(name, ".") { // "localhost.localdomain." not "localhost.localdomain" 626 t.Errorf("%s: got %s; want name ending with trailing dot", mode, name) 627 } 628 } 629 } 630 } 631 632 func TestLookupDotsWithRemoteSource(t *testing.T) { 633 if runtime.GOOS == "darwin" || runtime.GOOS == "ios" { 634 testenv.SkipFlaky(t, 27992) 635 } 636 mustHaveExternalNetwork(t) 637 testenv.SkipFlakyNet(t) 638 639 if !supportsIPv4() || !*testIPv4 { 640 t.Skip("IPv4 is required") 641 } 642 643 if iOS() { 644 t.Skip("no resolv.conf on iOS") 645 } 646 647 defer dnsWaitGroup.Wait() 648 649 if fixup := forceGoDNS(); fixup != nil { 650 testDots(t, "go") 651 fixup() 652 } 653 if fixup := forceCgoDNS(); fixup != nil { 654 testDots(t, "cgo") 655 fixup() 656 } 657 } 658 659 func testDots(t *testing.T, mode string) { 660 names, err := LookupAddr("8.8.8.8") // Google dns server 661 if err != nil { 662 t.Errorf("LookupAddr(8.8.8.8): %v (mode=%v)", err, mode) 663 } else { 664 for _, name := range names { 665 if !hasSuffixFold(name, ".google.com.") && !hasSuffixFold(name, ".google.") { 666 t.Errorf("LookupAddr(8.8.8.8) = %v, want names ending in .google.com or .google with trailing dot (mode=%v)", names, mode) 667 break 668 } 669 } 670 } 671 672 cname, err := LookupCNAME("www.mit.edu") 673 if err != nil { 674 t.Errorf("LookupCNAME(www.mit.edu, mode=%v): %v", mode, err) 675 } else if !strings.HasSuffix(cname, ".") { 676 t.Errorf("LookupCNAME(www.mit.edu) = %v, want cname ending in . with trailing dot (mode=%v)", cname, mode) 677 } 678 679 mxs, err := LookupMX("google.com") 680 if err != nil { 681 t.Errorf("LookupMX(google.com): %v (mode=%v)", err, mode) 682 } else { 683 for _, mx := range mxs { 684 if !hasSuffixFold(mx.Host, ".google.com.") { 685 t.Errorf("LookupMX(google.com) = %v, want names ending in .google.com. with trailing dot (mode=%v)", mxString(mxs), mode) 686 break 687 } 688 } 689 } 690 691 nss, err := LookupNS("google.com") 692 if err != nil { 693 t.Errorf("LookupNS(google.com): %v (mode=%v)", err, mode) 694 } else { 695 for _, ns := range nss { 696 if !hasSuffixFold(ns.Host, ".google.com.") { 697 t.Errorf("LookupNS(google.com) = %v, want names ending in .google.com. with trailing dot (mode=%v)", nsString(nss), mode) 698 break 699 } 700 } 701 } 702 703 cname, srvs, err := LookupSRV("xmpp-server", "tcp", "google.com") 704 if err != nil { 705 t.Errorf("LookupSRV(xmpp-server, tcp, google.com): %v (mode=%v)", err, mode) 706 } else { 707 if !hasSuffixFold(cname, ".google.com.") { 708 t.Errorf("LookupSRV(xmpp-server, tcp, google.com) returned cname=%v, want name ending in .google.com. with trailing dot (mode=%v)", cname, mode) 709 } 710 for _, srv := range srvs { 711 if !hasSuffixFold(srv.Target, ".google.com.") { 712 t.Errorf("LookupSRV(xmpp-server, tcp, google.com) returned addrs=%v, want names ending in .google.com. with trailing dot (mode=%v)", srvString(srvs), mode) 713 break 714 } 715 } 716 } 717 } 718 719 func mxString(mxs []*MX) string { 720 var buf strings.Builder 721 sep := "" 722 fmt.Fprintf(&buf, "[") 723 for _, mx := range mxs { 724 fmt.Fprintf(&buf, "%s%s:%d", sep, mx.Host, mx.Pref) 725 sep = " " 726 } 727 fmt.Fprintf(&buf, "]") 728 return buf.String() 729 } 730 731 func nsString(nss []*NS) string { 732 var buf strings.Builder 733 sep := "" 734 fmt.Fprintf(&buf, "[") 735 for _, ns := range nss { 736 fmt.Fprintf(&buf, "%s%s", sep, ns.Host) 737 sep = " " 738 } 739 fmt.Fprintf(&buf, "]") 740 return buf.String() 741 } 742 743 func srvString(srvs []*SRV) string { 744 var buf strings.Builder 745 sep := "" 746 fmt.Fprintf(&buf, "[") 747 for _, srv := range srvs { 748 fmt.Fprintf(&buf, "%s%s:%d:%d:%d", sep, srv.Target, srv.Port, srv.Priority, srv.Weight) 749 sep = " " 750 } 751 fmt.Fprintf(&buf, "]") 752 return buf.String() 753 } 754 755 func TestLookupPort(t *testing.T) { 756 // See https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml 757 // 758 // Please be careful about adding new test cases. 759 // There are platforms which have incomplete mappings for 760 // restricted resource access and security reasons. 761 type test struct { 762 network string 763 name string 764 port int 765 ok bool 766 } 767 var tests = []test{ 768 {"tcp", "0", 0, true}, 769 {"udp", "0", 0, true}, 770 {"udp", "domain", 53, true}, 771 772 {"--badnet--", "zzz", 0, false}, 773 {"tcp", "--badport--", 0, false}, 774 {"tcp", "-1", 0, false}, 775 {"tcp", "65536", 0, false}, 776 {"udp", "-1", 0, false}, 777 {"udp", "65536", 0, false}, 778 {"tcp", "123456789", 0, false}, 779 780 // Issue 13610: LookupPort("tcp", "") 781 {"tcp", "", 0, true}, 782 {"tcp4", "", 0, true}, 783 {"tcp6", "", 0, true}, 784 {"udp", "", 0, true}, 785 {"udp4", "", 0, true}, 786 {"udp6", "", 0, true}, 787 } 788 789 switch runtime.GOOS { 790 case "android": 791 if netGo { 792 t.Skipf("not supported on %s without cgo; see golang.org/issues/14576", runtime.GOOS) 793 } 794 default: 795 tests = append(tests, test{"tcp", "http", 80, true}) 796 } 797 798 for _, tt := range tests { 799 port, err := LookupPort(tt.network, tt.name) 800 if port != tt.port || (err == nil) != tt.ok { 801 t.Errorf("LookupPort(%q, %q) = %d, %v; want %d, error=%t", tt.network, tt.name, port, err, tt.port, !tt.ok) 802 } 803 if err != nil { 804 if perr := parseLookupPortError(err); perr != nil { 805 t.Error(perr) 806 } 807 } 808 } 809 } 810 811 // Like TestLookupPort but with minimal tests that should always pass 812 // because the answers are baked-in to the net package. 813 func TestLookupPort_Minimal(t *testing.T) { 814 type test struct { 815 network string 816 name string 817 port int 818 } 819 var tests = []test{ 820 {"tcp", "http", 80}, 821 {"tcp", "HTTP", 80}, // case shouldn't matter 822 {"tcp", "https", 443}, 823 {"tcp", "ssh", 22}, 824 {"tcp", "gopher", 70}, 825 {"tcp4", "http", 80}, 826 {"tcp6", "http", 80}, 827 } 828 829 for _, tt := range tests { 830 port, err := LookupPort(tt.network, tt.name) 831 if port != tt.port || err != nil { 832 t.Errorf("LookupPort(%q, %q) = %d, %v; want %d, error=nil", tt.network, tt.name, port, err, tt.port) 833 } 834 } 835 } 836 837 func TestLookupProtocol_Minimal(t *testing.T) { 838 type test struct { 839 name string 840 want int 841 } 842 var tests = []test{ 843 {"tcp", 6}, 844 {"TcP", 6}, // case shouldn't matter 845 {"icmp", 1}, 846 {"igmp", 2}, 847 {"udp", 17}, 848 {"ipv6-icmp", 58}, 849 } 850 851 for _, tt := range tests { 852 got, err := lookupProtocol(context.Background(), tt.name) 853 if got != tt.want || err != nil { 854 t.Errorf("LookupProtocol(%q) = %d, %v; want %d, error=nil", tt.name, got, err, tt.want) 855 } 856 } 857 858 } 859 860 func TestLookupNonLDH(t *testing.T) { 861 defer dnsWaitGroup.Wait() 862 863 if fixup := forceGoDNS(); fixup != nil { 864 defer fixup() 865 } 866 867 // "LDH" stands for letters, digits, and hyphens and is the usual 868 // description of standard DNS names. 869 // This test is checking that other kinds of names are reported 870 // as not found, not reported as invalid names. 871 addrs, err := LookupHost("!!!.###.bogus..domain.") 872 if err == nil { 873 t.Fatalf("lookup succeeded: %v", addrs) 874 } 875 if !strings.HasSuffix(err.Error(), errNoSuchHost.Error()) { 876 t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost) 877 } 878 if !err.(*DNSError).IsNotFound { 879 t.Fatalf("lookup error = %v, want true", err.(*DNSError).IsNotFound) 880 } 881 } 882 883 func TestLookupContextCancel(t *testing.T) { 884 mustHaveExternalNetwork(t) 885 testenv.SkipFlakyNet(t) 886 887 origTestHookLookupIP := testHookLookupIP 888 defer func() { 889 dnsWaitGroup.Wait() 890 testHookLookupIP = origTestHookLookupIP 891 }() 892 893 lookupCtx, cancelLookup := context.WithCancel(context.Background()) 894 unblockLookup := make(chan struct{}) 895 896 // Set testHookLookupIP to start a new, concurrent call to LookupIPAddr 897 // and cancel the original one, then block until the canceled call has returned 898 // (ensuring that it has performed any synchronous cleanup). 899 testHookLookupIP = func( 900 ctx context.Context, 901 fn func(context.Context, string, string) ([]IPAddr, error), 902 network string, 903 host string, 904 ) ([]IPAddr, error) { 905 select { 906 case <-unblockLookup: 907 default: 908 // Start a concurrent LookupIPAddr for the same host while the caller is 909 // still blocked, and sleep a little to give it time to be deduplicated 910 // before we cancel (and unblock) the caller. 911 // (If the timing doesn't quite work out, we'll end up testing sequential 912 // calls instead of concurrent ones, but the test should still pass.) 913 t.Logf("starting concurrent LookupIPAddr") 914 dnsWaitGroup.Add(1) 915 go func() { 916 defer dnsWaitGroup.Done() 917 _, err := DefaultResolver.LookupIPAddr(context.Background(), host) 918 if err != nil { 919 t.Error(err) 920 } 921 }() 922 time.Sleep(1 * time.Millisecond) 923 } 924 925 cancelLookup() 926 <-unblockLookup 927 // If the concurrent lookup above is deduplicated to this one 928 // (as we expect to happen most of the time), it is important 929 // that the original call does not cancel the shared Context. 930 // (See https://go.dev/issue/22724.) Explicitly check for 931 // cancellation now, just in case fn itself doesn't notice it. 932 if err := ctx.Err(); err != nil { 933 t.Logf("testHookLookupIP canceled") 934 return nil, err 935 } 936 t.Logf("testHookLookupIP performing lookup") 937 return fn(ctx, network, host) 938 } 939 940 _, err := DefaultResolver.LookupIPAddr(lookupCtx, "google.com") 941 if dnsErr, ok := err.(*DNSError); !ok || dnsErr.Err != errCanceled.Error() { 942 t.Errorf("unexpected error from canceled, blocked LookupIPAddr: %v", err) 943 } 944 close(unblockLookup) 945 } 946 947 // Issue 24330: treat the nil *Resolver like a zero value. Verify nothing 948 // crashes if nil is used. 949 func TestNilResolverLookup(t *testing.T) { 950 mustHaveExternalNetwork(t) 951 var r *Resolver = nil 952 ctx := context.Background() 953 954 // Don't care about the results, just that nothing panics: 955 r.LookupAddr(ctx, "8.8.8.8") 956 r.LookupCNAME(ctx, "google.com") 957 r.LookupHost(ctx, "google.com") 958 r.LookupIPAddr(ctx, "google.com") 959 r.LookupIP(ctx, "ip", "google.com") 960 r.LookupMX(ctx, "gmail.com") 961 r.LookupNS(ctx, "google.com") 962 r.LookupPort(ctx, "tcp", "smtp") 963 r.LookupSRV(ctx, "service", "proto", "name") 964 r.LookupTXT(ctx, "gmail.com") 965 } 966 967 // TestLookupHostCancel verifies that lookup works even after many 968 // canceled lookups (see golang.org/issue/24178 for details). 969 func TestLookupHostCancel(t *testing.T) { 970 mustHaveExternalNetwork(t) 971 testenv.SkipFlakyNet(t) 972 t.Parallel() // Executes 600ms worth of sequential sleeps. 973 974 const ( 975 google = "www.google.com" 976 invalidDomain = "invalid.invalid" // RFC 2606 reserves .invalid 977 n = 600 // this needs to be larger than threadLimit size 978 ) 979 980 _, err := LookupHost(google) 981 if err != nil { 982 t.Fatal(err) 983 } 984 985 ctx, cancel := context.WithCancel(context.Background()) 986 cancel() 987 for i := 0; i < n; i++ { 988 addr, err := DefaultResolver.LookupHost(ctx, invalidDomain) 989 if err == nil { 990 t.Fatalf("LookupHost(%q): returns %v, but should fail", invalidDomain, addr) 991 } 992 993 // Don't verify what the actual error is. 994 // We know that it must be non-nil because the domain is invalid, 995 // but we don't have any guarantee that LookupHost actually bothers 996 // to check for cancellation on the fast path. 997 // (For example, it could use a local cache to avoid blocking entirely.) 998 999 // The lookup may deduplicate in-flight requests, so give it time to settle 1000 // in between. 1001 time.Sleep(time.Millisecond * 1) 1002 } 1003 1004 _, err = LookupHost(google) 1005 if err != nil { 1006 t.Fatal(err) 1007 } 1008 } 1009 1010 type lookupCustomResolver struct { 1011 *Resolver 1012 mu sync.RWMutex 1013 dialed bool 1014 } 1015 1016 func (lcr *lookupCustomResolver) dial() func(ctx context.Context, network, address string) (Conn, error) { 1017 return func(ctx context.Context, network, address string) (Conn, error) { 1018 lcr.mu.Lock() 1019 lcr.dialed = true 1020 lcr.mu.Unlock() 1021 return Dial(network, address) 1022 } 1023 } 1024 1025 // TestConcurrentPreferGoResolversDial tests that multiple resolvers with the 1026 // PreferGo option used concurrently are all dialed properly. 1027 func TestConcurrentPreferGoResolversDial(t *testing.T) { 1028 // The windows and plan9 implementation of the resolver does not use 1029 // the Dial function. 1030 switch runtime.GOOS { 1031 case "windows", "plan9": 1032 t.Skipf("skip on %v", runtime.GOOS) 1033 } 1034 1035 testenv.MustHaveExternalNetwork(t) 1036 testenv.SkipFlakyNet(t) 1037 1038 defer dnsWaitGroup.Wait() 1039 1040 resolvers := make([]*lookupCustomResolver, 2) 1041 for i := range resolvers { 1042 cs := lookupCustomResolver{Resolver: &Resolver{PreferGo: true}} 1043 cs.Dial = cs.dial() 1044 resolvers[i] = &cs 1045 } 1046 1047 var wg sync.WaitGroup 1048 wg.Add(len(resolvers)) 1049 for i, resolver := range resolvers { 1050 go func(r *Resolver, index int) { 1051 defer wg.Done() 1052 _, err := r.LookupIPAddr(context.Background(), "google.com") 1053 if err != nil { 1054 t.Errorf("lookup failed for resolver %d: %q", index, err) 1055 } 1056 }(resolver.Resolver, i) 1057 } 1058 wg.Wait() 1059 1060 if t.Failed() { 1061 t.FailNow() 1062 } 1063 1064 for i, resolver := range resolvers { 1065 if !resolver.dialed { 1066 t.Errorf("custom resolver %d not dialed during lookup", i) 1067 } 1068 } 1069 } 1070 1071 var ipVersionTests = []struct { 1072 network string 1073 version byte 1074 }{ 1075 {"tcp", 0}, 1076 {"tcp4", '4'}, 1077 {"tcp6", '6'}, 1078 {"udp", 0}, 1079 {"udp4", '4'}, 1080 {"udp6", '6'}, 1081 {"ip", 0}, 1082 {"ip4", '4'}, 1083 {"ip6", '6'}, 1084 {"ip7", 0}, 1085 {"", 0}, 1086 } 1087 1088 func TestIPVersion(t *testing.T) { 1089 for _, tt := range ipVersionTests { 1090 if version := ipVersion(tt.network); version != tt.version { 1091 t.Errorf("Family for: %s. Expected: %s, Got: %s", tt.network, 1092 string(tt.version), string(version)) 1093 } 1094 } 1095 } 1096 1097 // Issue 28600: The context that is used to lookup ips should always 1098 // preserve the values from the context that was passed into LookupIPAddr. 1099 func TestLookupIPAddrPreservesContextValues(t *testing.T) { 1100 origTestHookLookupIP := testHookLookupIP 1101 defer func() { testHookLookupIP = origTestHookLookupIP }() 1102 1103 keyValues := []struct { 1104 key, value any 1105 }{ 1106 {"key-1", 12}, 1107 {384, "value2"}, 1108 {new(float64), 137}, 1109 } 1110 ctx := context.Background() 1111 for _, kv := range keyValues { 1112 ctx = context.WithValue(ctx, kv.key, kv.value) 1113 } 1114 1115 wantIPs := []IPAddr{ 1116 {IP: IPv4(127, 0, 0, 1)}, 1117 {IP: IPv6loopback}, 1118 } 1119 1120 checkCtxValues := func(ctx_ context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) { 1121 for _, kv := range keyValues { 1122 g, w := ctx_.Value(kv.key), kv.value 1123 if !reflect.DeepEqual(g, w) { 1124 t.Errorf("Value lookup:\n\tGot: %v\n\tWant: %v", g, w) 1125 } 1126 } 1127 return wantIPs, nil 1128 } 1129 testHookLookupIP = checkCtxValues 1130 1131 resolvers := []*Resolver{ 1132 nil, 1133 new(Resolver), 1134 } 1135 1136 for i, resolver := range resolvers { 1137 gotIPs, err := resolver.LookupIPAddr(ctx, "golang.org") 1138 if err != nil { 1139 t.Errorf("Resolver #%d: unexpected error: %v", i, err) 1140 } 1141 if !reflect.DeepEqual(gotIPs, wantIPs) { 1142 t.Errorf("#%d: mismatched IPAddr results\n\tGot: %v\n\tWant: %v", i, gotIPs, wantIPs) 1143 } 1144 } 1145 } 1146 1147 // Issue 30521: The lookup group should call the resolver for each network. 1148 func TestLookupIPAddrConcurrentCallsForNetworks(t *testing.T) { 1149 origTestHookLookupIP := testHookLookupIP 1150 defer func() { testHookLookupIP = origTestHookLookupIP }() 1151 1152 queries := [][]string{ 1153 {"udp", "golang.org"}, 1154 {"udp4", "golang.org"}, 1155 {"udp6", "golang.org"}, 1156 {"udp", "golang.org"}, 1157 {"udp", "golang.org"}, 1158 } 1159 results := map[[2]string][]IPAddr{ 1160 {"udp", "golang.org"}: { 1161 {IP: IPv4(127, 0, 0, 1)}, 1162 {IP: IPv6loopback}, 1163 }, 1164 {"udp4", "golang.org"}: { 1165 {IP: IPv4(127, 0, 0, 1)}, 1166 }, 1167 {"udp6", "golang.org"}: { 1168 {IP: IPv6loopback}, 1169 }, 1170 } 1171 calls := int32(0) 1172 waitCh := make(chan struct{}) 1173 testHookLookupIP = func(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) { 1174 // We'll block until this is called one time for each different 1175 // expected result. This will ensure that the lookup group would wait 1176 // for the existing call if it was to be reused. 1177 if atomic.AddInt32(&calls, 1) == int32(len(results)) { 1178 close(waitCh) 1179 } 1180 select { 1181 case <-waitCh: 1182 case <-ctx.Done(): 1183 return nil, ctx.Err() 1184 } 1185 return results[[2]string{network, host}], nil 1186 } 1187 1188 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 1189 defer cancel() 1190 wg := sync.WaitGroup{} 1191 for _, q := range queries { 1192 network := q[0] 1193 host := q[1] 1194 wg.Add(1) 1195 go func() { 1196 defer wg.Done() 1197 gotIPs, err := DefaultResolver.lookupIPAddr(ctx, network, host) 1198 if err != nil { 1199 t.Errorf("lookupIPAddr(%v, %v): unexpected error: %v", network, host, err) 1200 } 1201 wantIPs := results[[2]string{network, host}] 1202 if !reflect.DeepEqual(gotIPs, wantIPs) { 1203 t.Errorf("lookupIPAddr(%v, %v): mismatched IPAddr results\n\tGot: %v\n\tWant: %v", network, host, gotIPs, wantIPs) 1204 } 1205 }() 1206 } 1207 wg.Wait() 1208 } 1209 1210 // Issue 53995: Resolver.LookupIP should return error for empty host name. 1211 func TestResolverLookupIPWithEmptyHost(t *testing.T) { 1212 _, err := DefaultResolver.LookupIP(context.Background(), "ip", "") 1213 if err == nil { 1214 t.Fatal("DefaultResolver.LookupIP for empty host success, want no host error") 1215 } 1216 if !strings.HasSuffix(err.Error(), errNoSuchHost.Error()) { 1217 t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost) 1218 } 1219 } 1220 1221 func TestWithUnexpiredValuesPreserved(t *testing.T) { 1222 ctx, cancel := context.WithCancel(context.Background()) 1223 1224 // Insert a value into it. 1225 key, value := "key-1", 2 1226 ctx = context.WithValue(ctx, key, value) 1227 1228 // Now use the "values preserving context" like 1229 // we would for LookupIPAddr. See Issue 28600. 1230 ctx = withUnexpiredValuesPreserved(ctx) 1231 1232 // Lookup before expiry. 1233 if g, w := ctx.Value(key), value; g != w { 1234 t.Errorf("Lookup before expiry: Got %v Want %v", g, w) 1235 } 1236 1237 // Cancel the context. 1238 cancel() 1239 1240 // Lookup after expiry should return nil 1241 if g := ctx.Value(key); g != nil { 1242 t.Errorf("Lookup after expiry: Got %v want nil", g) 1243 } 1244 } 1245 1246 // Issue 31597: don't panic on null byte in name 1247 func TestLookupNullByte(t *testing.T) { 1248 testenv.MustHaveExternalNetwork(t) 1249 testenv.SkipFlakyNet(t) 1250 LookupHost("foo\x00bar") // check that it doesn't panic; it used to on Windows 1251 } 1252 1253 func TestResolverLookupIP(t *testing.T) { 1254 testenv.MustHaveExternalNetwork(t) 1255 1256 v4Ok := supportsIPv4() && *testIPv4 1257 v6Ok := supportsIPv6() && *testIPv6 1258 1259 defer dnsWaitGroup.Wait() 1260 1261 for _, impl := range []struct { 1262 name string 1263 fn func() func() 1264 }{ 1265 {"go", forceGoDNS}, 1266 {"cgo", forceCgoDNS}, 1267 } { 1268 t.Run("implementation: "+impl.name, func(t *testing.T) { 1269 fixup := impl.fn() 1270 if fixup == nil { 1271 t.Skip("not supported") 1272 } 1273 defer fixup() 1274 1275 for _, network := range []string{"ip", "ip4", "ip6"} { 1276 t.Run("network: "+network, func(t *testing.T) { 1277 switch { 1278 case network == "ip4" && !v4Ok: 1279 t.Skip("IPv4 is not supported") 1280 case network == "ip6" && !v6Ok: 1281 t.Skip("IPv6 is not supported") 1282 } 1283 1284 // google.com has both A and AAAA records. 1285 const host = "google.com" 1286 ips, err := DefaultResolver.LookupIP(context.Background(), network, host) 1287 if err != nil { 1288 testenv.SkipFlakyNet(t) 1289 t.Fatalf("DefaultResolver.LookupIP(%q, %q): failed with unexpected error: %v", network, host, err) 1290 } 1291 1292 var v4Addrs []IP 1293 var v6Addrs []IP 1294 for _, ip := range ips { 1295 switch { 1296 case ip.To4() != nil: 1297 // We need to skip the test below because To16 will 1298 // convent an IPv4 address to an IPv4-mapped IPv6 1299 // address. 1300 v4Addrs = append(v4Addrs, ip) 1301 case ip.To16() != nil: 1302 v6Addrs = append(v6Addrs, ip) 1303 default: 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 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 }