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// +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris
6
7package net
8
9import (
10	"internal/bytealg"
11	"os"
12	"runtime"
13	"sync"
14	"syscall"
15)
16
17// conf represents a system's network configuration.
18type conf struct {
19	// forceCgoLookupHost forces CGO to always be used, if available.
20	forceCgoLookupHost bool
21
22	netGo  bool // go DNS resolution forced
23	netCgo bool // cgo DNS resolution forced
24
25	// machine has an /etc/mdns.allow file
26	hasMDNSAllow bool
27
28	goos          string // the runtime.GOOS, to ease testing
29	dnsDebugLevel int
30
31	nss    *nssConf
32	resolv *dnsConfig
33}
34
35var (
36	confOnce sync.Once // guards init of confVal via initConfVal
37	confVal  = &conf{goos: runtime.GOOS}
38)
39
40// systemConf returns the machine's network configuration.
41func systemConf() *conf {
42	confOnce.Do(initConfVal)
43	return confVal
44}
45
46func initConfVal() {
47	dnsMode, debugLevel := goDebugNetDNS()
48	confVal.dnsDebugLevel = debugLevel
49	confVal.netGo = netGo || dnsMode == "go"
50	confVal.netCgo = netCgo || dnsMode == "cgo"
51
52	if confVal.dnsDebugLevel > 0 {
53		defer func() {
54			switch {
55			case confVal.netGo:
56				if netGo {
57					println("go package net: built with netgo build tag; using Go's DNS resolver")
58				} else {
59					println("go package net: GODEBUG setting forcing use of Go's resolver")
60				}
61			case confVal.forceCgoLookupHost:
62				println("go package net: using cgo DNS resolver")
63			default:
64				println("go package net: dynamic selection of DNS resolver")
65			}
66		}()
67	}
68
69	// Darwin pops up annoying dialog boxes if programs try to do
70	// their own DNS requests. So always use cgo instead, which
71	// avoids that.
72	if runtime.GOOS == "darwin" {
73		confVal.forceCgoLookupHost = true
74		return
75	}
76
77	// If any environment-specified resolver options are specified,
78	// force cgo. Note that LOCALDOMAIN can change behavior merely
79	// by being specified with the empty string.
80	_, localDomainDefined := syscall.Getenv("LOCALDOMAIN")
81	if os.Getenv("RES_OPTIONS") != "" ||
82		os.Getenv("HOSTALIASES") != "" ||
83		confVal.netCgo ||
84		localDomainDefined {
85		confVal.forceCgoLookupHost = true
86		return
87	}
88
89	// OpenBSD apparently lets you override the location of resolv.conf
90	// with ASR_CONFIG. If we notice that, defer to libc.
91	if runtime.GOOS == "openbsd" && os.Getenv("ASR_CONFIG") != "" {
92		confVal.forceCgoLookupHost = true
93		return
94	}
95
96	if runtime.GOOS != "openbsd" {
97		confVal.nss = parseNSSConfFile("/etc/nsswitch.conf")
98	}
99
100	confVal.resolv = dnsReadConfig("/etc/resolv.conf")
101	if confVal.resolv.err != nil && !os.IsNotExist(confVal.resolv.err) &&
102		!os.IsPermission(confVal.resolv.err) {
103		// If we can't read the resolv.conf file, assume it
104		// had something important in it and defer to cgo.
105		// libc's resolver might then fail too, but at least
106		// it wasn't our fault.
107		confVal.forceCgoLookupHost = true
108	}
109
110	if _, err := os.Stat("/etc/mdns.allow"); err == nil {
111		confVal.hasMDNSAllow = true
112	}
113}
114
115// canUseCgo reports whether calling cgo functions is allowed
116// for non-hostname lookups.
117func (c *conf) canUseCgo() bool {
118	return c.hostLookupOrder(nil, "") == hostLookupCgo
119}
120
121// hostLookupOrder determines which strategy to use to resolve hostname.
122// The provided Resolver is optional. nil means to not consider its options.
123func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder) {
124	if c.dnsDebugLevel > 1 {
125		defer func() {
126			print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n")
127		}()
128	}
129	fallbackOrder := hostLookupCgo
130	if c.netGo || r.preferGo() {
131		fallbackOrder = hostLookupFilesDNS
132	}
133	if c.forceCgoLookupHost || c.resolv.unknownOpt || c.goos == "android" {
134		return fallbackOrder
135	}
136	if bytealg.IndexByteString(hostname, '\\') != -1 || bytealg.IndexByteString(hostname, '%') != -1 {
137		// Don't deal with special form hostnames with backslashes
138		// or '%'.
139		return fallbackOrder
140	}
141
142	// OpenBSD is unique and doesn't use nsswitch.conf.
143	// It also doesn't support mDNS.
144	if c.goos == "openbsd" {
145		// OpenBSD's resolv.conf manpage says that a non-existent
146		// resolv.conf means "lookup" defaults to only "files",
147		// without DNS lookups.
148		if os.IsNotExist(c.resolv.err) {
149			return hostLookupFiles
150		}
151		lookup := c.resolv.lookup
152		if len(lookup) == 0 {
153			// https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5
154			// "If the lookup keyword is not used in the
155			// system's resolv.conf file then the assumed
156			// order is 'bind file'"
157			return hostLookupDNSFiles
158		}
159		if len(lookup) < 1 || len(lookup) > 2 {
160			return fallbackOrder
161		}
162		switch lookup[0] {
163		case "bind":
164			if len(lookup) == 2 {
165				if lookup[1] == "file" {
166					return hostLookupDNSFiles
167				}
168				return fallbackOrder
169			}
170			return hostLookupDNS
171		case "file":
172			if len(lookup) == 2 {
173				if lookup[1] == "bind" {
174					return hostLookupFilesDNS
175				}
176				return fallbackOrder
177			}
178			return hostLookupFiles
179		default:
180			return fallbackOrder
181		}
182	}
183
184	// Canonicalize the hostname by removing any trailing dot.
185	if stringsHasSuffix(hostname, ".") {
186		hostname = hostname[:len(hostname)-1]
187	}
188	if stringsHasSuffixFold(hostname, ".local") {
189		// Per RFC 6762, the ".local" TLD is special. And
190		// because Go's native resolver doesn't do mDNS or
191		// similar local resolution mechanisms, assume that
192		// libc might (via Avahi, etc) and use cgo.
193		return fallbackOrder
194	}
195
196	nss := c.nss
197	srcs := nss.sources["hosts"]
198	// If /etc/nsswitch.conf doesn't exist or doesn't specify any
199	// sources for "hosts", assume Go's DNS will work fine.
200	if os.IsNotExist(nss.err) || (nss.err == nil && len(srcs) == 0) {
201		if c.goos == "solaris" {
202			// illumos defaults to "nis [NOTFOUND=return] files"
203			return fallbackOrder
204		}
205		if c.goos == "linux" {
206			// glibc says the default is "dns [!UNAVAIL=return] files"
207			// https://www.gnu.org/software/libc/manual/html_node/Notes-on-NSS-Configuration-File.html.
208			return hostLookupDNSFiles
209		}
210		return hostLookupFilesDNS
211	}
212	if nss.err != nil {
213		// We failed to parse or open nsswitch.conf, so
214		// conservatively assume we should use cgo if it's
215		// available.
216		return fallbackOrder
217	}
218
219	var mdnsSource, filesSource, dnsSource bool
220	var first string
221	for _, src := range srcs {
222		if src.source == "myhostname" {
223			if isLocalhost(hostname) || isGateway(hostname) {
224				return fallbackOrder
225			}
226			hn, err := getHostname()
227			if err != nil || stringsEqualFold(hostname, hn) {
228				return fallbackOrder
229			}
230			continue
231		}
232		if src.source == "files" || src.source == "dns" {
233			if !src.standardCriteria() {
234				return fallbackOrder // non-standard; let libc deal with it.
235			}
236			if src.source == "files" {
237				filesSource = true
238			} else if src.source == "dns" {
239				dnsSource = true
240			}
241			if first == "" {
242				first = src.source
243			}
244			continue
245		}
246		if stringsHasPrefix(src.source, "mdns") {
247			// e.g. "mdns4", "mdns4_minimal"
248			// We already returned true before if it was *.local.
249			// libc wouldn't have found a hit on this anyway.
250			mdnsSource = true
251			continue
252		}
253		// Some source we don't know how to deal with.
254		return fallbackOrder
255	}
256
257	// We don't parse mdns.allow files. They're rare. If one
258	// exists, it might list other TLDs (besides .local) or even
259	// '*', so just let libc deal with it.
260	if mdnsSource && c.hasMDNSAllow {
261		return fallbackOrder
262	}
263
264	// Cases where Go can handle it without cgo and C thread
265	// overhead.
266	switch {
267	case filesSource && dnsSource:
268		if first == "files" {
269			return hostLookupFilesDNS
270		} else {
271			return hostLookupDNSFiles
272		}
273	case filesSource:
274		return hostLookupFiles
275	case dnsSource:
276		return hostLookupDNS
277	}
278
279	// Something weird. Let libc deal with it.
280	return fallbackOrder
281}
282
283// goDebugNetDNS parses the value of the GODEBUG "netdns" value.
284// The netdns value can be of the form:
285//    1       // debug level 1
286//    2       // debug level 2
287//    cgo     // use cgo for DNS lookups
288//    go      // use go for DNS lookups
289//    cgo+1   // use cgo for DNS lookups + debug level 1
290//    1+cgo   // same
291//    cgo+2   // same, but debug level 2
292// etc.
293func goDebugNetDNS() (dnsMode string, debugLevel int) {
294	goDebug := goDebugString("netdns")
295	parsePart := func(s string) {
296		if s == "" {
297			return
298		}
299		if '0' <= s[0] && s[0] <= '9' {
300			debugLevel, _, _ = dtoi(s)
301		} else {
302			dnsMode = s
303		}
304	}
305	if i := bytealg.IndexByteString(goDebug, '+'); i != -1 {
306		parsePart(goDebug[:i])
307		parsePart(goDebug[i+1:])
308		return
309	}
310	parsePart(goDebug)
311	return
312}
313
314// isLocalhost reports whether h should be considered a "localhost"
315// name for the myhostname NSS module.
316func isLocalhost(h string) bool {
317	return stringsEqualFold(h, "localhost") || stringsEqualFold(h, "localhost.localdomain") || stringsHasSuffixFold(h, ".localhost") || stringsHasSuffixFold(h, ".localhost.localdomain")
318}
319
320// isGateway reports whether h should be considered a "gateway"
321// name for the myhostname NSS module.
322func isGateway(h string) bool {
323	return stringsEqualFold(h, "gateway")
324}
325