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