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  	"internal/bytealg"
    11  	"internal/godebug"
    12  	"os"
    13  	"runtime"
    14  	"sync"
    15  	"syscall"
    16  )
    17  
    18  // conf represents a system's network configuration.
    19  type conf struct {
    20  	// forceCgoLookupHost forces CGO to always be used, if available.
    21  	forceCgoLookupHost bool
    22  
    23  	netGo  bool // go DNS resolution forced
    24  	netCgo bool // non-go DNS resolution forced (cgo, or win32)
    25  
    26  	// machine has an /etc/mdns.allow file
    27  	hasMDNSAllow bool
    28  
    29  	goos          string // the runtime.GOOS, to ease testing
    30  	dnsDebugLevel int
    31  
    32  	nss    *nssConf
    33  	resolv *dnsConfig
    34  }
    35  
    36  var (
    37  	confOnce sync.Once // guards init of confVal via initConfVal
    38  	confVal  = &conf{goos: runtime.GOOS}
    39  )
    40  
    41  // systemConf returns the machine's network configuration.
    42  func systemConf() *conf {
    43  	confOnce.Do(initConfVal)
    44  	return confVal
    45  }
    46  
    47  func initConfVal() {
    48  	dnsMode, debugLevel := goDebugNetDNS()
    49  	confVal.dnsDebugLevel = debugLevel
    50  	confVal.netGo = netGo || dnsMode == "go"
    51  	confVal.netCgo = netCgo || dnsMode == "cgo"
    52  	if !confVal.netGo && !confVal.netCgo && (runtime.GOOS == "windows" || runtime.GOOS == "plan9") {
    53  		// Neither of these platforms actually use cgo.
    54  		//
    55  		// The meaning of "cgo" mode in the net package is
    56  		// really "the native OS way", which for libc meant
    57  		// cgo on the original platforms that motivated
    58  		// PreferGo support before Windows and Plan9 got support,
    59  		// at which time the GODEBUG=netdns=go and GODEBUG=netdns=cgo
    60  		// names were already kinda locked in.
    61  		confVal.netCgo = true
    62  	}
    63  
    64  	if confVal.dnsDebugLevel > 0 {
    65  		defer func() {
    66  			if confVal.dnsDebugLevel > 1 {
    67  				println("go package net: confVal.netCgo =", confVal.netCgo, " netGo =", confVal.netGo)
    68  			}
    69  			switch {
    70  			case confVal.netGo:
    71  				if netGo {
    72  					println("go package net: built with netgo build tag; using Go's DNS resolver")
    73  				} else {
    74  					println("go package net: GODEBUG setting forcing use of Go's resolver")
    75  				}
    76  			case confVal.forceCgoLookupHost:
    77  				println("go package net: using cgo DNS resolver")
    78  			default:
    79  				println("go package net: dynamic selection of DNS resolver")
    80  			}
    81  		}()
    82  	}
    83  
    84  	// Darwin pops up annoying dialog boxes if programs try to do
    85  	// their own DNS requests. So always use cgo instead, which
    86  	// avoids that.
    87  	if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
    88  		confVal.forceCgoLookupHost = true
    89  		return
    90  	}
    91  
    92  	if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
    93  		return
    94  	}
    95  
    96  	// If any environment-specified resolver options are specified,
    97  	// force cgo. Note that LOCALDOMAIN can change behavior merely
    98  	// by being specified with the empty string.
    99  	_, localDomainDefined := syscall.Getenv("LOCALDOMAIN")
   100  	if os.Getenv("RES_OPTIONS") != "" ||
   101  		os.Getenv("HOSTALIASES") != "" ||
   102  		confVal.netCgo ||
   103  		localDomainDefined {
   104  		confVal.forceCgoLookupHost = true
   105  		return
   106  	}
   107  
   108  	// OpenBSD apparently lets you override the location of resolv.conf
   109  	// with ASR_CONFIG. If we notice that, defer to libc.
   110  	if runtime.GOOS == "openbsd" && os.Getenv("ASR_CONFIG") != "" {
   111  		confVal.forceCgoLookupHost = true
   112  		return
   113  	}
   114  
   115  	if runtime.GOOS != "openbsd" {
   116  		confVal.nss = parseNSSConfFile("/etc/nsswitch.conf")
   117  	}
   118  
   119  	confVal.resolv = dnsReadConfig("/etc/resolv.conf")
   120  	if confVal.resolv.err != nil && !os.IsNotExist(confVal.resolv.err) &&
   121  		!os.IsPermission(confVal.resolv.err) {
   122  		// If we can't read the resolv.conf file, assume it
   123  		// had something important in it and defer to cgo.
   124  		// libc's resolver might then fail too, but at least
   125  		// it wasn't our fault.
   126  		confVal.forceCgoLookupHost = true
   127  	}
   128  
   129  	if _, err := os.Stat("/etc/mdns.allow"); err == nil {
   130  		confVal.hasMDNSAllow = true
   131  	}
   132  }
   133  
   134  // canUseCgo reports whether calling cgo functions is allowed
   135  // for non-hostname lookups.
   136  func (c *conf) canUseCgo() bool {
   137  	return c.hostLookupOrder(nil, "") == hostLookupCgo
   138  }
   139  
   140  // hostLookupOrder determines which strategy to use to resolve hostname.
   141  // The provided Resolver is optional. nil means to not consider its options.
   142  func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder) {
   143  	if c.dnsDebugLevel > 1 {
   144  		defer func() {
   145  			print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n")
   146  		}()
   147  	}
   148  	fallbackOrder := hostLookupCgo
   149  	if c.netGo || r.preferGo() {
   150  		switch c.goos {
   151  		case "windows":
   152  			// TODO(bradfitz): implement files-based
   153  			// lookup on Windows too? I guess /etc/hosts
   154  			// kinda exists on Windows. But for now, only
   155  			// do DNS.
   156  			fallbackOrder = hostLookupDNS
   157  		default:
   158  			fallbackOrder = hostLookupFilesDNS
   159  		}
   160  	}
   161  	if c.goos == "windows" || c.goos == "plan9" {
   162  		return fallbackOrder
   163  	}
   164  	if c.forceCgoLookupHost || c.resolv.unknownOpt || c.goos == "android" {
   165  		return fallbackOrder
   166  	}
   167  	if bytealg.IndexByteString(hostname, '\\') != -1 || bytealg.IndexByteString(hostname, '%') != -1 {
   168  		// Don't deal with special form hostnames with backslashes
   169  		// or '%'.
   170  		return fallbackOrder
   171  	}
   172  
   173  	// OpenBSD is unique and doesn't use nsswitch.conf.
   174  	// It also doesn't support mDNS.
   175  	if c.goos == "openbsd" {
   176  		// OpenBSD's resolv.conf manpage says that a non-existent
   177  		// resolv.conf means "lookup" defaults to only "files",
   178  		// without DNS lookups.
   179  		if os.IsNotExist(c.resolv.err) {
   180  			return hostLookupFiles
   181  		}
   182  		lookup := c.resolv.lookup
   183  		if len(lookup) == 0 {
   184  			// https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5
   185  			// "If the lookup keyword is not used in the
   186  			// system's resolv.conf file then the assumed
   187  			// order is 'bind file'"
   188  			return hostLookupDNSFiles
   189  		}
   190  		if len(lookup) < 1 || len(lookup) > 2 {
   191  			return fallbackOrder
   192  		}
   193  		switch lookup[0] {
   194  		case "bind":
   195  			if len(lookup) == 2 {
   196  				if lookup[1] == "file" {
   197  					return hostLookupDNSFiles
   198  				}
   199  				return fallbackOrder
   200  			}
   201  			return hostLookupDNS
   202  		case "file":
   203  			if len(lookup) == 2 {
   204  				if lookup[1] == "bind" {
   205  					return hostLookupFilesDNS
   206  				}
   207  				return fallbackOrder
   208  			}
   209  			return hostLookupFiles
   210  		default:
   211  			return fallbackOrder
   212  		}
   213  	}
   214  
   215  	// Canonicalize the hostname by removing any trailing dot.
   216  	if stringsHasSuffix(hostname, ".") {
   217  		hostname = hostname[:len(hostname)-1]
   218  	}
   219  	if stringsHasSuffixFold(hostname, ".local") {
   220  		// Per RFC 6762, the ".local" TLD is special. And
   221  		// because Go's native resolver doesn't do mDNS or
   222  		// similar local resolution mechanisms, assume that
   223  		// libc might (via Avahi, etc) and use cgo.
   224  		return fallbackOrder
   225  	}
   226  
   227  	nss := c.nss
   228  	srcs := nss.sources["hosts"]
   229  	// If /etc/nsswitch.conf doesn't exist or doesn't specify any
   230  	// sources for "hosts", assume Go's DNS will work fine.
   231  	if os.IsNotExist(nss.err) || (nss.err == nil && len(srcs) == 0) {
   232  		if c.goos == "solaris" {
   233  			// illumos defaults to "nis [NOTFOUND=return] files"
   234  			return fallbackOrder
   235  		}
   236  		return hostLookupFilesDNS
   237  	}
   238  	if nss.err != nil {
   239  		// We failed to parse or open nsswitch.conf, so
   240  		// conservatively assume we should use cgo if it's
   241  		// available.
   242  		return fallbackOrder
   243  	}
   244  
   245  	var mdnsSource, filesSource, dnsSource bool
   246  	var first string
   247  	for _, src := range srcs {
   248  		if src.source == "myhostname" {
   249  			if isLocalhost(hostname) || isGateway(hostname) {
   250  				return fallbackOrder
   251  			}
   252  			hn, err := getHostname()
   253  			if err != nil || stringsEqualFold(hostname, hn) {
   254  				return fallbackOrder
   255  			}
   256  			continue
   257  		}
   258  		if src.source == "files" || src.source == "dns" {
   259  			if !src.standardCriteria() {
   260  				return fallbackOrder // non-standard; let libc deal with it.
   261  			}
   262  			if src.source == "files" {
   263  				filesSource = true
   264  			} else if src.source == "dns" {
   265  				dnsSource = true
   266  			}
   267  			if first == "" {
   268  				first = src.source
   269  			}
   270  			continue
   271  		}
   272  		if stringsHasPrefix(src.source, "mdns") {
   273  			// e.g. "mdns4", "mdns4_minimal"
   274  			// We already returned true before if it was *.local.
   275  			// libc wouldn't have found a hit on this anyway.
   276  			mdnsSource = true
   277  			continue
   278  		}
   279  		// Some source we don't know how to deal with.
   280  		return fallbackOrder
   281  	}
   282  
   283  	// We don't parse mdns.allow files. They're rare. If one
   284  	// exists, it might list other TLDs (besides .local) or even
   285  	// '*', so just let libc deal with it.
   286  	if mdnsSource && c.hasMDNSAllow {
   287  		return fallbackOrder
   288  	}
   289  
   290  	// Cases where Go can handle it without cgo and C thread
   291  	// overhead.
   292  	switch {
   293  	case filesSource && dnsSource:
   294  		if first == "files" {
   295  			return hostLookupFilesDNS
   296  		} else {
   297  			return hostLookupDNSFiles
   298  		}
   299  	case filesSource:
   300  		return hostLookupFiles
   301  	case dnsSource:
   302  		return hostLookupDNS
   303  	}
   304  
   305  	// Something weird. Let libc deal with it.
   306  	return fallbackOrder
   307  }
   308  
   309  // goDebugNetDNS parses the value of the GODEBUG "netdns" value.
   310  // The netdns value can be of the form:
   311  //
   312  //	1       // debug level 1
   313  //	2       // debug level 2
   314  //	cgo     // use cgo for DNS lookups
   315  //	go      // use go for DNS lookups
   316  //	cgo+1   // use cgo for DNS lookups + debug level 1
   317  //	1+cgo   // same
   318  //	cgo+2   // same, but debug level 2
   319  //
   320  // etc.
   321  func goDebugNetDNS() (dnsMode string, debugLevel int) {
   322  	goDebug := godebug.Get("netdns")
   323  	parsePart := func(s string) {
   324  		if s == "" {
   325  			return
   326  		}
   327  		if '0' <= s[0] && s[0] <= '9' {
   328  			debugLevel, _, _ = dtoi(s)
   329  		} else {
   330  			dnsMode = s
   331  		}
   332  	}
   333  	if i := bytealg.IndexByteString(goDebug, '+'); i != -1 {
   334  		parsePart(goDebug[:i])
   335  		parsePart(goDebug[i+1:])
   336  		return
   337  	}
   338  	parsePart(goDebug)
   339  	return
   340  }
   341  
   342  // isLocalhost reports whether h should be considered a "localhost"
   343  // name for the myhostname NSS module.
   344  func isLocalhost(h string) bool {
   345  	return stringsEqualFold(h, "localhost") || stringsEqualFold(h, "localhost.localdomain") || stringsHasSuffixFold(h, ".localhost") || stringsHasSuffixFold(h, ".localhost.localdomain")
   346  }
   347  
   348  // isGateway reports whether h should be considered a "gateway"
   349  // name for the myhostname NSS module.
   350  func isGateway(h string) bool {
   351  	return stringsEqualFold(h, "gateway")
   352  }
   353  

View as plain text