| // 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 ( |
| "internal/bytealg" |
| "internal/godebug" |
| "os" |
| "runtime" |
| "sync" |
| "syscall" |
| ) |
| |
| // conf represents a system's network configuration. |
| type conf struct { |
| // forceCgoLookupHost forces CGO to always be used, if available. |
| forceCgoLookupHost bool |
| |
| netGo bool // go DNS resolution forced |
| netCgo bool // non-go DNS resolution forced (cgo, or win32) |
| |
| // machine has an /etc/mdns.allow file |
| hasMDNSAllow bool |
| |
| goos string // the runtime.GOOS, to ease testing |
| dnsDebugLevel int |
| } |
| |
| 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 |
| } |
| |
| func initConfVal() { |
| dnsMode, debugLevel := goDebugNetDNS() |
| confVal.dnsDebugLevel = debugLevel |
| confVal.netGo = netGo || dnsMode == "go" |
| confVal.netCgo = netCgo || dnsMode == "cgo" |
| if !confVal.netGo && !confVal.netCgo && (runtime.GOOS == "windows" || runtime.GOOS == "plan9") { |
| // Neither of these platforms actually use cgo. |
| // |
| // The meaning of "cgo" mode in the net package is |
| // really "the native OS way", which for libc meant |
| // cgo on the original platforms that motivated |
| // PreferGo support before Windows and Plan9 got support, |
| // at which time the GODEBUG=netdns=go and GODEBUG=netdns=cgo |
| // names were already kinda locked in. |
| confVal.netCgo = true |
| } |
| |
| 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 netGo { |
| 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 confVal.forceCgoLookupHost: |
| println("go package net: using cgo DNS resolver") |
| default: |
| println("go package net: dynamic selection of DNS resolver") |
| } |
| }() |
| } |
| |
| // Darwin pops up annoying dialog boxes if programs try to do |
| // their own DNS requests. So always use cgo instead, which |
| // avoids that. |
| if runtime.GOOS == "darwin" || runtime.GOOS == "ios" { |
| confVal.forceCgoLookupHost = true |
| return |
| } |
| |
| if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { |
| return |
| } |
| |
| // If any environment-specified resolver options are specified, |
| // force cgo. Note that LOCALDOMAIN can change behavior merely |
| // by being specified with the empty string. |
| _, localDomainDefined := syscall.Getenv("LOCALDOMAIN") |
| if os.Getenv("RES_OPTIONS") != "" || |
| os.Getenv("HOSTALIASES") != "" || |
| confVal.netCgo || |
| localDomainDefined { |
| confVal.forceCgoLookupHost = 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.forceCgoLookupHost = true |
| return |
| } |
| |
| if _, err := os.Stat("/etc/mdns.allow"); err == nil { |
| confVal.hasMDNSAllow = true |
| } |
| } |
| |
| // canUseCgo reports whether calling cgo functions is allowed |
| // for non-hostname lookups. |
| func (c *conf) canUseCgo() bool { |
| ret, _ := c.hostLookupOrder(nil, "") |
| return ret == hostLookupCgo |
| } |
| |
| // 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, dnsConfig *dnsConfig) { |
| if c.dnsDebugLevel > 1 { |
| defer func() { |
| print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n") |
| }() |
| } |
| fallbackOrder := hostLookupCgo |
| if c.netGo || r.preferGo() { |
| 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 |
| } |
| } |
| if c.forceCgoLookupHost || c.goos == "android" || c.goos == "windows" || c.goos == "plan9" { |
| return fallbackOrder, nil |
| } |
| if bytealg.IndexByteString(hostname, '\\') != -1 || bytealg.IndexByteString(hostname, '%') != -1 { |
| // Don't deal with special form hostnames with backslashes |
| // or '%'. |
| return fallbackOrder, nil |
| } |
| |
| conf := getSystemDNSConfig() |
| if conf.err != nil && !os.IsNotExist(conf.err) && !os.IsPermission(conf.err) { |
| // If we can't read the resolv.conf file, assume it |
| // had something important in it and defer to cgo. |
| // libc's resolver might then fail too, but at least |
| // it wasn't our fault. |
| return fallbackOrder, conf |
| } |
| |
| if conf.unknownOpt { |
| return fallbackOrder, conf |
| } |
| |
| // 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 os.IsNotExist(conf.err) { |
| return hostLookupFiles, conf |
| } |
| |
| lookup := conf.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, conf |
| } |
| if len(lookup) < 1 || len(lookup) > 2 { |
| return fallbackOrder, conf |
| } |
| switch lookup[0] { |
| case "bind": |
| if len(lookup) == 2 { |
| if lookup[1] == "file" { |
| return hostLookupDNSFiles, conf |
| } |
| return fallbackOrder, conf |
| } |
| return hostLookupDNS, conf |
| case "file": |
| if len(lookup) == 2 { |
| if lookup[1] == "bind" { |
| return hostLookupFilesDNS, conf |
| } |
| return fallbackOrder, conf |
| } |
| return hostLookupFiles, conf |
| default: |
| return fallbackOrder, conf |
| } |
| } |
| |
| // Canonicalize the hostname by removing any trailing dot. |
| if stringsHasSuffix(hostname, ".") { |
| hostname = hostname[:len(hostname)-1] |
| } |
| if 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 fallbackOrder, conf |
| } |
| |
| 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 os.IsNotExist(nss.err) || (nss.err == nil && len(srcs) == 0) { |
| if c.goos == "solaris" { |
| // illumos defaults to "nis [NOTFOUND=return] files" |
| return fallbackOrder, conf |
| } |
| |
| return hostLookupFilesDNS, conf |
| } |
| if nss.err != nil { |
| // We failed to parse or open nsswitch.conf, so |
| // conservatively assume we should use cgo if it's |
| // available. |
| return fallbackOrder, conf |
| } |
| |
| var mdnsSource, filesSource, dnsSource bool |
| var first string |
| for _, src := range srcs { |
| if src.source == "myhostname" { |
| if isLocalhost(hostname) || isGateway(hostname) || isOutbound(hostname) { |
| return fallbackOrder, conf |
| } |
| hn, err := getHostname() |
| if err != nil || stringsEqualFold(hostname, hn) { |
| return fallbackOrder, conf |
| } |
| continue |
| } |
| if src.source == "files" || src.source == "dns" { |
| if !src.standardCriteria() { |
| return fallbackOrder, conf // non-standard; let libc deal with it. |
| } |
| if src.source == "files" { |
| filesSource = true |
| } else if src.source == "dns" { |
| dnsSource = true |
| } |
| if first == "" { |
| first = src.source |
| } |
| continue |
| } |
| if 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. |
| mdnsSource = true |
| continue |
| } |
| // Some source we don't know how to deal with. |
| return fallbackOrder, conf |
| } |
| |
| // 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. |
| if mdnsSource && c.hasMDNSAllow { |
| return fallbackOrder, conf |
| } |
| |
| // Cases where Go can handle it without cgo and C thread |
| // overhead. |
| switch { |
| case filesSource && dnsSource: |
| if first == "files" { |
| return hostLookupFilesDNS, conf |
| } else { |
| return hostLookupDNSFiles, conf |
| } |
| case filesSource: |
| return hostLookupFiles, conf |
| case dnsSource: |
| return hostLookupDNS, conf |
| } |
| |
| // Something weird. Let libc deal with it. |
| return fallbackOrder, conf |
| } |
| |
| 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") |
| } |