| // 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 aix 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 | 
 | 		} | 
 | 	} | 
 |  | 
 | 	// 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 isLocalhost(hostname) || isGateway(hostname) { | 
 | 				return fallbackOrder | 
 | 			} | 
 | 			hn, err := getHostname() | 
 | 			if err != nil || stringsEqualFold(hostname, hn) { | 
 | 				return fallbackOrder | 
 | 			} | 
 | 			continue | 
 | 		} | 
 | 		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) | 
 | 		} else { | 
 | 			dnsMode = s | 
 | 		} | 
 | 	} | 
 | 	if i := byteIndex(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") | 
 | } |