1// Copyright 2015 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//go:build unix
6
7package net
8
9import (
10	"io/fs"
11	"os"
12	"testing"
13	"time"
14)
15
16type nssHostTest struct {
17	host      string
18	localhost string
19	want      hostLookupOrder
20}
21
22func nssStr(t *testing.T, s string) *nssConf {
23	f, err := os.CreateTemp(t.TempDir(), "nss")
24	if err != nil {
25		t.Fatal(err)
26	}
27	if _, err := f.WriteString(s); err != nil {
28		t.Fatal(err)
29	}
30	if err := f.Close(); err != nil {
31		t.Fatal(err)
32	}
33	return parseNSSConfFile(f.Name())
34}
35
36// represents a dnsConfig returned by parsing a nonexistent resolv.conf
37var defaultResolvConf = &dnsConfig{
38	servers:  defaultNS,
39	ndots:    1,
40	timeout:  5,
41	attempts: 2,
42	err:      fs.ErrNotExist,
43}
44
45func TestConfHostLookupOrder(t *testing.T) {
46	// These tests are written for a system with cgo available,
47	// without using the netgo tag.
48	if netGoBuildTag {
49		t.Skip("skipping test because net package built with netgo tag")
50	}
51	if !cgoAvailable {
52		t.Skip("skipping test because cgo resolver not available")
53	}
54
55	tests := []struct {
56		name      string
57		c         *conf
58		nss       *nssConf
59		resolver  *Resolver
60		resolv    *dnsConfig
61		hostTests []nssHostTest
62	}{
63		{
64			name: "force",
65			c: &conf{
66				preferCgo: true,
67				netCgo:    true,
68			},
69			resolv: defaultResolvConf,
70			nss:    nssStr(t, "foo: bar"),
71			hostTests: []nssHostTest{
72				{"foo.local", "myhostname", hostLookupCgo},
73				{"google.com", "myhostname", hostLookupCgo},
74			},
75		},
76		{
77			name: "netgo_dns_before_files",
78			c: &conf{
79				netGo: true,
80			},
81			resolv: defaultResolvConf,
82			nss:    nssStr(t, "hosts: dns files"),
83			hostTests: []nssHostTest{
84				{"x.com", "myhostname", hostLookupDNSFiles},
85			},
86		},
87		{
88			name: "netgo_fallback_on_cgo",
89			c: &conf{
90				netGo: true,
91			},
92			resolv: defaultResolvConf,
93			nss:    nssStr(t, "hosts: dns files something_custom"),
94			hostTests: []nssHostTest{
95				{"x.com", "myhostname", hostLookupDNSFiles},
96			},
97		},
98		{
99			name: "ubuntu_trusty_avahi",
100			c: &conf{
101				mdnsTest: mdnsAssumeDoesNotExist,
102			},
103			resolv: defaultResolvConf,
104			nss:    nssStr(t, "hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4"),
105			hostTests: []nssHostTest{
106				{"foo.local", "myhostname", hostLookupCgo},
107				{"foo.local.", "myhostname", hostLookupCgo},
108				{"foo.LOCAL", "myhostname", hostLookupCgo},
109				{"foo.LOCAL.", "myhostname", hostLookupCgo},
110				{"google.com", "myhostname", hostLookupFilesDNS},
111			},
112		},
113		{
114			name: "freebsdlinux_no_resolv_conf",
115			c: &conf{
116				goos: "freebsd",
117			},
118			resolv:    defaultResolvConf,
119			nss:       nssStr(t, "foo: bar"),
120			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
121		},
122		// On OpenBSD, no resolv.conf means no DNS.
123		{
124			name: "openbsd_no_resolv_conf",
125			c: &conf{
126				goos: "openbsd",
127			},
128			resolv:    defaultResolvConf,
129			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFiles}},
130		},
131		{
132			name: "solaris_no_nsswitch",
133			c: &conf{
134				goos: "solaris",
135			},
136			resolv:    defaultResolvConf,
137			nss:       &nssConf{err: fs.ErrNotExist},
138			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
139		},
140		{
141			name: "openbsd_lookup_bind_file",
142			c: &conf{
143				goos: "openbsd",
144			},
145			resolv: &dnsConfig{lookup: []string{"bind", "file"}},
146			hostTests: []nssHostTest{
147				{"google.com", "myhostname", hostLookupDNSFiles},
148				{"foo.local", "myhostname", hostLookupDNSFiles},
149			},
150		},
151		{
152			name: "openbsd_lookup_file_bind",
153			c: &conf{
154				goos: "openbsd",
155			},
156			resolv:    &dnsConfig{lookup: []string{"file", "bind"}},
157			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
158		},
159		{
160			name: "openbsd_lookup_bind",
161			c: &conf{
162				goos: "openbsd",
163			},
164			resolv:    &dnsConfig{lookup: []string{"bind"}},
165			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupDNS}},
166		},
167		{
168			name: "openbsd_lookup_file",
169			c: &conf{
170				goos: "openbsd",
171			},
172			resolv:    &dnsConfig{lookup: []string{"file"}},
173			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFiles}},
174		},
175		{
176			name: "openbsd_lookup_yp",
177			c: &conf{
178				goos: "openbsd",
179			},
180			resolv:    &dnsConfig{lookup: []string{"file", "bind", "yp"}},
181			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
182		},
183		{
184			name: "openbsd_lookup_two",
185			c: &conf{
186				goos: "openbsd",
187			},
188			resolv:    &dnsConfig{lookup: []string{"file", "foo"}},
189			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
190		},
191		{
192			name: "openbsd_lookup_empty",
193			c: &conf{
194				goos: "openbsd",
195			},
196			resolv:    &dnsConfig{lookup: nil},
197			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupDNSFiles}},
198		},
199		{
200			name: "linux_no_nsswitch.conf",
201			c: &conf{
202				goos: "linux",
203			},
204			resolv:    defaultResolvConf,
205			nss:       &nssConf{err: fs.ErrNotExist},
206			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
207		},
208		{
209			name: "linux_empty_nsswitch.conf",
210			c: &conf{
211				goos: "linux",
212			},
213			resolv:    defaultResolvConf,
214			nss:       nssStr(t, ""),
215			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
216		},
217		{
218			name: "files_mdns_dns",
219			c: &conf{
220				mdnsTest: mdnsAssumeDoesNotExist,
221			},
222			resolv: defaultResolvConf,
223			nss:    nssStr(t, "hosts: files mdns dns"),
224			hostTests: []nssHostTest{
225				{"x.com", "myhostname", hostLookupFilesDNS},
226				{"x.local", "myhostname", hostLookupCgo},
227			},
228		},
229		{
230			name:   "dns_special_hostnames",
231			c:      &conf{},
232			resolv: defaultResolvConf,
233			nss:    nssStr(t, "hosts: dns"),
234			hostTests: []nssHostTest{
235				{"x.com", "myhostname", hostLookupDNS},
236				{"x\\.com", "myhostname", hostLookupCgo},     // punt on weird glibc escape
237				{"foo.com%en0", "myhostname", hostLookupCgo}, // and IPv6 zones
238			},
239		},
240		{
241			name: "mdns_allow",
242			c: &conf{
243				mdnsTest: mdnsAssumeExists,
244			},
245			resolv: defaultResolvConf,
246			nss:    nssStr(t, "hosts: files mdns dns"),
247			hostTests: []nssHostTest{
248				{"x.com", "myhostname", hostLookupCgo},
249				{"x.local", "myhostname", hostLookupCgo},
250			},
251		},
252		{
253			name:   "files_dns",
254			c:      &conf{},
255			resolv: defaultResolvConf,
256			nss:    nssStr(t, "hosts: files dns"),
257			hostTests: []nssHostTest{
258				{"x.com", "myhostname", hostLookupFilesDNS},
259				{"x", "myhostname", hostLookupFilesDNS},
260				{"x.local", "myhostname", hostLookupFilesDNS},
261			},
262		},
263		{
264			name:   "dns_files",
265			c:      &conf{},
266			resolv: defaultResolvConf,
267			nss:    nssStr(t, "hosts: dns files"),
268			hostTests: []nssHostTest{
269				{"x.com", "myhostname", hostLookupDNSFiles},
270				{"x", "myhostname", hostLookupDNSFiles},
271				{"x.local", "myhostname", hostLookupDNSFiles},
272			},
273		},
274		{
275			name:   "something_custom",
276			c:      &conf{},
277			resolv: defaultResolvConf,
278			nss:    nssStr(t, "hosts: dns files something_custom"),
279			hostTests: []nssHostTest{
280				{"x.com", "myhostname", hostLookupCgo},
281			},
282		},
283		{
284			name:   "myhostname",
285			c:      &conf{},
286			resolv: defaultResolvConf,
287			nss:    nssStr(t, "hosts: files dns myhostname"),
288			hostTests: []nssHostTest{
289				{"x.com", "myhostname", hostLookupFilesDNS},
290				{"myhostname", "myhostname", hostLookupCgo},
291				{"myHostname", "myhostname", hostLookupCgo},
292				{"myhostname.dot", "myhostname.dot", hostLookupCgo},
293				{"myHostname.dot", "myhostname.dot", hostLookupCgo},
294				{"_gateway", "myhostname", hostLookupCgo},
295				{"_Gateway", "myhostname", hostLookupCgo},
296				{"_outbound", "myhostname", hostLookupCgo},
297				{"_Outbound", "myhostname", hostLookupCgo},
298				{"localhost", "myhostname", hostLookupCgo},
299				{"Localhost", "myhostname", hostLookupCgo},
300				{"anything.localhost", "myhostname", hostLookupCgo},
301				{"Anything.localhost", "myhostname", hostLookupCgo},
302				{"localhost.localdomain", "myhostname", hostLookupCgo},
303				{"Localhost.Localdomain", "myhostname", hostLookupCgo},
304				{"anything.localhost.localdomain", "myhostname", hostLookupCgo},
305				{"Anything.Localhost.Localdomain", "myhostname", hostLookupCgo},
306				{"somehostname", "myhostname", hostLookupFilesDNS},
307			},
308		},
309		{
310			name: "ubuntu14.04.02",
311			c: &conf{
312				mdnsTest: mdnsAssumeDoesNotExist,
313			},
314			resolv: defaultResolvConf,
315			nss:    nssStr(t, "hosts: files myhostname mdns4_minimal [NOTFOUND=return] dns mdns4"),
316			hostTests: []nssHostTest{
317				{"x.com", "myhostname", hostLookupFilesDNS},
318				{"somehostname", "myhostname", hostLookupFilesDNS},
319				{"myhostname", "myhostname", hostLookupCgo},
320			},
321		},
322		// Debian Squeeze is just "dns,files", but lists all
323		// the default criteria for dns, but then has a
324		// non-standard but redundant notfound=return for the
325		// files.
326		{
327			name:   "debian_squeeze",
328			c:      &conf{},
329			resolv: defaultResolvConf,
330			nss:    nssStr(t, "hosts: dns [success=return notfound=continue unavail=continue tryagain=continue] files [notfound=return]"),
331			hostTests: []nssHostTest{
332				{"x.com", "myhostname", hostLookupDNSFiles},
333				{"somehostname", "myhostname", hostLookupDNSFiles},
334			},
335		},
336		{
337			name:      "resolv.conf-unknown",
338			c:         &conf{},
339			resolv:    &dnsConfig{servers: defaultNS, ndots: 1, timeout: 5, attempts: 2, unknownOpt: true},
340			nss:       nssStr(t, "foo: bar"),
341			hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
342		},
343		// Issue 24393: make sure "Resolver.PreferGo = true" acts like netgo.
344		{
345			name:     "resolver-prefergo",
346			resolver: &Resolver{PreferGo: true},
347			c: &conf{
348				preferCgo: true,
349				netCgo:    true,
350			},
351			resolv: defaultResolvConf,
352			nss:    nssStr(t, ""),
353			hostTests: []nssHostTest{
354				{"localhost", "myhostname", hostLookupFilesDNS},
355			},
356		},
357		{
358			name:     "unknown-source",
359			resolver: &Resolver{PreferGo: true},
360			c:        &conf{},
361			resolv:   defaultResolvConf,
362			nss:      nssStr(t, "hosts: resolve files"),
363			hostTests: []nssHostTest{
364				{"x.com", "myhostname", hostLookupDNSFiles},
365			},
366		},
367		{
368			name:     "dns-among-unknown-sources",
369			resolver: &Resolver{PreferGo: true},
370			c:        &conf{},
371			resolv:   defaultResolvConf,
372			nss:      nssStr(t, "hosts: mymachines files dns"),
373			hostTests: []nssHostTest{
374				{"x.com", "myhostname", hostLookupFilesDNS},
375			},
376		},
377		{
378			name:     "dns-among-unknown-sources-2",
379			resolver: &Resolver{PreferGo: true},
380			c:        &conf{},
381			resolv:   defaultResolvConf,
382			nss:      nssStr(t, "hosts: dns mymachines files"),
383			hostTests: []nssHostTest{
384				{"x.com", "myhostname", hostLookupDNSFiles},
385			},
386		},
387	}
388
389	origGetHostname := getHostname
390	defer func() { getHostname = origGetHostname }()
391	defer setSystemNSS(getSystemNSS(), 0)
392	conf, err := newResolvConfTest()
393	if err != nil {
394		t.Fatal(err)
395	}
396	defer conf.teardown()
397
398	for _, tt := range tests {
399		if !conf.forceUpdateConf(tt.resolv, time.Now().Add(time.Hour)) {
400			t.Errorf("%s: failed to change resolv config", tt.name)
401		}
402		for _, ht := range tt.hostTests {
403			getHostname = func() (string, error) { return ht.localhost, nil }
404			setSystemNSS(tt.nss, time.Hour)
405
406			gotOrder, _ := tt.c.hostLookupOrder(tt.resolver, ht.host)
407			if gotOrder != ht.want {
408				t.Errorf("%s: hostLookupOrder(%q) = %v; want %v", tt.name, ht.host, gotOrder, ht.want)
409			}
410		}
411	}
412}
413
414func TestAddrLookupOrder(t *testing.T) {
415	// This test is written for a system with cgo available,
416	// without using the netgo tag.
417	if netGoBuildTag {
418		t.Skip("skipping test because net package built with netgo tag")
419	}
420	if !cgoAvailable {
421		t.Skip("skipping test because cgo resolver not available")
422	}
423
424	defer setSystemNSS(getSystemNSS(), 0)
425	c, err := newResolvConfTest()
426	if err != nil {
427		t.Fatal(err)
428	}
429	defer c.teardown()
430
431	if !c.forceUpdateConf(defaultResolvConf, time.Now().Add(time.Hour)) {
432		t.Fatal("failed to change resolv config")
433	}
434
435	setSystemNSS(nssStr(t, "hosts: files myhostname dns"), time.Hour)
436	cnf := &conf{}
437	order, _ := cnf.addrLookupOrder(nil, "192.0.2.1")
438	if order != hostLookupCgo {
439		t.Errorf("addrLookupOrder returned: %v, want cgo", order)
440	}
441
442	setSystemNSS(nssStr(t, "hosts: files mdns4 dns"), time.Hour)
443	order, _ = cnf.addrLookupOrder(nil, "192.0.2.1")
444	if order != hostLookupCgo {
445		t.Errorf("addrLookupOrder returned: %v, want cgo", order)
446	}
447
448}
449
450func setSystemNSS(nss *nssConf, addDur time.Duration) {
451	nssConfig.mu.Lock()
452	nssConfig.nssConf = nss
453	nssConfig.mu.Unlock()
454	nssConfig.acquireSema()
455	nssConfig.lastChecked = time.Now().Add(addDur)
456	nssConfig.releaseSema()
457}
458
459func TestSystemConf(t *testing.T) {
460	systemConf()
461}
462