github.com/d4l3k/go@v0.0.0-20151015000803-65fc379daeda/src/net/dnsclient_unix_test.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build darwin dragonfly freebsd linux netbsd openbsd solaris
     6  
     7  package net
     8  
     9  import (
    10  	"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  }