github.com/riscv/riscv-go@v0.0.0-20200123204226-124ebd6fcc8e/src/net/dnsclient_unix_test.go (about) 1 // Copyright 2013 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 // +build darwin dragonfly freebsd linux netbsd openbsd solaris 6 7 package net 8 9 import ( 10 "context" 11 "fmt" 12 "internal/testenv" 13 "io/ioutil" 14 "os" 15 "path" 16 "reflect" 17 "strings" 18 "sync" 19 "testing" 20 "time" 21 ) 22 23 // Test address from 192.0.2.0/24 block, reserved by RFC 5737 for documentation. 24 const TestAddr uint32 = 0xc0000201 25 26 var dnsTransportFallbackTests = []struct { 27 server string 28 name string 29 qtype uint16 30 timeout int 31 rcode int 32 }{ 33 // Querying "com." with qtype=255 usually makes an answer 34 // which requires more than 512 bytes. 35 {"8.8.8.8:53", "com.", dnsTypeALL, 2, dnsRcodeSuccess}, 36 {"8.8.4.4:53", "com.", dnsTypeALL, 4, dnsRcodeSuccess}, 37 } 38 39 func TestDNSTransportFallback(t *testing.T) { 40 testenv.MustHaveExternalNetwork(t) 41 42 for _, tt := range dnsTransportFallbackTests { 43 ctx, cancel := context.WithCancel(context.Background()) 44 defer cancel() 45 msg, err := exchange(ctx, tt.server, tt.name, tt.qtype, time.Second) 46 if err != nil { 47 t.Error(err) 48 continue 49 } 50 switch msg.rcode { 51 case tt.rcode, dnsRcodeServerFailure: 52 default: 53 t.Errorf("got %v from %v; want %v", msg.rcode, tt.server, tt.rcode) 54 continue 55 } 56 } 57 } 58 59 // See RFC 6761 for further information about the reserved, pseudo 60 // domain names. 61 var specialDomainNameTests = []struct { 62 name string 63 qtype uint16 64 rcode int 65 }{ 66 // Name resolution APIs and libraries should not recognize the 67 // followings as special. 68 {"1.0.168.192.in-addr.arpa.", dnsTypePTR, dnsRcodeNameError}, 69 {"test.", dnsTypeALL, dnsRcodeNameError}, 70 {"example.com.", dnsTypeALL, dnsRcodeSuccess}, 71 72 // Name resolution APIs and libraries should recognize the 73 // followings as special and should not send any queries. 74 // Though, we test those names here for verifying negative 75 // answers at DNS query-response interaction level. 76 {"localhost.", dnsTypeALL, dnsRcodeNameError}, 77 {"invalid.", dnsTypeALL, dnsRcodeNameError}, 78 } 79 80 func TestSpecialDomainName(t *testing.T) { 81 testenv.MustHaveExternalNetwork(t) 82 83 server := "8.8.8.8:53" 84 for _, tt := range specialDomainNameTests { 85 ctx, cancel := context.WithCancel(context.Background()) 86 defer cancel() 87 msg, err := exchange(ctx, server, tt.name, tt.qtype, 3*time.Second) 88 if err != nil { 89 t.Error(err) 90 continue 91 } 92 switch msg.rcode { 93 case tt.rcode, dnsRcodeServerFailure: 94 default: 95 t.Errorf("got %v from %v; want %v", msg.rcode, server, tt.rcode) 96 continue 97 } 98 } 99 } 100 101 // Issue 13705: don't try to resolve onion addresses, etc 102 func TestAvoidDNSName(t *testing.T) { 103 tests := []struct { 104 name string 105 avoid bool 106 }{ 107 {"foo.com", false}, 108 {"foo.com.", false}, 109 110 {"foo.onion.", true}, 111 {"foo.onion", true}, 112 {"foo.ONION", true}, 113 {"foo.ONION.", true}, 114 115 // But do resolve *.local address; Issue 16739 116 {"foo.local.", false}, 117 {"foo.local", false}, 118 {"foo.LOCAL", false}, 119 {"foo.LOCAL.", false}, 120 121 {"", true}, // will be rejected earlier too 122 123 // Without stuff before onion/local, they're fine to 124 // use DNS. With a search path, 125 // "onion.vegegtables.com" can use DNS. Without a 126 // search path (or with a trailing dot), the queries 127 // are just kinda useless, but don't reveal anything 128 // private. 129 {"local", false}, 130 {"onion", false}, 131 {"local.", false}, 132 {"onion.", false}, 133 } 134 for _, tt := range tests { 135 got := avoidDNS(tt.name) 136 if got != tt.avoid { 137 t.Errorf("avoidDNS(%q) = %v; want %v", tt.name, got, tt.avoid) 138 } 139 } 140 } 141 142 // Issue 13705: don't try to resolve onion addresses, etc 143 func TestLookupTorOnion(t *testing.T) { 144 addrs, err := goLookupIP(context.Background(), "foo.onion") 145 if len(addrs) > 0 { 146 t.Errorf("unexpected addresses: %v", addrs) 147 } 148 if err != nil { 149 t.Fatalf("lookup = %v; want nil", err) 150 } 151 } 152 153 type resolvConfTest struct { 154 dir string 155 path string 156 *resolverConfig 157 } 158 159 func newResolvConfTest() (*resolvConfTest, error) { 160 dir, err := ioutil.TempDir("", "go-resolvconftest") 161 if err != nil { 162 return nil, err 163 } 164 conf := &resolvConfTest{ 165 dir: dir, 166 path: path.Join(dir, "resolv.conf"), 167 resolverConfig: &resolvConf, 168 } 169 conf.initOnce.Do(conf.init) 170 return conf, nil 171 } 172 173 func (conf *resolvConfTest) writeAndUpdate(lines []string) error { 174 f, err := os.OpenFile(conf.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) 175 if err != nil { 176 return err 177 } 178 if _, err := f.WriteString(strings.Join(lines, "\n")); err != nil { 179 f.Close() 180 return err 181 } 182 f.Close() 183 if err := conf.forceUpdate(conf.path, time.Now().Add(time.Hour)); err != nil { 184 return err 185 } 186 return nil 187 } 188 189 func (conf *resolvConfTest) forceUpdate(name string, lastChecked time.Time) error { 190 dnsConf := dnsReadConfig(name) 191 conf.mu.Lock() 192 conf.dnsConfig = dnsConf 193 conf.mu.Unlock() 194 for i := 0; i < 5; i++ { 195 if conf.tryAcquireSema() { 196 conf.lastChecked = lastChecked 197 conf.releaseSema() 198 return nil 199 } 200 } 201 return fmt.Errorf("tryAcquireSema for %s failed", name) 202 } 203 204 func (conf *resolvConfTest) servers() []string { 205 conf.mu.RLock() 206 servers := conf.dnsConfig.servers 207 conf.mu.RUnlock() 208 return servers 209 } 210 211 func (conf *resolvConfTest) teardown() error { 212 err := conf.forceUpdate("/etc/resolv.conf", time.Time{}) 213 os.RemoveAll(conf.dir) 214 return err 215 } 216 217 var updateResolvConfTests = []struct { 218 name string // query name 219 lines []string // resolver configuration lines 220 servers []string // expected name servers 221 }{ 222 { 223 name: "golang.org", 224 lines: []string{"nameserver 8.8.8.8"}, 225 servers: []string{"8.8.8.8:53"}, 226 }, 227 { 228 name: "", 229 lines: nil, // an empty resolv.conf should use defaultNS as name servers 230 servers: defaultNS, 231 }, 232 { 233 name: "www.example.com", 234 lines: []string{"nameserver 8.8.4.4"}, 235 servers: []string{"8.8.4.4:53"}, 236 }, 237 } 238 239 func TestUpdateResolvConf(t *testing.T) { 240 testenv.MustHaveExternalNetwork(t) 241 242 conf, err := newResolvConfTest() 243 if err != nil { 244 t.Fatal(err) 245 } 246 defer conf.teardown() 247 248 for i, tt := range updateResolvConfTests { 249 if err := conf.writeAndUpdate(tt.lines); err != nil { 250 t.Error(err) 251 continue 252 } 253 if tt.name != "" { 254 var wg sync.WaitGroup 255 const N = 10 256 wg.Add(N) 257 for j := 0; j < N; j++ { 258 go func(name string) { 259 defer wg.Done() 260 ips, err := goLookupIP(context.Background(), name) 261 if err != nil { 262 t.Error(err) 263 return 264 } 265 if len(ips) == 0 { 266 t.Errorf("no records for %s", name) 267 return 268 } 269 }(tt.name) 270 } 271 wg.Wait() 272 } 273 servers := conf.servers() 274 if !reflect.DeepEqual(servers, tt.servers) { 275 t.Errorf("#%d: got %v; want %v", i, servers, tt.servers) 276 continue 277 } 278 } 279 } 280 281 var goLookupIPWithResolverConfigTests = []struct { 282 name string 283 lines []string // resolver configuration lines 284 error 285 a, aaaa bool // whether response contains A, AAAA-record 286 }{ 287 // no records, transport timeout 288 { 289 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", 290 []string{ 291 "options timeout:1 attempts:1", 292 "nameserver 255.255.255.255", // please forgive us for abuse of limited broadcast address 293 }, 294 &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "255.255.255.255:53", IsTimeout: true}, 295 false, false, 296 }, 297 298 // no records, non-existent domain 299 { 300 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", 301 []string{ 302 "options timeout:3 attempts:1", 303 "nameserver 8.8.8.8", 304 }, 305 &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "8.8.8.8:53", IsTimeout: false}, 306 false, false, 307 }, 308 309 // a few A records, no AAAA records 310 { 311 "ipv4.google.com.", 312 []string{ 313 "nameserver 8.8.8.8", 314 "nameserver 2001:4860:4860::8888", 315 }, 316 nil, 317 true, false, 318 }, 319 { 320 "ipv4.google.com", 321 []string{ 322 "domain golang.org", 323 "nameserver 2001:4860:4860::8888", 324 "nameserver 8.8.8.8", 325 }, 326 nil, 327 true, false, 328 }, 329 { 330 "ipv4.google.com", 331 []string{ 332 "search x.golang.org y.golang.org", 333 "nameserver 2001:4860:4860::8888", 334 "nameserver 8.8.8.8", 335 }, 336 nil, 337 true, false, 338 }, 339 340 // no A records, a few AAAA records 341 { 342 "ipv6.google.com.", 343 []string{ 344 "nameserver 2001:4860:4860::8888", 345 "nameserver 8.8.8.8", 346 }, 347 nil, 348 false, true, 349 }, 350 { 351 "ipv6.google.com", 352 []string{ 353 "domain golang.org", 354 "nameserver 8.8.8.8", 355 "nameserver 2001:4860:4860::8888", 356 }, 357 nil, 358 false, true, 359 }, 360 { 361 "ipv6.google.com", 362 []string{ 363 "search x.golang.org y.golang.org", 364 "nameserver 8.8.8.8", 365 "nameserver 2001:4860:4860::8888", 366 }, 367 nil, 368 false, true, 369 }, 370 371 // both A and AAAA records 372 { 373 "hostname.as112.net", // see RFC 7534 374 []string{ 375 "domain golang.org", 376 "nameserver 2001:4860:4860::8888", 377 "nameserver 8.8.8.8", 378 }, 379 nil, 380 true, true, 381 }, 382 { 383 "hostname.as112.net", // see RFC 7534 384 []string{ 385 "search x.golang.org y.golang.org", 386 "nameserver 2001:4860:4860::8888", 387 "nameserver 8.8.8.8", 388 }, 389 nil, 390 true, true, 391 }, 392 } 393 394 func TestGoLookupIPWithResolverConfig(t *testing.T) { 395 testenv.MustHaveExternalNetwork(t) 396 397 conf, err := newResolvConfTest() 398 if err != nil { 399 t.Fatal(err) 400 } 401 defer conf.teardown() 402 403 for _, tt := range goLookupIPWithResolverConfigTests { 404 if err := conf.writeAndUpdate(tt.lines); err != nil { 405 t.Error(err) 406 continue 407 } 408 addrs, err := goLookupIP(context.Background(), tt.name) 409 if err != nil { 410 // This test uses external network connectivity. 411 // We need to take care with errors on both 412 // DNS message exchange layer and DNS 413 // transport layer because goLookupIP may fail 414 // when the IP connectivity on node under test 415 // gets lost during its run. 416 if err, ok := err.(*DNSError); !ok || tt.error != nil && (err.Name != tt.error.(*DNSError).Name || err.Server != tt.error.(*DNSError).Server || err.IsTimeout != tt.error.(*DNSError).IsTimeout) { 417 t.Errorf("got %v; want %v", err, tt.error) 418 } 419 continue 420 } 421 if len(addrs) == 0 { 422 t.Errorf("no records for %s", tt.name) 423 } 424 if !tt.a && !tt.aaaa && len(addrs) > 0 { 425 t.Errorf("unexpected %v for %s", addrs, tt.name) 426 } 427 for _, addr := range addrs { 428 if !tt.a && addr.IP.To4() != nil { 429 t.Errorf("got %v; must not be IPv4 address", addr) 430 } 431 if !tt.aaaa && addr.IP.To16() != nil && addr.IP.To4() == nil { 432 t.Errorf("got %v; must not be IPv6 address", addr) 433 } 434 } 435 } 436 } 437 438 // Test that goLookupIPOrder falls back to the host file when no DNS servers are available. 439 func TestGoLookupIPOrderFallbackToFile(t *testing.T) { 440 testenv.MustHaveExternalNetwork(t) 441 442 // Add a config that simulates no dns servers being available. 443 conf, err := newResolvConfTest() 444 if err != nil { 445 t.Fatal(err) 446 } 447 if err := conf.writeAndUpdate([]string{}); err != nil { 448 t.Fatal(err) 449 } 450 // Redirect host file lookups. 451 defer func(orig string) { testHookHostsPath = orig }(testHookHostsPath) 452 testHookHostsPath = "testdata/hosts" 453 454 for _, order := range []hostLookupOrder{hostLookupFilesDNS, hostLookupDNSFiles} { 455 name := fmt.Sprintf("order %v", order) 456 457 // First ensure that we get an error when contacting a non-existent host. 458 _, _, err := goLookupIPCNAMEOrder(context.Background(), "notarealhost", order) 459 if err == nil { 460 t.Errorf("%s: expected error while looking up name not in hosts file", name) 461 continue 462 } 463 464 // Now check that we get an address when the name appears in the hosts file. 465 addrs, _, err := goLookupIPCNAMEOrder(context.Background(), "thor", order) // entry is in "testdata/hosts" 466 if err != nil { 467 t.Errorf("%s: expected to successfully lookup host entry", name) 468 continue 469 } 470 if len(addrs) != 1 { 471 t.Errorf("%s: expected exactly one result, but got %v", name, addrs) 472 continue 473 } 474 if got, want := addrs[0].String(), "127.1.1.1"; got != want { 475 t.Errorf("%s: address doesn't match expectation. got %v, want %v", name, got, want) 476 } 477 } 478 defer conf.teardown() 479 } 480 481 // Issue 12712. 482 // When using search domains, return the error encountered 483 // querying the original name instead of an error encountered 484 // querying a generated name. 485 func TestErrorForOriginalNameWhenSearching(t *testing.T) { 486 const fqdn = "doesnotexist.domain" 487 488 origTestHookDNSDialer := testHookDNSDialer 489 defer func() { testHookDNSDialer = origTestHookDNSDialer }() 490 491 conf, err := newResolvConfTest() 492 if err != nil { 493 t.Fatal(err) 494 } 495 defer conf.teardown() 496 497 if err := conf.writeAndUpdate([]string{"search servfail"}); err != nil { 498 t.Fatal(err) 499 } 500 501 d := &fakeDNSDialer{} 502 testHookDNSDialer = func() dnsDialer { return d } 503 504 d.rh = func(s string, q *dnsMsg, _ time.Time) (*dnsMsg, error) { 505 r := &dnsMsg{ 506 dnsMsgHdr: dnsMsgHdr{ 507 id: q.id, 508 }, 509 } 510 511 switch q.question[0].Name { 512 case fqdn + ".servfail.": 513 r.rcode = dnsRcodeServerFailure 514 default: 515 r.rcode = dnsRcodeNameError 516 } 517 518 return r, nil 519 } 520 521 _, err = goLookupIP(context.Background(), fqdn) 522 if err == nil { 523 t.Fatal("expected an error") 524 } 525 526 want := &DNSError{Name: fqdn, Err: errNoSuchHost.Error()} 527 if err, ok := err.(*DNSError); !ok || err.Name != want.Name || err.Err != want.Err { 528 t.Errorf("got %v; want %v", err, want) 529 } 530 } 531 532 // Issue 15434. If a name server gives a lame referral, continue to the next. 533 func TestIgnoreLameReferrals(t *testing.T) { 534 origTestHookDNSDialer := testHookDNSDialer 535 defer func() { testHookDNSDialer = origTestHookDNSDialer }() 536 537 conf, err := newResolvConfTest() 538 if err != nil { 539 t.Fatal(err) 540 } 541 defer conf.teardown() 542 543 if err := conf.writeAndUpdate([]string{"nameserver 192.0.2.1", // the one that will give a lame referral 544 "nameserver 192.0.2.2"}); err != nil { 545 t.Fatal(err) 546 } 547 548 d := &fakeDNSDialer{} 549 testHookDNSDialer = func() dnsDialer { return d } 550 551 d.rh = func(s string, q *dnsMsg, _ time.Time) (*dnsMsg, error) { 552 t.Log(s, q) 553 r := &dnsMsg{ 554 dnsMsgHdr: dnsMsgHdr{ 555 id: q.id, 556 response: true, 557 }, 558 question: q.question, 559 } 560 561 if s == "192.0.2.2:53" { 562 r.recursion_available = true 563 if q.question[0].Qtype == dnsTypeA { 564 r.answer = []dnsRR{ 565 &dnsRR_A{ 566 Hdr: dnsRR_Header{ 567 Name: q.question[0].Name, 568 Rrtype: dnsTypeA, 569 Class: dnsClassINET, 570 Rdlength: 4, 571 }, 572 A: TestAddr, 573 }, 574 } 575 } 576 } 577 578 return r, nil 579 } 580 581 addrs, err := goLookupIP(context.Background(), "www.golang.org") 582 if err != nil { 583 t.Fatal(err) 584 } 585 586 if got := len(addrs); got != 1 { 587 t.Fatalf("got %d addresses, want 1", got) 588 } 589 590 if got, want := addrs[0].String(), "192.0.2.1"; got != want { 591 t.Fatalf("got address %v, want %v", got, want) 592 } 593 } 594 595 func BenchmarkGoLookupIP(b *testing.B) { 596 testHookUninstaller.Do(uninstallTestHooks) 597 ctx := context.Background() 598 599 for i := 0; i < b.N; i++ { 600 goLookupIP(ctx, "www.example.com") 601 } 602 } 603 604 func BenchmarkGoLookupIPNoSuchHost(b *testing.B) { 605 testHookUninstaller.Do(uninstallTestHooks) 606 ctx := context.Background() 607 608 for i := 0; i < b.N; i++ { 609 goLookupIP(ctx, "some.nonexistent") 610 } 611 } 612 613 func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) { 614 testHookUninstaller.Do(uninstallTestHooks) 615 616 conf, err := newResolvConfTest() 617 if err != nil { 618 b.Fatal(err) 619 } 620 defer conf.teardown() 621 622 lines := []string{ 623 "nameserver 203.0.113.254", // use TEST-NET-3 block, see RFC 5737 624 "nameserver 8.8.8.8", 625 } 626 if err := conf.writeAndUpdate(lines); err != nil { 627 b.Fatal(err) 628 } 629 ctx := context.Background() 630 631 for i := 0; i < b.N; i++ { 632 goLookupIP(ctx, "www.example.com") 633 } 634 } 635 636 type fakeDNSDialer struct { 637 // reply handler 638 rh func(s string, q *dnsMsg, t time.Time) (*dnsMsg, error) 639 } 640 641 func (f *fakeDNSDialer) dialDNS(_ context.Context, n, s string) (dnsConn, error) { 642 return &fakeDNSConn{f.rh, s, time.Time{}}, nil 643 } 644 645 type fakeDNSConn struct { 646 rh func(s string, q *dnsMsg, t time.Time) (*dnsMsg, error) 647 s string 648 t time.Time 649 } 650 651 func (f *fakeDNSConn) Close() error { 652 return nil 653 } 654 655 func (f *fakeDNSConn) SetDeadline(t time.Time) error { 656 f.t = t 657 return nil 658 } 659 660 func (f *fakeDNSConn) dnsRoundTrip(q *dnsMsg) (*dnsMsg, error) { 661 return f.rh(f.s, q, f.t) 662 } 663 664 // UDP round-tripper algorithm should ignore invalid DNS responses (issue 13281). 665 func TestIgnoreDNSForgeries(t *testing.T) { 666 c, s := Pipe() 667 go func() { 668 b := make([]byte, 512) 669 n, err := s.Read(b) 670 if err != nil { 671 t.Error(err) 672 return 673 } 674 675 msg := &dnsMsg{} 676 if !msg.Unpack(b[:n]) { 677 t.Error("invalid DNS query") 678 return 679 } 680 681 s.Write([]byte("garbage DNS response packet")) 682 683 msg.response = true 684 msg.id++ // make invalid ID 685 b, ok := msg.Pack() 686 if !ok { 687 t.Error("failed to pack DNS response") 688 return 689 } 690 s.Write(b) 691 692 msg.id-- // restore original ID 693 msg.answer = []dnsRR{ 694 &dnsRR_A{ 695 Hdr: dnsRR_Header{ 696 Name: "www.example.com.", 697 Rrtype: dnsTypeA, 698 Class: dnsClassINET, 699 Rdlength: 4, 700 }, 701 A: TestAddr, 702 }, 703 } 704 705 b, ok = msg.Pack() 706 if !ok { 707 t.Error("failed to pack DNS response") 708 return 709 } 710 s.Write(b) 711 }() 712 713 msg := &dnsMsg{ 714 dnsMsgHdr: dnsMsgHdr{ 715 id: 42, 716 }, 717 question: []dnsQuestion{ 718 { 719 Name: "www.example.com.", 720 Qtype: dnsTypeA, 721 Qclass: dnsClassINET, 722 }, 723 }, 724 } 725 726 resp, err := dnsRoundTripUDP(c, msg) 727 if err != nil { 728 t.Fatalf("dnsRoundTripUDP failed: %v", err) 729 } 730 731 if got := resp.answer[0].(*dnsRR_A).A; got != TestAddr { 732 t.Errorf("got address %v, want %v", got, TestAddr) 733 } 734 } 735 736 // Issue 16865. If a name server times out, continue to the next. 737 func TestRetryTimeout(t *testing.T) { 738 origTestHookDNSDialer := testHookDNSDialer 739 defer func() { testHookDNSDialer = origTestHookDNSDialer }() 740 741 conf, err := newResolvConfTest() 742 if err != nil { 743 t.Fatal(err) 744 } 745 defer conf.teardown() 746 747 testConf := []string{ 748 "nameserver 192.0.2.1", // the one that will timeout 749 "nameserver 192.0.2.2", 750 } 751 if err := conf.writeAndUpdate(testConf); err != nil { 752 t.Fatal(err) 753 } 754 755 d := &fakeDNSDialer{} 756 testHookDNSDialer = func() dnsDialer { return d } 757 758 var deadline0 time.Time 759 760 d.rh = func(s string, q *dnsMsg, deadline time.Time) (*dnsMsg, error) { 761 t.Log(s, q, deadline) 762 763 if deadline.IsZero() { 764 t.Error("zero deadline") 765 } 766 767 if s == "192.0.2.1:53" { 768 deadline0 = deadline 769 time.Sleep(10 * time.Millisecond) 770 return nil, errTimeout 771 } 772 773 if deadline == deadline0 { 774 t.Error("deadline didn't change") 775 } 776 777 return mockTXTResponse(q), nil 778 } 779 780 _, err = LookupTXT("www.golang.org") 781 if err != nil { 782 t.Fatal(err) 783 } 784 785 if deadline0.IsZero() { 786 t.Error("deadline0 still zero", deadline0) 787 } 788 } 789 790 func TestRotate(t *testing.T) { 791 // without rotation, always uses the first server 792 testRotate(t, false, []string{"192.0.2.1", "192.0.2.2"}, []string{"192.0.2.1:53", "192.0.2.1:53", "192.0.2.1:53"}) 793 794 // with rotation, rotates through back to first 795 testRotate(t, true, []string{"192.0.2.1", "192.0.2.2"}, []string{"192.0.2.1:53", "192.0.2.2:53", "192.0.2.1:53"}) 796 } 797 798 func testRotate(t *testing.T, rotate bool, nameservers, wantServers []string) { 799 origTestHookDNSDialer := testHookDNSDialer 800 defer func() { testHookDNSDialer = origTestHookDNSDialer }() 801 802 conf, err := newResolvConfTest() 803 if err != nil { 804 t.Fatal(err) 805 } 806 defer conf.teardown() 807 808 var confLines []string 809 for _, ns := range nameservers { 810 confLines = append(confLines, "nameserver "+ns) 811 } 812 if rotate { 813 confLines = append(confLines, "options rotate") 814 } 815 816 if err := conf.writeAndUpdate(confLines); err != nil { 817 t.Fatal(err) 818 } 819 820 d := &fakeDNSDialer{} 821 testHookDNSDialer = func() dnsDialer { return d } 822 823 var usedServers []string 824 d.rh = func(s string, q *dnsMsg, _ time.Time) (*dnsMsg, error) { 825 usedServers = append(usedServers, s) 826 return mockTXTResponse(q), nil 827 } 828 829 // len(nameservers) + 1 to allow rotation to get back to start 830 for i := 0; i < len(nameservers)+1; i++ { 831 if _, err := LookupTXT("www.golang.org"); err != nil { 832 t.Fatal(err) 833 } 834 } 835 836 if !reflect.DeepEqual(usedServers, wantServers) { 837 t.Errorf("rotate=%t got used servers:\n%v\nwant:\n%v", rotate, usedServers, wantServers) 838 } 839 } 840 841 func mockTXTResponse(q *dnsMsg) *dnsMsg { 842 r := &dnsMsg{ 843 dnsMsgHdr: dnsMsgHdr{ 844 id: q.id, 845 response: true, 846 recursion_available: true, 847 }, 848 question: q.question, 849 answer: []dnsRR{ 850 &dnsRR_TXT{ 851 Hdr: dnsRR_Header{ 852 Name: q.question[0].Name, 853 Rrtype: dnsTypeTXT, 854 Class: dnsClassINET, 855 }, 856 Txt: "ok", 857 }, 858 }, 859 } 860 861 return r 862 }