Source file src/net/conf.go

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

View as plain text