mirror of
https://github.com/mjl-/mox.git
synced 2024-12-27 08:53:48 +03:00
499 lines
15 KiB
Go
499 lines
15 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 !js
|
|
|
|
package adns
|
|
|
|
import (
|
|
"errors"
|
|
"io/fs"
|
|
"os"
|
|
"runtime"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/mjl-/adns/internal/bytealg"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
var netGoBuildTag = true
|
|
var netCgoBuildTag = false
|
|
var cgoAvailable = false
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// goosPrefersCgo 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.
|
|
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
|
|
}
|
|
|
|
// 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) {
|
|
return "go", 0
|
|
}
|
|
|
|
// 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")
|
|
}
|