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  	"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.
    52  type 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.
    65  type mdnsTest int
    66  
    67  const (
    68  	mdnsFromSystem mdnsTest = iota
    69  	mdnsAssumeExists
    70  	mdnsAssumeDoesNotExist
    71  )
    72  
    73  var (
    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.
    79  func 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.
    86  func 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.
   157  func 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.
   186  func (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.
   210  func (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.
   222  func (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  
   231  func (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  
   474  var 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.
   488  func 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.
   511  func 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.
   517  func 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.
   523  func isOutbound(h string) bool {
   524  	return stringsEqualFold(h, "_outbound")
   525  }
   526  

View as plain text