summaryrefslogtreecommitdiffstats
path: root/src/net/conf.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/conf.go')
-rw-r--r--src/net/conf.go523
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")
+}