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