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 5package net 6 7import ( 8 "errors" 9 "internal/bytealg" 10 "internal/godebug" 11 "internal/stringslite" 12 "io/fs" 13 "os" 14 "runtime" 15 "sync" 16 "syscall" 17) 18 19// The net package's name resolution is rather complicated. 20// There are two main approaches, go and cgo. 21// The cgo resolver uses C functions like getaddrinfo. 22// The go resolver reads system files directly and 23// sends DNS packets directly to servers. 24// 25// The netgo build tag prefers the go resolver. 26// The netcgo build tag prefers the cgo resolver. 27// 28// The netgo build tag also prohibits the use of the cgo tool. 29// However, on Darwin, Plan 9, and Windows the cgo resolver is still available. 30// On those systems the cgo resolver does not require the cgo tool. 31// (The term "cgo resolver" was locked in by GODEBUG settings 32// at a time when the cgo resolver did require the cgo tool.) 33// 34// Adding netdns=go to GODEBUG will prefer the go resolver. 35// Adding netdns=cgo to GODEBUG will prefer the cgo resolver. 36// 37// The Resolver struct has a PreferGo field that user code 38// may set to prefer the go resolver. It is documented as being 39// equivalent to adding netdns=go to GODEBUG. 40// 41// When deciding which resolver to use, we first check the PreferGo field. 42// If that is not set, we check the GODEBUG setting. 43// If that is not set, we check the netgo or netcgo build tag. 44// If none of those are set, we normally prefer the go resolver by default. 45// However, if the cgo resolver is available, 46// there is a complex set of conditions for which we prefer the cgo resolver. 47// 48// Other files define the netGoBuildTag, netCgoBuildTag, and cgoAvailable 49// constants. 50 51// conf is used to determine name resolution configuration. 52type conf struct { 53 netGo bool // prefer go approach, based on build tag and GODEBUG 54 netCgo bool // prefer cgo approach, based on build tag and GODEBUG 55 56 dnsDebugLevel int // from GODEBUG 57 58 preferCgo bool // if no explicit preference, use cgo 59 60 goos string // copy of runtime.GOOS, used for testing 61 mdnsTest mdnsTest // assume /etc/mdns.allow exists, for testing 62} 63 64// mdnsTest is for testing only. 65type mdnsTest int 66 67const ( 68 mdnsFromSystem mdnsTest = iota 69 mdnsAssumeExists 70 mdnsAssumeDoesNotExist 71) 72 73var ( 74 confOnce sync.Once // guards init of confVal via initConfVal 75 confVal = &conf{goos: runtime.GOOS} 76) 77 78// systemConf returns the machine's network configuration. 79func systemConf() *conf { 80 confOnce.Do(initConfVal) 81 return confVal 82} 83 84// initConfVal initializes confVal based on the environment 85// that will not change during program execution. 86func initConfVal() { 87 dnsMode, debugLevel := goDebugNetDNS() 88 confVal.netGo = netGoBuildTag || dnsMode == "go" 89 confVal.netCgo = netCgoBuildTag || dnsMode == "cgo" 90 confVal.dnsDebugLevel = debugLevel 91 92 if confVal.dnsDebugLevel > 0 { 93 defer func() { 94 if confVal.dnsDebugLevel > 1 { 95 println("go package net: confVal.netCgo =", confVal.netCgo, " netGo =", confVal.netGo) 96 } 97 switch { 98 case confVal.netGo: 99 if netGoBuildTag { 100 println("go package net: built with netgo build tag; using Go's DNS resolver") 101 } else { 102 println("go package net: GODEBUG setting forcing use of Go's resolver") 103 } 104 case !cgoAvailable: 105 println("go package net: cgo resolver not supported; using Go's DNS resolver") 106 case confVal.netCgo || confVal.preferCgo: 107 println("go package net: using cgo DNS resolver") 108 default: 109 println("go package net: dynamic selection of DNS resolver") 110 } 111 }() 112 } 113 114 // The remainder of this function sets preferCgo based on 115 // conditions that will not change during program execution. 116 117 // By default, prefer the go resolver. 118 confVal.preferCgo = false 119 120 // If the cgo resolver is not available, we can't prefer it. 121 if !cgoAvailable { 122 return 123 } 124 125 // Some operating systems always prefer the cgo resolver. 126 if goosPrefersCgo() { 127 confVal.preferCgo = true 128 return 129 } 130 131 // The remaining checks are specific to Unix systems. 132 switch runtime.GOOS { 133 case "plan9", "windows", "js", "wasip1": 134 return 135 } 136 137 // If any environment-specified resolver options are specified, 138 // prefer the cgo resolver. 139 // Note that LOCALDOMAIN can change behavior merely by being 140 // specified with the empty string. 141 _, localDomainDefined := syscall.Getenv("LOCALDOMAIN") 142 if localDomainDefined || os.Getenv("RES_OPTIONS") != "" || os.Getenv("HOSTALIASES") != "" { 143 confVal.preferCgo = true 144 return 145 } 146 147 // OpenBSD apparently lets you override the location of resolv.conf 148 // with ASR_CONFIG. If we notice that, defer to libc. 149 if runtime.GOOS == "openbsd" && os.Getenv("ASR_CONFIG") != "" { 150 confVal.preferCgo = true 151 return 152 } 153} 154 155// goosPrefersCgo reports whether the GOOS value passed in prefers 156// the cgo resolver. 157func goosPrefersCgo() bool { 158 switch runtime.GOOS { 159 // Historically on Windows and Plan 9 we prefer the 160 // cgo resolver (which doesn't use the cgo tool) rather than 161 // the go resolver. This is because originally these 162 // systems did not support the go resolver. 163 // Keep it this way for better compatibility. 164 // Perhaps we can revisit this some day. 165 case "windows", "plan9": 166 return true 167 168 // Darwin pops up annoying dialog boxes if programs try to 169 // do their own DNS requests, so prefer cgo. 170 case "darwin", "ios": 171 return true 172 173 // DNS requests don't work on Android, so prefer the cgo resolver. 174 // Issue #10714. 175 case "android": 176 return true 177 178 default: 179 return false 180 } 181} 182 183// mustUseGoResolver reports whether a DNS lookup of any sort is 184// required to use the go resolver. The provided Resolver is optional. 185// This will report true if the cgo resolver is not available. 186func (c *conf) mustUseGoResolver(r *Resolver) bool { 187 if !cgoAvailable { 188 return true 189 } 190 191 if runtime.GOOS == "plan9" { 192 // TODO(bradfitz): for now we only permit use of the PreferGo 193 // implementation when there's a non-nil Resolver with a 194 // non-nil Dialer. This is a sign that the code is trying 195 // to use their DNS-speaking net.Conn (such as an in-memory 196 // DNS cache) and they don't want to actually hit the network. 197 // Once we add support for looking the default DNS servers 198 // from plan9, though, then we can relax this. 199 if r == nil || r.Dial == nil { 200 return false 201 } 202 } 203 204 return c.netGo || r.preferGo() 205} 206 207// addrLookupOrder determines which strategy to use to resolve addresses. 208// The provided Resolver is optional. nil means to not consider its options. 209// It also returns dnsConfig when it was used to determine the lookup order. 210func (c *conf) addrLookupOrder(r *Resolver, addr string) (ret hostLookupOrder, dnsConf *dnsConfig) { 211 if c.dnsDebugLevel > 1 { 212 defer func() { 213 print("go package net: addrLookupOrder(", addr, ") = ", ret.String(), "\n") 214 }() 215 } 216 return c.lookupOrder(r, "") 217} 218 219// hostLookupOrder determines which strategy to use to resolve hostname. 220// The provided Resolver is optional. nil means to not consider its options. 221// It also returns dnsConfig when it was used to determine the lookup order. 222func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder, dnsConf *dnsConfig) { 223 if c.dnsDebugLevel > 1 { 224 defer func() { 225 print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n") 226 }() 227 } 228 return c.lookupOrder(r, hostname) 229} 230 231func (c *conf) lookupOrder(r *Resolver, hostname string) (ret hostLookupOrder, dnsConf *dnsConfig) { 232 // fallbackOrder is the order we return if we can't figure it out. 233 var fallbackOrder hostLookupOrder 234 235 var canUseCgo bool 236 if c.mustUseGoResolver(r) { 237 // Go resolver was explicitly requested 238 // or cgo resolver is not available. 239 // Figure out the order below. 240 fallbackOrder = hostLookupFilesDNS 241 canUseCgo = false 242 } else if c.netCgo { 243 // Cgo resolver was explicitly requested. 244 return hostLookupCgo, nil 245 } else if c.preferCgo { 246 // Given a choice, we prefer the cgo resolver. 247 return hostLookupCgo, nil 248 } else { 249 // Neither resolver was explicitly requested 250 // and we have no preference. 251 252 if bytealg.IndexByteString(hostname, '\\') != -1 || bytealg.IndexByteString(hostname, '%') != -1 { 253 // Don't deal with special form hostnames 254 // with backslashes or '%'. 255 return hostLookupCgo, nil 256 } 257 258 // If something is unrecognized, use cgo. 259 fallbackOrder = hostLookupCgo 260 canUseCgo = true 261 } 262 263 // On systems that don't use /etc/resolv.conf or /etc/nsswitch.conf, we are done. 264 switch c.goos { 265 case "windows", "plan9", "android", "ios": 266 return fallbackOrder, nil 267 } 268 269 // Try to figure out the order to use for searches. 270 // If we don't recognize something, use fallbackOrder. 271 // That will use cgo unless the Go resolver was explicitly requested. 272 // If we do figure out the order, return something other 273 // than fallbackOrder to use the Go resolver with that order. 274 275 dnsConf = getSystemDNSConfig() 276 277 if canUseCgo && dnsConf.err != nil && !errors.Is(dnsConf.err, fs.ErrNotExist) && !errors.Is(dnsConf.err, fs.ErrPermission) { 278 // We can't read the resolv.conf file, so use cgo if we can. 279 return hostLookupCgo, dnsConf 280 } 281 282 if canUseCgo && dnsConf.unknownOpt { 283 // We didn't recognize something in resolv.conf, 284 // so use cgo if we can. 285 return hostLookupCgo, dnsConf 286 } 287 288 // OpenBSD is unique and doesn't use nsswitch.conf. 289 // It also doesn't support mDNS. 290 if c.goos == "openbsd" { 291 // OpenBSD's resolv.conf manpage says that a 292 // non-existent resolv.conf means "lookup" defaults 293 // to only "files", without DNS lookups. 294 if errors.Is(dnsConf.err, fs.ErrNotExist) { 295 return hostLookupFiles, dnsConf 296 } 297 298 lookup := dnsConf.lookup 299 if len(lookup) == 0 { 300 // https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5 301 // "If the lookup keyword is not used in the 302 // system's resolv.conf file then the assumed 303 // order is 'bind file'" 304 return hostLookupDNSFiles, dnsConf 305 } 306 if len(lookup) < 1 || len(lookup) > 2 { 307 // We don't recognize this format. 308 return fallbackOrder, dnsConf 309 } 310 switch lookup[0] { 311 case "bind": 312 if len(lookup) == 2 { 313 if lookup[1] == "file" { 314 return hostLookupDNSFiles, dnsConf 315 } 316 // Unrecognized. 317 return fallbackOrder, dnsConf 318 } 319 return hostLookupDNS, dnsConf 320 case "file": 321 if len(lookup) == 2 { 322 if lookup[1] == "bind" { 323 return hostLookupFilesDNS, dnsConf 324 } 325 // Unrecognized. 326 return fallbackOrder, dnsConf 327 } 328 return hostLookupFiles, dnsConf 329 default: 330 // Unrecognized. 331 return fallbackOrder, dnsConf 332 } 333 334 // We always return before this point. 335 // The code below is for non-OpenBSD. 336 } 337 338 // Canonicalize the hostname by removing any trailing dot. 339 hostname = stringslite.TrimSuffix(hostname, ".") 340 341 nss := getSystemNSS() 342 srcs := nss.sources["hosts"] 343 // If /etc/nsswitch.conf doesn't exist or doesn't specify any 344 // sources for "hosts", assume Go's DNS will work fine. 345 if errors.Is(nss.err, fs.ErrNotExist) || (nss.err == nil && len(srcs) == 0) { 346 if canUseCgo && c.goos == "solaris" { 347 // illumos defaults to 348 // "nis [NOTFOUND=return] files", 349 // which the go resolver doesn't support. 350 return hostLookupCgo, dnsConf 351 } 352 353 return hostLookupFilesDNS, dnsConf 354 } 355 if nss.err != nil { 356 // We failed to parse or open nsswitch.conf, so 357 // we have nothing to base an order on. 358 return fallbackOrder, dnsConf 359 } 360 361 var hasDNSSource bool 362 var hasDNSSourceChecked bool 363 364 var filesSource, dnsSource bool 365 var first string 366 for i, src := range srcs { 367 if src.source == "files" || src.source == "dns" { 368 if canUseCgo && !src.standardCriteria() { 369 // non-standard; let libc deal with it. 370 return hostLookupCgo, dnsConf 371 } 372 if src.source == "files" { 373 filesSource = true 374 } else { 375 hasDNSSource = true 376 hasDNSSourceChecked = true 377 dnsSource = true 378 } 379 if first == "" { 380 first = src.source 381 } 382 continue 383 } 384 385 if canUseCgo { 386 switch { 387 case hostname != "" && src.source == "myhostname": 388 // Let the cgo resolver handle myhostname 389 // if we are looking up the local hostname. 390 if isLocalhost(hostname) || isGateway(hostname) || isOutbound(hostname) { 391 return hostLookupCgo, dnsConf 392 } 393 hn, err := getHostname() 394 if err != nil || stringsEqualFold(hostname, hn) { 395 return hostLookupCgo, dnsConf 396 } 397 continue 398 case hostname != "" && stringslite.HasPrefix(src.source, "mdns"): 399 if stringsHasSuffixFold(hostname, ".local") { 400 // Per RFC 6762, the ".local" TLD is special. And 401 // because Go's native resolver doesn't do mDNS or 402 // similar local resolution mechanisms, assume that 403 // libc might (via Avahi, etc) and use cgo. 404 return hostLookupCgo, dnsConf 405 } 406 407 // We don't parse mdns.allow files. They're rare. If one 408 // exists, it might list other TLDs (besides .local) or even 409 // '*', so just let libc deal with it. 410 var haveMDNSAllow bool 411 switch c.mdnsTest { 412 case mdnsFromSystem: 413 _, err := os.Stat("/etc/mdns.allow") 414 if err != nil && !errors.Is(err, fs.ErrNotExist) { 415 // Let libc figure out what is going on. 416 return hostLookupCgo, dnsConf 417 } 418 haveMDNSAllow = err == nil 419 case mdnsAssumeExists: 420 haveMDNSAllow = true 421 case mdnsAssumeDoesNotExist: 422 haveMDNSAllow = false 423 } 424 if haveMDNSAllow { 425 return hostLookupCgo, dnsConf 426 } 427 continue 428 default: 429 // Some source we don't know how to deal with. 430 return hostLookupCgo, dnsConf 431 } 432 } 433 434 if !hasDNSSourceChecked { 435 hasDNSSourceChecked = true 436 for _, v := range srcs[i+1:] { 437 if v.source == "dns" { 438 hasDNSSource = true 439 break 440 } 441 } 442 } 443 444 // If we saw a source we don't recognize, which can only 445 // happen if we can't use the cgo resolver, treat it as DNS, 446 // but only when there is no dns in all other sources. 447 if !hasDNSSource { 448 dnsSource = true 449 if first == "" { 450 first = "dns" 451 } 452 } 453 } 454 455 // Cases where Go can handle it without cgo and C thread overhead, 456 // or where the Go resolver has been forced. 457 switch { 458 case filesSource && dnsSource: 459 if first == "files" { 460 return hostLookupFilesDNS, dnsConf 461 } else { 462 return hostLookupDNSFiles, dnsConf 463 } 464 case filesSource: 465 return hostLookupFiles, dnsConf 466 case dnsSource: 467 return hostLookupDNS, dnsConf 468 } 469 470 // Something weird. Fallback to the default. 471 return fallbackOrder, dnsConf 472} 473 474var netdns = godebug.New("netdns") 475 476// goDebugNetDNS parses the value of the GODEBUG "netdns" value. 477// The netdns value can be of the form: 478// 479// 1 // debug level 1 480// 2 // debug level 2 481// cgo // use cgo for DNS lookups 482// go // use go for DNS lookups 483// cgo+1 // use cgo for DNS lookups + debug level 1 484// 1+cgo // same 485// cgo+2 // same, but debug level 2 486// 487// etc. 488func goDebugNetDNS() (dnsMode string, debugLevel int) { 489 goDebug := netdns.Value() 490 parsePart := func(s string) { 491 if s == "" { 492 return 493 } 494 if '0' <= s[0] && s[0] <= '9' { 495 debugLevel, _, _ = dtoi(s) 496 } else { 497 dnsMode = s 498 } 499 } 500 if i := bytealg.IndexByteString(goDebug, '+'); i != -1 { 501 parsePart(goDebug[:i]) 502 parsePart(goDebug[i+1:]) 503 return 504 } 505 parsePart(goDebug) 506 return 507} 508 509// isLocalhost reports whether h should be considered a "localhost" 510// name for the myhostname NSS module. 511func isLocalhost(h string) bool { 512 return stringsEqualFold(h, "localhost") || stringsEqualFold(h, "localhost.localdomain") || stringsHasSuffixFold(h, ".localhost") || stringsHasSuffixFold(h, ".localhost.localdomain") 513} 514 515// isGateway reports whether h should be considered a "gateway" 516// name for the myhostname NSS module. 517func isGateway(h string) bool { 518 return stringsEqualFold(h, "_gateway") 519} 520 521// isOutbound reports whether h should be considered an "outbound" 522// name for the myhostname NSS module. 523func isOutbound(h string) bool { 524 return stringsEqualFold(h, "_outbound") 525} 526