diff options
Diffstat (limited to 'src/net/conf.go')
-rw-r--r-- | src/net/conf.go | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/src/net/conf.go b/src/net/conf.go new file mode 100644 index 0000000..77cc635 --- /dev/null +++ b/src/net/conf.go @@ -0,0 +1,523 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !js + +package net + +import ( + "errors" + "internal/bytealg" + "internal/godebug" + "io/fs" + "os" + "runtime" + "sync" + "syscall" +) + +// The net package's name resolution is rather complicated. +// There are two main approaches, go and cgo. +// The cgo resolver uses C functions like getaddrinfo. +// The go resolver reads system files directly and +// sends DNS packets directly to servers. +// +// The netgo build tag prefers the go resolver. +// The netcgo build tag prefers the cgo resolver. +// +// The netgo build tag also prohibits the use of the cgo tool. +// However, on Darwin, Plan 9, and Windows the cgo resolver is still available. +// On those systems the cgo resolver does not require the cgo tool. +// (The term "cgo resolver" was locked in by GODEBUG settings +// at a time when the cgo resolver did require the cgo tool.) +// +// Adding netdns=go to GODEBUG will prefer the go resolver. +// Adding netdns=cgo to GODEBUG will prefer the cgo resolver. +// +// The Resolver struct has a PreferGo field that user code +// may set to prefer the go resolver. It is documented as being +// equivalent to adding netdns=go to GODEBUG. +// +// When deciding which resolver to use, we first check the PreferGo field. +// If that is not set, we check the GODEBUG setting. +// If that is not set, we check the netgo or netcgo build tag. +// If none of those are set, we normally prefer the go resolver by default. +// However, if the cgo resolver is available, +// there is a complex set of conditions for which we prefer the cgo resolver. +// +// Other files define the netGoBuildTag, netCgoBuildTag, and cgoAvailable +// constants. + +// conf is used to determine name resolution configuration. +type conf struct { + netGo bool // prefer go approach, based on build tag and GODEBUG + netCgo bool // prefer cgo approach, based on build tag and GODEBUG + + dnsDebugLevel int // from GODEBUG + + preferCgo bool // if no explicit preference, use cgo + + goos string // copy of runtime.GOOS, used for testing + mdnsTest mdnsTest // assume /etc/mdns.allow exists, for testing +} + +// mdnsTest is for testing only. +type mdnsTest int + +const ( + mdnsFromSystem mdnsTest = iota + mdnsAssumeExists + mdnsAssumeDoesNotExist +) + +var ( + confOnce sync.Once // guards init of confVal via initConfVal + confVal = &conf{goos: runtime.GOOS} +) + +// systemConf returns the machine's network configuration. +func systemConf() *conf { + confOnce.Do(initConfVal) + return confVal +} + +// initConfVal initializes confVal based on the environment +// that will not change during program execution. +func initConfVal() { + dnsMode, debugLevel := goDebugNetDNS() + confVal.netGo = netGoBuildTag || dnsMode == "go" + confVal.netCgo = netCgoBuildTag || dnsMode == "cgo" + confVal.dnsDebugLevel = debugLevel + + if confVal.dnsDebugLevel > 0 { + defer func() { + if confVal.dnsDebugLevel > 1 { + println("go package net: confVal.netCgo =", confVal.netCgo, " netGo =", confVal.netGo) + } + switch { + case confVal.netGo: + if netGoBuildTag { + println("go package net: built with netgo build tag; using Go's DNS resolver") + } else { + println("go package net: GODEBUG setting forcing use of Go's resolver") + } + case !cgoAvailable: + println("go package net: cgo resolver not supported; using Go's DNS resolver") + case confVal.netCgo || confVal.preferCgo: + println("go package net: using cgo DNS resolver") + default: + println("go package net: dynamic selection of DNS resolver") + } + }() + } + + // The remainder of this function sets preferCgo based on + // conditions that will not change during program execution. + + // By default, prefer the go resolver. + confVal.preferCgo = false + + // If the cgo resolver is not available, we can't prefer it. + if !cgoAvailable { + return + } + + // Some operating systems always prefer the cgo resolver. + if goosPrefersCgo() { + confVal.preferCgo = true + return + } + + // The remaining checks are specific to Unix systems. + switch runtime.GOOS { + case "plan9", "windows", "js", "wasip1": + return + } + + // If any environment-specified resolver options are specified, + // prefer the cgo resolver. + // Note that LOCALDOMAIN can change behavior merely by being + // specified with the empty string. + _, localDomainDefined := syscall.Getenv("LOCALDOMAIN") + if localDomainDefined || os.Getenv("RES_OPTIONS") != "" || os.Getenv("HOSTALIASES") != "" { + confVal.preferCgo = true + return + } + + // OpenBSD apparently lets you override the location of resolv.conf + // with ASR_CONFIG. If we notice that, defer to libc. + if runtime.GOOS == "openbsd" && os.Getenv("ASR_CONFIG") != "" { + confVal.preferCgo = true + return + } +} + +// goosPreferCgo reports whether the GOOS value passed in prefers +// the cgo resolver. +func goosPrefersCgo() bool { + switch runtime.GOOS { + // Historically on Windows and Plan 9 we prefer the + // cgo resolver (which doesn't use the cgo tool) rather than + // the go resolver. This is because originally these + // systems did not support the go resolver. + // Keep it this way for better compatibility. + // Perhaps we can revisit this some day. + case "windows", "plan9": + return true + + // Darwin pops up annoying dialog boxes if programs try to + // do their own DNS requests, so prefer cgo. + case "darwin", "ios": + return true + + // DNS requests don't work on Android, so prefer the cgo resolver. + // Issue #10714. + case "android": + return true + + default: + return false + } +} + +// mustUseGoResolver reports whether a DNS lookup of any sort is +// required to use the go resolver. The provided Resolver is optional. +// This will report true if the cgo resolver is not available. +func (c *conf) mustUseGoResolver(r *Resolver) bool { + return c.netGo || r.preferGo() || !cgoAvailable +} + +// addrLookupOrder determines which strategy to use to resolve addresses. +// The provided Resolver is optional. nil means to not consider its options. +// It also returns dnsConfig when it was used to determine the lookup order. +func (c *conf) addrLookupOrder(r *Resolver, addr string) (ret hostLookupOrder, dnsConf *dnsConfig) { + if c.dnsDebugLevel > 1 { + defer func() { + print("go package net: addrLookupOrder(", addr, ") = ", ret.String(), "\n") + }() + } + return c.lookupOrder(r, "") +} + +// hostLookupOrder determines which strategy to use to resolve hostname. +// The provided Resolver is optional. nil means to not consider its options. +// It also returns dnsConfig when it was used to determine the lookup order. +func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder, dnsConf *dnsConfig) { + if c.dnsDebugLevel > 1 { + defer func() { + print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n") + }() + } + return c.lookupOrder(r, hostname) +} + +func (c *conf) lookupOrder(r *Resolver, hostname string) (ret hostLookupOrder, dnsConf *dnsConfig) { + // fallbackOrder is the order we return if we can't figure it out. + var fallbackOrder hostLookupOrder + + var canUseCgo bool + if c.mustUseGoResolver(r) { + // Go resolver was explicitly requested + // or cgo resolver is not available. + // Figure out the order below. + switch c.goos { + case "windows": + // TODO(bradfitz): implement files-based + // lookup on Windows too? I guess /etc/hosts + // kinda exists on Windows. But for now, only + // do DNS. + fallbackOrder = hostLookupDNS + default: + fallbackOrder = hostLookupFilesDNS + } + canUseCgo = false + } else if c.netCgo { + // Cgo resolver was explicitly requested. + return hostLookupCgo, nil + } else if c.preferCgo { + // Given a choice, we prefer the cgo resolver. + return hostLookupCgo, nil + } else { + // Neither resolver was explicitly requested + // and we have no preference. + + if bytealg.IndexByteString(hostname, '\\') != -1 || bytealg.IndexByteString(hostname, '%') != -1 { + // Don't deal with special form hostnames + // with backslashes or '%'. + return hostLookupCgo, nil + } + + // If something is unrecognized, use cgo. + fallbackOrder = hostLookupCgo + canUseCgo = true + } + + // On systems that don't use /etc/resolv.conf or /etc/nsswitch.conf, we are done. + switch c.goos { + case "windows", "plan9", "android", "ios": + return fallbackOrder, nil + } + + // Try to figure out the order to use for searches. + // If we don't recognize something, use fallbackOrder. + // That will use cgo unless the Go resolver was explicitly requested. + // If we do figure out the order, return something other + // than fallbackOrder to use the Go resolver with that order. + + dnsConf = getSystemDNSConfig() + + if canUseCgo && dnsConf.err != nil && !errors.Is(dnsConf.err, fs.ErrNotExist) && !errors.Is(dnsConf.err, fs.ErrPermission) { + // We can't read the resolv.conf file, so use cgo if we can. + return hostLookupCgo, dnsConf + } + + if canUseCgo && dnsConf.unknownOpt { + // We didn't recognize something in resolv.conf, + // so use cgo if we can. + return hostLookupCgo, dnsConf + } + + // OpenBSD is unique and doesn't use nsswitch.conf. + // It also doesn't support mDNS. + if c.goos == "openbsd" { + // OpenBSD's resolv.conf manpage says that a + // non-existent resolv.conf means "lookup" defaults + // to only "files", without DNS lookups. + if errors.Is(dnsConf.err, fs.ErrNotExist) { + return hostLookupFiles, dnsConf + } + + lookup := dnsConf.lookup + if len(lookup) == 0 { + // https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5 + // "If the lookup keyword is not used in the + // system's resolv.conf file then the assumed + // order is 'bind file'" + return hostLookupDNSFiles, dnsConf + } + if len(lookup) < 1 || len(lookup) > 2 { + // We don't recognize this format. + return fallbackOrder, dnsConf + } + switch lookup[0] { + case "bind": + if len(lookup) == 2 { + if lookup[1] == "file" { + return hostLookupDNSFiles, dnsConf + } + // Unrecognized. + return fallbackOrder, dnsConf + } + return hostLookupDNS, dnsConf + case "file": + if len(lookup) == 2 { + if lookup[1] == "bind" { + return hostLookupFilesDNS, dnsConf + } + // Unrecognized. + return fallbackOrder, dnsConf + } + return hostLookupFiles, dnsConf + default: + // Unrecognized. + return fallbackOrder, dnsConf + } + + // We always return before this point. + // The code below is for non-OpenBSD. + } + + // Canonicalize the hostname by removing any trailing dot. + if stringsHasSuffix(hostname, ".") { + hostname = hostname[:len(hostname)-1] + } + if canUseCgo && stringsHasSuffixFold(hostname, ".local") { + // Per RFC 6762, the ".local" TLD is special. And + // because Go's native resolver doesn't do mDNS or + // similar local resolution mechanisms, assume that + // libc might (via Avahi, etc) and use cgo. + return hostLookupCgo, dnsConf + } + + nss := getSystemNSS() + srcs := nss.sources["hosts"] + // If /etc/nsswitch.conf doesn't exist or doesn't specify any + // sources for "hosts", assume Go's DNS will work fine. + if errors.Is(nss.err, fs.ErrNotExist) || (nss.err == nil && len(srcs) == 0) { + if canUseCgo && c.goos == "solaris" { + // illumos defaults to + // "nis [NOTFOUND=return] files", + // which the go resolver doesn't support. + return hostLookupCgo, dnsConf + } + + return hostLookupFilesDNS, dnsConf + } + if nss.err != nil { + // We failed to parse or open nsswitch.conf, so + // we have nothing to base an order on. + return fallbackOrder, dnsConf + } + + var hasDNSSource bool + var hasDNSSourceChecked bool + + var filesSource, dnsSource bool + var first string + for i, src := range srcs { + if src.source == "files" || src.source == "dns" { + if canUseCgo && !src.standardCriteria() { + // non-standard; let libc deal with it. + return hostLookupCgo, dnsConf + } + if src.source == "files" { + filesSource = true + } else { + hasDNSSource = true + hasDNSSourceChecked = true + dnsSource = true + } + if first == "" { + first = src.source + } + continue + } + + if canUseCgo { + switch { + case hostname != "" && src.source == "myhostname": + // Let the cgo resolver handle myhostname + // if we are looking up the local hostname. + if isLocalhost(hostname) || isGateway(hostname) || isOutbound(hostname) { + return hostLookupCgo, dnsConf + } + hn, err := getHostname() + if err != nil || stringsEqualFold(hostname, hn) { + return hostLookupCgo, dnsConf + } + continue + case hostname != "" && stringsHasPrefix(src.source, "mdns"): + // e.g. "mdns4", "mdns4_minimal" + // We already returned true before if it was *.local. + // libc wouldn't have found a hit on this anyway. + + // We don't parse mdns.allow files. They're rare. If one + // exists, it might list other TLDs (besides .local) or even + // '*', so just let libc deal with it. + var haveMDNSAllow bool + switch c.mdnsTest { + case mdnsFromSystem: + _, err := os.Stat("/etc/mdns.allow") + if err != nil && !errors.Is(err, fs.ErrNotExist) { + // Let libc figure out what is going on. + return hostLookupCgo, dnsConf + } + haveMDNSAllow = err == nil + case mdnsAssumeExists: + haveMDNSAllow = true + case mdnsAssumeDoesNotExist: + haveMDNSAllow = false + } + if haveMDNSAllow { + return hostLookupCgo, dnsConf + } + continue + default: + // Some source we don't know how to deal with. + return hostLookupCgo, dnsConf + } + } + + if !hasDNSSourceChecked { + hasDNSSourceChecked = true + for _, v := range srcs[i+1:] { + if v.source == "dns" { + hasDNSSource = true + break + } + } + } + + // If we saw a source we don't recognize, which can only + // happen if we can't use the cgo resolver, treat it as DNS, + // but only when there is no dns in all other sources. + if !hasDNSSource { + dnsSource = true + if first == "" { + first = "dns" + } + } + } + + // Cases where Go can handle it without cgo and C thread overhead, + // or where the Go resolver has been forced. + switch { + case filesSource && dnsSource: + if first == "files" { + return hostLookupFilesDNS, dnsConf + } else { + return hostLookupDNSFiles, dnsConf + } + case filesSource: + return hostLookupFiles, dnsConf + case dnsSource: + return hostLookupDNS, dnsConf + } + + // Something weird. Fallback to the default. + return fallbackOrder, dnsConf +} + +var netdns = godebug.New("netdns") + +// goDebugNetDNS parses the value of the GODEBUG "netdns" value. +// The netdns value can be of the form: +// +// 1 // debug level 1 +// 2 // debug level 2 +// cgo // use cgo for DNS lookups +// go // use go for DNS lookups +// cgo+1 // use cgo for DNS lookups + debug level 1 +// 1+cgo // same +// cgo+2 // same, but debug level 2 +// +// etc. +func goDebugNetDNS() (dnsMode string, debugLevel int) { + goDebug := netdns.Value() + parsePart := func(s string) { + if s == "" { + return + } + if '0' <= s[0] && s[0] <= '9' { + debugLevel, _, _ = dtoi(s) + } else { + dnsMode = s + } + } + if i := bytealg.IndexByteString(goDebug, '+'); i != -1 { + parsePart(goDebug[:i]) + parsePart(goDebug[i+1:]) + return + } + parsePart(goDebug) + return +} + +// isLocalhost reports whether h should be considered a "localhost" +// name for the myhostname NSS module. +func isLocalhost(h string) bool { + return stringsEqualFold(h, "localhost") || stringsEqualFold(h, "localhost.localdomain") || stringsHasSuffixFold(h, ".localhost") || stringsHasSuffixFold(h, ".localhost.localdomain") +} + +// isGateway reports whether h should be considered a "gateway" +// name for the myhostname NSS module. +func isGateway(h string) bool { + return stringsEqualFold(h, "_gateway") +} + +// isOutbound reports whether h should be considered a "outbound" +// name for the myhostname NSS module. +func isOutbound(h string) bool { + return stringsEqualFold(h, "_outbound") +} |