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: 's avatarRuss Cox <rsc@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
parent 4c332508
......@@ -6,6 +6,8 @@
package net
func init() { netGo = true }
type addrinfoErrno int
func (eai addrinfoErrno) Error() string { return "<nil>" }
......
......@@ -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
}
// 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) {
}
}
func TestSystemConf(t *testing.T) {
systemConf()
}
......@@ -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()
......
......@@ -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) {
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