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  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     string   // 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 string) (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 string) (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 string) (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 string
   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 string, debugLevel int) {
   499  	goDebug := netdns.Value()
   500  	parsePart := func(s string) {
   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 string) 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 string) 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 string) bool {
   534  	return stringsEqualFold(h, "_outbound")
   535  }
   536  

View as plain text