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

View as plain text