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
5package net
6
7import (
8	"errors"
9	"internal/bytealg"
10	"internal/godebug"
11	"internal/stringslite"
12	"io/fs"
13	"os"
14	"runtime"
15	"sync"
16	"syscall"
17)
18
19// The net package's name resolution is rather complicated.
20// There are two main approaches, go and cgo.
21// The cgo resolver uses C functions like getaddrinfo.
22// The go resolver reads system files directly and
23// sends DNS packets directly to servers.
24//
25// The netgo build tag prefers the go resolver.
26// The netcgo build tag prefers the cgo resolver.
27//
28// The netgo build tag also prohibits the use of the cgo tool.
29// However, on Darwin, Plan 9, and Windows the cgo resolver is still available.
30// On those systems the cgo resolver does not require the cgo tool.
31// (The term "cgo resolver" was locked in by GODEBUG settings
32// at a time when the cgo resolver did require the cgo tool.)
33//
34// Adding netdns=go to GODEBUG will prefer the go resolver.
35// Adding netdns=cgo to GODEBUG will prefer the cgo resolver.
36//
37// The Resolver struct has a PreferGo field that user code
38// may set to prefer the go resolver. It is documented as being
39// equivalent to adding netdns=go to GODEBUG.
40//
41// When deciding which resolver to use, we first check the PreferGo field.
42// If that is not set, we check the GODEBUG setting.
43// If that is not set, we check the netgo or netcgo build tag.
44// If none of those are set, we normally prefer the go resolver by default.
45// However, if the cgo resolver is available,
46// there is a complex set of conditions for which we prefer the cgo resolver.
47//
48// Other files define the netGoBuildTag, netCgoBuildTag, and cgoAvailable
49// constants.
50
51// conf is used to determine name resolution configuration.
52type conf struct {
53	netGo  bool // prefer go approach, based on build tag and GODEBUG
54	netCgo bool // prefer cgo approach, based on build tag and GODEBUG
55
56	dnsDebugLevel int // from GODEBUG
57
58	preferCgo bool // if no explicit preference, use cgo
59
60	goos     string   // copy of runtime.GOOS, used for testing
61	mdnsTest mdnsTest // assume /etc/mdns.allow exists, for testing
62}
63
64// mdnsTest is for testing only.
65type mdnsTest int
66
67const (
68	mdnsFromSystem mdnsTest = iota
69	mdnsAssumeExists
70	mdnsAssumeDoesNotExist
71)
72
73var (
74	confOnce sync.Once // guards init of confVal via initConfVal
75	confVal  = &conf{goos: runtime.GOOS}
76)
77
78// systemConf returns the machine's network configuration.
79func systemConf() *conf {
80	confOnce.Do(initConfVal)
81	return confVal
82}
83
84// initConfVal initializes confVal based on the environment
85// that will not change during program execution.
86func initConfVal() {
87	dnsMode, debugLevel := goDebugNetDNS()
88	confVal.netGo = netGoBuildTag || dnsMode == "go"
89	confVal.netCgo = netCgoBuildTag || dnsMode == "cgo"
90	confVal.dnsDebugLevel = debugLevel
91
92	if confVal.dnsDebugLevel > 0 {
93		defer func() {
94			if confVal.dnsDebugLevel > 1 {
95				println("go package net: confVal.netCgo =", confVal.netCgo, " netGo =", confVal.netGo)
96			}
97			switch {
98			case confVal.netGo:
99				if netGoBuildTag {
100					println("go package net: built with netgo build tag; using Go's DNS resolver")
101				} else {
102					println("go package net: GODEBUG setting forcing use of Go's resolver")
103				}
104			case !cgoAvailable:
105				println("go package net: cgo resolver not supported; using Go's DNS resolver")
106			case confVal.netCgo || confVal.preferCgo:
107				println("go package net: using cgo DNS resolver")
108			default:
109				println("go package net: dynamic selection of DNS resolver")
110			}
111		}()
112	}
113
114	// The remainder of this function sets preferCgo based on
115	// conditions that will not change during program execution.
116
117	// By default, prefer the go resolver.
118	confVal.preferCgo = false
119
120	// If the cgo resolver is not available, we can't prefer it.
121	if !cgoAvailable {
122		return
123	}
124
125	// Some operating systems always prefer the cgo resolver.
126	if goosPrefersCgo() {
127		confVal.preferCgo = true
128		return
129	}
130
131	// The remaining checks are specific to Unix systems.
132	switch runtime.GOOS {
133	case "plan9", "windows", "js", "wasip1":
134		return
135	}
136
137	// If any environment-specified resolver options are specified,
138	// prefer the cgo resolver.
139	// Note that LOCALDOMAIN can change behavior merely by being
140	// specified with the empty string.
141	_, localDomainDefined := syscall.Getenv("LOCALDOMAIN")
142	if localDomainDefined || os.Getenv("RES_OPTIONS") != "" || os.Getenv("HOSTALIASES") != "" {
143		confVal.preferCgo = true
144		return
145	}
146
147	// OpenBSD apparently lets you override the location of resolv.conf
148	// with ASR_CONFIG. If we notice that, defer to libc.
149	if runtime.GOOS == "openbsd" && os.Getenv("ASR_CONFIG") != "" {
150		confVal.preferCgo = true
151		return
152	}
153}
154
155// goosPrefersCgo reports whether the GOOS value passed in prefers
156// the cgo resolver.
157func goosPrefersCgo() bool {
158	switch runtime.GOOS {
159	// Historically on Windows and Plan 9 we prefer the
160	// cgo resolver (which doesn't use the cgo tool) rather than
161	// the go resolver. This is because originally these
162	// systems did not support the go resolver.
163	// Keep it this way for better compatibility.
164	// Perhaps we can revisit this some day.
165	case "windows", "plan9":
166		return true
167
168	// Darwin pops up annoying dialog boxes if programs try to
169	// do their own DNS requests, so prefer cgo.
170	case "darwin", "ios":
171		return true
172
173	// DNS requests don't work on Android, so prefer the cgo resolver.
174	// Issue #10714.
175	case "android":
176		return true
177
178	default:
179		return false
180	}
181}
182
183// mustUseGoResolver reports whether a DNS lookup of any sort is
184// required to use the go resolver. The provided Resolver is optional.
185// This will report true if the cgo resolver is not available.
186func (c *conf) mustUseGoResolver(r *Resolver) bool {
187	if !cgoAvailable {
188		return true
189	}
190
191	if runtime.GOOS == "plan9" {
192		// TODO(bradfitz): for now we only permit use of the PreferGo
193		// implementation when there's a non-nil Resolver with a
194		// non-nil Dialer. This is a sign that the code is trying
195		// to use their DNS-speaking net.Conn (such as an in-memory
196		// DNS cache) and they don't want to actually hit the network.
197		// Once we add support for looking the default DNS servers
198		// from plan9, though, then we can relax this.
199		if r == nil || r.Dial == nil {
200			return false
201		}
202	}
203
204	return c.netGo || r.preferGo()
205}
206
207// addrLookupOrder determines which strategy to use to resolve addresses.
208// The provided Resolver is optional. nil means to not consider its options.
209// It also returns dnsConfig when it was used to determine the lookup order.
210func (c *conf) addrLookupOrder(r *Resolver, addr string) (ret hostLookupOrder, dnsConf *dnsConfig) {
211	if c.dnsDebugLevel > 1 {
212		defer func() {
213			print("go package net: addrLookupOrder(", addr, ") = ", ret.String(), "\n")
214		}()
215	}
216	return c.lookupOrder(r, "")
217}
218
219// hostLookupOrder determines which strategy to use to resolve hostname.
220// The provided Resolver is optional. nil means to not consider its options.
221// It also returns dnsConfig when it was used to determine the lookup order.
222func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder, dnsConf *dnsConfig) {
223	if c.dnsDebugLevel > 1 {
224		defer func() {
225			print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n")
226		}()
227	}
228	return c.lookupOrder(r, hostname)
229}
230
231func (c *conf) lookupOrder(r *Resolver, hostname string) (ret hostLookupOrder, dnsConf *dnsConfig) {
232	// fallbackOrder is the order we return if we can't figure it out.
233	var fallbackOrder hostLookupOrder
234
235	var canUseCgo bool
236	if c.mustUseGoResolver(r) {
237		// Go resolver was explicitly requested
238		// or cgo resolver is not available.
239		// Figure out the order below.
240		fallbackOrder = hostLookupFilesDNS
241		canUseCgo = false
242	} else if c.netCgo {
243		// Cgo resolver was explicitly requested.
244		return hostLookupCgo, nil
245	} else if c.preferCgo {
246		// Given a choice, we prefer the cgo resolver.
247		return hostLookupCgo, nil
248	} else {
249		// Neither resolver was explicitly requested
250		// and we have no preference.
251
252		if bytealg.IndexByteString(hostname, '\\') != -1 || bytealg.IndexByteString(hostname, '%') != -1 {
253			// Don't deal with special form hostnames
254			// with backslashes or '%'.
255			return hostLookupCgo, nil
256		}
257
258		// If something is unrecognized, use cgo.
259		fallbackOrder = hostLookupCgo
260		canUseCgo = true
261	}
262
263	// On systems that don't use /etc/resolv.conf or /etc/nsswitch.conf, we are done.
264	switch c.goos {
265	case "windows", "plan9", "android", "ios":
266		return fallbackOrder, nil
267	}
268
269	// Try to figure out the order to use for searches.
270	// If we don't recognize something, use fallbackOrder.
271	// That will use cgo unless the Go resolver was explicitly requested.
272	// If we do figure out the order, return something other
273	// than fallbackOrder to use the Go resolver with that order.
274
275	dnsConf = getSystemDNSConfig()
276
277	if canUseCgo && dnsConf.err != nil && !errors.Is(dnsConf.err, fs.ErrNotExist) && !errors.Is(dnsConf.err, fs.ErrPermission) {
278		// We can't read the resolv.conf file, so use cgo if we can.
279		return hostLookupCgo, dnsConf
280	}
281
282	if canUseCgo && dnsConf.unknownOpt {
283		// We didn't recognize something in resolv.conf,
284		// so use cgo if we can.
285		return hostLookupCgo, dnsConf
286	}
287
288	// OpenBSD is unique and doesn't use nsswitch.conf.
289	// It also doesn't support mDNS.
290	if c.goos == "openbsd" {
291		// OpenBSD's resolv.conf manpage says that a
292		// non-existent resolv.conf means "lookup" defaults
293		// to only "files", without DNS lookups.
294		if errors.Is(dnsConf.err, fs.ErrNotExist) {
295			return hostLookupFiles, dnsConf
296		}
297
298		lookup := dnsConf.lookup
299		if len(lookup) == 0 {
300			// https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5
301			// "If the lookup keyword is not used in the
302			// system's resolv.conf file then the assumed
303			// order is 'bind file'"
304			return hostLookupDNSFiles, dnsConf
305		}
306		if len(lookup) < 1 || len(lookup) > 2 {
307			// We don't recognize this format.
308			return fallbackOrder, dnsConf
309		}
310		switch lookup[0] {
311		case "bind":
312			if len(lookup) == 2 {
313				if lookup[1] == "file" {
314					return hostLookupDNSFiles, dnsConf
315				}
316				// Unrecognized.
317				return fallbackOrder, dnsConf
318			}
319			return hostLookupDNS, dnsConf
320		case "file":
321			if len(lookup) == 2 {
322				if lookup[1] == "bind" {
323					return hostLookupFilesDNS, dnsConf
324				}
325				// Unrecognized.
326				return fallbackOrder, dnsConf
327			}
328			return hostLookupFiles, dnsConf
329		default:
330			// Unrecognized.
331			return fallbackOrder, dnsConf
332		}
333
334		// We always return before this point.
335		// The code below is for non-OpenBSD.
336	}
337
338	// Canonicalize the hostname by removing any trailing dot.
339	hostname = stringslite.TrimSuffix(hostname, ".")
340
341	nss := getSystemNSS()
342	srcs := nss.sources["hosts"]
343	// If /etc/nsswitch.conf doesn't exist or doesn't specify any
344	// sources for "hosts", assume Go's DNS will work fine.
345	if errors.Is(nss.err, fs.ErrNotExist) || (nss.err == nil && len(srcs) == 0) {
346		if canUseCgo && c.goos == "solaris" {
347			// illumos defaults to
348			// "nis [NOTFOUND=return] files",
349			// which the go resolver doesn't support.
350			return hostLookupCgo, dnsConf
351		}
352
353		return hostLookupFilesDNS, dnsConf
354	}
355	if nss.err != nil {
356		// We failed to parse or open nsswitch.conf, so
357		// we have nothing to base an order on.
358		return fallbackOrder, dnsConf
359	}
360
361	var hasDNSSource bool
362	var hasDNSSourceChecked bool
363
364	var filesSource, dnsSource bool
365	var first string
366	for i, src := range srcs {
367		if src.source == "files" || src.source == "dns" {
368			if canUseCgo && !src.standardCriteria() {
369				// non-standard; let libc deal with it.
370				return hostLookupCgo, dnsConf
371			}
372			if src.source == "files" {
373				filesSource = true
374			} else {
375				hasDNSSource = true
376				hasDNSSourceChecked = true
377				dnsSource = true
378			}
379			if first == "" {
380				first = src.source
381			}
382			continue
383		}
384
385		if canUseCgo {
386			switch {
387			case hostname != "" && src.source == "myhostname":
388				// Let the cgo resolver handle myhostname
389				// if we are looking up the local hostname.
390				if isLocalhost(hostname) || isGateway(hostname) || isOutbound(hostname) {
391					return hostLookupCgo, dnsConf
392				}
393				hn, err := getHostname()
394				if err != nil || stringsEqualFold(hostname, hn) {
395					return hostLookupCgo, dnsConf
396				}
397				continue
398			case hostname != "" && stringslite.HasPrefix(src.source, "mdns"):
399				if stringsHasSuffixFold(hostname, ".local") {
400					// Per RFC 6762, the ".local" TLD is special. And
401					// because Go's native resolver doesn't do mDNS or
402					// similar local resolution mechanisms, assume that
403					// libc might (via Avahi, etc) and use cgo.
404					return hostLookupCgo, dnsConf
405				}
406
407				// We don't parse mdns.allow files. They're rare. If one
408				// exists, it might list other TLDs (besides .local) or even
409				// '*', so just let libc deal with it.
410				var haveMDNSAllow bool
411				switch c.mdnsTest {
412				case mdnsFromSystem:
413					_, err := os.Stat("/etc/mdns.allow")
414					if err != nil && !errors.Is(err, fs.ErrNotExist) {
415						// Let libc figure out what is going on.
416						return hostLookupCgo, dnsConf
417					}
418					haveMDNSAllow = err == nil
419				case mdnsAssumeExists:
420					haveMDNSAllow = true
421				case mdnsAssumeDoesNotExist:
422					haveMDNSAllow = false
423				}
424				if haveMDNSAllow {
425					return hostLookupCgo, dnsConf
426				}
427				continue
428			default:
429				// Some source we don't know how to deal with.
430				return hostLookupCgo, dnsConf
431			}
432		}
433
434		if !hasDNSSourceChecked {
435			hasDNSSourceChecked = true
436			for _, v := range srcs[i+1:] {
437				if v.source == "dns" {
438					hasDNSSource = true
439					break
440				}
441			}
442		}
443
444		// If we saw a source we don't recognize, which can only
445		// happen if we can't use the cgo resolver, treat it as DNS,
446		// but only when there is no dns in all other sources.
447		if !hasDNSSource {
448			dnsSource = true
449			if first == "" {
450				first = "dns"
451			}
452		}
453	}
454
455	// Cases where Go can handle it without cgo and C thread overhead,
456	// or where the Go resolver has been forced.
457	switch {
458	case filesSource && dnsSource:
459		if first == "files" {
460			return hostLookupFilesDNS, dnsConf
461		} else {
462			return hostLookupDNSFiles, dnsConf
463		}
464	case filesSource:
465		return hostLookupFiles, dnsConf
466	case dnsSource:
467		return hostLookupDNS, dnsConf
468	}
469
470	// Something weird. Fallback to the default.
471	return fallbackOrder, dnsConf
472}
473
474var netdns = godebug.New("netdns")
475
476// goDebugNetDNS parses the value of the GODEBUG "netdns" value.
477// The netdns value can be of the form:
478//
479//	1       // debug level 1
480//	2       // debug level 2
481//	cgo     // use cgo for DNS lookups
482//	go      // use go for DNS lookups
483//	cgo+1   // use cgo for DNS lookups + debug level 1
484//	1+cgo   // same
485//	cgo+2   // same, but debug level 2
486//
487// etc.
488func goDebugNetDNS() (dnsMode string, debugLevel int) {
489	goDebug := netdns.Value()
490	parsePart := func(s string) {
491		if s == "" {
492			return
493		}
494		if '0' <= s[0] && s[0] <= '9' {
495			debugLevel, _, _ = dtoi(s)
496		} else {
497			dnsMode = s
498		}
499	}
500	if i := bytealg.IndexByteString(goDebug, '+'); i != -1 {
501		parsePart(goDebug[:i])
502		parsePart(goDebug[i+1:])
503		return
504	}
505	parsePart(goDebug)
506	return
507}
508
509// isLocalhost reports whether h should be considered a "localhost"
510// name for the myhostname NSS module.
511func isLocalhost(h string) bool {
512	return stringsEqualFold(h, "localhost") || stringsEqualFold(h, "localhost.localdomain") || stringsHasSuffixFold(h, ".localhost") || stringsHasSuffixFold(h, ".localhost.localdomain")
513}
514
515// isGateway reports whether h should be considered a "gateway"
516// name for the myhostname NSS module.
517func isGateway(h string) bool {
518	return stringsEqualFold(h, "_gateway")
519}
520
521// isOutbound reports whether h should be considered an "outbound"
522// name for the myhostname NSS module.
523func isOutbound(h string) bool {
524	return stringsEqualFold(h, "_outbound")
525}
526