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 "fmt" 11 "io/ioutil" 12 "os" 13 "path" 14 "reflect" 15 "strings" 16 "sync" 17 "testing" 18 "time" 19 ) 20 21 var 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 34 func 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. 57 var 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 76 func 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, 0) 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 97 type resolvConfTest struct { 98 dir string 99 path string 100 *resolverConfig 101 } 102 103 func 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 117 func (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 133 func (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 148 func (conf *resolvConfTest) servers() []string { 149 conf.mu.RLock() 150 servers := conf.dnsConfig.servers 151 conf.mu.RUnlock() 152 return servers 153 } 154 155 func (conf *resolvConfTest) teardown() error { 156 err := conf.forceUpdate("/etc/resolv.conf") 157 os.RemoveAll(conf.dir) 158 return err 159 } 160 161 var 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 183 func 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 227 var 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 340 func 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 func BenchmarkGoLookupIP(b *testing.B) { 382 testHookUninstaller.Do(uninstallTestHooks) 383 384 for i := 0; i < b.N; i++ { 385 goLookupIP("www.example.com") 386 } 387 } 388 389 func BenchmarkGoLookupIPNoSuchHost(b *testing.B) { 390 testHookUninstaller.Do(uninstallTestHooks) 391 392 for i := 0; i < b.N; i++ { 393 goLookupIP("some.nonexistent") 394 } 395 } 396 397 func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) { 398 testHookUninstaller.Do(uninstallTestHooks) 399 400 conf, err := newResolvConfTest() 401 if err != nil { 402 b.Fatal(err) 403 } 404 defer conf.teardown() 405 406 lines := []string{ 407 "nameserver 203.0.113.254", // use TEST-NET-3 block, see RFC 5737 408 "nameserver 8.8.8.8", 409 } 410 if err := conf.writeAndUpdate(lines); err != nil { 411 b.Fatal(err) 412 } 413 414 for i := 0; i < b.N; i++ { 415 goLookupIP("www.example.com") 416 } 417 } 418