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.
18 // The net package's name resolution is rather complicated.
19 // There are two main approaches, go and cgo.
20 // The cgo resolver uses C functions like getaddrinfo.
21 // The go resolver reads system files directly and
22 // sends DNS packets directly to servers.
24 // The netgo build tag prefers the go resolver.
25 // The netcgo build tag prefers the cgo resolver.
27 // The netgo build tag also prohibits the use of the cgo tool.
28 // However, on Darwin, Plan 9, and Windows the cgo resolver is still available.
29 // On those systems the cgo resolver does not require the cgo tool.
30 // (The term "cgo resolver" was locked in by GODEBUG settings
31 // at a time when the cgo resolver did require the cgo tool.)
33 // Adding netdns=go to GODEBUG will prefer the go resolver.
34 // Adding netdns=cgo to GODEBUG will prefer the cgo resolver.
36 // The Resolver struct has a PreferGo field that user code
37 // may set to prefer the go resolver. It is documented as being
38 // equivalent to adding netdns=go to GODEBUG.
40 // When deciding which resolver to use, we first check the PreferGo field.
41 // If that is not set, we check the GODEBUG setting.
42 // If that is not set, we check the netgo or netcgo build tag.
43 // If none of those are set, we normally prefer the go resolver by default.
44 // However, if the cgo resolver is available,
45 // there is a complex set of conditions for which we prefer the cgo resolver.
47 // Other files define the netGoBuildTag, netCgoBuildTag, and cgoAvailable
50 // conf is used to determine name resolution configuration.
52 netGo bool // prefer go approach, based on build tag and GODEBUG
53 netCgo bool // prefer cgo approach, based on build tag and GODEBUG
55 dnsDebugLevel int // from GODEBUG
57 preferCgo bool // if no explicit preference, use cgo
59 goos string // copy of runtime.GOOS, used for testing
60 mdnsTest mdnsTest // assume /etc/mdns.allow exists, for testing
63 // mdnsTest is for testing only.
67 mdnsFromSystem mdnsTest = iota
69 mdnsAssumeDoesNotExist
73 confOnce sync.Once // guards init of confVal via initConfVal
74 confVal = &conf{goos: runtime.GOOS}
77 // systemConf returns the machine's network configuration.
78 func systemConf() *conf {
79 confOnce.Do(initConfVal)
83 // initConfVal initializes confVal based on the environment
84 // that will not change during program execution.
86 dnsMode, debugLevel := goDebugNetDNS()
87 confVal.netGo = netGoBuildTag || dnsMode == "go"
88 confVal.netCgo = netCgoBuildTag || dnsMode == "cgo"
89 confVal.dnsDebugLevel = debugLevel
91 if confVal.dnsDebugLevel > 0 {
93 if confVal.dnsDebugLevel > 1 {
94 println("go package net: confVal.netCgo =", confVal.netCgo, " netGo =", confVal.netGo)
99 println("go package net: built with netgo build tag; using Go's DNS resolver")
101 println("go package net: GODEBUG setting forcing use of Go's resolver")
104 println("go package net: cgo resolver not supported; using Go's DNS resolver")
105 case confVal.netCgo || confVal.preferCgo:
106 println("go package net: using cgo DNS resolver")
108 println("go package net: dynamic selection of DNS resolver")
113 // The remainder of this function sets preferCgo based on
114 // conditions that will not change during program execution.
116 // By default, prefer the go resolver.
117 confVal.preferCgo = false
119 // If the cgo resolver is not available, we can't prefer it.
124 // Some operating systems always prefer the cgo resolver.
125 if goosPrefersCgo() {
126 confVal.preferCgo = true
130 // The remaining checks are specific to Unix systems.
131 switch runtime.GOOS {
132 case "plan9", "windows", "js", "wasip1":
136 // If any environment-specified resolver options are specified,
137 // prefer the cgo resolver.
138 // Note that LOCALDOMAIN can change behavior merely by being
139 // specified with the empty string.
140 _, localDomainDefined := syscall.Getenv("LOCALDOMAIN")
141 if localDomainDefined || os.Getenv("RES_OPTIONS") != "" || os.Getenv("HOSTALIASES") != "" {
142 confVal.preferCgo = true
146 // OpenBSD apparently lets you override the location of resolv.conf
147 // with ASR_CONFIG. If we notice that, defer to libc.
148 if runtime.GOOS == "openbsd" && os.Getenv("ASR_CONFIG") != "" {
149 confVal.preferCgo = true
154 // goosPrefersCgo reports whether the GOOS value passed in prefers
156 func goosPrefersCgo() bool {
157 switch runtime.GOOS {
158 // Historically on Windows and Plan 9 we prefer the
159 // cgo resolver (which doesn't use the cgo tool) rather than
160 // the go resolver. This is because originally these
161 // systems did not support the go resolver.
162 // Keep it this way for better compatibility.
163 // Perhaps we can revisit this some day.
164 case "windows", "plan9":
167 // Darwin pops up annoying dialog boxes if programs try to
168 // do their own DNS requests, so prefer cgo.
169 case "darwin", "ios":
172 // DNS requests don't work on Android, so prefer the cgo resolver.
182 // mustUseGoResolver reports whether a DNS lookup of any sort is
183 // required to use the go resolver. The provided Resolver is optional.
184 // This will report true if the cgo resolver is not available.
185 func (c *conf) mustUseGoResolver(r *Resolver) bool {
190 if runtime.GOOS == "plan9" {
191 // TODO(bradfitz): for now we only permit use of the PreferGo
192 // implementation when there's a non-nil Resolver with a
193 // non-nil Dialer. This is a sign that they the code is trying
194 // to use their DNS-speaking net.Conn (such as an in-memory
195 // DNS cache) and they don't want to actually hit the network.
196 // Once we add support for looking the default DNS servers
197 // from plan9, though, then we can relax this.
198 if r == nil || r.Dial == nil {
203 return c.netGo || r.preferGo()
206 // addrLookupOrder determines which strategy to use to resolve addresses.
207 // The provided Resolver is optional. nil means to not consider its options.
208 // It also returns dnsConfig when it was used to determine the lookup order.
209 func (c *conf) addrLookupOrder(r *Resolver, addr string) (ret hostLookupOrder, dnsConf *dnsConfig) {
210 if c.dnsDebugLevel > 1 {
212 print("go package net: addrLookupOrder(", addr, ") = ", ret.String(), "\n")
215 return c.lookupOrder(r, "")
218 // hostLookupOrder determines which strategy to use to resolve hostname.
219 // The provided Resolver is optional. nil means to not consider its options.
220 // It also returns dnsConfig when it was used to determine the lookup order.
221 func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder, dnsConf *dnsConfig) {
222 if c.dnsDebugLevel > 1 {
224 print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n")
227 return c.lookupOrder(r, hostname)
230 func (c *conf) lookupOrder(r *Resolver, hostname string) (ret hostLookupOrder, dnsConf *dnsConfig) {
231 // fallbackOrder is the order we return if we can't figure it out.
232 var fallbackOrder hostLookupOrder
235 if c.mustUseGoResolver(r) {
236 // Go resolver was explicitly requested
237 // or cgo resolver is not available.
238 // Figure out the order below.
239 fallbackOrder = hostLookupFilesDNS
242 // Cgo resolver was explicitly requested.
243 return hostLookupCgo, nil
244 } else if c.preferCgo {
245 // Given a choice, we prefer the cgo resolver.
246 return hostLookupCgo, nil
248 // Neither resolver was explicitly requested
249 // and we have no preference.
251 if bytealg.IndexByteString(hostname, '\\') != -1 || bytealg.IndexByteString(hostname, '%') != -1 {
252 // Don't deal with special form hostnames
253 // with backslashes or '%'.
254 return hostLookupCgo, nil
257 // If something is unrecognized, use cgo.
258 fallbackOrder = hostLookupCgo
262 // On systems that don't use /etc/resolv.conf or /etc/nsswitch.conf, we are done.
264 case "windows", "plan9", "android", "ios":
265 return fallbackOrder, nil
268 // Try to figure out the order to use for searches.
269 // If we don't recognize something, use fallbackOrder.
270 // That will use cgo unless the Go resolver was explicitly requested.
271 // If we do figure out the order, return something other
272 // than fallbackOrder to use the Go resolver with that order.
274 dnsConf = getSystemDNSConfig()
276 if canUseCgo && dnsConf.err != nil && !errors.Is(dnsConf.err, fs.ErrNotExist) && !errors.Is(dnsConf.err, fs.ErrPermission) {
277 // We can't read the resolv.conf file, so use cgo if we can.
278 return hostLookupCgo, dnsConf
281 if canUseCgo && dnsConf.unknownOpt {
282 // We didn't recognize something in resolv.conf,
283 // so use cgo if we can.
284 return hostLookupCgo, dnsConf
287 // OpenBSD is unique and doesn't use nsswitch.conf.
288 // It also doesn't support mDNS.
289 if c.goos == "openbsd" {
290 // OpenBSD's resolv.conf manpage says that a
291 // non-existent resolv.conf means "lookup" defaults
292 // to only "files", without DNS lookups.
293 if errors.Is(dnsConf.err, fs.ErrNotExist) {
294 return hostLookupFiles, dnsConf
297 lookup := dnsConf.lookup
298 if len(lookup) == 0 {
299 // https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5
300 // "If the lookup keyword is not used in the
301 // system's resolv.conf file then the assumed
302 // order is 'bind file'"
303 return hostLookupDNSFiles, dnsConf
305 if len(lookup) < 1 || len(lookup) > 2 {
306 // We don't recognize this format.
307 return fallbackOrder, dnsConf
311 if len(lookup) == 2 {
312 if lookup[1] == "file" {
313 return hostLookupDNSFiles, dnsConf
316 return fallbackOrder, dnsConf
318 return hostLookupDNS, dnsConf
320 if len(lookup) == 2 {
321 if lookup[1] == "bind" {
322 return hostLookupFilesDNS, dnsConf
325 return fallbackOrder, dnsConf
327 return hostLookupFiles, dnsConf
330 return fallbackOrder, dnsConf
333 // We always return before this point.
334 // The code below is for non-OpenBSD.
337 // Canonicalize the hostname by removing any trailing dot.
338 if stringsHasSuffix(hostname, ".") {
339 hostname = hostname[:len(hostname)-1]
341 if canUseCgo && stringsHasSuffixFold(hostname, ".local") {
342 // Per RFC 6762, the ".local" TLD is special. And
343 // because Go's native resolver doesn't do mDNS or
344 // similar local resolution mechanisms, assume that
345 // libc might (via Avahi, etc) and use cgo.
346 return hostLookupCgo, dnsConf
349 nss := getSystemNSS()
350 srcs := nss.sources["hosts"]
351 // If /etc/nsswitch.conf doesn't exist or doesn't specify any
352 // sources for "hosts", assume Go's DNS will work fine.
353 if errors.Is(nss.err, fs.ErrNotExist) || (nss.err == nil && len(srcs) == 0) {
354 if canUseCgo && c.goos == "solaris" {
355 // illumos defaults to
356 // "nis [NOTFOUND=return] files",
357 // which the go resolver doesn't support.
358 return hostLookupCgo, dnsConf
361 return hostLookupFilesDNS, dnsConf
364 // We failed to parse or open nsswitch.conf, so
365 // we have nothing to base an order on.
366 return fallbackOrder, dnsConf
369 var hasDNSSource bool
370 var hasDNSSourceChecked bool
372 var filesSource, dnsSource bool
374 for i, src := range srcs {
375 if src.source == "files" || src.source == "dns" {
376 if canUseCgo && !src.standardCriteria() {
377 // non-standard; let libc deal with it.
378 return hostLookupCgo, dnsConf
380 if src.source == "files" {
384 hasDNSSourceChecked = true
395 case hostname != "" && src.source == "myhostname":
396 // Let the cgo resolver handle myhostname
397 // if we are looking up the local hostname.
398 if isLocalhost(hostname) || isGateway(hostname) || isOutbound(hostname) {
399 return hostLookupCgo, dnsConf
401 hn, err := getHostname()
402 if err != nil || stringsEqualFold(hostname, hn) {
403 return hostLookupCgo, dnsConf
406 case hostname != "" && stringsHasPrefix(src.source, "mdns"):
407 // e.g. "mdns4", "mdns4_minimal"
408 // We already returned true before if it was *.local.
409 // libc wouldn't have found a hit on this anyway.
411 // We don't parse mdns.allow files. They're rare. If one
412 // exists, it might list other TLDs (besides .local) or even
413 // '*', so just let libc deal with it.
414 var haveMDNSAllow bool
417 _, err := os.Stat("/etc/mdns.allow")
418 if err != nil && !errors.Is(err, fs.ErrNotExist) {
419 // Let libc figure out what is going on.
420 return hostLookupCgo, dnsConf
422 haveMDNSAllow = err == nil
423 case mdnsAssumeExists:
425 case mdnsAssumeDoesNotExist:
426 haveMDNSAllow = false
429 return hostLookupCgo, dnsConf
433 // Some source we don't know how to deal with.
434 return hostLookupCgo, dnsConf
438 if !hasDNSSourceChecked {
439 hasDNSSourceChecked = true
440 for _, v := range srcs[i+1:] {
441 if v.source == "dns" {
448 // If we saw a source we don't recognize, which can only
449 // happen if we can't use the cgo resolver, treat it as DNS,
450 // but only when there is no dns in all other sources.
459 // Cases where Go can handle it without cgo and C thread overhead,
460 // or where the Go resolver has been forced.
462 case filesSource && dnsSource:
463 if first == "files" {
464 return hostLookupFilesDNS, dnsConf
466 return hostLookupDNSFiles, dnsConf
469 return hostLookupFiles, dnsConf
471 return hostLookupDNS, dnsConf
474 // Something weird. Fallback to the default.
475 return fallbackOrder, dnsConf
478 var netdns = godebug.New("netdns")
480 // goDebugNetDNS parses the value of the GODEBUG "netdns" value.
481 // The netdns value can be of the form:
483 // 1 // debug level 1
484 // 2 // debug level 2
485 // cgo // use cgo for DNS lookups
486 // go // use go for DNS lookups
487 // cgo+1 // use cgo for DNS lookups + debug level 1
489 // cgo+2 // same, but debug level 2
492 func goDebugNetDNS() (dnsMode string, debugLevel int) {
493 goDebug := netdns.Value()
494 parsePart := func(s string) {
498 if '0' <= s[0] && s[0] <= '9' {
499 debugLevel, _, _ = dtoi(s)
504 if i := bytealg.IndexByteString(goDebug, '+'); i != -1 {
505 parsePart(goDebug[:i])
506 parsePart(goDebug[i+1:])
513 // isLocalhost reports whether h should be considered a "localhost"
514 // name for the myhostname NSS module.
515 func isLocalhost(h string) bool {
516 return stringsEqualFold(h, "localhost") || stringsEqualFold(h, "localhost.localdomain") || stringsHasSuffixFold(h, ".localhost") || stringsHasSuffixFold(h, ".localhost.localdomain")
519 // isGateway reports whether h should be considered a "gateway"
520 // name for the myhostname NSS module.
521 func isGateway(h string) bool {
522 return stringsEqualFold(h, "_gateway")
525 // isOutbound reports whether h should be considered a "outbound"
526 // name for the myhostname NSS module.
527 func isOutbound(h string) bool {
528 return stringsEqualFold(h, "_outbound")