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