Skip to content

Commit

Permalink
net: add mechanisms to force go or cgo lookup, and to debug default s…
Browse files Browse the repository at this point in the history
…trategy

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 <[email protected]>
Run-TryBot: Brad Fitzpatrick <[email protected]>
  • Loading branch information
bradfitz committed Jul 9, 2015
1 parent 4c33250 commit b615ad8
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/net/cgo_stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

package net

func init() { netGo = true }

type addrinfoErrno int

func (eai addrinfoErrno) Error() string { return "<nil>" }
Expand Down
74 changes: 71 additions & 3 deletions src/net/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package net
import (
"os"
"runtime"
"strconv"
"sync"
"syscall"
)
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
18 changes: 18 additions & 0 deletions src/net/conf_netcgo.go
Original file line number Diff line number Diff line change
@@ -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 }
4 changes: 4 additions & 0 deletions src/net/conf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,7 @@ func TestConfHostLookupOrder(t *testing.T) {
}

}

func TestSystemConf(t *testing.T) {
systemConf()
}
8 changes: 8 additions & 0 deletions src/net/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
23 changes: 23 additions & 0 deletions src/net/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
}
27 changes: 27 additions & 0 deletions src/net/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

0 comments on commit b615ad8

Please sign in to comment.