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 7package net 8 9import ( 10 "os" 11 "runtime" 12 "sync" 13 "syscall" 14) 15 16// conf represents a system's network configuration. 17type 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 34var ( 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. 40func systemConf() *conf { 41 confOnce.Do(initConfVal) 42 return confVal 43} 44 45func 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. 116func (c *conf) canUseCgo() bool { 117 return c.hostLookupOrder("") == hostLookupCgo 118} 119 120// hostLookupOrder determines which strategy to use to resolve hostname. 121func (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. 291func 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. 314func 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. 320func isGateway(h string) bool { 321 return stringsEqualFold(h, "gateway") 322} 323