github.com/x04/go/src@v0.0.0-20200202162449-3d481ceb3525/net/conf.go (about)

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