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 7package net 8 9import ( 10 "fmt" 11 "io/ioutil" 12 "os" 13 "path" 14 "reflect" 15 "strings" 16 "sync" 17 "testing" 18 "time" 19) 20 21var dnsTransportFallbackTests = []struct { 22 server string 23 name string 24 qtype uint16 25 timeout int 26 rcode int 27}{ 28 // Querying "com." with qtype=255 usually makes an answer 29 // which requires more than 512 bytes. 30 {"8.8.8.8:53", "com.", dnsTypeALL, 2, dnsRcodeSuccess}, 31 {"8.8.4.4:53", "com.", dnsTypeALL, 4, dnsRcodeSuccess}, 32} 33 34func TestDNSTransportFallback(t *testing.T) { 35 if testing.Short() || !*testExternal { 36 t.Skip("avoid external network") 37 } 38 39 for _, tt := range dnsTransportFallbackTests { 40 timeout := time.Duration(tt.timeout) * time.Second 41 msg, err := exchange(tt.server, tt.name, tt.qtype, timeout) 42 if err != nil { 43 t.Error(err) 44 continue 45 } 46 switch msg.rcode { 47 case tt.rcode, dnsRcodeServerFailure: 48 default: 49 t.Errorf("got %v from %v; want %v", msg.rcode, tt.server, tt.rcode) 50 continue 51 } 52 } 53} 54 55// See RFC 6761 for further information about the reserved, pseudo 56// domain names. 57var specialDomainNameTests = []struct { 58 name string 59 qtype uint16 60 rcode int 61}{ 62 // Name resolution APIs and libraries should not recognize the 63 // followings as special. 64 {"1.0.168.192.in-addr.arpa.", dnsTypePTR, dnsRcodeNameError}, 65 {"test.", dnsTypeALL, dnsRcodeNameError}, 66 {"example.com.", dnsTypeALL, dnsRcodeSuccess}, 67 68 // Name resolution APIs and libraries should recognize the 69 // followings as special and should not send any queries. 70 // Though, we test those names here for verifying nagative 71 // answers at DNS query-response interaction level. 72 {"localhost.", dnsTypeALL, dnsRcodeNameError}, 73 {"invalid.", dnsTypeALL, dnsRcodeNameError}, 74} 75 76func TestSpecialDomainName(t *testing.T) { 77 if testing.Short() || !*testExternal { 78 t.Skip("avoid external network") 79 } 80 81 server := "8.8.8.8:53" 82 for _, tt := range specialDomainNameTests { 83 msg, err := exchange(server, tt.name, tt.qtype, 3*time.Second) 84 if err != nil { 85 t.Error(err) 86 continue 87 } 88 switch msg.rcode { 89 case tt.rcode, dnsRcodeServerFailure: 90 default: 91 t.Errorf("got %v from %v; want %v", msg.rcode, server, tt.rcode) 92 continue 93 } 94 } 95} 96 97type resolvConfTest struct { 98 dir string 99 path string 100 *resolverConfig 101} 102 103func newResolvConfTest() (*resolvConfTest, error) { 104 dir, err := ioutil.TempDir("", "go-resolvconftest") 105 if err != nil { 106 return nil, err 107 } 108 conf := &resolvConfTest{ 109 dir: dir, 110 path: path.Join(dir, "resolv.conf"), 111 resolverConfig: &resolvConf, 112 } 113 conf.initOnce.Do(conf.init) 114 return conf, nil 115} 116 117func (conf *resolvConfTest) writeAndUpdate(lines []string) error { 118 f, err := os.OpenFile(conf.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) 119 if err != nil { 120 return err 121 } 122 if _, err := f.WriteString(strings.Join(lines, "\n")); err != nil { 123 f.Close() 124 return err 125 } 126 f.Close() 127 if err := conf.forceUpdate(conf.path); err != nil { 128 return err 129 } 130 return nil 131} 132 133func (conf *resolvConfTest) forceUpdate(name string) error { 134 dnsConf := dnsReadConfig(name) 135 conf.mu.Lock() 136 conf.dnsConfig = dnsConf 137 conf.mu.Unlock() 138 for i := 0; i < 5; i++ { 139 if conf.tryAcquireSema() { 140 conf.lastChecked = time.Time{} 141 conf.releaseSema() 142 return nil 143 } 144 } 145 return fmt.Errorf("tryAcquireSema for %s failed", name) 146} 147 148func (conf *resolvConfTest) servers() []string { 149 conf.mu.RLock() 150 servers := conf.dnsConfig.servers 151 conf.mu.RUnlock() 152 return servers 153} 154 155func (conf *resolvConfTest) teardown() error { 156 err := conf.forceUpdate("/etc/resolv.conf") 157 os.RemoveAll(conf.dir) 158 return err 159} 160 161var updateResolvConfTests = []struct { 162 name string // query name 163 lines []string // resolver configuration lines 164 servers []string // expected name servers 165}{ 166 { 167 name: "golang.org", 168 lines: []string{"nameserver 8.8.8.8"}, 169 servers: []string{"8.8.8.8"}, 170 }, 171 { 172 name: "", 173 lines: nil, // an empty resolv.conf should use defaultNS as name servers 174 servers: defaultNS, 175 }, 176 { 177 name: "www.example.com", 178 lines: []string{"nameserver 8.8.4.4"}, 179 servers: []string{"8.8.4.4"}, 180 }, 181} 182 183func TestUpdateResolvConf(t *testing.T) { 184 if testing.Short() || !*testExternal { 185 t.Skip("avoid external network") 186 } 187 188 conf, err := newResolvConfTest() 189 if err != nil { 190 t.Fatal(err) 191 } 192 defer conf.teardown() 193 194 for i, tt := range updateResolvConfTests { 195 if err := conf.writeAndUpdate(tt.lines); err != nil { 196 t.Error(err) 197 continue 198 } 199 if tt.name != "" { 200 var wg sync.WaitGroup 201 const N = 10 202 wg.Add(N) 203 for j := 0; j < N; j++ { 204 go func(name string) { 205 defer wg.Done() 206 ips, err := goLookupIP(name) 207 if err != nil { 208 t.Error(err) 209 return 210 } 211 if len(ips) == 0 { 212 t.Errorf("no records for %s", name) 213 return 214 } 215 }(tt.name) 216 } 217 wg.Wait() 218 } 219 servers := conf.servers() 220 if !reflect.DeepEqual(servers, tt.servers) { 221 t.Errorf("#%d: got %v; want %v", i, servers, tt.servers) 222 continue 223 } 224 } 225} 226 227var goLookupIPWithResolverConfigTests = []struct { 228 name string 229 lines []string // resolver configuration lines 230 error 231 a, aaaa bool // whether response contains A, AAAA-record 232}{ 233 // no records, transport timeout 234 { 235 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", 236 []string{ 237 "options timeout:1 attempts:1", 238 "nameserver 255.255.255.255", // please forgive us for abuse of limited broadcast address 239 }, 240 &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "255.255.255.255:53", IsTimeout: true}, 241 false, false, 242 }, 243 244 // no records, non-existent domain 245 { 246 "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", 247 []string{ 248 "options timeout:3 attempts:1", 249 "nameserver 8.8.8.8", 250 }, 251 &DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "8.8.8.8:53", IsTimeout: false}, 252 false, false, 253 }, 254 255 // a few A records, no AAAA records 256 { 257 "ipv4.google.com.", 258 []string{ 259 "nameserver 8.8.8.8", 260 "nameserver 2001:4860:4860::8888", 261 }, 262 nil, 263 true, false, 264 }, 265 { 266 "ipv4.google.com", 267 []string{ 268 "domain golang.org", 269 "nameserver 2001:4860:4860::8888", 270 "nameserver 8.8.8.8", 271 }, 272 nil, 273 true, false, 274 }, 275 { 276 "ipv4.google.com", 277 []string{ 278 "search x.golang.org y.golang.org", 279 "nameserver 2001:4860:4860::8888", 280 "nameserver 8.8.8.8", 281 }, 282 nil, 283 true, false, 284 }, 285 286 // no A records, a few AAAA records 287 { 288 "ipv6.google.com.", 289 []string{ 290 "nameserver 2001:4860:4860::8888", 291 "nameserver 8.8.8.8", 292 }, 293 nil, 294 false, true, 295 }, 296 { 297 "ipv6.google.com", 298 []string{ 299 "domain golang.org", 300 "nameserver 8.8.8.8", 301 "nameserver 2001:4860:4860::8888", 302 }, 303 nil, 304 false, true, 305 }, 306 { 307 "ipv6.google.com", 308 []string{ 309 "search x.golang.org y.golang.org", 310 "nameserver 8.8.8.8", 311 "nameserver 2001:4860:4860::8888", 312 }, 313 nil, 314 false, true, 315 }, 316 317 // both A and AAAA records 318 { 319 "hostname.as112.net", // see RFC 7534 320 []string{ 321 "domain golang.org", 322 "nameserver 2001:4860:4860::8888", 323 "nameserver 8.8.8.8", 324 }, 325 nil, 326 true, true, 327 }, 328 { 329 "hostname.as112.net", // see RFC 7534 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, true, 337 }, 338} 339 340func TestGoLookupIPWithResolverConfig(t *testing.T) { 341 if testing.Short() || !*testExternal { 342 t.Skip("avoid external network") 343 } 344 345 conf, err := newResolvConfTest() 346 if err != nil { 347 t.Fatal(err) 348 } 349 defer conf.teardown() 350 351 for _, tt := range goLookupIPWithResolverConfigTests { 352 if err := conf.writeAndUpdate(tt.lines); err != nil { 353 t.Error(err) 354 continue 355 } 356 conf.tryUpdate(conf.path) 357 addrs, err := goLookupIP(tt.name) 358 if err != nil { 359 if err, ok := err.(*DNSError); !ok || (err.Name != tt.error.(*DNSError).Name || err.Server != tt.error.(*DNSError).Server || err.IsTimeout != tt.error.(*DNSError).IsTimeout) { 360 t.Errorf("got %v; want %v", err, tt.error) 361 } 362 continue 363 } 364 if len(addrs) == 0 { 365 t.Errorf("no records for %s", tt.name) 366 } 367 if !tt.a && !tt.aaaa && len(addrs) > 0 { 368 t.Errorf("unexpected %v for %s", addrs, tt.name) 369 } 370 for _, addr := range addrs { 371 if !tt.a && addr.IP.To4() != nil { 372 t.Errorf("got %v; must not be IPv4 address", addr) 373 } 374 if !tt.aaaa && addr.IP.To16() != nil && addr.IP.To4() == nil { 375 t.Errorf("got %v; must not be IPv6 address", addr) 376 } 377 } 378 } 379} 380 381// Test that goLookupIPOrder falls back to the host file when no DNS servers are available. 382func TestGoLookupIPOrderFallbackToFile(t *testing.T) { 383 if testing.Short() || !*testExternal { 384 t.Skip("avoid external network") 385 } 386 387 // Add a config that simulates no dns servers being available. 388 conf, err := newResolvConfTest() 389 if err != nil { 390 t.Fatal(err) 391 } 392 if err := conf.writeAndUpdate([]string{}); err != nil { 393 t.Fatal(err) 394 } 395 conf.tryUpdate(conf.path) 396 // Redirect host file lookups. 397 defer func(orig string) { testHookHostsPath = orig }(testHookHostsPath) 398 testHookHostsPath = "testdata/hosts" 399 400 for _, order := range []hostLookupOrder{hostLookupFilesDNS, hostLookupDNSFiles} { 401 name := fmt.Sprintf("order %v", order) 402 403 // First ensure that we get an error when contacting a non-existant host. 404 _, err := goLookupIPOrder("notarealhost", order) 405 if err == nil { 406 t.Errorf("%s: expected error while looking up name not in hosts file", name) 407 continue 408 } 409 410 // Now check that we get an address when the name appears in the hosts file. 411 addrs, err := goLookupIPOrder("thor", order) // entry is in "testdata/hosts" 412 if err != nil { 413 t.Errorf("%s: expected to successfully lookup host entry", name) 414 continue 415 } 416 if len(addrs) != 1 { 417 t.Errorf("%s: expected exactly one result, but got %v", name, addrs) 418 continue 419 } 420 if got, want := addrs[0].String(), "127.1.1.1"; got != want { 421 t.Errorf("%s: address doesn't match expectation. got %v, want %v", name, got, want) 422 } 423 } 424 defer conf.teardown() 425} 426 427// Issue 12712. 428// When using search domains, return the error encountered 429// querying the original name instead of an error encountered 430// querying a generated name. 431func TestErrorForOriginalNameWhenSearching(t *testing.T) { 432 const fqdn = "doesnotexist.domain" 433 434 origTestHookDNSDialer := testHookDNSDialer 435 defer func() { testHookDNSDialer = origTestHookDNSDialer }() 436 437 conf, err := newResolvConfTest() 438 if err != nil { 439 t.Fatal(err) 440 } 441 defer conf.teardown() 442 443 if err := conf.writeAndUpdate([]string{"search servfail"}); err != nil { 444 t.Fatal(err) 445 } 446 447 d := &fakeDNSConn{} 448 testHookDNSDialer = func(time.Duration) dnsDialer { return d } 449 450 d.rh = func(q *dnsMsg) (*dnsMsg, error) { 451 r := &dnsMsg{ 452 dnsMsgHdr: dnsMsgHdr{ 453 id: q.id, 454 }, 455 } 456 457 switch q.question[0].Name { 458 case fqdn + ".servfail.": 459 r.rcode = dnsRcodeServerFailure 460 default: 461 r.rcode = dnsRcodeNameError 462 } 463 464 return r, nil 465 } 466 467 _, err = goLookupIP(fqdn) 468 if err == nil { 469 t.Fatal("expected an error") 470 } 471 472 want := &DNSError{Name: fqdn, Err: errNoSuchHost.Error()} 473 if err, ok := err.(*DNSError); !ok || err.Name != want.Name || err.Err != want.Err { 474 t.Errorf("got %v; want %v", err, want) 475 } 476} 477 478func BenchmarkGoLookupIP(b *testing.B) { 479 testHookUninstaller.Do(uninstallTestHooks) 480 481 for i := 0; i < b.N; i++ { 482 goLookupIP("www.example.com") 483 } 484} 485 486func BenchmarkGoLookupIPNoSuchHost(b *testing.B) { 487 testHookUninstaller.Do(uninstallTestHooks) 488 489 for i := 0; i < b.N; i++ { 490 goLookupIP("some.nonexistent") 491 } 492} 493 494func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) { 495 testHookUninstaller.Do(uninstallTestHooks) 496 497 conf, err := newResolvConfTest() 498 if err != nil { 499 b.Fatal(err) 500 } 501 defer conf.teardown() 502 503 lines := []string{ 504 "nameserver 203.0.113.254", // use TEST-NET-3 block, see RFC 5737 505 "nameserver 8.8.8.8", 506 } 507 if err := conf.writeAndUpdate(lines); err != nil { 508 b.Fatal(err) 509 } 510 511 for i := 0; i < b.N; i++ { 512 goLookupIP("www.example.com") 513 } 514} 515 516type fakeDNSConn struct { 517 // last query 518 qmu sync.Mutex // guards q 519 q *dnsMsg 520 // reply handler 521 rh func(*dnsMsg) (*dnsMsg, error) 522} 523 524func (f *fakeDNSConn) dialDNS(n, s string) (dnsConn, error) { 525 return f, nil 526} 527 528func (f *fakeDNSConn) Close() error { 529 return nil 530} 531 532func (f *fakeDNSConn) SetDeadline(time.Time) error { 533 return nil 534} 535 536func (f *fakeDNSConn) writeDNSQuery(q *dnsMsg) error { 537 f.qmu.Lock() 538 defer f.qmu.Unlock() 539 f.q = q 540 return nil 541} 542 543func (f *fakeDNSConn) readDNSResponse() (*dnsMsg, error) { 544 f.qmu.Lock() 545 q := f.q 546 f.qmu.Unlock() 547 return f.rh(q) 548} 549