github.com/code-reading/golang@v0.0.0-20220303082512-ba5bc0e589a3/go/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 //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris 6 // +build aix darwin dragonfly freebsd linux netbsd openbsd solaris 7 8 package net 9 10 import ( 11 "context" 12 "errors" 13 "fmt" 14 "os" 15 "path" 16 "reflect" 17 "strings" 18 "sync" 19 "sync/atomic" 20 "testing" 21 "time" 22 23 "golang.org/x/net/dns/dnsmessage" 24 ) 25 26 var goResolver = Resolver{PreferGo: true} 27 28 // Test address from 192.0.2.0/24 block, reserved by RFC 5737 for documentation. 29 var TestAddr = [4]byte{0xc0, 0x00, 0x02, 0x01} 30 31 // Test address from 2001:db8::/32 block, reserved by RFC 3849 for documentation. 32 var TestAddr6 = [16]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} 33 34 func mustNewName(name string) dnsmessage.Name { 35 nn, err := dnsmessage.NewName(name) 36 if err != nil { 37 panic(fmt.Sprint("creating name: ", err)) 38 } 39 return nn 40 } 41 42 func mustQuestion(name string, qtype dnsmessage.Type, class dnsmessage.Class) dnsmessage.Question { 43 return dnsmessage.Question{ 44 Name: mustNewName(name), 45 Type: qtype, 46 Class: class, 47 } 48 } 49 50 var dnsTransportFallbackTests = []struct { 51 server string 52 question dnsmessage.Question 53 timeout int 54 rcode dnsmessage.RCode 55 }{ 56 // Querying "com." with qtype=255 usually makes an answer 57 // which requires more than 512 bytes. 58 {"8.8.8.8:53", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), 2, dnsmessage.RCodeSuccess}, 59 {"8.8.4.4:53", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), 4, dnsmessage.RCodeSuccess}, 60 } 61 62 func TestDNSTransportFallback(t *testing.T) { 63 fake := fakeDNSServer{ 64 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 65 r := dnsmessage.Message{ 66 Header: dnsmessage.Header{ 67 ID: q.Header.ID, 68 Response: true, 69 RCode: dnsmessage.RCodeSuccess, 70 }, 71 Questions: q.Questions, 72 } 73 if n == "udp" { 74 r.Header.Truncated = true 75 } 76 return r, nil 77 }, 78 } 79 r := Resolver{PreferGo: true, Dial: fake.DialContext} 80 for _, tt := range dnsTransportFallbackTests { 81 ctx, cancel := context.WithCancel(context.Background()) 82 defer cancel() 83 _, h, err := r.exchange(ctx, tt.server, tt.question, time.Second, useUDPOrTCP) 84 if err != nil { 85 t.Error(err) 86 continue 87 } 88 if h.RCode != tt.rcode { 89 t.Errorf("got %v from %v; want %v", h.RCode, tt.server, tt.rcode) 90 continue 91 } 92 } 93 } 94 95 // See RFC 6761 for further information about the reserved, pseudo 96 // domain names. 97 var specialDomainNameTests = []struct { 98 question dnsmessage.Question 99 rcode dnsmessage.RCode 100 }{ 101 // Name resolution APIs and libraries should not recognize the 102 // followings as special. 103 {mustQuestion("1.0.168.192.in-addr.arpa.", dnsmessage.TypePTR, dnsmessage.ClassINET), dnsmessage.RCodeNameError}, 104 {mustQuestion("test.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError}, 105 {mustQuestion("example.com.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeSuccess}, 106 107 // Name resolution APIs and libraries should recognize the 108 // followings as special and should not send any queries. 109 // Though, we test those names here for verifying negative 110 // answers at DNS query-response interaction level. 111 {mustQuestion("localhost.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError}, 112 {mustQuestion("invalid.", dnsmessage.TypeALL, dnsmessage.ClassINET), dnsmessage.RCodeNameError}, 113 } 114 115 func TestSpecialDomainName(t *testing.T) { 116 fake := fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 117 r := dnsmessage.Message{ 118 Header: dnsmessage.Header{ 119 ID: q.ID, 120 Response: true, 121 }, 122 Questions: q.Questions, 123 } 124 125 switch q.Questions[0].Name.String() { 126 case "example.com.": 127 r.Header.RCode = dnsmessage.RCodeSuccess 128 default: 129 r.Header.RCode = dnsmessage.RCodeNameError 130 } 131 132 return r, nil 133 }} 134 r := Resolver{PreferGo: true, Dial: fake.DialContext} 135 server := "8.8.8.8:53" 136 for _, tt := range specialDomainNameTests { 137 ctx, cancel := context.WithCancel(context.Background()) 138 defer cancel() 139 _, h, err := r.exchange(ctx, server, tt.question, 3*time.Second, useUDPOrTCP) 140 if err != nil { 141 t.Error(err) 142 continue 143 } 144 if h.RCode != tt.rcode { 145 t.Errorf("got %v from %v; want %v", h.RCode, server, tt.rcode) 146 continue 147 } 148 } 149 } 150 151 // Issue 13705: don't try to resolve onion addresses, etc 152 func TestAvoidDNSName(t *testing.T) { 153 tests := []struct { 154 name string 155 avoid bool 156 }{ 157 {"foo.com", false}, 158 {"foo.com.", false}, 159 160 {"foo.onion.", true}, 161 {"foo.onion", true}, 162 {"foo.ONION", true}, 163 {"foo.ONION.", true}, 164 165 // But do resolve *.local address; Issue 16739 166 {"foo.local.", false}, 167 {"foo.local", false}, 168 {"foo.LOCAL", false}, 169 {"foo.LOCAL.", false}, 170 171 {"", true}, // will be rejected earlier too 172 173 // Without stuff before onion/local, they're fine to 174 // use DNS. With a search path, 175 // "onion.vegetables.com" can use DNS. Without a 176 // search path (or with a trailing dot), the queries 177 // are just kinda useless, but don't reveal anything 178 // private. 179 {"local", false}, 180 {"onion", false}, 181 {"local.", false}, 182 {"onion.", false}, 183 } 184 for _, tt := range tests { 185 got := avoidDNS(tt.name) 186 if got != tt.avoid { 187 t.Errorf("avoidDNS(%q) = %v; want %v", tt.name, got, tt.avoid) 188 } 189 } 190 } 191 192 var fakeDNSServerSuccessful = fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 193 r := dnsmessage.Message{ 194 Header: dnsmessage.Header{ 195 ID: q.ID, 196 Response: true, 197 }, 198 Questions: q.Questions, 199 } 200 if len(q.Questions) == 1 && q.Questions[0].Type == dnsmessage.TypeA { 201 r.Answers = []dnsmessage.Resource{ 202 { 203 Header: dnsmessage.ResourceHeader{ 204 Name: q.Questions[0].Name, 205 Type: dnsmessage.TypeA, 206 Class: dnsmessage.ClassINET, 207 Length: 4, 208 }, 209 Body: &dnsmessage.AResource{ 210 A: TestAddr, 211 }, 212 }, 213 } 214 } 215 return r, nil 216 }} 217 218 // Issue 13705: don't try to resolve onion addresses, etc 219 func TestLookupTorOnion(t *testing.T) { 220 defer dnsWaitGroup.Wait() 221 r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext} 222 addrs, err := r.LookupIPAddr(context.Background(), "foo.onion") 223 if err != nil { 224 t.Fatalf("lookup = %v; want nil", err) 225 } 226 if len(addrs) > 0 { 227 t.Errorf("unexpected addresses: %v", addrs) 228 } 229 } 230 231 type resolvConfTest struct { 232 dir string 233 path string 234 *resolverConfig 235 } 236 237 func newResolvConfTest() (*resolvConfTest, error) { 238 dir, err := os.MkdirTemp("", "go-resolvconftest") 239 if err != nil { 240 return nil, err 241 } 242 conf := &resolvConfTest{ 243 dir: dir, 244 path: path.Join(dir, "resolv.conf"), 245 resolverConfig: &resolvConf, 246 } 247 conf.initOnce.Do(conf.init) 248 return conf, nil 249 } 250 251 func (conf *resolvConfTest) writeAndUpdate(lines []string) error { 252 f, err := os.OpenFile(conf.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) 253 if err != nil { 254 return err 255 } 256 if _, err := f.WriteString(strings.Join(lines, "\n")); err != nil { 257 f.Close() 258 return err 259 } 260 f.Close() 261 if err := conf.forceUpdate(conf.path, time.Now().Add(time.Hour)); err != nil { 262 return err 263 } 264 return nil 265 } 266 267 func (conf *resolvConfTest) forceUpdate(name string, lastChecked time.Time) error { 268 dnsConf := dnsReadConfig(name) 269 conf.mu.Lock() 270 conf.dnsConfig = dnsConf 271 conf.mu.Unlock() 272 for i := 0; i < 5; i++ { 273 if conf.tryAcquireSema() { 274 conf.lastChecked = lastChecked 275 conf.releaseSema() 276 return nil 277 } 278 } 279 return fmt.Errorf("tryAcquireSema for %s failed", name) 280 } 281 282 func (conf *resolvConfTest) servers() []string { 283 conf.mu.RLock() 284 servers := conf.dnsConfig.servers 285 conf.mu.RUnlock() 286 return servers 287 } 288 289 func (conf *resolvConfTest) teardown() error { 290 err := conf.forceUpdate("/etc/resolv.conf", time.Time{}) 291 os.RemoveAll(conf.dir) 292 return err 293 } 294 295 var updateResolvConfTests = []struct { 296 name string // query name 297 lines []string // resolver configuration lines 298 servers []string // expected name servers 299 }{ 300 { 301 name: "golang.org", 302 lines: []string{"nameserver 8.8.8.8"}, 303 servers: []string{"8.8.8.8:53"}, 304 }, 305 { 306 name: "", 307 lines: nil, // an empty resolv.conf should use defaultNS as name servers 308 servers: defaultNS, 309 }, 310 { 311 name: "www.example.com", 312 lines: []string{"nameserver 8.8.4.4"}, 313 servers: []string{"8.8.4.4:53"}, 314 }, 315 } 316 317 func TestUpdateResolvConf(t *testing.T) { 318 defer dnsWaitGroup.Wait() 319 320 r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext} 321 322 conf, err := newResolvConfTest() 323 if err != nil { 324 t.Fatal(err) 325 } 326 defer conf.teardown() 327 328 for i, tt := range updateResolvConfTests { 329 if err := conf.writeAndUpdate(tt.lines); err != nil { 330 t.Error(err) 331 continue 332 } 333 if tt.name != "" { 334 var wg sync.WaitGroup 335 const N = 10 336 wg.Add(N) 337 for j := 0; j < N; j++ { 338 go func(name string) { 339 defer wg.Done() 340 ips, err := r.LookupIPAddr(context.Background(), name) 341 if err != nil { 342 t.Error(err) 343 return 344 } 345 if len(ips) == 0 { 346 t.Errorf("no records for %s", name) 347 return 348 } 349 }(tt.name) 350 } 351 wg.Wait() 352 } 353 servers := conf.servers() 354 if !reflect.DeepEqual(servers, tt.servers) { 355 t.Errorf("#%d: got %v; want %v", i, servers, tt.servers) 356 continue 357 } 358 } 359 } 360 361 var goLookupIPWithResolverConfigTests = []struct { 362 name string 363 lines []string // resolver configuration lines 364 error 365 a, aaaa bool // whether response contains A, AAAA-record 366 }{ 367 // no records, transport timeout 368 { 369 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", 370 []string{ 371 "options timeout:1 attempts:1", 372 "nameserver 255.255.255.255", // please forgive us for abuse of limited broadcast address 373 }, 374 &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "255.255.255.255:53", IsTimeout: true}, 375 false, false, 376 }, 377 378 // no records, non-existent domain 379 { 380 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", 381 []string{ 382 "options timeout:3 attempts:1", 383 "nameserver 8.8.8.8", 384 }, 385 &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "8.8.8.8:53", IsTimeout: false}, 386 false, false, 387 }, 388 389 // a few A records, no AAAA records 390 { 391 "ipv4.google.com.", 392 []string{ 393 "nameserver 8.8.8.8", 394 "nameserver 2001:4860:4860::8888", 395 }, 396 nil, 397 true, false, 398 }, 399 { 400 "ipv4.google.com", 401 []string{ 402 "domain golang.org", 403 "nameserver 2001:4860:4860::8888", 404 "nameserver 8.8.8.8", 405 }, 406 nil, 407 true, false, 408 }, 409 { 410 "ipv4.google.com", 411 []string{ 412 "search x.golang.org y.golang.org", 413 "nameserver 2001:4860:4860::8888", 414 "nameserver 8.8.8.8", 415 }, 416 nil, 417 true, false, 418 }, 419 420 // no A records, a few AAAA records 421 { 422 "ipv6.google.com.", 423 []string{ 424 "nameserver 2001:4860:4860::8888", 425 "nameserver 8.8.8.8", 426 }, 427 nil, 428 false, true, 429 }, 430 { 431 "ipv6.google.com", 432 []string{ 433 "domain golang.org", 434 "nameserver 8.8.8.8", 435 "nameserver 2001:4860:4860::8888", 436 }, 437 nil, 438 false, true, 439 }, 440 { 441 "ipv6.google.com", 442 []string{ 443 "search x.golang.org y.golang.org", 444 "nameserver 8.8.8.8", 445 "nameserver 2001:4860:4860::8888", 446 }, 447 nil, 448 false, true, 449 }, 450 451 // both A and AAAA records 452 { 453 "hostname.as112.net", // see RFC 7534 454 []string{ 455 "domain golang.org", 456 "nameserver 2001:4860:4860::8888", 457 "nameserver 8.8.8.8", 458 }, 459 nil, 460 true, true, 461 }, 462 { 463 "hostname.as112.net", // see RFC 7534 464 []string{ 465 "search x.golang.org y.golang.org", 466 "nameserver 2001:4860:4860::8888", 467 "nameserver 8.8.8.8", 468 }, 469 nil, 470 true, true, 471 }, 472 } 473 474 func TestGoLookupIPWithResolverConfig(t *testing.T) { 475 defer dnsWaitGroup.Wait() 476 fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 477 switch s { 478 case "[2001:4860:4860::8888]:53", "8.8.8.8:53": 479 break 480 default: 481 time.Sleep(10 * time.Millisecond) 482 return dnsmessage.Message{}, os.ErrDeadlineExceeded 483 } 484 r := dnsmessage.Message{ 485 Header: dnsmessage.Header{ 486 ID: q.ID, 487 Response: true, 488 }, 489 Questions: q.Questions, 490 } 491 for _, question := range q.Questions { 492 switch question.Type { 493 case dnsmessage.TypeA: 494 switch question.Name.String() { 495 case "hostname.as112.net.": 496 break 497 case "ipv4.google.com.": 498 r.Answers = append(r.Answers, dnsmessage.Resource{ 499 Header: dnsmessage.ResourceHeader{ 500 Name: q.Questions[0].Name, 501 Type: dnsmessage.TypeA, 502 Class: dnsmessage.ClassINET, 503 Length: 4, 504 }, 505 Body: &dnsmessage.AResource{ 506 A: TestAddr, 507 }, 508 }) 509 default: 510 511 } 512 case dnsmessage.TypeAAAA: 513 switch question.Name.String() { 514 case "hostname.as112.net.": 515 break 516 case "ipv6.google.com.": 517 r.Answers = append(r.Answers, dnsmessage.Resource{ 518 Header: dnsmessage.ResourceHeader{ 519 Name: q.Questions[0].Name, 520 Type: dnsmessage.TypeAAAA, 521 Class: dnsmessage.ClassINET, 522 Length: 16, 523 }, 524 Body: &dnsmessage.AAAAResource{ 525 AAAA: TestAddr6, 526 }, 527 }) 528 } 529 } 530 } 531 return r, nil 532 }} 533 r := Resolver{PreferGo: true, Dial: fake.DialContext} 534 535 conf, err := newResolvConfTest() 536 if err != nil { 537 t.Fatal(err) 538 } 539 defer conf.teardown() 540 541 for _, tt := range goLookupIPWithResolverConfigTests { 542 if err := conf.writeAndUpdate(tt.lines); err != nil { 543 t.Error(err) 544 continue 545 } 546 addrs, err := r.LookupIPAddr(context.Background(), tt.name) 547 if err != nil { 548 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) { 549 t.Errorf("got %v; want %v", err, tt.error) 550 } 551 continue 552 } 553 if len(addrs) == 0 { 554 t.Errorf("no records for %s", tt.name) 555 } 556 if !tt.a && !tt.aaaa && len(addrs) > 0 { 557 t.Errorf("unexpected %v for %s", addrs, tt.name) 558 } 559 for _, addr := range addrs { 560 if !tt.a && addr.IP.To4() != nil { 561 t.Errorf("got %v; must not be IPv4 address", addr) 562 } 563 if !tt.aaaa && addr.IP.To16() != nil && addr.IP.To4() == nil { 564 t.Errorf("got %v; must not be IPv6 address", addr) 565 } 566 } 567 } 568 } 569 570 // Test that goLookupIPOrder falls back to the host file when no DNS servers are available. 571 func TestGoLookupIPOrderFallbackToFile(t *testing.T) { 572 defer dnsWaitGroup.Wait() 573 574 fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, tm time.Time) (dnsmessage.Message, error) { 575 r := dnsmessage.Message{ 576 Header: dnsmessage.Header{ 577 ID: q.ID, 578 Response: true, 579 }, 580 Questions: q.Questions, 581 } 582 return r, nil 583 }} 584 r := Resolver{PreferGo: true, Dial: fake.DialContext} 585 586 // Add a config that simulates no dns servers being available. 587 conf, err := newResolvConfTest() 588 if err != nil { 589 t.Fatal(err) 590 } 591 defer conf.teardown() 592 593 if err := conf.writeAndUpdate([]string{}); err != nil { 594 t.Fatal(err) 595 } 596 // Redirect host file lookups. 597 defer func(orig string) { testHookHostsPath = orig }(testHookHostsPath) 598 testHookHostsPath = "testdata/hosts" 599 600 for _, order := range []hostLookupOrder{hostLookupFilesDNS, hostLookupDNSFiles} { 601 name := fmt.Sprintf("order %v", order) 602 603 // First ensure that we get an error when contacting a non-existent host. 604 _, _, err := r.goLookupIPCNAMEOrder(context.Background(), "ip", "notarealhost", order) 605 if err == nil { 606 t.Errorf("%s: expected error while looking up name not in hosts file", name) 607 continue 608 } 609 610 // Now check that we get an address when the name appears in the hosts file. 611 addrs, _, err := r.goLookupIPCNAMEOrder(context.Background(), "ip", "thor", order) // entry is in "testdata/hosts" 612 if err != nil { 613 t.Errorf("%s: expected to successfully lookup host entry", name) 614 continue 615 } 616 if len(addrs) != 1 { 617 t.Errorf("%s: expected exactly one result, but got %v", name, addrs) 618 continue 619 } 620 if got, want := addrs[0].String(), "127.1.1.1"; got != want { 621 t.Errorf("%s: address doesn't match expectation. got %v, want %v", name, got, want) 622 } 623 } 624 } 625 626 // Issue 12712. 627 // When using search domains, return the error encountered 628 // querying the original name instead of an error encountered 629 // querying a generated name. 630 func TestErrorForOriginalNameWhenSearching(t *testing.T) { 631 defer dnsWaitGroup.Wait() 632 633 const fqdn = "doesnotexist.domain" 634 635 conf, err := newResolvConfTest() 636 if err != nil { 637 t.Fatal(err) 638 } 639 defer conf.teardown() 640 641 if err := conf.writeAndUpdate([]string{"search servfail"}); err != nil { 642 t.Fatal(err) 643 } 644 645 fake := fakeDNSServer{rh: func(_, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 646 r := dnsmessage.Message{ 647 Header: dnsmessage.Header{ 648 ID: q.ID, 649 Response: true, 650 }, 651 Questions: q.Questions, 652 } 653 654 switch q.Questions[0].Name.String() { 655 case fqdn + ".servfail.": 656 r.Header.RCode = dnsmessage.RCodeServerFailure 657 default: 658 r.Header.RCode = dnsmessage.RCodeNameError 659 } 660 661 return r, nil 662 }} 663 664 cases := []struct { 665 strictErrors bool 666 wantErr *DNSError 667 }{ 668 {true, &DNSError{Name: fqdn, Err: "server misbehaving", IsTemporary: true}}, 669 {false, &DNSError{Name: fqdn, Err: errNoSuchHost.Error(), IsNotFound: true}}, 670 } 671 for _, tt := range cases { 672 r := Resolver{PreferGo: true, StrictErrors: tt.strictErrors, Dial: fake.DialContext} 673 _, err = r.LookupIPAddr(context.Background(), fqdn) 674 if err == nil { 675 t.Fatal("expected an error") 676 } 677 678 want := tt.wantErr 679 if err, ok := err.(*DNSError); !ok || err.Name != want.Name || err.Err != want.Err || err.IsTemporary != want.IsTemporary { 680 t.Errorf("got %v; want %v", err, want) 681 } 682 } 683 } 684 685 // Issue 15434. If a name server gives a lame referral, continue to the next. 686 func TestIgnoreLameReferrals(t *testing.T) { 687 defer dnsWaitGroup.Wait() 688 689 conf, err := newResolvConfTest() 690 if err != nil { 691 t.Fatal(err) 692 } 693 defer conf.teardown() 694 695 if err := conf.writeAndUpdate([]string{"nameserver 192.0.2.1", // the one that will give a lame referral 696 "nameserver 192.0.2.2"}); err != nil { 697 t.Fatal(err) 698 } 699 700 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 701 t.Log(s, q) 702 r := dnsmessage.Message{ 703 Header: dnsmessage.Header{ 704 ID: q.ID, 705 Response: true, 706 }, 707 Questions: q.Questions, 708 } 709 710 if s == "192.0.2.2:53" { 711 r.Header.RecursionAvailable = true 712 if q.Questions[0].Type == dnsmessage.TypeA { 713 r.Answers = []dnsmessage.Resource{ 714 { 715 Header: dnsmessage.ResourceHeader{ 716 Name: q.Questions[0].Name, 717 Type: dnsmessage.TypeA, 718 Class: dnsmessage.ClassINET, 719 Length: 4, 720 }, 721 Body: &dnsmessage.AResource{ 722 A: TestAddr, 723 }, 724 }, 725 } 726 } 727 } 728 729 return r, nil 730 }} 731 r := Resolver{PreferGo: true, Dial: fake.DialContext} 732 733 addrs, err := r.LookupIPAddr(context.Background(), "www.golang.org") 734 if err != nil { 735 t.Fatal(err) 736 } 737 738 if got := len(addrs); got != 1 { 739 t.Fatalf("got %d addresses, want 1", got) 740 } 741 742 if got, want := addrs[0].String(), "192.0.2.1"; got != want { 743 t.Fatalf("got address %v, want %v", got, want) 744 } 745 } 746 747 func BenchmarkGoLookupIP(b *testing.B) { 748 testHookUninstaller.Do(uninstallTestHooks) 749 ctx := context.Background() 750 b.ReportAllocs() 751 752 for i := 0; i < b.N; i++ { 753 goResolver.LookupIPAddr(ctx, "www.example.com") 754 } 755 } 756 757 func BenchmarkGoLookupIPNoSuchHost(b *testing.B) { 758 testHookUninstaller.Do(uninstallTestHooks) 759 ctx := context.Background() 760 b.ReportAllocs() 761 762 for i := 0; i < b.N; i++ { 763 goResolver.LookupIPAddr(ctx, "some.nonexistent") 764 } 765 } 766 767 func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) { 768 testHookUninstaller.Do(uninstallTestHooks) 769 770 conf, err := newResolvConfTest() 771 if err != nil { 772 b.Fatal(err) 773 } 774 defer conf.teardown() 775 776 lines := []string{ 777 "nameserver 203.0.113.254", // use TEST-NET-3 block, see RFC 5737 778 "nameserver 8.8.8.8", 779 } 780 if err := conf.writeAndUpdate(lines); err != nil { 781 b.Fatal(err) 782 } 783 ctx := context.Background() 784 b.ReportAllocs() 785 786 for i := 0; i < b.N; i++ { 787 goResolver.LookupIPAddr(ctx, "www.example.com") 788 } 789 } 790 791 type fakeDNSServer struct { 792 rh func(n, s string, q dnsmessage.Message, t time.Time) (dnsmessage.Message, error) 793 alwaysTCP bool 794 } 795 796 func (server *fakeDNSServer) DialContext(_ context.Context, n, s string) (Conn, error) { 797 if server.alwaysTCP || n == "tcp" || n == "tcp4" || n == "tcp6" { 798 return &fakeDNSConn{tcp: true, server: server, n: n, s: s}, nil 799 } 800 return &fakeDNSPacketConn{fakeDNSConn: fakeDNSConn{tcp: false, server: server, n: n, s: s}}, nil 801 } 802 803 type fakeDNSConn struct { 804 Conn 805 tcp bool 806 server *fakeDNSServer 807 n string 808 s string 809 q dnsmessage.Message 810 t time.Time 811 buf []byte 812 } 813 814 func (f *fakeDNSConn) Close() error { 815 return nil 816 } 817 818 func (f *fakeDNSConn) Read(b []byte) (int, error) { 819 if len(f.buf) > 0 { 820 n := copy(b, f.buf) 821 f.buf = f.buf[n:] 822 return n, nil 823 } 824 825 resp, err := f.server.rh(f.n, f.s, f.q, f.t) 826 if err != nil { 827 return 0, err 828 } 829 830 bb := make([]byte, 2, 514) 831 bb, err = resp.AppendPack(bb) 832 if err != nil { 833 return 0, fmt.Errorf("cannot marshal DNS message: %v", err) 834 } 835 836 if f.tcp { 837 l := len(bb) - 2 838 bb[0] = byte(l >> 8) 839 bb[1] = byte(l) 840 f.buf = bb 841 return f.Read(b) 842 } 843 844 bb = bb[2:] 845 if len(b) < len(bb) { 846 return 0, errors.New("read would fragment DNS message") 847 } 848 849 copy(b, bb) 850 return len(bb), nil 851 } 852 853 func (f *fakeDNSConn) Write(b []byte) (int, error) { 854 if f.tcp && len(b) >= 2 { 855 b = b[2:] 856 } 857 if f.q.Unpack(b) != nil { 858 return 0, fmt.Errorf("cannot unmarshal DNS message fake %s (%d)", f.n, len(b)) 859 } 860 return len(b), nil 861 } 862 863 func (f *fakeDNSConn) SetDeadline(t time.Time) error { 864 f.t = t 865 return nil 866 } 867 868 type fakeDNSPacketConn struct { 869 PacketConn 870 fakeDNSConn 871 } 872 873 func (f *fakeDNSPacketConn) SetDeadline(t time.Time) error { 874 return f.fakeDNSConn.SetDeadline(t) 875 } 876 877 func (f *fakeDNSPacketConn) Close() error { 878 return f.fakeDNSConn.Close() 879 } 880 881 // UDP round-tripper algorithm should ignore invalid DNS responses (issue 13281). 882 func TestIgnoreDNSForgeries(t *testing.T) { 883 c, s := Pipe() 884 go func() { 885 b := make([]byte, 512) 886 n, err := s.Read(b) 887 if err != nil { 888 t.Error(err) 889 return 890 } 891 892 var msg dnsmessage.Message 893 if msg.Unpack(b[:n]) != nil { 894 t.Error("invalid DNS query:", err) 895 return 896 } 897 898 s.Write([]byte("garbage DNS response packet")) 899 900 msg.Header.Response = true 901 msg.Header.ID++ // make invalid ID 902 903 if b, err = msg.Pack(); err != nil { 904 t.Error("failed to pack DNS response:", err) 905 return 906 } 907 s.Write(b) 908 909 msg.Header.ID-- // restore original ID 910 msg.Answers = []dnsmessage.Resource{ 911 { 912 Header: dnsmessage.ResourceHeader{ 913 Name: mustNewName("www.example.com."), 914 Type: dnsmessage.TypeA, 915 Class: dnsmessage.ClassINET, 916 Length: 4, 917 }, 918 Body: &dnsmessage.AResource{ 919 A: TestAddr, 920 }, 921 }, 922 } 923 924 b, err = msg.Pack() 925 if err != nil { 926 t.Error("failed to pack DNS response:", err) 927 return 928 } 929 s.Write(b) 930 }() 931 932 msg := dnsmessage.Message{ 933 Header: dnsmessage.Header{ 934 ID: 42, 935 }, 936 Questions: []dnsmessage.Question{ 937 { 938 Name: mustNewName("www.example.com."), 939 Type: dnsmessage.TypeA, 940 Class: dnsmessage.ClassINET, 941 }, 942 }, 943 } 944 945 b, err := msg.Pack() 946 if err != nil { 947 t.Fatal("Pack failed:", err) 948 } 949 950 p, _, err := dnsPacketRoundTrip(c, 42, msg.Questions[0], b) 951 if err != nil { 952 t.Fatalf("dnsPacketRoundTrip failed: %v", err) 953 } 954 955 p.SkipAllQuestions() 956 as, err := p.AllAnswers() 957 if err != nil { 958 t.Fatal("AllAnswers failed:", err) 959 } 960 if got := as[0].Body.(*dnsmessage.AResource).A; got != TestAddr { 961 t.Errorf("got address %v, want %v", got, TestAddr) 962 } 963 } 964 965 // Issue 16865. If a name server times out, continue to the next. 966 func TestRetryTimeout(t *testing.T) { 967 defer dnsWaitGroup.Wait() 968 969 conf, err := newResolvConfTest() 970 if err != nil { 971 t.Fatal(err) 972 } 973 defer conf.teardown() 974 975 testConf := []string{ 976 "nameserver 192.0.2.1", // the one that will timeout 977 "nameserver 192.0.2.2", 978 } 979 if err := conf.writeAndUpdate(testConf); err != nil { 980 t.Fatal(err) 981 } 982 983 var deadline0 time.Time 984 985 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) { 986 t.Log(s, q, deadline) 987 988 if deadline.IsZero() { 989 t.Error("zero deadline") 990 } 991 992 if s == "192.0.2.1:53" { 993 deadline0 = deadline 994 time.Sleep(10 * time.Millisecond) 995 return dnsmessage.Message{}, os.ErrDeadlineExceeded 996 } 997 998 if deadline.Equal(deadline0) { 999 t.Error("deadline didn't change") 1000 } 1001 1002 return mockTXTResponse(q), nil 1003 }} 1004 r := &Resolver{PreferGo: true, Dial: fake.DialContext} 1005 1006 _, err = r.LookupTXT(context.Background(), "www.golang.org") 1007 if err != nil { 1008 t.Fatal(err) 1009 } 1010 1011 if deadline0.IsZero() { 1012 t.Error("deadline0 still zero", deadline0) 1013 } 1014 } 1015 1016 func TestRotate(t *testing.T) { 1017 // without rotation, always uses the first server 1018 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"}) 1019 1020 // with rotation, rotates through back to first 1021 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"}) 1022 } 1023 1024 func testRotate(t *testing.T, rotate bool, nameservers, wantServers []string) { 1025 defer dnsWaitGroup.Wait() 1026 1027 conf, err := newResolvConfTest() 1028 if err != nil { 1029 t.Fatal(err) 1030 } 1031 defer conf.teardown() 1032 1033 var confLines []string 1034 for _, ns := range nameservers { 1035 confLines = append(confLines, "nameserver "+ns) 1036 } 1037 if rotate { 1038 confLines = append(confLines, "options rotate") 1039 } 1040 1041 if err := conf.writeAndUpdate(confLines); err != nil { 1042 t.Fatal(err) 1043 } 1044 1045 var usedServers []string 1046 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) { 1047 usedServers = append(usedServers, s) 1048 return mockTXTResponse(q), nil 1049 }} 1050 r := Resolver{PreferGo: true, Dial: fake.DialContext} 1051 1052 // len(nameservers) + 1 to allow rotation to get back to start 1053 for i := 0; i < len(nameservers)+1; i++ { 1054 if _, err := r.LookupTXT(context.Background(), "www.golang.org"); err != nil { 1055 t.Fatal(err) 1056 } 1057 } 1058 1059 if !reflect.DeepEqual(usedServers, wantServers) { 1060 t.Errorf("rotate=%t got used servers:\n%v\nwant:\n%v", rotate, usedServers, wantServers) 1061 } 1062 } 1063 1064 func mockTXTResponse(q dnsmessage.Message) dnsmessage.Message { 1065 r := dnsmessage.Message{ 1066 Header: dnsmessage.Header{ 1067 ID: q.ID, 1068 Response: true, 1069 RecursionAvailable: true, 1070 }, 1071 Questions: q.Questions, 1072 Answers: []dnsmessage.Resource{ 1073 { 1074 Header: dnsmessage.ResourceHeader{ 1075 Name: q.Questions[0].Name, 1076 Type: dnsmessage.TypeTXT, 1077 Class: dnsmessage.ClassINET, 1078 }, 1079 Body: &dnsmessage.TXTResource{ 1080 TXT: []string{"ok"}, 1081 }, 1082 }, 1083 }, 1084 } 1085 1086 return r 1087 } 1088 1089 // Issue 17448. With StrictErrors enabled, temporary errors should make 1090 // LookupIP fail rather than return a partial result. 1091 func TestStrictErrorsLookupIP(t *testing.T) { 1092 defer dnsWaitGroup.Wait() 1093 1094 conf, err := newResolvConfTest() 1095 if err != nil { 1096 t.Fatal(err) 1097 } 1098 defer conf.teardown() 1099 1100 confData := []string{ 1101 "nameserver 192.0.2.53", 1102 "search x.golang.org y.golang.org", 1103 } 1104 if err := conf.writeAndUpdate(confData); err != nil { 1105 t.Fatal(err) 1106 } 1107 1108 const name = "test-issue19592" 1109 const server = "192.0.2.53:53" 1110 const searchX = "test-issue19592.x.golang.org." 1111 const searchY = "test-issue19592.y.golang.org." 1112 const ip4 = "192.0.2.1" 1113 const ip6 = "2001:db8::1" 1114 1115 type resolveWhichEnum int 1116 const ( 1117 resolveOK resolveWhichEnum = iota 1118 resolveOpError 1119 resolveServfail 1120 resolveTimeout 1121 ) 1122 1123 makeTempError := func(err string) error { 1124 return &DNSError{ 1125 Err: err, 1126 Name: name, 1127 Server: server, 1128 IsTemporary: true, 1129 } 1130 } 1131 makeTimeout := func() error { 1132 return &DNSError{ 1133 Err: os.ErrDeadlineExceeded.Error(), 1134 Name: name, 1135 Server: server, 1136 IsTimeout: true, 1137 } 1138 } 1139 makeNxDomain := func() error { 1140 return &DNSError{ 1141 Err: errNoSuchHost.Error(), 1142 Name: name, 1143 Server: server, 1144 IsNotFound: true, 1145 } 1146 } 1147 1148 cases := []struct { 1149 desc string 1150 resolveWhich func(quest dnsmessage.Question) resolveWhichEnum 1151 wantStrictErr error 1152 wantLaxErr error 1153 wantIPs []string 1154 }{ 1155 { 1156 desc: "No errors", 1157 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum { 1158 return resolveOK 1159 }, 1160 wantIPs: []string{ip4, ip6}, 1161 }, 1162 { 1163 desc: "searchX error fails in strict mode", 1164 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum { 1165 if quest.Name.String() == searchX { 1166 return resolveTimeout 1167 } 1168 return resolveOK 1169 }, 1170 wantStrictErr: makeTimeout(), 1171 wantIPs: []string{ip4, ip6}, 1172 }, 1173 { 1174 desc: "searchX IPv4-only timeout fails in strict mode", 1175 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum { 1176 if quest.Name.String() == searchX && quest.Type == dnsmessage.TypeA { 1177 return resolveTimeout 1178 } 1179 return resolveOK 1180 }, 1181 wantStrictErr: makeTimeout(), 1182 wantIPs: []string{ip4, ip6}, 1183 }, 1184 { 1185 desc: "searchX IPv6-only servfail fails in strict mode", 1186 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum { 1187 if quest.Name.String() == searchX && quest.Type == dnsmessage.TypeAAAA { 1188 return resolveServfail 1189 } 1190 return resolveOK 1191 }, 1192 wantStrictErr: makeTempError("server misbehaving"), 1193 wantIPs: []string{ip4, ip6}, 1194 }, 1195 { 1196 desc: "searchY error always fails", 1197 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum { 1198 if quest.Name.String() == searchY { 1199 return resolveTimeout 1200 } 1201 return resolveOK 1202 }, 1203 wantStrictErr: makeTimeout(), 1204 wantLaxErr: makeNxDomain(), // This one reaches the "test." FQDN. 1205 }, 1206 { 1207 desc: "searchY IPv4-only socket error fails in strict mode", 1208 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum { 1209 if quest.Name.String() == searchY && quest.Type == dnsmessage.TypeA { 1210 return resolveOpError 1211 } 1212 return resolveOK 1213 }, 1214 wantStrictErr: makeTempError("write: socket on fire"), 1215 wantIPs: []string{ip6}, 1216 }, 1217 { 1218 desc: "searchY IPv6-only timeout fails in strict mode", 1219 resolveWhich: func(quest dnsmessage.Question) resolveWhichEnum { 1220 if quest.Name.String() == searchY && quest.Type == dnsmessage.TypeAAAA { 1221 return resolveTimeout 1222 } 1223 return resolveOK 1224 }, 1225 wantStrictErr: makeTimeout(), 1226 wantIPs: []string{ip4}, 1227 }, 1228 } 1229 1230 for i, tt := range cases { 1231 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) { 1232 t.Log(s, q) 1233 1234 switch tt.resolveWhich(q.Questions[0]) { 1235 case resolveOK: 1236 // Handle below. 1237 case resolveOpError: 1238 return dnsmessage.Message{}, &OpError{Op: "write", Err: fmt.Errorf("socket on fire")} 1239 case resolveServfail: 1240 return dnsmessage.Message{ 1241 Header: dnsmessage.Header{ 1242 ID: q.ID, 1243 Response: true, 1244 RCode: dnsmessage.RCodeServerFailure, 1245 }, 1246 Questions: q.Questions, 1247 }, nil 1248 case resolveTimeout: 1249 return dnsmessage.Message{}, os.ErrDeadlineExceeded 1250 default: 1251 t.Fatal("Impossible resolveWhich") 1252 } 1253 1254 switch q.Questions[0].Name.String() { 1255 case searchX, name + ".": 1256 // Return NXDOMAIN to utilize the search list. 1257 return dnsmessage.Message{ 1258 Header: dnsmessage.Header{ 1259 ID: q.ID, 1260 Response: true, 1261 RCode: dnsmessage.RCodeNameError, 1262 }, 1263 Questions: q.Questions, 1264 }, nil 1265 case searchY: 1266 // Return records below. 1267 default: 1268 return dnsmessage.Message{}, fmt.Errorf("Unexpected Name: %v", q.Questions[0].Name) 1269 } 1270 1271 r := dnsmessage.Message{ 1272 Header: dnsmessage.Header{ 1273 ID: q.ID, 1274 Response: true, 1275 }, 1276 Questions: q.Questions, 1277 } 1278 switch q.Questions[0].Type { 1279 case dnsmessage.TypeA: 1280 r.Answers = []dnsmessage.Resource{ 1281 { 1282 Header: dnsmessage.ResourceHeader{ 1283 Name: q.Questions[0].Name, 1284 Type: dnsmessage.TypeA, 1285 Class: dnsmessage.ClassINET, 1286 Length: 4, 1287 }, 1288 Body: &dnsmessage.AResource{ 1289 A: TestAddr, 1290 }, 1291 }, 1292 } 1293 case dnsmessage.TypeAAAA: 1294 r.Answers = []dnsmessage.Resource{ 1295 { 1296 Header: dnsmessage.ResourceHeader{ 1297 Name: q.Questions[0].Name, 1298 Type: dnsmessage.TypeAAAA, 1299 Class: dnsmessage.ClassINET, 1300 Length: 16, 1301 }, 1302 Body: &dnsmessage.AAAAResource{ 1303 AAAA: TestAddr6, 1304 }, 1305 }, 1306 } 1307 default: 1308 return dnsmessage.Message{}, fmt.Errorf("Unexpected Type: %v", q.Questions[0].Type) 1309 } 1310 return r, nil 1311 }} 1312 1313 for _, strict := range []bool{true, false} { 1314 r := Resolver{PreferGo: true, StrictErrors: strict, Dial: fake.DialContext} 1315 ips, err := r.LookupIPAddr(context.Background(), name) 1316 1317 var wantErr error 1318 if strict { 1319 wantErr = tt.wantStrictErr 1320 } else { 1321 wantErr = tt.wantLaxErr 1322 } 1323 if !reflect.DeepEqual(err, wantErr) { 1324 t.Errorf("#%d (%s) strict=%v: got err %#v; want %#v", i, tt.desc, strict, err, wantErr) 1325 } 1326 1327 gotIPs := map[string]struct{}{} 1328 for _, ip := range ips { 1329 gotIPs[ip.String()] = struct{}{} 1330 } 1331 wantIPs := map[string]struct{}{} 1332 if wantErr == nil { 1333 for _, ip := range tt.wantIPs { 1334 wantIPs[ip] = struct{}{} 1335 } 1336 } 1337 if !reflect.DeepEqual(gotIPs, wantIPs) { 1338 t.Errorf("#%d (%s) strict=%v: got ips %v; want %v", i, tt.desc, strict, gotIPs, wantIPs) 1339 } 1340 } 1341 } 1342 } 1343 1344 // Issue 17448. With StrictErrors enabled, temporary errors should make 1345 // LookupTXT stop walking the search list. 1346 func TestStrictErrorsLookupTXT(t *testing.T) { 1347 defer dnsWaitGroup.Wait() 1348 1349 conf, err := newResolvConfTest() 1350 if err != nil { 1351 t.Fatal(err) 1352 } 1353 defer conf.teardown() 1354 1355 confData := []string{ 1356 "nameserver 192.0.2.53", 1357 "search x.golang.org y.golang.org", 1358 } 1359 if err := conf.writeAndUpdate(confData); err != nil { 1360 t.Fatal(err) 1361 } 1362 1363 const name = "test" 1364 const server = "192.0.2.53:53" 1365 const searchX = "test.x.golang.org." 1366 const searchY = "test.y.golang.org." 1367 const txt = "Hello World" 1368 1369 fake := fakeDNSServer{rh: func(_, s string, q dnsmessage.Message, deadline time.Time) (dnsmessage.Message, error) { 1370 t.Log(s, q) 1371 1372 switch q.Questions[0].Name.String() { 1373 case searchX: 1374 return dnsmessage.Message{}, os.ErrDeadlineExceeded 1375 case searchY: 1376 return mockTXTResponse(q), nil 1377 default: 1378 return dnsmessage.Message{}, fmt.Errorf("Unexpected Name: %v", q.Questions[0].Name) 1379 } 1380 }} 1381 1382 for _, strict := range []bool{true, false} { 1383 r := Resolver{StrictErrors: strict, Dial: fake.DialContext} 1384 p, _, err := r.lookup(context.Background(), name, dnsmessage.TypeTXT) 1385 var wantErr error 1386 var wantRRs int 1387 if strict { 1388 wantErr = &DNSError{ 1389 Err: os.ErrDeadlineExceeded.Error(), 1390 Name: name, 1391 Server: server, 1392 IsTimeout: true, 1393 } 1394 } else { 1395 wantRRs = 1 1396 } 1397 if !reflect.DeepEqual(err, wantErr) { 1398 t.Errorf("strict=%v: got err %#v; want %#v", strict, err, wantErr) 1399 } 1400 a, err := p.AllAnswers() 1401 if err != nil { 1402 a = nil 1403 } 1404 if len(a) != wantRRs { 1405 t.Errorf("strict=%v: got %v; want %v", strict, len(a), wantRRs) 1406 } 1407 } 1408 } 1409 1410 // Test for a race between uninstalling the test hooks and closing a 1411 // socket connection. This used to fail when testing with -race. 1412 func TestDNSGoroutineRace(t *testing.T) { 1413 defer dnsWaitGroup.Wait() 1414 1415 fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, t time.Time) (dnsmessage.Message, error) { 1416 time.Sleep(10 * time.Microsecond) 1417 return dnsmessage.Message{}, os.ErrDeadlineExceeded 1418 }} 1419 r := Resolver{PreferGo: true, Dial: fake.DialContext} 1420 1421 // The timeout here is less than the timeout used by the server, 1422 // so the goroutine started to query the (fake) server will hang 1423 // around after this test is done if we don't call dnsWaitGroup.Wait. 1424 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Microsecond) 1425 defer cancel() 1426 _, err := r.LookupIPAddr(ctx, "where.are.they.now") 1427 if err == nil { 1428 t.Fatal("fake DNS lookup unexpectedly succeeded") 1429 } 1430 } 1431 1432 func lookupWithFake(fake fakeDNSServer, name string, typ dnsmessage.Type) error { 1433 r := Resolver{PreferGo: true, Dial: fake.DialContext} 1434 1435 resolvConf.mu.RLock() 1436 conf := resolvConf.dnsConfig 1437 resolvConf.mu.RUnlock() 1438 1439 ctx, cancel := context.WithCancel(context.Background()) 1440 defer cancel() 1441 1442 _, _, err := r.tryOneName(ctx, conf, name, typ) 1443 return err 1444 } 1445 1446 // Issue 8434: verify that Temporary returns true on an error when rcode 1447 // is SERVFAIL 1448 func TestIssue8434(t *testing.T) { 1449 err := lookupWithFake(fakeDNSServer{ 1450 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 1451 return dnsmessage.Message{ 1452 Header: dnsmessage.Header{ 1453 ID: q.ID, 1454 Response: true, 1455 RCode: dnsmessage.RCodeServerFailure, 1456 }, 1457 Questions: q.Questions, 1458 }, nil 1459 }, 1460 }, "golang.org.", dnsmessage.TypeALL) 1461 if err == nil { 1462 t.Fatal("expected an error") 1463 } 1464 if ne, ok := err.(Error); !ok { 1465 t.Fatalf("err = %#v; wanted something supporting net.Error", err) 1466 } else if !ne.Temporary() { 1467 t.Fatalf("Temporary = false for err = %#v; want Temporary == true", err) 1468 } 1469 if de, ok := err.(*DNSError); !ok { 1470 t.Fatalf("err = %#v; wanted a *net.DNSError", err) 1471 } else if !de.IsTemporary { 1472 t.Fatalf("IsTemporary = false for err = %#v; want IsTemporary == true", err) 1473 } 1474 } 1475 1476 func TestIssueNoSuchHostExists(t *testing.T) { 1477 err := lookupWithFake(fakeDNSServer{ 1478 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 1479 return dnsmessage.Message{ 1480 Header: dnsmessage.Header{ 1481 ID: q.ID, 1482 Response: true, 1483 RCode: dnsmessage.RCodeNameError, 1484 }, 1485 Questions: q.Questions, 1486 }, nil 1487 }, 1488 }, "golang.org.", dnsmessage.TypeALL) 1489 if err == nil { 1490 t.Fatal("expected an error") 1491 } 1492 if _, ok := err.(Error); !ok { 1493 t.Fatalf("err = %#v; wanted something supporting net.Error", err) 1494 } 1495 if de, ok := err.(*DNSError); !ok { 1496 t.Fatalf("err = %#v; wanted a *net.DNSError", err) 1497 } else if !de.IsNotFound { 1498 t.Fatalf("IsNotFound = false for err = %#v; want IsNotFound == true", err) 1499 } 1500 } 1501 1502 // TestNoSuchHost verifies that tryOneName works correctly when the domain does 1503 // not exist. 1504 // 1505 // Issue 12778: verify that NXDOMAIN without RA bit errors as "no such host" 1506 // and not "server misbehaving" 1507 // 1508 // Issue 25336: verify that NXDOMAIN errors fail fast. 1509 // 1510 // Issue 27525: verify that empty answers fail fast. 1511 func TestNoSuchHost(t *testing.T) { 1512 tests := []struct { 1513 name string 1514 f func(string, string, dnsmessage.Message, time.Time) (dnsmessage.Message, error) 1515 }{ 1516 { 1517 "NXDOMAIN", 1518 func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 1519 return dnsmessage.Message{ 1520 Header: dnsmessage.Header{ 1521 ID: q.ID, 1522 Response: true, 1523 RCode: dnsmessage.RCodeNameError, 1524 RecursionAvailable: false, 1525 }, 1526 Questions: q.Questions, 1527 }, nil 1528 }, 1529 }, 1530 { 1531 "no answers", 1532 func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 1533 return dnsmessage.Message{ 1534 Header: dnsmessage.Header{ 1535 ID: q.ID, 1536 Response: true, 1537 RCode: dnsmessage.RCodeSuccess, 1538 RecursionAvailable: false, 1539 Authoritative: true, 1540 }, 1541 Questions: q.Questions, 1542 }, nil 1543 }, 1544 }, 1545 } 1546 1547 for _, test := range tests { 1548 t.Run(test.name, func(t *testing.T) { 1549 lookups := 0 1550 err := lookupWithFake(fakeDNSServer{ 1551 rh: func(n, s string, q dnsmessage.Message, d time.Time) (dnsmessage.Message, error) { 1552 lookups++ 1553 return test.f(n, s, q, d) 1554 }, 1555 }, ".", dnsmessage.TypeALL) 1556 1557 if lookups != 1 { 1558 t.Errorf("got %d lookups, wanted 1", lookups) 1559 } 1560 1561 if err == nil { 1562 t.Fatal("expected an error") 1563 } 1564 de, ok := err.(*DNSError) 1565 if !ok { 1566 t.Fatalf("err = %#v; wanted a *net.DNSError", err) 1567 } 1568 if de.Err != errNoSuchHost.Error() { 1569 t.Fatalf("Err = %#v; wanted %q", de.Err, errNoSuchHost.Error()) 1570 } 1571 if !de.IsNotFound { 1572 t.Fatalf("IsNotFound = %v wanted true", de.IsNotFound) 1573 } 1574 }) 1575 } 1576 } 1577 1578 // Issue 26573: verify that Conns that don't implement PacketConn are treated 1579 // as streams even when udp was requested. 1580 func TestDNSDialTCP(t *testing.T) { 1581 fake := fakeDNSServer{ 1582 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 1583 r := dnsmessage.Message{ 1584 Header: dnsmessage.Header{ 1585 ID: q.Header.ID, 1586 Response: true, 1587 RCode: dnsmessage.RCodeSuccess, 1588 }, 1589 Questions: q.Questions, 1590 } 1591 return r, nil 1592 }, 1593 alwaysTCP: true, 1594 } 1595 r := Resolver{PreferGo: true, Dial: fake.DialContext} 1596 ctx := context.Background() 1597 _, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second, useUDPOrTCP) 1598 if err != nil { 1599 t.Fatal("exhange failed:", err) 1600 } 1601 } 1602 1603 // Issue 27763: verify that two strings in one TXT record are concatenated. 1604 func TestTXTRecordTwoStrings(t *testing.T) { 1605 fake := fakeDNSServer{ 1606 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 1607 r := dnsmessage.Message{ 1608 Header: dnsmessage.Header{ 1609 ID: q.Header.ID, 1610 Response: true, 1611 RCode: dnsmessage.RCodeSuccess, 1612 }, 1613 Questions: q.Questions, 1614 Answers: []dnsmessage.Resource{ 1615 { 1616 Header: dnsmessage.ResourceHeader{ 1617 Name: q.Questions[0].Name, 1618 Type: dnsmessage.TypeA, 1619 Class: dnsmessage.ClassINET, 1620 }, 1621 Body: &dnsmessage.TXTResource{ 1622 TXT: []string{"string1 ", "string2"}, 1623 }, 1624 }, 1625 { 1626 Header: dnsmessage.ResourceHeader{ 1627 Name: q.Questions[0].Name, 1628 Type: dnsmessage.TypeA, 1629 Class: dnsmessage.ClassINET, 1630 }, 1631 Body: &dnsmessage.TXTResource{ 1632 TXT: []string{"onestring"}, 1633 }, 1634 }, 1635 }, 1636 } 1637 return r, nil 1638 }, 1639 } 1640 r := Resolver{PreferGo: true, Dial: fake.DialContext} 1641 txt, err := r.lookupTXT(context.Background(), "golang.org") 1642 if err != nil { 1643 t.Fatal("LookupTXT failed:", err) 1644 } 1645 if want := 2; len(txt) != want { 1646 t.Fatalf("len(txt), got %d, want %d", len(txt), want) 1647 } 1648 if want := "string1 string2"; txt[0] != want { 1649 t.Errorf("txt[0], got %q, want %q", txt[0], want) 1650 } 1651 if want := "onestring"; txt[1] != want { 1652 t.Errorf("txt[1], got %q, want %q", txt[1], want) 1653 } 1654 } 1655 1656 // Issue 29644: support single-request resolv.conf option in pure Go resolver. 1657 // The A and AAAA queries will be sent sequentially, not in parallel. 1658 func TestSingleRequestLookup(t *testing.T) { 1659 defer dnsWaitGroup.Wait() 1660 var ( 1661 firstcalled int32 1662 ipv4 int32 = 1 1663 ipv6 int32 = 2 1664 ) 1665 fake := fakeDNSServer{rh: func(n, s string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 1666 r := dnsmessage.Message{ 1667 Header: dnsmessage.Header{ 1668 ID: q.ID, 1669 Response: true, 1670 }, 1671 Questions: q.Questions, 1672 } 1673 for _, question := range q.Questions { 1674 switch question.Type { 1675 case dnsmessage.TypeA: 1676 if question.Name.String() == "slowipv4.example.net." { 1677 time.Sleep(10 * time.Millisecond) 1678 } 1679 if !atomic.CompareAndSwapInt32(&firstcalled, 0, ipv4) { 1680 t.Errorf("the A query was received after the AAAA query !") 1681 } 1682 r.Answers = append(r.Answers, dnsmessage.Resource{ 1683 Header: dnsmessage.ResourceHeader{ 1684 Name: q.Questions[0].Name, 1685 Type: dnsmessage.TypeA, 1686 Class: dnsmessage.ClassINET, 1687 Length: 4, 1688 }, 1689 Body: &dnsmessage.AResource{ 1690 A: TestAddr, 1691 }, 1692 }) 1693 case dnsmessage.TypeAAAA: 1694 atomic.CompareAndSwapInt32(&firstcalled, 0, ipv6) 1695 r.Answers = append(r.Answers, dnsmessage.Resource{ 1696 Header: dnsmessage.ResourceHeader{ 1697 Name: q.Questions[0].Name, 1698 Type: dnsmessage.TypeAAAA, 1699 Class: dnsmessage.ClassINET, 1700 Length: 16, 1701 }, 1702 Body: &dnsmessage.AAAAResource{ 1703 AAAA: TestAddr6, 1704 }, 1705 }) 1706 } 1707 } 1708 return r, nil 1709 }} 1710 r := Resolver{PreferGo: true, Dial: fake.DialContext} 1711 1712 conf, err := newResolvConfTest() 1713 if err != nil { 1714 t.Fatal(err) 1715 } 1716 defer conf.teardown() 1717 if err := conf.writeAndUpdate([]string{"options single-request"}); err != nil { 1718 t.Fatal(err) 1719 } 1720 for _, name := range []string{"hostname.example.net", "slowipv4.example.net"} { 1721 firstcalled = 0 1722 _, err := r.LookupIPAddr(context.Background(), name) 1723 if err != nil { 1724 t.Error(err) 1725 } 1726 } 1727 } 1728 1729 // Issue 29358. Add configuration knob to force TCP-only DNS requests in the pure Go resolver. 1730 func TestDNSUseTCP(t *testing.T) { 1731 fake := fakeDNSServer{ 1732 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 1733 r := dnsmessage.Message{ 1734 Header: dnsmessage.Header{ 1735 ID: q.Header.ID, 1736 Response: true, 1737 RCode: dnsmessage.RCodeSuccess, 1738 }, 1739 Questions: q.Questions, 1740 } 1741 if n == "udp" { 1742 t.Fatal("udp protocol was used instead of tcp") 1743 } 1744 return r, nil 1745 }, 1746 } 1747 r := Resolver{PreferGo: true, Dial: fake.DialContext} 1748 ctx, cancel := context.WithCancel(context.Background()) 1749 defer cancel() 1750 _, _, err := r.exchange(ctx, "0.0.0.0", mustQuestion("com.", dnsmessage.TypeALL, dnsmessage.ClassINET), time.Second, useTCPOnly) 1751 if err != nil { 1752 t.Fatal("exchange failed:", err) 1753 } 1754 } 1755 1756 // Issue 34660: PTR response with non-PTR answers should ignore non-PTR 1757 func TestPTRandNonPTR(t *testing.T) { 1758 fake := fakeDNSServer{ 1759 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 1760 r := dnsmessage.Message{ 1761 Header: dnsmessage.Header{ 1762 ID: q.Header.ID, 1763 Response: true, 1764 RCode: dnsmessage.RCodeSuccess, 1765 }, 1766 Questions: q.Questions, 1767 Answers: []dnsmessage.Resource{ 1768 { 1769 Header: dnsmessage.ResourceHeader{ 1770 Name: q.Questions[0].Name, 1771 Type: dnsmessage.TypePTR, 1772 Class: dnsmessage.ClassINET, 1773 }, 1774 Body: &dnsmessage.PTRResource{ 1775 PTR: dnsmessage.MustNewName("golang.org."), 1776 }, 1777 }, 1778 { 1779 Header: dnsmessage.ResourceHeader{ 1780 Name: q.Questions[0].Name, 1781 Type: dnsmessage.TypeTXT, 1782 Class: dnsmessage.ClassINET, 1783 }, 1784 Body: &dnsmessage.TXTResource{ 1785 TXT: []string{"PTR 8 6 60 ..."}, // fake RRSIG 1786 }, 1787 }, 1788 }, 1789 } 1790 return r, nil 1791 }, 1792 } 1793 r := Resolver{PreferGo: true, Dial: fake.DialContext} 1794 names, err := r.lookupAddr(context.Background(), "192.0.2.123") 1795 if err != nil { 1796 t.Fatalf("LookupAddr: %v", err) 1797 } 1798 if want := []string{"golang.org."}; !reflect.DeepEqual(names, want) { 1799 t.Errorf("names = %q; want %q", names, want) 1800 } 1801 } 1802 1803 func TestCVE202133195(t *testing.T) { 1804 fake := fakeDNSServer{ 1805 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 1806 r := dnsmessage.Message{ 1807 Header: dnsmessage.Header{ 1808 ID: q.Header.ID, 1809 Response: true, 1810 RCode: dnsmessage.RCodeSuccess, 1811 RecursionAvailable: true, 1812 }, 1813 Questions: q.Questions, 1814 } 1815 switch q.Questions[0].Type { 1816 case dnsmessage.TypeCNAME: 1817 r.Answers = []dnsmessage.Resource{} 1818 case dnsmessage.TypeA: // CNAME lookup uses a A/AAAA as a proxy 1819 r.Answers = append(r.Answers, 1820 dnsmessage.Resource{ 1821 Header: dnsmessage.ResourceHeader{ 1822 Name: dnsmessage.MustNewName("<html>.golang.org."), 1823 Type: dnsmessage.TypeA, 1824 Class: dnsmessage.ClassINET, 1825 Length: 4, 1826 }, 1827 Body: &dnsmessage.AResource{ 1828 A: TestAddr, 1829 }, 1830 }, 1831 ) 1832 case dnsmessage.TypeSRV: 1833 n := q.Questions[0].Name 1834 if n.String() == "_hdr._tcp.golang.org." { 1835 n = dnsmessage.MustNewName("<html>.golang.org.") 1836 } 1837 r.Answers = append(r.Answers, 1838 dnsmessage.Resource{ 1839 Header: dnsmessage.ResourceHeader{ 1840 Name: n, 1841 Type: dnsmessage.TypeSRV, 1842 Class: dnsmessage.ClassINET, 1843 Length: 4, 1844 }, 1845 Body: &dnsmessage.SRVResource{ 1846 Target: dnsmessage.MustNewName("<html>.golang.org."), 1847 }, 1848 }, 1849 dnsmessage.Resource{ 1850 Header: dnsmessage.ResourceHeader{ 1851 Name: n, 1852 Type: dnsmessage.TypeSRV, 1853 Class: dnsmessage.ClassINET, 1854 Length: 4, 1855 }, 1856 Body: &dnsmessage.SRVResource{ 1857 Target: dnsmessage.MustNewName("good.golang.org."), 1858 }, 1859 }, 1860 ) 1861 case dnsmessage.TypeMX: 1862 r.Answers = append(r.Answers, 1863 dnsmessage.Resource{ 1864 Header: dnsmessage.ResourceHeader{ 1865 Name: dnsmessage.MustNewName("<html>.golang.org."), 1866 Type: dnsmessage.TypeMX, 1867 Class: dnsmessage.ClassINET, 1868 Length: 4, 1869 }, 1870 Body: &dnsmessage.MXResource{ 1871 MX: dnsmessage.MustNewName("<html>.golang.org."), 1872 }, 1873 }, 1874 dnsmessage.Resource{ 1875 Header: dnsmessage.ResourceHeader{ 1876 Name: dnsmessage.MustNewName("good.golang.org."), 1877 Type: dnsmessage.TypeMX, 1878 Class: dnsmessage.ClassINET, 1879 Length: 4, 1880 }, 1881 Body: &dnsmessage.MXResource{ 1882 MX: dnsmessage.MustNewName("good.golang.org."), 1883 }, 1884 }, 1885 ) 1886 case dnsmessage.TypeNS: 1887 r.Answers = append(r.Answers, 1888 dnsmessage.Resource{ 1889 Header: dnsmessage.ResourceHeader{ 1890 Name: dnsmessage.MustNewName("<html>.golang.org."), 1891 Type: dnsmessage.TypeNS, 1892 Class: dnsmessage.ClassINET, 1893 Length: 4, 1894 }, 1895 Body: &dnsmessage.NSResource{ 1896 NS: dnsmessage.MustNewName("<html>.golang.org."), 1897 }, 1898 }, 1899 dnsmessage.Resource{ 1900 Header: dnsmessage.ResourceHeader{ 1901 Name: dnsmessage.MustNewName("good.golang.org."), 1902 Type: dnsmessage.TypeNS, 1903 Class: dnsmessage.ClassINET, 1904 Length: 4, 1905 }, 1906 Body: &dnsmessage.NSResource{ 1907 NS: dnsmessage.MustNewName("good.golang.org."), 1908 }, 1909 }, 1910 ) 1911 case dnsmessage.TypePTR: 1912 r.Answers = append(r.Answers, 1913 dnsmessage.Resource{ 1914 Header: dnsmessage.ResourceHeader{ 1915 Name: dnsmessage.MustNewName("<html>.golang.org."), 1916 Type: dnsmessage.TypePTR, 1917 Class: dnsmessage.ClassINET, 1918 Length: 4, 1919 }, 1920 Body: &dnsmessage.PTRResource{ 1921 PTR: dnsmessage.MustNewName("<html>.golang.org."), 1922 }, 1923 }, 1924 dnsmessage.Resource{ 1925 Header: dnsmessage.ResourceHeader{ 1926 Name: dnsmessage.MustNewName("good.golang.org."), 1927 Type: dnsmessage.TypePTR, 1928 Class: dnsmessage.ClassINET, 1929 Length: 4, 1930 }, 1931 Body: &dnsmessage.PTRResource{ 1932 PTR: dnsmessage.MustNewName("good.golang.org."), 1933 }, 1934 }, 1935 ) 1936 } 1937 return r, nil 1938 }, 1939 } 1940 1941 r := Resolver{PreferGo: true, Dial: fake.DialContext} 1942 // Change the default resolver to match our manipulated resolver 1943 originalDefault := DefaultResolver 1944 DefaultResolver = &r 1945 defer func() { DefaultResolver = originalDefault }() 1946 // Redirect host file lookups. 1947 defer func(orig string) { testHookHostsPath = orig }(testHookHostsPath) 1948 testHookHostsPath = "testdata/hosts" 1949 1950 tests := []struct { 1951 name string 1952 f func(*testing.T) 1953 }{ 1954 { 1955 name: "CNAME", 1956 f: func(t *testing.T) { 1957 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"} 1958 _, err := r.LookupCNAME(context.Background(), "golang.org") 1959 if err.Error() != expectedErr.Error() { 1960 t.Fatalf("unexpected error: %s", err) 1961 } 1962 _, err = LookupCNAME("golang.org") 1963 if err.Error() != expectedErr.Error() { 1964 t.Fatalf("unexpected error: %s", err) 1965 } 1966 }, 1967 }, 1968 { 1969 name: "SRV (bad record)", 1970 f: func(t *testing.T) { 1971 expected := []*SRV{ 1972 { 1973 Target: "good.golang.org.", 1974 }, 1975 } 1976 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"} 1977 _, records, err := r.LookupSRV(context.Background(), "target", "tcp", "golang.org") 1978 if err.Error() != expectedErr.Error() { 1979 t.Fatalf("unexpected error: %s", err) 1980 } 1981 if !reflect.DeepEqual(records, expected) { 1982 t.Error("Unexpected record set") 1983 } 1984 _, records, err = LookupSRV("target", "tcp", "golang.org") 1985 if err.Error() != expectedErr.Error() { 1986 t.Errorf("unexpected error: %s", err) 1987 } 1988 if !reflect.DeepEqual(records, expected) { 1989 t.Error("Unexpected record set") 1990 } 1991 }, 1992 }, 1993 { 1994 name: "SRV (bad header)", 1995 f: func(t *testing.T) { 1996 _, _, err := r.LookupSRV(context.Background(), "hdr", "tcp", "golang.org.") 1997 if expected := "lookup golang.org.: SRV header name is invalid"; err == nil || err.Error() != expected { 1998 t.Errorf("Resolver.LookupSRV returned unexpected error, got %q, want %q", err, expected) 1999 } 2000 _, _, err = LookupSRV("hdr", "tcp", "golang.org.") 2001 if expected := "lookup golang.org.: SRV header name is invalid"; err == nil || err.Error() != expected { 2002 t.Errorf("LookupSRV returned unexpected error, got %q, want %q", err, expected) 2003 } 2004 }, 2005 }, 2006 { 2007 name: "MX", 2008 f: func(t *testing.T) { 2009 expected := []*MX{ 2010 { 2011 Host: "good.golang.org.", 2012 }, 2013 } 2014 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"} 2015 records, err := r.LookupMX(context.Background(), "golang.org") 2016 if err.Error() != expectedErr.Error() { 2017 t.Fatalf("unexpected error: %s", err) 2018 } 2019 if !reflect.DeepEqual(records, expected) { 2020 t.Error("Unexpected record set") 2021 } 2022 records, err = LookupMX("golang.org") 2023 if err.Error() != expectedErr.Error() { 2024 t.Fatalf("unexpected error: %s", err) 2025 } 2026 if !reflect.DeepEqual(records, expected) { 2027 t.Error("Unexpected record set") 2028 } 2029 }, 2030 }, 2031 { 2032 name: "NS", 2033 f: func(t *testing.T) { 2034 expected := []*NS{ 2035 { 2036 Host: "good.golang.org.", 2037 }, 2038 } 2039 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "golang.org"} 2040 records, err := r.LookupNS(context.Background(), "golang.org") 2041 if err.Error() != expectedErr.Error() { 2042 t.Fatalf("unexpected error: %s", err) 2043 } 2044 if !reflect.DeepEqual(records, expected) { 2045 t.Error("Unexpected record set") 2046 } 2047 records, err = LookupNS("golang.org") 2048 if err.Error() != expectedErr.Error() { 2049 t.Fatalf("unexpected error: %s", err) 2050 } 2051 if !reflect.DeepEqual(records, expected) { 2052 t.Error("Unexpected record set") 2053 } 2054 }, 2055 }, 2056 { 2057 name: "Addr", 2058 f: func(t *testing.T) { 2059 expected := []string{"good.golang.org."} 2060 expectedErr := &DNSError{Err: errMalformedDNSRecordsDetail, Name: "192.0.2.42"} 2061 records, err := r.LookupAddr(context.Background(), "192.0.2.42") 2062 if err.Error() != expectedErr.Error() { 2063 t.Fatalf("unexpected error: %s", err) 2064 } 2065 if !reflect.DeepEqual(records, expected) { 2066 t.Error("Unexpected record set") 2067 } 2068 records, err = LookupAddr("192.0.2.42") 2069 if err.Error() != expectedErr.Error() { 2070 t.Fatalf("unexpected error: %s", err) 2071 } 2072 if !reflect.DeepEqual(records, expected) { 2073 t.Error("Unexpected record set") 2074 } 2075 }, 2076 }, 2077 } 2078 2079 for _, tc := range tests { 2080 t.Run(tc.name, tc.f) 2081 } 2082 2083 } 2084 2085 func TestNullMX(t *testing.T) { 2086 fake := fakeDNSServer{ 2087 rh: func(n, _ string, q dnsmessage.Message, _ time.Time) (dnsmessage.Message, error) { 2088 r := dnsmessage.Message{ 2089 Header: dnsmessage.Header{ 2090 ID: q.Header.ID, 2091 Response: true, 2092 RCode: dnsmessage.RCodeSuccess, 2093 }, 2094 Questions: q.Questions, 2095 Answers: []dnsmessage.Resource{ 2096 { 2097 Header: dnsmessage.ResourceHeader{ 2098 Name: q.Questions[0].Name, 2099 Type: dnsmessage.TypeMX, 2100 Class: dnsmessage.ClassINET, 2101 }, 2102 Body: &dnsmessage.MXResource{ 2103 MX: dnsmessage.MustNewName("."), 2104 }, 2105 }, 2106 }, 2107 } 2108 return r, nil 2109 }, 2110 } 2111 r := Resolver{PreferGo: true, Dial: fake.DialContext} 2112 rrset, err := r.LookupMX(context.Background(), "golang.org") 2113 if err != nil { 2114 t.Fatalf("LookupMX: %v", err) 2115 } 2116 if want := []*MX{&MX{Host: "."}}; !reflect.DeepEqual(rrset, want) { 2117 records := []string{} 2118 for _, rr := range rrset { 2119 records = append(records, fmt.Sprintf("%v", rr)) 2120 } 2121 t.Errorf("records = [%v]; want [%v]", strings.Join(records, " "), want[0]) 2122 } 2123 }