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