github.com/mh-cbon/go@v0.0.0-20160603070303-9e112a3fe4c0/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.WithTimeout(context.Background(), time.Duration(tt.timeout)*time.Second) 44 defer cancel() 45 msg, err := exchange(ctx, tt.server, tt.name, tt.qtype) 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.WithTimeout(context.Background(), 3*time.Second) 86 defer cancel() 87 msg, err := exchange(ctx, server, tt.name, tt.qtype) 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 {"foo.local.", true}, 116 {"foo.local", true}, 117 {"foo.LOCAL", true}, 118 {"foo.LOCAL.", true}, 119 120 {"", true}, // will be rejected earlier too 121 122 // Without stuff before onion/local, they're fine to 123 // use DNS. With a search path, 124 // "onion.vegegtables.com" can use DNS. Without a 125 // search path (or with a trailing dot), the queries 126 // are just kinda useless, but don't reveal anything 127 // private. 128 {"local", false}, 129 {"onion", false}, 130 {"local.", false}, 131 {"onion.", false}, 132 } 133 for _, tt := range tests { 134 got := avoidDNS(tt.name) 135 if got != tt.avoid { 136 t.Errorf("avoidDNS(%q) = %v; want %v", tt.name, got, tt.avoid) 137 } 138 } 139 } 140 141 // Issue 13705: don't try to resolve onion addresses, etc 142 func TestLookupTorOnion(t *testing.T) { 143 addrs, err := goLookupIP(context.Background(), "foo.onion") 144 if len(addrs) > 0 { 145 t.Errorf("unexpected addresses: %v", addrs) 146 } 147 if err != nil { 148 t.Fatalf("lookup = %v; want nil", err) 149 } 150 } 151 152 type resolvConfTest struct { 153 dir string 154 path string 155 *resolverConfig 156 } 157 158 func newResolvConfTest() (*resolvConfTest, error) { 159 dir, err := ioutil.TempDir("", "go-resolvconftest") 160 if err != nil { 161 return nil, err 162 } 163 conf := &resolvConfTest{ 164 dir: dir, 165 path: path.Join(dir, "resolv.conf"), 166 resolverConfig: &resolvConf, 167 } 168 conf.initOnce.Do(conf.init) 169 return conf, nil 170 } 171 172 func (conf *resolvConfTest) writeAndUpdate(lines []string) error { 173 f, err := os.OpenFile(conf.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) 174 if err != nil { 175 return err 176 } 177 if _, err := f.WriteString(strings.Join(lines, "\n")); err != nil { 178 f.Close() 179 return err 180 } 181 f.Close() 182 if err := conf.forceUpdate(conf.path, time.Now().Add(time.Hour)); err != nil { 183 return err 184 } 185 return nil 186 } 187 188 func (conf *resolvConfTest) forceUpdate(name string, lastChecked time.Time) error { 189 dnsConf := dnsReadConfig(name) 190 conf.mu.Lock() 191 conf.dnsConfig = dnsConf 192 conf.mu.Unlock() 193 for i := 0; i < 5; i++ { 194 if conf.tryAcquireSema() { 195 conf.lastChecked = lastChecked 196 conf.releaseSema() 197 return nil 198 } 199 } 200 return fmt.Errorf("tryAcquireSema for %s failed", name) 201 } 202 203 func (conf *resolvConfTest) servers() []string { 204 conf.mu.RLock() 205 servers := conf.dnsConfig.servers 206 conf.mu.RUnlock() 207 return servers 208 } 209 210 func (conf *resolvConfTest) teardown() error { 211 err := conf.forceUpdate("/etc/resolv.conf", time.Time{}) 212 os.RemoveAll(conf.dir) 213 return err 214 } 215 216 var updateResolvConfTests = []struct { 217 name string // query name 218 lines []string // resolver configuration lines 219 servers []string // expected name servers 220 }{ 221 { 222 name: "golang.org", 223 lines: []string{"nameserver 8.8.8.8"}, 224 servers: []string{"8.8.8.8:53"}, 225 }, 226 { 227 name: "", 228 lines: nil, // an empty resolv.conf should use defaultNS as name servers 229 servers: defaultNS, 230 }, 231 { 232 name: "www.example.com", 233 lines: []string{"nameserver 8.8.4.4"}, 234 servers: []string{"8.8.4.4:53"}, 235 }, 236 } 237 238 func TestUpdateResolvConf(t *testing.T) { 239 testenv.MustHaveExternalNetwork(t) 240 241 conf, err := newResolvConfTest() 242 if err != nil { 243 t.Fatal(err) 244 } 245 defer conf.teardown() 246 247 for i, tt := range updateResolvConfTests { 248 if err := conf.writeAndUpdate(tt.lines); err != nil { 249 t.Error(err) 250 continue 251 } 252 if tt.name != "" { 253 var wg sync.WaitGroup 254 const N = 10 255 wg.Add(N) 256 for j := 0; j < N; j++ { 257 go func(name string) { 258 defer wg.Done() 259 ips, err := goLookupIP(context.Background(), name) 260 if err != nil { 261 t.Error(err) 262 return 263 } 264 if len(ips) == 0 { 265 t.Errorf("no records for %s", name) 266 return 267 } 268 }(tt.name) 269 } 270 wg.Wait() 271 } 272 servers := conf.servers() 273 if !reflect.DeepEqual(servers, tt.servers) { 274 t.Errorf("#%d: got %v; want %v", i, servers, tt.servers) 275 continue 276 } 277 } 278 } 279 280 var goLookupIPWithResolverConfigTests = []struct { 281 name string 282 lines []string // resolver configuration lines 283 error 284 a, aaaa bool // whether response contains A, AAAA-record 285 }{ 286 // no records, transport timeout 287 { 288 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", 289 []string{ 290 "options timeout:1 attempts:1", 291 "nameserver 255.255.255.255", // please forgive us for abuse of limited broadcast address 292 }, 293 &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "255.255.255.255:53", IsTimeout: true}, 294 false, false, 295 }, 296 297 // no records, non-existent domain 298 { 299 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", 300 []string{ 301 "options timeout:3 attempts:1", 302 "nameserver 8.8.8.8", 303 }, 304 &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "8.8.8.8:53", IsTimeout: false}, 305 false, false, 306 }, 307 308 // a few A records, no AAAA records 309 { 310 "ipv4.google.com.", 311 []string{ 312 "nameserver 8.8.8.8", 313 "nameserver 2001:4860:4860::8888", 314 }, 315 nil, 316 true, false, 317 }, 318 { 319 "ipv4.google.com", 320 []string{ 321 "domain golang.org", 322 "nameserver 2001:4860:4860::8888", 323 "nameserver 8.8.8.8", 324 }, 325 nil, 326 true, false, 327 }, 328 { 329 "ipv4.google.com", 330 []string{ 331 "search x.golang.org y.golang.org", 332 "nameserver 2001:4860:4860::8888", 333 "nameserver 8.8.8.8", 334 }, 335 nil, 336 true, false, 337 }, 338 339 // no A records, a few AAAA records 340 { 341 "ipv6.google.com.", 342 []string{ 343 "nameserver 2001:4860:4860::8888", 344 "nameserver 8.8.8.8", 345 }, 346 nil, 347 false, true, 348 }, 349 { 350 "ipv6.google.com", 351 []string{ 352 "domain golang.org", 353 "nameserver 8.8.8.8", 354 "nameserver 2001:4860:4860::8888", 355 }, 356 nil, 357 false, true, 358 }, 359 { 360 "ipv6.google.com", 361 []string{ 362 "search x.golang.org y.golang.org", 363 "nameserver 8.8.8.8", 364 "nameserver 2001:4860:4860::8888", 365 }, 366 nil, 367 false, true, 368 }, 369 370 // both A and AAAA records 371 { 372 "hostname.as112.net", // see RFC 7534 373 []string{ 374 "domain golang.org", 375 "nameserver 2001:4860:4860::8888", 376 "nameserver 8.8.8.8", 377 }, 378 nil, 379 true, true, 380 }, 381 { 382 "hostname.as112.net", // see RFC 7534 383 []string{ 384 "search x.golang.org y.golang.org", 385 "nameserver 2001:4860:4860::8888", 386 "nameserver 8.8.8.8", 387 }, 388 nil, 389 true, true, 390 }, 391 } 392 393 func TestGoLookupIPWithResolverConfig(t *testing.T) { 394 testenv.MustHaveExternalNetwork(t) 395 396 conf, err := newResolvConfTest() 397 if err != nil { 398 t.Fatal(err) 399 } 400 defer conf.teardown() 401 402 for _, tt := range goLookupIPWithResolverConfigTests { 403 if err := conf.writeAndUpdate(tt.lines); err != nil { 404 t.Error(err) 405 continue 406 } 407 addrs, err := goLookupIP(context.Background(), tt.name) 408 if err != nil { 409 // This test uses external network connectivity. 410 // We need to take care with errors on both 411 // DNS message exchange layer and DNS 412 // transport layer because goLookupIP may fail 413 // when the IP connectivty on node under test 414 // gets lost during its run. 415 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) { 416 t.Errorf("got %v; want %v", err, tt.error) 417 } 418 continue 419 } 420 if len(addrs) == 0 { 421 t.Errorf("no records for %s", tt.name) 422 } 423 if !tt.a && !tt.aaaa && len(addrs) > 0 { 424 t.Errorf("unexpected %v for %s", addrs, tt.name) 425 } 426 for _, addr := range addrs { 427 if !tt.a && addr.IP.To4() != nil { 428 t.Errorf("got %v; must not be IPv4 address", addr) 429 } 430 if !tt.aaaa && addr.IP.To16() != nil && addr.IP.To4() == nil { 431 t.Errorf("got %v; must not be IPv6 address", addr) 432 } 433 } 434 } 435 } 436 437 // Test that goLookupIPOrder falls back to the host file when no DNS servers are available. 438 func TestGoLookupIPOrderFallbackToFile(t *testing.T) { 439 testenv.MustHaveExternalNetwork(t) 440 441 // Add a config that simulates no dns servers being available. 442 conf, err := newResolvConfTest() 443 if err != nil { 444 t.Fatal(err) 445 } 446 if err := conf.writeAndUpdate([]string{}); err != nil { 447 t.Fatal(err) 448 } 449 // Redirect host file lookups. 450 defer func(orig string) { testHookHostsPath = orig }(testHookHostsPath) 451 testHookHostsPath = "testdata/hosts" 452 453 for _, order := range []hostLookupOrder{hostLookupFilesDNS, hostLookupDNSFiles} { 454 name := fmt.Sprintf("order %v", order) 455 456 // First ensure that we get an error when contacting a non-existent host. 457 _, err := goLookupIPOrder(context.Background(), "notarealhost", order) 458 if err == nil { 459 t.Errorf("%s: expected error while looking up name not in hosts file", name) 460 continue 461 } 462 463 // Now check that we get an address when the name appears in the hosts file. 464 addrs, err := goLookupIPOrder(context.Background(), "thor", order) // entry is in "testdata/hosts" 465 if err != nil { 466 t.Errorf("%s: expected to successfully lookup host entry", name) 467 continue 468 } 469 if len(addrs) != 1 { 470 t.Errorf("%s: expected exactly one result, but got %v", name, addrs) 471 continue 472 } 473 if got, want := addrs[0].String(), "127.1.1.1"; got != want { 474 t.Errorf("%s: address doesn't match expectation. got %v, want %v", name, got, want) 475 } 476 } 477 defer conf.teardown() 478 } 479 480 // Issue 12712. 481 // When using search domains, return the error encountered 482 // querying the original name instead of an error encountered 483 // querying a generated name. 484 func TestErrorForOriginalNameWhenSearching(t *testing.T) { 485 const fqdn = "doesnotexist.domain" 486 487 origTestHookDNSDialer := testHookDNSDialer 488 defer func() { testHookDNSDialer = origTestHookDNSDialer }() 489 490 conf, err := newResolvConfTest() 491 if err != nil { 492 t.Fatal(err) 493 } 494 defer conf.teardown() 495 496 if err := conf.writeAndUpdate([]string{"search servfail"}); err != nil { 497 t.Fatal(err) 498 } 499 500 d := &fakeDNSDialer{} 501 testHookDNSDialer = func() dnsDialer { return d } 502 503 d.rh = func(s string, q *dnsMsg) (*dnsMsg, error) { 504 r := &dnsMsg{ 505 dnsMsgHdr: dnsMsgHdr{ 506 id: q.id, 507 }, 508 } 509 510 switch q.question[0].Name { 511 case fqdn + ".servfail.": 512 r.rcode = dnsRcodeServerFailure 513 default: 514 r.rcode = dnsRcodeNameError 515 } 516 517 return r, nil 518 } 519 520 _, err = goLookupIP(context.Background(), fqdn) 521 if err == nil { 522 t.Fatal("expected an error") 523 } 524 525 want := &DNSError{Name: fqdn, Err: errNoSuchHost.Error()} 526 if err, ok := err.(*DNSError); !ok || err.Name != want.Name || err.Err != want.Err { 527 t.Errorf("got %v; want %v", err, want) 528 } 529 } 530 531 // Issue 15434. If a name server gives a lame referral, continue to the next. 532 func TestIgnoreLameReferrals(t *testing.T) { 533 origTestHookDNSDialer := testHookDNSDialer 534 defer func() { testHookDNSDialer = origTestHookDNSDialer }() 535 536 conf, err := newResolvConfTest() 537 if err != nil { 538 t.Fatal(err) 539 } 540 defer conf.teardown() 541 542 if err := conf.writeAndUpdate([]string{"nameserver 192.0.2.1", "nameserver 192.0.2.2"}); err != nil { 543 t.Fatal(err) 544 } 545 546 d := &fakeDNSDialer{} 547 testHookDNSDialer = func() dnsDialer { return d } 548 549 d.rh = func(s string, q *dnsMsg) (*dnsMsg, error) { 550 t.Log(s, q) 551 r := &dnsMsg{ 552 dnsMsgHdr: dnsMsgHdr{ 553 id: q.id, 554 response: true, 555 }, 556 question: q.question, 557 } 558 559 if s == "192.0.2.2:53" { 560 r.recursion_available = true 561 if q.question[0].Qtype == dnsTypeA { 562 r.answer = []dnsRR{ 563 &dnsRR_A{ 564 Hdr: dnsRR_Header{ 565 Name: q.question[0].Name, 566 Rrtype: dnsTypeA, 567 Class: dnsClassINET, 568 Rdlength: 4, 569 }, 570 A: TestAddr, 571 }, 572 } 573 } 574 } 575 576 return r, nil 577 } 578 579 addrs, err := goLookupIP(context.Background(), "www.golang.org") 580 if err != nil { 581 t.Fatal(err) 582 } 583 584 if got := len(addrs); got != 1 { 585 t.Fatalf("got %d addresses, want 1", got) 586 } 587 588 if got, want := addrs[0].String(), "192.0.2.1"; got != want { 589 t.Fatalf("got address %v, want %v", got, want) 590 } 591 } 592 593 func BenchmarkGoLookupIP(b *testing.B) { 594 testHookUninstaller.Do(uninstallTestHooks) 595 ctx := context.Background() 596 597 for i := 0; i < b.N; i++ { 598 goLookupIP(ctx, "www.example.com") 599 } 600 } 601 602 func BenchmarkGoLookupIPNoSuchHost(b *testing.B) { 603 testHookUninstaller.Do(uninstallTestHooks) 604 ctx := context.Background() 605 606 for i := 0; i < b.N; i++ { 607 goLookupIP(ctx, "some.nonexistent") 608 } 609 } 610 611 func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) { 612 testHookUninstaller.Do(uninstallTestHooks) 613 614 conf, err := newResolvConfTest() 615 if err != nil { 616 b.Fatal(err) 617 } 618 defer conf.teardown() 619 620 lines := []string{ 621 "nameserver 203.0.113.254", // use TEST-NET-3 block, see RFC 5737 622 "nameserver 8.8.8.8", 623 } 624 if err := conf.writeAndUpdate(lines); err != nil { 625 b.Fatal(err) 626 } 627 ctx := context.Background() 628 629 for i := 0; i < b.N; i++ { 630 goLookupIP(ctx, "www.example.com") 631 } 632 } 633 634 type fakeDNSDialer struct { 635 // reply handler 636 rh func(s string, q *dnsMsg) (*dnsMsg, error) 637 } 638 639 func (f *fakeDNSDialer) dialDNS(_ context.Context, n, s string) (dnsConn, error) { 640 return &fakeDNSConn{f.rh, s}, nil 641 } 642 643 type fakeDNSConn struct { 644 rh func(s string, q *dnsMsg) (*dnsMsg, error) 645 s string 646 } 647 648 func (f *fakeDNSConn) Close() error { 649 return nil 650 } 651 652 func (f *fakeDNSConn) SetDeadline(time.Time) error { 653 return nil 654 } 655 656 func (f *fakeDNSConn) dnsRoundTrip(q *dnsMsg) (*dnsMsg, error) { 657 return f.rh(f.s, q) 658 } 659 660 // UDP round-tripper algorithm should ignore invalid DNS responses (issue 13281). 661 func TestIgnoreDNSForgeries(t *testing.T) { 662 c, s := Pipe() 663 go func() { 664 b := make([]byte, 512) 665 n, err := s.Read(b) 666 if err != nil { 667 t.Fatal(err) 668 } 669 670 msg := &dnsMsg{} 671 if !msg.Unpack(b[:n]) { 672 t.Fatal("invalid DNS query") 673 } 674 675 s.Write([]byte("garbage DNS response packet")) 676 677 msg.response = true 678 msg.id++ // make invalid ID 679 b, ok := msg.Pack() 680 if !ok { 681 t.Fatal("failed to pack DNS response") 682 } 683 s.Write(b) 684 685 msg.id-- // restore original ID 686 msg.answer = []dnsRR{ 687 &dnsRR_A{ 688 Hdr: dnsRR_Header{ 689 Name: "www.example.com.", 690 Rrtype: dnsTypeA, 691 Class: dnsClassINET, 692 Rdlength: 4, 693 }, 694 A: TestAddr, 695 }, 696 } 697 698 b, ok = msg.Pack() 699 if !ok { 700 t.Fatal("failed to pack DNS response") 701 } 702 s.Write(b) 703 }() 704 705 msg := &dnsMsg{ 706 dnsMsgHdr: dnsMsgHdr{ 707 id: 42, 708 }, 709 question: []dnsQuestion{ 710 { 711 Name: "www.example.com.", 712 Qtype: dnsTypeA, 713 Qclass: dnsClassINET, 714 }, 715 }, 716 } 717 718 resp, err := dnsRoundTripUDP(c, msg) 719 if err != nil { 720 t.Fatalf("dnsRoundTripUDP failed: %v", err) 721 } 722 723 if got := resp.answer[0].(*dnsRR_A).A; got != TestAddr { 724 t.Errorf("got address %v, want %v", got, TestAddr) 725 } 726 }