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