Commit b615ad8f authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

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/11584Reviewed-by: default avatarRuss Cox <rsc@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
parent 4c332508
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
package net package net
func init() { netGo = true }
type addrinfoErrno int type addrinfoErrno int
func (eai addrinfoErrno) Error() string { return "<nil>" } func (eai addrinfoErrno) Error() string { return "<nil>" }
......
...@@ -9,6 +9,7 @@ package net ...@@ -9,6 +9,7 @@ package net
import ( import (
"os" "os"
"runtime" "runtime"
"strconv"
"sync" "sync"
"syscall" "syscall"
) )
...@@ -18,10 +19,14 @@ type conf struct { ...@@ -18,10 +19,14 @@ type conf struct {
// forceCgoLookupHost forces CGO to always be used, if available. // forceCgoLookupHost forces CGO to always be used, if available.
forceCgoLookupHost bool 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 // machine has an /etc/mdns.allow file
hasMDNSAllow bool hasMDNSAllow bool
goos string // the runtime.GOOS, to ease testing goos string // the runtime.GOOS, to ease testing
dnsDebugLevel int
nss *nssConf nss *nssConf
resolv *dnsConfig resolv *dnsConfig
...@@ -39,6 +44,28 @@ func systemConf() *conf { ...@@ -39,6 +44,28 @@ func systemConf() *conf {
} }
func initConfVal() { 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 // Darwin pops up annoying dialog boxes if programs try to do
// their own DNS requests. So always use cgo instead, which // their own DNS requests. So always use cgo instead, which
// avoids that. // avoids that.
...@@ -51,7 +78,9 @@ func initConfVal() { ...@@ -51,7 +78,9 @@ func initConfVal() {
// force cgo. Note that LOCALDOMAIN can change behavior merely // force cgo. Note that LOCALDOMAIN can change behavior merely
// by being specified with the empty string. // by being specified with the empty string.
_, localDomainDefined := syscall.Getenv("LOCALDOMAIN") _, localDomainDefined := syscall.Getenv("LOCALDOMAIN")
if os.Getenv("RES_OPTIONS") != "" || os.Getenv("HOSTALIASES") != "" || if os.Getenv("RES_OPTIONS") != "" ||
os.Getenv("HOSTALIASES") != "" ||
netCgo ||
localDomainDefined { localDomainDefined {
confVal.forceCgoLookupHost = true confVal.forceCgoLookupHost = true
return return
...@@ -84,7 +113,15 @@ func initConfVal() { ...@@ -84,7 +113,15 @@ func initConfVal() {
} }
// hostLookupOrder determines which strategy to use to resolve hostname. // 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" { if c.forceCgoLookupHost || c.resolv.unknownOpt || c.goos == "android" {
return hostLookupCgo return hostLookupCgo
} }
...@@ -232,3 +269,34 @@ func (c *conf) hostLookupOrder(hostname string) hostLookupOrder { ...@@ -232,3 +269,34 @@ func (c *conf) hostLookupOrder(hostname string) hostLookupOrder {
// Something weird. Let libc deal with it. // Something weird. Let libc deal with it.
return hostLookupCgo 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
}
// 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 }
...@@ -295,3 +295,7 @@ func TestConfHostLookupOrder(t *testing.T) { ...@@ -295,3 +295,7 @@ func TestConfHostLookupOrder(t *testing.T) {
} }
} }
func TestSystemConf(t *testing.T) {
systemConf()
}
...@@ -46,6 +46,14 @@ import ( ...@@ -46,6 +46,14 @@ import (
"time" "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() { func init() {
sysInit() sysInit()
supportsIPv4 = probeIPv4Stack() supportsIPv4 = probeIPv4Stack()
......
...@@ -361,3 +361,26 @@ func readFull(r io.Reader) (all []byte, err error) { ...@@ -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 ""
}
...@@ -50,3 +50,30 @@ func TestReadLine(t *testing.T) { ...@@ -50,3 +50,30 @@ func TestReadLine(t *testing.T) {
byteno += len(line) + 1 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)
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment