Home | History | Annotate | Download | only in net
      1 // Copyright 2015 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // +build darwin dragonfly freebsd linux netbsd openbsd solaris
      6 
      7 package net
      8 
      9 import (
     10 	"os"
     11 	"runtime"
     12 	"sync"
     13 	"syscall"
     14 )
     15 
     16 // conf represents a system's network configuration.
     17 type conf struct {
     18 	// forceCgoLookupHost forces CGO to always be used, if available.
     19 	forceCgoLookupHost bool
     20 
     21 	netGo  bool // go DNS resolution forced
     22 	netCgo bool // cgo DNS resolution forced
     23 
     24 	// machine has an /etc/mdns.allow file
     25 	hasMDNSAllow bool
     26 
     27 	goos          string // the runtime.GOOS, to ease testing
     28 	dnsDebugLevel int
     29 
     30 	nss    *nssConf
     31 	resolv *dnsConfig
     32 }
     33 
     34 var (
     35 	confOnce sync.Once // guards init of confVal via initConfVal
     36 	confVal  = &conf{goos: runtime.GOOS}
     37 )
     38 
     39 // systemConf returns the machine's network configuration.
     40 func systemConf() *conf {
     41 	confOnce.Do(initConfVal)
     42 	return confVal
     43 }
     44 
     45 func initConfVal() {
     46 	dnsMode, debugLevel := goDebugNetDNS()
     47 	confVal.dnsDebugLevel = debugLevel
     48 	confVal.netGo = netGo || dnsMode == "go"
     49 	confVal.netCgo = netCgo || dnsMode == "cgo"
     50 
     51 	if confVal.dnsDebugLevel > 0 {
     52 		defer func() {
     53 			switch {
     54 			case confVal.netGo:
     55 				if netGo {
     56 					println("go package net: built with netgo build tag; using Go's DNS resolver")
     57 				} else {
     58 					println("go package net: GODEBUG setting forcing use of Go's resolver")
     59 				}
     60 			case confVal.forceCgoLookupHost:
     61 				println("go package net: using cgo DNS resolver")
     62 			default:
     63 				println("go package net: dynamic selection of DNS resolver")
     64 			}
     65 		}()
     66 	}
     67 
     68 	// Darwin pops up annoying dialog boxes if programs try to do
     69 	// their own DNS requests. So always use cgo instead, which
     70 	// avoids that.
     71 	if runtime.GOOS == "darwin" {
     72 		confVal.forceCgoLookupHost = true
     73 		return
     74 	}
     75 
     76 	// If any environment-specified resolver options are specified,
     77 	// force cgo. Note that LOCALDOMAIN can change behavior merely
     78 	// by being specified with the empty string.
     79 	_, localDomainDefined := syscall.Getenv("LOCALDOMAIN")
     80 	if os.Getenv("RES_OPTIONS") != "" ||
     81 		os.Getenv("HOSTALIASES") != "" ||
     82 		confVal.netCgo ||
     83 		localDomainDefined {
     84 		confVal.forceCgoLookupHost = true
     85 		return
     86 	}
     87 
     88 	// OpenBSD apparently lets you override the location of resolv.conf
     89 	// with ASR_CONFIG. If we notice that, defer to libc.
     90 	if runtime.GOOS == "openbsd" && os.Getenv("ASR_CONFIG") != "" {
     91 		confVal.forceCgoLookupHost = true
     92 		return
     93 	}
     94 
     95 	if runtime.GOOS != "openbsd" {
     96 		confVal.nss = parseNSSConfFile("/etc/nsswitch.conf")
     97 	}
     98 
     99 	confVal.resolv = dnsReadConfig("/etc/resolv.conf")
    100 	if confVal.resolv.err != nil && !os.IsNotExist(confVal.resolv.err) &&
    101 		!os.IsPermission(confVal.resolv.err) {
    102 		// If we can't read the resolv.conf file, assume it
    103 		// had something important in it and defer to cgo.
    104 		// libc's resolver might then fail too, but at least
    105 		// it wasn't our fault.
    106 		confVal.forceCgoLookupHost = true
    107 	}
    108 
    109 	if _, err := os.Stat("/etc/mdns.allow"); err == nil {
    110 		confVal.hasMDNSAllow = true
    111 	}
    112 }
    113 
    114 // canUseCgo reports whether calling cgo functions is allowed
    115 // for non-hostname lookups.
    116 func (c *conf) canUseCgo() bool {
    117 	return c.hostLookupOrder("") == hostLookupCgo
    118 }
    119 
    120 // hostLookupOrder determines which strategy to use to resolve hostname.
    121 func (c *conf) hostLookupOrder(hostname string) (ret hostLookupOrder) {
    122 	if c.dnsDebugLevel > 1 {
    123 		defer func() {
    124 			print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n")
    125 		}()
    126 	}
    127 	fallbackOrder := hostLookupCgo
    128 	if c.netGo {
    129 		fallbackOrder = hostLookupFilesDNS
    130 	}
    131 	if c.forceCgoLookupHost || c.resolv.unknownOpt || c.goos == "android" {
    132 		return fallbackOrder
    133 	}
    134 	if byteIndex(hostname, '\\') != -1 || byteIndex(hostname, '%') != -1 {
    135 		// Don't deal with special form hostnames with backslashes
    136 		// or '%'.
    137 		return fallbackOrder
    138 	}
    139 
    140 	// OpenBSD is unique and doesn't use nsswitch.conf.
    141 	// It also doesn't support mDNS.
    142 	if c.goos == "openbsd" {
    143 		// OpenBSD's resolv.conf manpage says that a non-existent
    144 		// resolv.conf means "lookup" defaults to only "files",
    145 		// without DNS lookups.
    146 		if os.IsNotExist(c.resolv.err) {
    147 			return hostLookupFiles
    148 		}
    149 		lookup := c.resolv.lookup
    150 		if len(lookup) == 0 {
    151 			// http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5
    152 			// "If the lookup keyword is not used in the
    153 			// system's resolv.conf file then the assumed
    154 			// order is 'bind file'"
    155 			return hostLookupDNSFiles
    156 		}
    157 		if len(lookup) < 1 || len(lookup) > 2 {
    158 			return fallbackOrder
    159 		}
    160 		switch lookup[0] {
    161 		case "bind":
    162 			if len(lookup) == 2 {
    163 				if lookup[1] == "file" {
    164 					return hostLookupDNSFiles
    165 				}
    166 				return fallbackOrder
    167 			}
    168 			return hostLookupDNS
    169 		case "file":
    170 			if len(lookup) == 2 {
    171 				if lookup[1] == "bind" {
    172 					return hostLookupFilesDNS
    173 				}
    174 				return fallbackOrder
    175 			}
    176 			return hostLookupFiles
    177 		default:
    178 			return fallbackOrder
    179 		}
    180 	}
    181 
    182 	// Canonicalize the hostname by removing any trailing dot.
    183 	if stringsHasSuffix(hostname, ".") {
    184 		hostname = hostname[:len(hostname)-1]
    185 	}
    186 	if stringsHasSuffixFold(hostname, ".local") {
    187 		// Per RFC 6762, the ".local" TLD is special. And
    188 		// because Go's native resolver doesn't do mDNS or
    189 		// similar local resolution mechanisms, assume that
    190 		// libc might (via Avahi, etc) and use cgo.
    191 		return fallbackOrder
    192 	}
    193 
    194 	nss := c.nss
    195 	srcs := nss.sources["hosts"]
    196 	// If /etc/nsswitch.conf doesn't exist or doesn't specify any
    197 	// sources for "hosts", assume Go's DNS will work fine.
    198 	if os.IsNotExist(nss.err) || (nss.err == nil && len(srcs) == 0) {
    199 		if c.goos == "solaris" {
    200 			// illumos defaults to "nis [NOTFOUND=return] files"
    201 			return fallbackOrder
    202 		}
    203 		if c.goos == "linux" {
    204 			// glibc says the default is "dns [!UNAVAIL=return] files"
    205 			// http://www.gnu.org/software/libc/manual/html_node/Notes-on-NSS-Configuration-File.html.
    206 			return hostLookupDNSFiles
    207 		}
    208 		return hostLookupFilesDNS
    209 	}
    210 	if nss.err != nil {
    211 		// We failed to parse or open nsswitch.conf, so
    212 		// conservatively assume we should use cgo if it's
    213 		// available.
    214 		return fallbackOrder
    215 	}
    216 
    217 	var mdnsSource, filesSource, dnsSource bool
    218 	var first string
    219 	for _, src := range srcs {
    220 		if src.source == "myhostname" {
    221 			if isLocalhost(hostname) || isGateway(hostname) {
    222 				return fallbackOrder
    223 			}
    224 			hn, err := getHostname()
    225 			if err != nil || stringsEqualFold(hostname, hn) {
    226 				return fallbackOrder
    227 			}
    228 			continue
    229 		}
    230 		if src.source == "files" || src.source == "dns" {
    231 			if !src.standardCriteria() {
    232 				return fallbackOrder // non-standard; let libc deal with it.
    233 			}
    234 			if src.source == "files" {
    235 				filesSource = true
    236 			} else if src.source == "dns" {
    237 				dnsSource = true
    238 			}
    239 			if first == "" {
    240 				first = src.source
    241 			}
    242 			continue
    243 		}
    244 		if stringsHasPrefix(src.source, "mdns") {
    245 			// e.g. "mdns4", "mdns4_minimal"
    246 			// We already returned true before if it was *.local.
    247 			// libc wouldn't have found a hit on this anyway.
    248 			mdnsSource = true
    249 			continue
    250 		}
    251 		// Some source we don't know how to deal with.
    252 		return fallbackOrder
    253 	}
    254 
    255 	// We don't parse mdns.allow files. They're rare. If one
    256 	// exists, it might list other TLDs (besides .local) or even
    257 	// '*', so just let libc deal with it.
    258 	if mdnsSource && c.hasMDNSAllow {
    259 		return fallbackOrder
    260 	}
    261 
    262 	// Cases where Go can handle it without cgo and C thread
    263 	// overhead.
    264 	switch {
    265 	case filesSource && dnsSource:
    266 		if first == "files" {
    267 			return hostLookupFilesDNS
    268 		} else {
    269 			return hostLookupDNSFiles
    270 		}
    271 	case filesSource:
    272 		return hostLookupFiles
    273 	case dnsSource:
    274 		return hostLookupDNS
    275 	}
    276 
    277 	// Something weird. Let libc deal with it.
    278 	return fallbackOrder
    279 }
    280 
    281 // goDebugNetDNS parses the value of the GODEBUG "netdns" value.
    282 // The netdns value can be of the form:
    283 //    1       // debug level 1
    284 //    2       // debug level 2
    285 //    cgo     // use cgo for DNS lookups
    286 //    go      // use go for DNS lookups
    287 //    cgo+1   // use cgo for DNS lookups + debug level 1
    288 //    1+cgo   // same
    289 //    cgo+2   // same, but debug level 2
    290 // etc.
    291 func goDebugNetDNS() (dnsMode string, debugLevel int) {
    292 	goDebug := goDebugString("netdns")
    293 	parsePart := func(s string) {
    294 		if s == "" {
    295 			return
    296 		}
    297 		if '0' <= s[0] && s[0] <= '9' {
    298 			debugLevel, _, _ = dtoi(s)
    299 		} else {
    300 			dnsMode = s
    301 		}
    302 	}
    303 	if i := byteIndex(goDebug, '+'); i != -1 {
    304 		parsePart(goDebug[:i])
    305 		parsePart(goDebug[i+1:])
    306 		return
    307 	}
    308 	parsePart(goDebug)
    309 	return
    310 }
    311 
    312 // isLocalhost reports whether h should be considered a "localhost"
    313 // name for the myhostname NSS module.
    314 func isLocalhost(h string) bool {
    315 	return stringsEqualFold(h, "localhost") || stringsEqualFold(h, "localhost.localdomain") || stringsHasSuffixFold(h, ".localhost") || stringsHasSuffixFold(h, ".localhost.localdomain")
    316 }
    317 
    318 // isGateway reports whether h should be considered a "gateway"
    319 // name for the myhostname NSS module.
    320 func isGateway(h string) bool {
    321 	return stringsEqualFold(h, "gateway")
    322 }
    323