// 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. // +build darwin dragonfly freebsd linux netbsd openbsd solaris package net import ( "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 // cgo DNS resolution forced // machine has an /etc/mdns.allow file hasMDNSAllow bool goos string // the runtime.GOOS, to ease testing dnsDebugLevel int nss *nssConf resolv *dnsConfig } 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.dnsDebugLevel > 0 { defer func() { 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" { confVal.forceCgoLookupHost = true 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 runtime.GOOS != "openbsd" { confVal.nss = parseNSSConfFile("/etc/nsswitch.conf") } confVal.resolv = dnsReadConfig("/etc/resolv.conf") if confVal.resolv.err != nil && !os.IsNotExist(confVal.resolv.err) && !os.IsPermission(confVal.resolv.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. confVal.forceCgoLookupHost = true } 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 { return c.hostLookupOrder("") == hostLookupCgo } // hostLookupOrder determines which strategy to use to resolve hostname. func (c *conf) hostLookupOrder(hostname string) (ret hostLookupOrder) { if c.dnsDebugLevel > 1 { defer func() { print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n") }() } fallbackOrder := hostLookupCgo if c.netGo { fallbackOrder = hostLookupFilesDNS } if c.forceCgoLookupHost || c.resolv.unknownOpt || c.goos == "android" { return fallbackOrder } if byteIndex(hostname, '\\') != -1 || byteIndex(hostname, '%') != -1 { // Don't deal with special form hostnames with backslashes // or '%'. return fallbackOrder } // 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(c.resolv.err) { return hostLookupFiles } lookup := c.resolv.lookup if len(lookup) == 0 { // http://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 } if len(lookup) < 1 || len(lookup) > 2 { return fallbackOrder } switch lookup[0] { case "bind": if len(lookup) == 2 { if lookup[1] == "file" { return hostLookupDNSFiles } return fallbackOrder } return hostLookupDNS case "file": if len(lookup) == 2 { if lookup[1] == "bind" { return hostLookupFilesDNS } return fallbackOrder } return hostLookupFiles default: return fallbackOrder } } hasDot := byteIndex(hostname, '.') != -1 // 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 } nss := c.nss 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 } if c.goos == "linux" { // glibc says the default is "dns [!UNAVAIL=return] files" // http://www.gnu.org/software/libc/manual/html_node/Notes-on-NSS-Configuration-File.html. return hostLookupDNSFiles } return hostLookupFilesDNS } 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 } var mdnsSource, filesSource, dnsSource bool var first string for _, src := range srcs { if src.source == "myhostname" { if hostname == "" || hasDot { continue } return fallbackOrder } if src.source == "files" || src.source == "dns" { if !src.standardCriteria() { return fallbackOrder // 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 } // 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 } // Cases where Go can handle it without cgo and C thread // overhead. switch { case filesSource && dnsSource: if first == "files" { return hostLookupFilesDNS } else { return hostLookupDNSFiles } case filesSource: return hostLookupFiles case dnsSource: return hostLookupDNS } // Something weird. Let libc deal with it. return fallbackOrder } // 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 := goDebugString("netdns") parsePart := func(s string) { if s == "" { return } if '0' <= s[0] && s[0] <= '9' { debugLevel, _, _ = dtoi(s, 0) } else { dnsMode = s } } if i := byteIndex(goDebug, '+'); i != -1 { parsePart(goDebug[:i]) parsePart(goDebug[i+1:]) return } parsePart(goDebug) return }