From b615ad8fd57f9394db14e403d12061c369379c52 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 25 Jun 2015 12:52:54 +0200 Subject: [PATCH] net: add mechanisms to force go or cgo lookup, and to debug default strategy GODEBUG=netdns=1 prints a one-time strategy decision. (cgo or go DNS lookups) GODEBUG=netdns=2 prints the per-lookup strategy as a function of the hostname. The new "netcgo" build tag forces cgo DNS lookups. GODEBUG=netdns=go (or existing build tag "netgo") forces Go DNS resolution. GODEBUG=netdns=cgo (or new build tag "netcgo") forces libc DNS resolution. Options can be combined with e.g. GODEBUG=netdns=go+1 or GODEBUG=netdns=2+cgo. Fixes #11322 Fixes #11450 Change-Id: I7a67e9f759fd0a02320e7803f9ded1638b19e861 Reviewed-on: https://go-review.googlesource.com/11584 Reviewed-by: Russ Cox Run-TryBot: Brad Fitzpatrick --- src/net/cgo_stub.go | 2 ++ src/net/conf.go | 74 ++++++++++++++++++++++++++++++++++++++++-- src/net/conf_netcgo.go | 18 ++++++++++ src/net/conf_test.go | 4 +++ src/net/net.go | 8 +++++ src/net/parse.go | 23 +++++++++++++ src/net/parse_test.go | 27 +++++++++++++++ 7 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 src/net/conf_netcgo.go diff --git a/src/net/cgo_stub.go b/src/net/cgo_stub.go index 6ee052d138..b86ff7daf1 100644 --- a/src/net/cgo_stub.go +++ b/src/net/cgo_stub.go @@ -6,6 +6,8 @@ package net +func init() { netGo = true } + type addrinfoErrno int func (eai addrinfoErrno) Error() string { return "" } diff --git a/src/net/conf.go b/src/net/conf.go index ca7fa8708f..e70178d34c 100644 --- a/src/net/conf.go +++ b/src/net/conf.go @@ -9,6 +9,7 @@ package net import ( "os" "runtime" + "strconv" "sync" "syscall" ) @@ -18,10 +19,14 @@ type conf struct { // forceCgoLookupHost forces CGO to always be used, if available. forceCgoLookupHost bool + netGo bool // "netgo" build tag in use (or no cgo) + netCgo bool // cgo DNS resolution forced + // machine has an /etc/mdns.allow file hasMDNSAllow bool - goos string // the runtime.GOOS, to ease testing + goos string // the runtime.GOOS, to ease testing + dnsDebugLevel int nss *nssConf resolv *dnsConfig @@ -39,6 +44,28 @@ func systemConf() *conf { } func initConfVal() { + dnsMode, debugLevel := goDebugNetDNS() + confVal.dnsDebugLevel = debugLevel + confVal.netGo = netGo || dnsMode == "go" + confVal.netCgo = netCgo || dnsMode == "cgo" + + if confVal.dnsDebugLevel > 0 { + defer func() { + switch { + case confVal.netGo: + if netGo { + println("go package net: built with netgo build tag; using Go's DNS resolver") + } else { + println("go package net: GODEBUG setting forcing use of Go's resolver") + } + case confVal.forceCgoLookupHost: + println("go package net: using cgo DNS resolver") + default: + println("go package net: dynamic selection of DNS resolver") + } + }() + } + // Darwin pops up annoying dialog boxes if programs try to do // their own DNS requests. So always use cgo instead, which // avoids that. @@ -51,7 +78,9 @@ func initConfVal() { // force cgo. Note that LOCALDOMAIN can change behavior merely // by being specified with the empty string. _, localDomainDefined := syscall.Getenv("LOCALDOMAIN") - if os.Getenv("RES_OPTIONS") != "" || os.Getenv("HOSTALIASES") != "" || + if os.Getenv("RES_OPTIONS") != "" || + os.Getenv("HOSTALIASES") != "" || + netCgo || localDomainDefined { confVal.forceCgoLookupHost = true return @@ -84,7 +113,15 @@ func initConfVal() { } // hostLookupOrder determines which strategy to use to resolve hostname. -func (c *conf) hostLookupOrder(hostname string) hostLookupOrder { +func (c *conf) hostLookupOrder(hostname string) (ret hostLookupOrder) { + if c.dnsDebugLevel > 1 { + defer func() { + print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n") + }() + } + if c.netGo { + return hostLookupFilesDNS + } if c.forceCgoLookupHost || c.resolv.unknownOpt || c.goos == "android" { return hostLookupCgo } @@ -232,3 +269,34 @@ func (c *conf) hostLookupOrder(hostname string) hostLookupOrder { // Something weird. Let libc deal with it. return hostLookupCgo } + +// goDebugNetDNS parses the value of the GODEBUG "netdns" value. +// The netdns value can be of the form: +// 1 // debug level 1 +// 2 // debug level 2 +// cgo // use cgo for DNS lookups +// go // use go for DNS lookups +// cgo+1 // use cgo for DNS lookups + debug level 1 +// 1+cgo // same +// cgo+2 // same, but debug level 2 +// etc. +func goDebugNetDNS() (dnsMode string, debugLevel int) { + goDebug := goDebugString("netdns") + parsePart := func(s string) { + if s == "" { + return + } + if '0' <= s[0] && s[0] <= '9' { + debugLevel, _ = strconv.Atoi(s) + } else { + dnsMode = s + } + } + if i := byteIndex(goDebug, '+'); i != -1 { + parsePart(goDebug[:i]) + parsePart(goDebug[i+1:]) + return + } + parsePart(goDebug) + return +} diff --git a/src/net/conf_netcgo.go b/src/net/conf_netcgo.go new file mode 100644 index 0000000000..678361fb60 --- /dev/null +++ b/src/net/conf_netcgo.go @@ -0,0 +1,18 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build netcgo + +package net + +/* + +// Fail if cgo isn't available. + +*/ +import "C" + +// The build tag "netcgo" forces use of the cgo DNS resolver. +// It is the opposite of "netgo". +func init() { netCgo = true } diff --git a/src/net/conf_test.go b/src/net/conf_test.go index 003c615eb8..86904bffde 100644 --- a/src/net/conf_test.go +++ b/src/net/conf_test.go @@ -295,3 +295,7 @@ func TestConfHostLookupOrder(t *testing.T) { } } + +func TestSystemConf(t *testing.T) { + systemConf() +} diff --git a/src/net/net.go b/src/net/net.go index cd1372fd02..75510a2223 100644 --- a/src/net/net.go +++ b/src/net/net.go @@ -46,6 +46,14 @@ import ( "time" ) +// netGo and netCgo contain the state of the build tags used +// to build this binary, and whether cgo is available. +// conf.go mirrors these into conf for easier testing. +var ( + netGo bool // set true in cgo_stub.go for build tag "netgo" (or no cgo) + netCgo bool // set true in conf_netcgo.go for build tag "netcgo" +) + func init() { sysInit() supportsIPv4 = probeIPv4Stack() diff --git a/src/net/parse.go b/src/net/parse.go index 5b834e64d4..c72e1c2eaf 100644 --- a/src/net/parse.go +++ b/src/net/parse.go @@ -361,3 +361,26 @@ func readFull(r io.Reader) (all []byte, err error) { } } } + +// goDebugString returns the value of the named GODEBUG key. +// GODEBUG is of the form "key=val,key2=val2" +func goDebugString(key string) string { + s := os.Getenv("GODEBUG") + for i := 0; i < len(s)-len(key)-1; i++ { + if i > 0 && s[i-1] != ',' { + continue + } + afterKey := s[i+len(key):] + if afterKey[0] != '=' || s[i:i+len(key)] != key { + continue + } + val := afterKey[1:] + for i, b := range val { + if b == ',' { + return val[:i] + } + } + return val + } + return "" +} diff --git a/src/net/parse_test.go b/src/net/parse_test.go index f5359d8c36..0f048fcea0 100644 --- a/src/net/parse_test.go +++ b/src/net/parse_test.go @@ -50,3 +50,30 @@ func TestReadLine(t *testing.T) { byteno += len(line) + 1 } } + +func TestGoDebugString(t *testing.T) { + defer os.Setenv("GODEBUG", os.Getenv("GODEBUG")) + tests := []struct { + godebug string + key string + want string + }{ + {"", "foo", ""}, + {"foo=", "foo", ""}, + {"foo=bar", "foo", "bar"}, + {"foo=bar,", "foo", "bar"}, + {"foo,foo=bar,", "foo", "bar"}, + {"foo1=bar,foo=bar,", "foo", "bar"}, + {"foo=bar,foo=bar,", "foo", "bar"}, + {"foo=", "foo", ""}, + {"foo", "foo", ""}, + {",foo", "foo", ""}, + {"foo=bar,baz", "loooooooong", ""}, + } + for _, tt := range tests { + os.Setenv("GODEBUG", tt.godebug) + if got := goDebugString(tt.key); got != tt.want { + t.Errorf("for %q, goDebugString(%q) = %q; want %q", tt.godebug, tt.key, got, tt.want) + } + } +} -- 2.50.0