320 lines
8.7 KiB
Go
320 lines
8.7 KiB
Go
// 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 aix || darwin || dragonfly || freebsd || hurd || linux || netbsd || openbsd || solaris
|
|
|
|
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 // 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" || runtime.GOOS == "ios" {
|
|
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(nil, "") == hostLookupCgo
|
|
}
|
|
|
|
// hostLookupOrder determines which strategy to use to resolve hostname.
|
|
// The provided Resolver is optional. nil means to not consider its options.
|
|
func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder) {
|
|
if c.dnsDebugLevel > 1 {
|
|
defer func() {
|
|
print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n")
|
|
}()
|
|
}
|
|
fallbackOrder := hostLookupCgo
|
|
if c.netGo || r.preferGo() {
|
|
fallbackOrder = hostLookupFilesDNS
|
|
}
|
|
if c.forceCgoLookupHost || c.resolv.unknownOpt || c.goos == "android" {
|
|
return fallbackOrder
|
|
}
|
|
if bytealg.IndexByteString(hostname, '\\') != -1 || bytealg.IndexByteString(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 {
|
|
// 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
|
|
}
|
|
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
|
|
}
|
|
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 := godebug.Get("netdns")
|
|
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")
|
|
}
|