Home | History | Annotate | Download | only in net
      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