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

View as plain text