conf.mx raw

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