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 aix darwin dragonfly freebsd linux netbsd openbsd solaris
6
7package net
8
9import (
10	"errors"
11	"os"
12	"reflect"
13	"strings"
14	"testing"
15	"time"
16)
17
18var dnsReadConfigTests = []struct {
19	name string
20	want *dnsConfig
21}{
22	{
23		name: "testdata/resolv.conf",
24		want: &dnsConfig{
25			servers:    []string{"8.8.8.8:53", "[2001:4860:4860::8888]:53", "[fe80::1%lo0]:53"},
26			search:     []string{"localdomain."},
27			ndots:      5,
28			timeout:    10 * time.Second,
29			attempts:   3,
30			rotate:     true,
31			unknownOpt: true, // the "options attempts 3" line
32		},
33	},
34	{
35		name: "testdata/domain-resolv.conf",
36		want: &dnsConfig{
37			servers:  []string{"8.8.8.8:53"},
38			search:   []string{"localdomain."},
39			ndots:    1,
40			timeout:  5 * time.Second,
41			attempts: 2,
42		},
43	},
44	{
45		name: "testdata/search-resolv.conf",
46		want: &dnsConfig{
47			servers:  []string{"8.8.8.8:53"},
48			search:   []string{"test.", "invalid."},
49			ndots:    1,
50			timeout:  5 * time.Second,
51			attempts: 2,
52		},
53	},
54	{
55		name: "testdata/empty-resolv.conf",
56		want: &dnsConfig{
57			servers:  defaultNS,
58			ndots:    1,
59			timeout:  5 * time.Second,
60			attempts: 2,
61			search:   []string{"domain.local."},
62		},
63	},
64	{
65		name: "testdata/invalid-ndots-resolv.conf",
66		want: &dnsConfig{
67			servers:  defaultNS,
68			ndots:    0,
69			timeout:  5 * time.Second,
70			attempts: 2,
71			search:   []string{"domain.local."},
72		},
73	},
74	{
75		name: "testdata/large-ndots-resolv.conf",
76		want: &dnsConfig{
77			servers:  defaultNS,
78			ndots:    15,
79			timeout:  5 * time.Second,
80			attempts: 2,
81			search:   []string{"domain.local."},
82		},
83	},
84	{
85		name: "testdata/negative-ndots-resolv.conf",
86		want: &dnsConfig{
87			servers:  defaultNS,
88			ndots:    0,
89			timeout:  5 * time.Second,
90			attempts: 2,
91			search:   []string{"domain.local."},
92		},
93	},
94	{
95		name: "testdata/openbsd-resolv.conf",
96		want: &dnsConfig{
97			ndots:    1,
98			timeout:  5 * time.Second,
99			attempts: 2,
100			lookup:   []string{"file", "bind"},
101			servers:  []string{"169.254.169.254:53", "10.240.0.1:53"},
102			search:   []string{"c.symbolic-datum-552.internal."},
103		},
104	},
105}
106
107func TestDNSReadConfig(t *testing.T) {
108	origGetHostname := getHostname
109	defer func() { getHostname = origGetHostname }()
110	getHostname = func() (string, error) { return "host.domain.local", nil }
111
112	for _, tt := range dnsReadConfigTests {
113		conf := dnsReadConfig(tt.name)
114		if conf.err != nil {
115			t.Fatal(conf.err)
116		}
117		conf.mtime = time.Time{}
118		if !reflect.DeepEqual(conf, tt.want) {
119			t.Errorf("%s:\ngot: %+v\nwant: %+v", tt.name, conf, tt.want)
120		}
121	}
122}
123
124func TestDNSReadMissingFile(t *testing.T) {
125	origGetHostname := getHostname
126	defer func() { getHostname = origGetHostname }()
127	getHostname = func() (string, error) { return "host.domain.local", nil }
128
129	conf := dnsReadConfig("a-nonexistent-file")
130	if !os.IsNotExist(conf.err) {
131		t.Errorf("missing resolv.conf:\ngot: %v\nwant: %v", conf.err, os.ErrNotExist)
132	}
133	conf.err = nil
134	want := &dnsConfig{
135		servers:  defaultNS,
136		ndots:    1,
137		timeout:  5 * time.Second,
138		attempts: 2,
139		search:   []string{"domain.local."},
140	}
141	if !reflect.DeepEqual(conf, want) {
142		t.Errorf("missing resolv.conf:\ngot: %+v\nwant: %+v", conf, want)
143	}
144}
145
146var dnsDefaultSearchTests = []struct {
147	name string
148	err  error
149	want []string
150}{
151	{
152		name: "host.long.domain.local",
153		want: []string{"long.domain.local."},
154	},
155	{
156		name: "host.local",
157		want: []string{"local."},
158	},
159	{
160		name: "host",
161		want: nil,
162	},
163	{
164		name: "host.domain.local",
165		err:  errors.New("errored"),
166		want: nil,
167	},
168	{
169		// ensures we don't return []string{""}
170		// which causes duplicate lookups
171		name: "foo.",
172		want: nil,
173	},
174}
175
176func TestDNSDefaultSearch(t *testing.T) {
177	origGetHostname := getHostname
178	defer func() { getHostname = origGetHostname }()
179
180	for _, tt := range dnsDefaultSearchTests {
181		getHostname = func() (string, error) { return tt.name, tt.err }
182		got := dnsDefaultSearch()
183		if !reflect.DeepEqual(got, tt.want) {
184			t.Errorf("dnsDefaultSearch with hostname %q and error %+v = %q, wanted %q", tt.name, tt.err, got, tt.want)
185		}
186	}
187}
188
189func TestDNSNameLength(t *testing.T) {
190	origGetHostname := getHostname
191	defer func() { getHostname = origGetHostname }()
192	getHostname = func() (string, error) { return "host.domain.local", nil }
193
194	var char63 = ""
195	for i := 0; i < 63; i++ {
196		char63 += "a"
197	}
198	longDomain := strings.Repeat(char63+".", 5) + "example"
199
200	for _, tt := range dnsReadConfigTests {
201		conf := dnsReadConfig(tt.name)
202		if conf.err != nil {
203			t.Fatal(conf.err)
204		}
205
206		var shortestSuffix int
207		for _, suffix := range tt.want.search {
208			if shortestSuffix == 0 || len(suffix) < shortestSuffix {
209				shortestSuffix = len(suffix)
210			}
211		}
212
213		// Test a name that will be maximally long when prefixing the shortest
214		// suffix (accounting for the intervening dot).
215		longName := longDomain[len(longDomain)-254+1+shortestSuffix:]
216		if longName[0] == '.' || longName[1] == '.' {
217			longName = "aa." + longName[3:]
218		}
219		for _, fqdn := range conf.nameList(longName) {
220			if len(fqdn) > 254 {
221				t.Errorf("got %d; want less than or equal to 254", len(fqdn))
222			}
223		}
224
225		// Now test a name that's too long for suffixing.
226		unsuffixable := "a." + longName[1:]
227		unsuffixableResults := conf.nameList(unsuffixable)
228		if len(unsuffixableResults) != 1 {
229			t.Errorf("suffixed names %v; want []", unsuffixableResults[1:])
230		}
231
232		// Now test a name that's too long for DNS.
233		tooLong := "a." + longDomain
234		tooLongResults := conf.nameList(tooLong)
235		if tooLongResults != nil {
236			t.Errorf("suffixed names %v; want nil", tooLongResults)
237		}
238	}
239}
240