mirror of
https://github.com/golang/go
synced 2024-11-18 14:44:41 -07:00
net: use Go's DNS resolver when system configuration permits
If the machine's network configuration files (resolv.conf, nsswitch.conf) don't have any unsupported options, prefer Go's DNS resolver, which doesn't have the cgo & thread over. It means users can have more than 500 DNS requests outstanding (our current limit for cgo lookups) and not have one blocked thread per outstanding request. Discussed in thread https://groups.google.com/d/msg/golang-dev/2ZUi792oztM/Q0rg_DkF5HMJ Change-Id: I3f685d70aff6b47bec30b63e9fba674b20507f95 Reviewed-on: https://go-review.googlesource.com/8945 Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
c231228085
commit
4a0ba7aa17
237
src/net/conf.go
Normal file
237
src/net/conf.go
Normal file
@ -0,0 +1,237 @@
|
||||
// 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 darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// conf represents a system's network configuration.
|
||||
type conf struct {
|
||||
// forceCgoLookupHost forces CGO to always be used, if available.
|
||||
forceCgoLookupHost bool
|
||||
|
||||
// machine has an /etc/mdns.allow file
|
||||
hasMDNSAllow bool
|
||||
|
||||
goos string // the runtime.GOOS, to ease testing
|
||||
|
||||
nss *nssConf
|
||||
resolv *dnsConfig
|
||||
}
|
||||
|
||||
var (
|
||||
confOnce sync.Once // guards init of confVal via initConfVal
|
||||
confVal = &conf{goos: runtime.GOOS}
|
||||
)
|
||||
|
||||
// systemConf returns the machine's network configuration.
|
||||
func systemConf() *conf {
|
||||
confOnce.Do(initConfVal)
|
||||
return confVal
|
||||
}
|
||||
|
||||
func initConfVal() {
|
||||
// Darwin pops up annoying dialog boxes if programs try to do
|
||||
// their own DNS requests. So always use cgo instead, which
|
||||
// avoids that.
|
||||
if runtime.GOOS == "darwin" {
|
||||
confVal.forceCgoLookupHost = true
|
||||
return
|
||||
}
|
||||
|
||||
// If any environment-specified resolver options are specified,
|
||||
// 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") != "" ||
|
||||
localDomainDefined {
|
||||
confVal.forceCgoLookupHost = true
|
||||
return
|
||||
}
|
||||
|
||||
// OpenBSD apparently lets you override the location of resolv.conf
|
||||
// with ASR_CONFIG. If we notice that, defer to libc.
|
||||
if runtime.GOOS == "openbsd" && os.Getenv("ASR_CONFIG") != "" {
|
||||
confVal.forceCgoLookupHost = true
|
||||
return
|
||||
}
|
||||
|
||||
if runtime.GOOS != "openbsd" {
|
||||
confVal.nss = parseNSSConfFile("/etc/nsswitch.conf")
|
||||
}
|
||||
|
||||
if resolv, err := dnsReadConfig("/etc/resolv.conf"); err == nil {
|
||||
confVal.resolv = resolv
|
||||
} else if !os.IsNotExist(err.(*DNSConfigError).Err) {
|
||||
// If we can't read the resolv.conf file, assume it
|
||||
// had something important in it and defer to cgo.
|
||||
// libc's resolver might then fail too, but at least
|
||||
// it wasn't our fault.
|
||||
confVal.forceCgoLookupHost = true
|
||||
}
|
||||
|
||||
if _, err := os.Stat("/etc/mdns.allow"); err == nil {
|
||||
confVal.hasMDNSAllow = true
|
||||
}
|
||||
}
|
||||
|
||||
// hostLookupOrder determines which strategy to use to resolve hostname.
|
||||
func (c *conf) hostLookupOrder(hostname string) hostLookupOrder {
|
||||
if c.forceCgoLookupHost {
|
||||
return hostLookupCgo
|
||||
}
|
||||
if byteIndex(hostname, '\\') != -1 || byteIndex(hostname, '%') != -1 {
|
||||
// Don't deal with special form hostnames with backslashes
|
||||
// or '%'.
|
||||
return hostLookupCgo
|
||||
}
|
||||
|
||||
// OpenBSD is unique and doesn't use nsswitch.conf.
|
||||
// It also doesn't support mDNS.
|
||||
if c.goos == "openbsd" {
|
||||
// OpenBSD's resolv.conf manpage says that a non-existent
|
||||
// resolv.conf means "lookup" defaults to only "files",
|
||||
// without DNS lookups.
|
||||
if c.resolv == nil {
|
||||
return hostLookupFiles
|
||||
}
|
||||
lookup := c.resolv.lookup
|
||||
if len(lookup) == 0 {
|
||||
// http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5
|
||||
// "If the lookup keyword is not used in the
|
||||
// system's resolv.conf file then the assumed
|
||||
// order is 'bind file'"
|
||||
return hostLookupDNSFiles
|
||||
}
|
||||
if len(lookup) < 1 || len(lookup) > 2 {
|
||||
return hostLookupCgo
|
||||
}
|
||||
switch lookup[0] {
|
||||
case "bind":
|
||||
if len(lookup) == 2 {
|
||||
if lookup[1] == "file" {
|
||||
return hostLookupDNSFiles
|
||||
}
|
||||
return hostLookupCgo
|
||||
}
|
||||
return hostLookupDNS
|
||||
case "file":
|
||||
if len(lookup) == 2 {
|
||||
if lookup[1] == "bind" {
|
||||
return hostLookupFilesDNS
|
||||
}
|
||||
return hostLookupCgo
|
||||
}
|
||||
return hostLookupFiles
|
||||
default:
|
||||
return hostLookupCgo
|
||||
}
|
||||
}
|
||||
if c.resolv != nil && c.resolv.unknownOpt {
|
||||
return hostLookupCgo
|
||||
}
|
||||
|
||||
hasDot := byteIndex(hostname, '.') != -1
|
||||
|
||||
// Canonicalize the hostname by removing any trailing dot.
|
||||
if stringsHasSuffix(hostname, ".") {
|
||||
hostname = hostname[:len(hostname)-1]
|
||||
}
|
||||
if stringsHasSuffixFold(hostname, ".local") {
|
||||
// Per RFC 6762, the ".local" TLD is special. And
|
||||
// because Go's native resolver doesn't do mDNS or
|
||||
// similar local resolution mechanisms, assume that
|
||||
// libc might (via Avahi, etc) and use cgo.
|
||||
return hostLookupCgo
|
||||
}
|
||||
|
||||
nss := c.nss
|
||||
srcs := nss.sources["hosts"]
|
||||
// If /etc/nsswitch.conf doesn't exist or doesn't specify any
|
||||
// sources for "hosts", assume Go's DNS will work fine.
|
||||
if os.IsNotExist(nss.err) || (nss.err == nil && len(srcs) == 0) {
|
||||
if c.goos == "solaris" {
|
||||
// illumos defaults to "nis [NOTFOUND=return] files"
|
||||
return hostLookupCgo
|
||||
}
|
||||
if c.goos == "linux" {
|
||||
// glibc says the default is "dns [!UNAVAIL=return] files"
|
||||
// http://www.gnu.org/software/libc/manual/html_node/Notes-on-NSS-Configuration-File.html.
|
||||
return hostLookupDNSFiles
|
||||
}
|
||||
return hostLookupFilesDNS
|
||||
}
|
||||
if nss.err != nil {
|
||||
// We failed to parse or open nsswitch.conf, so
|
||||
// conservatively assume we should use cgo if it's
|
||||
// available.
|
||||
return hostLookupCgo
|
||||
}
|
||||
|
||||
var mdnsSource, filesSource, dnsSource bool
|
||||
var first string
|
||||
for _, src := range srcs {
|
||||
if src.source == "myhostname" {
|
||||
if hasDot {
|
||||
continue
|
||||
}
|
||||
return hostLookupCgo
|
||||
}
|
||||
if src.source == "files" || src.source == "dns" {
|
||||
if !src.standardCriteria() {
|
||||
return hostLookupCgo // non-standard; let libc deal with it.
|
||||
}
|
||||
if src.source == "files" {
|
||||
filesSource = true
|
||||
} else if src.source == "dns" {
|
||||
dnsSource = true
|
||||
}
|
||||
if first == "" {
|
||||
first = src.source
|
||||
}
|
||||
continue
|
||||
}
|
||||
if stringsHasPrefix(src.source, "mdns") {
|
||||
// e.g. "mdns4", "mdns4_minimal"
|
||||
// We already returned true before if it was *.local.
|
||||
// libc wouldn't have found a hit on this anyway.
|
||||
mdnsSource = true
|
||||
continue
|
||||
}
|
||||
// Some source we don't know how to deal with.
|
||||
return hostLookupCgo
|
||||
}
|
||||
|
||||
// We don't parse mdns.allow files. They're rare. If one
|
||||
// exists, it might list other TLDs (besides .local) or even
|
||||
// '*', so just let libc deal with it.
|
||||
if mdnsSource && c.hasMDNSAllow {
|
||||
return hostLookupCgo
|
||||
}
|
||||
|
||||
// Cases where Go can handle it without cgo and C thread
|
||||
// overhead.
|
||||
switch {
|
||||
case filesSource && dnsSource:
|
||||
if first == "files" {
|
||||
return hostLookupFilesDNS
|
||||
} else {
|
||||
return hostLookupDNSFiles
|
||||
}
|
||||
case filesSource:
|
||||
return hostLookupFiles
|
||||
case dnsSource:
|
||||
return hostLookupDNS
|
||||
}
|
||||
|
||||
// Something weird. Let libc deal with it.
|
||||
return hostLookupCgo
|
||||
}
|
245
src/net/conf_test.go
Normal file
245
src/net/conf_test.go
Normal file
@ -0,0 +1,245 @@
|
||||
// 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 darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type nssHostTest struct {
|
||||
host string
|
||||
want hostLookupOrder
|
||||
}
|
||||
|
||||
func nssStr(s string) *nssConf { return parseNSSConf(strings.NewReader(s)) }
|
||||
|
||||
func TestConfHostLookupOrder(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
c *conf
|
||||
goos string
|
||||
hostTests []nssHostTest
|
||||
}{
|
||||
{
|
||||
name: "force",
|
||||
c: &conf{
|
||||
forceCgoLookupHost: true,
|
||||
nss: nssStr("foo: bar"),
|
||||
},
|
||||
hostTests: []nssHostTest{
|
||||
{"foo.local", hostLookupCgo},
|
||||
{"google.com", hostLookupCgo},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ubuntu_trusty_avahi",
|
||||
c: &conf{
|
||||
nss: nssStr("hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4"),
|
||||
},
|
||||
hostTests: []nssHostTest{
|
||||
{"foo.local", hostLookupCgo},
|
||||
{"foo.local.", hostLookupCgo},
|
||||
{"foo.LOCAL", hostLookupCgo},
|
||||
{"foo.LOCAL.", hostLookupCgo},
|
||||
{"google.com", hostLookupFilesDNS},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "freebsdlinux_no_resolv_conf",
|
||||
c: &conf{
|
||||
goos: "freebsd",
|
||||
nss: nssStr("foo: bar"),
|
||||
},
|
||||
hostTests: []nssHostTest{{"google.com", hostLookupFilesDNS}},
|
||||
},
|
||||
// On OpenBSD, no resolv.conf means no DNS.
|
||||
{
|
||||
name: "openbsd_no_resolv_conf",
|
||||
c: &conf{
|
||||
goos: "openbsd",
|
||||
},
|
||||
hostTests: []nssHostTest{{"google.com", hostLookupFiles}},
|
||||
},
|
||||
{
|
||||
name: "solaris_no_nsswitch",
|
||||
c: &conf{
|
||||
goos: "solaris",
|
||||
nss: &nssConf{err: os.ErrNotExist},
|
||||
},
|
||||
hostTests: []nssHostTest{{"google.com", hostLookupCgo}},
|
||||
},
|
||||
{
|
||||
name: "openbsd_lookup_bind_file",
|
||||
c: &conf{
|
||||
goos: "openbsd",
|
||||
resolv: &dnsConfig{lookup: []string{"bind", "file"}},
|
||||
},
|
||||
hostTests: []nssHostTest{
|
||||
{"google.com", hostLookupDNSFiles},
|
||||
{"foo.local", hostLookupDNSFiles},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "openbsd_lookup_file_bind",
|
||||
c: &conf{
|
||||
goos: "openbsd",
|
||||
resolv: &dnsConfig{lookup: []string{"file", "bind"}},
|
||||
},
|
||||
hostTests: []nssHostTest{{"google.com", hostLookupFilesDNS}},
|
||||
},
|
||||
{
|
||||
name: "openbsd_lookup_bind",
|
||||
c: &conf{
|
||||
goos: "openbsd",
|
||||
resolv: &dnsConfig{lookup: []string{"bind"}},
|
||||
},
|
||||
hostTests: []nssHostTest{{"google.com", hostLookupDNS}},
|
||||
},
|
||||
{
|
||||
name: "openbsd_lookup_file",
|
||||
c: &conf{
|
||||
goos: "openbsd",
|
||||
resolv: &dnsConfig{lookup: []string{"file"}},
|
||||
},
|
||||
hostTests: []nssHostTest{{"google.com", hostLookupFiles}},
|
||||
},
|
||||
{
|
||||
name: "openbsd_lookup_yp",
|
||||
c: &conf{
|
||||
goos: "openbsd",
|
||||
resolv: &dnsConfig{lookup: []string{"file", "bind", "yp"}},
|
||||
},
|
||||
hostTests: []nssHostTest{{"google.com", hostLookupCgo}},
|
||||
},
|
||||
{
|
||||
name: "openbsd_lookup_two",
|
||||
c: &conf{
|
||||
goos: "openbsd",
|
||||
resolv: &dnsConfig{lookup: []string{"file", "foo"}},
|
||||
},
|
||||
hostTests: []nssHostTest{{"google.com", hostLookupCgo}},
|
||||
},
|
||||
{
|
||||
name: "openbsd_lookup_empty",
|
||||
c: &conf{
|
||||
goos: "openbsd",
|
||||
resolv: &dnsConfig{lookup: nil},
|
||||
},
|
||||
hostTests: []nssHostTest{{"google.com", hostLookupDNSFiles}},
|
||||
},
|
||||
// glibc lacking an nsswitch.conf, per
|
||||
// http://www.gnu.org/software/libc/manual/html_node/Notes-on-NSS-Configuration-File.html
|
||||
{
|
||||
name: "linux_no_nsswitch.conf",
|
||||
c: &conf{
|
||||
goos: "linux",
|
||||
nss: &nssConf{err: os.ErrNotExist},
|
||||
},
|
||||
hostTests: []nssHostTest{{"google.com", hostLookupDNSFiles}},
|
||||
},
|
||||
{
|
||||
name: "files_mdns_dns",
|
||||
c: &conf{nss: nssStr("hosts: files mdns dns")},
|
||||
hostTests: []nssHostTest{
|
||||
{"x.com", hostLookupFilesDNS},
|
||||
{"x.local", hostLookupCgo},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dns_special_hostnames",
|
||||
c: &conf{nss: nssStr("hosts: dns")},
|
||||
hostTests: []nssHostTest{
|
||||
{"x.com", hostLookupDNS},
|
||||
{"x\\.com", hostLookupCgo}, // punt on weird glibc escape
|
||||
{"foo.com%en0", hostLookupCgo}, // and IPv6 zones
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mdns_allow",
|
||||
c: &conf{
|
||||
nss: nssStr("hosts: files mdns dns"),
|
||||
hasMDNSAllow: true,
|
||||
},
|
||||
hostTests: []nssHostTest{
|
||||
{"x.com", hostLookupCgo},
|
||||
{"x.local", hostLookupCgo},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files_dns",
|
||||
c: &conf{nss: nssStr("hosts: files dns")},
|
||||
hostTests: []nssHostTest{
|
||||
{"x.com", hostLookupFilesDNS},
|
||||
{"x", hostLookupFilesDNS},
|
||||
{"x.local", hostLookupCgo},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dns_files",
|
||||
c: &conf{nss: nssStr("hosts: dns files")},
|
||||
hostTests: []nssHostTest{
|
||||
{"x.com", hostLookupDNSFiles},
|
||||
{"x", hostLookupDNSFiles},
|
||||
{"x.local", hostLookupCgo},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "something_custom",
|
||||
c: &conf{nss: nssStr("hosts: dns files something_custom")},
|
||||
hostTests: []nssHostTest{
|
||||
{"x.com", hostLookupCgo},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "myhostname",
|
||||
c: &conf{nss: nssStr("hosts: files dns myhostname")},
|
||||
hostTests: []nssHostTest{
|
||||
{"x.com", hostLookupFilesDNS},
|
||||
{"somehostname", hostLookupCgo},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ubuntu14.04.02",
|
||||
c: &conf{nss: nssStr("hosts: files myhostname mdns4_minimal [NOTFOUND=return] dns mdns4")},
|
||||
hostTests: []nssHostTest{
|
||||
{"x.com", hostLookupFilesDNS},
|
||||
{"somehostname", hostLookupCgo},
|
||||
},
|
||||
},
|
||||
// Debian Squeeze is just "dns,files", but lists all
|
||||
// the default criteria for dns, but then has a
|
||||
// non-standard but redundant notfound=return for the
|
||||
// files.
|
||||
{
|
||||
name: "debian_squeeze",
|
||||
c: &conf{nss: nssStr("hosts: dns [success=return notfound=continue unavail=continue tryagain=continue] files [notfound=return]")},
|
||||
hostTests: []nssHostTest{
|
||||
{"x.com", hostLookupDNSFiles},
|
||||
{"somehostname", hostLookupDNSFiles},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "resolv.conf-unknown",
|
||||
c: &conf{
|
||||
nss: nssStr("foo: bar"),
|
||||
resolv: &dnsConfig{unknownOpt: true},
|
||||
},
|
||||
hostTests: []nssHostTest{{"google.com", hostLookupCgo}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
for _, ht := range tt.hostTests {
|
||||
gotOrder := tt.c.hostLookupOrder(ht.host)
|
||||
if gotOrder != ht.want {
|
||||
t.Errorf("%s: useCgoLookupHost(%q) = %v; want %v", tt.name, ht.host, gotOrder, ht.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -20,6 +20,7 @@ import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@ -332,6 +333,35 @@ func lookup(name string, qtype uint16) (cname string, rrs []dnsRR, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// hostLookupOrder specifies the order of LookupHost lookup strategies.
|
||||
// It is basically a simplified representation of nsswitch.conf.
|
||||
// "files" means /etc/hosts.
|
||||
type hostLookupOrder int
|
||||
|
||||
const (
|
||||
// hostLookupCgo means defer to cgo.
|
||||
hostLookupCgo hostLookupOrder = iota
|
||||
hostLookupFilesDNS // files first
|
||||
hostLookupDNSFiles // dns first
|
||||
hostLookupFiles // only files
|
||||
hostLookupDNS // only DNS
|
||||
)
|
||||
|
||||
var lookupOrderName = map[hostLookupOrder]string{
|
||||
hostLookupCgo: "cgo",
|
||||
hostLookupFilesDNS: "files,dns",
|
||||
hostLookupDNSFiles: "dns,files",
|
||||
hostLookupFiles: "files",
|
||||
hostLookupDNS: "dns",
|
||||
}
|
||||
|
||||
func (o hostLookupOrder) String() string {
|
||||
if s, ok := lookupOrderName[o]; ok {
|
||||
return s
|
||||
}
|
||||
return "hostLookupOrder=" + strconv.Itoa(int(o)) + "??"
|
||||
}
|
||||
|
||||
// goLookupHost is the native Go implementation of LookupHost.
|
||||
// Used only if cgoLookupHost refuses to handle the request
|
||||
// (that is, only if cgoLookupHost is the stub in cgo_stub.go).
|
||||
@ -339,12 +369,18 @@ func lookup(name string, qtype uint16) (cname string, rrs []dnsRR, err error) {
|
||||
// depending on our lookup code, so that Go and C get the same
|
||||
// answers.
|
||||
func goLookupHost(name string) (addrs []string, err error) {
|
||||
return goLookupHostOrder(name, hostLookupFilesDNS)
|
||||
}
|
||||
|
||||
func goLookupHostOrder(name string, order hostLookupOrder) (addrs []string, err error) {
|
||||
if order == hostLookupFilesDNS || order == hostLookupFiles {
|
||||
// Use entries from /etc/hosts if they match.
|
||||
addrs = lookupStaticHost(name)
|
||||
if len(addrs) > 0 {
|
||||
if len(addrs) > 0 || order == hostLookupFiles {
|
||||
return
|
||||
}
|
||||
ips, err := goLookupIP(name)
|
||||
}
|
||||
ips, err := goLookupIPOrder(name, order)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -355,25 +391,30 @@ func goLookupHost(name string) (addrs []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// goLookupIP is the native Go implementation of LookupIP.
|
||||
// Used only if cgoLookupIP refuses to handle the request
|
||||
// (that is, only if cgoLookupIP is the stub in cgo_stub.go).
|
||||
// Normally we let cgo use the C library resolver instead of
|
||||
// depending on our lookup code, so that Go and C get the same
|
||||
// answers.
|
||||
func goLookupIP(name string) (addrs []IPAddr, err error) {
|
||||
// Use entries from /etc/hosts if possible.
|
||||
haddrs := lookupStaticHost(name)
|
||||
if len(haddrs) > 0 {
|
||||
for _, haddr := range haddrs {
|
||||
// lookup entries from /etc/hosts
|
||||
func goLookupIPFiles(name string) (addrs []IPAddr) {
|
||||
for _, haddr := range lookupStaticHost(name) {
|
||||
haddr, zone := splitHostZone(haddr)
|
||||
if ip := ParseIP(haddr); ip != nil {
|
||||
addr := IPAddr{IP: ip, Zone: zone}
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
}
|
||||
if len(addrs) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// goLookupIP is the native Go implementation of LookupIP.
|
||||
// Used only if cgoLookupIP refuses to handle the request
|
||||
// (that is, only if cgoLookupIP is the stub in cgo_stub.go).
|
||||
func goLookupIP(name string) (addrs []IPAddr, err error) {
|
||||
return goLookupIPOrder(name, hostLookupFilesDNS)
|
||||
}
|
||||
|
||||
func goLookupIPOrder(name string, order hostLookupOrder) (addrs []IPAddr, err error) {
|
||||
if order == hostLookupFilesDNS || order == hostLookupFiles {
|
||||
addrs = goLookupIPFiles(name)
|
||||
if len(addrs) > 0 || order == hostLookupFiles {
|
||||
return addrs, nil
|
||||
}
|
||||
}
|
||||
type racer struct {
|
||||
@ -409,9 +450,14 @@ func goLookupIP(name string) (addrs []IPAddr, err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(addrs) == 0 && lastErr != nil {
|
||||
if len(addrs) == 0 {
|
||||
if lastErr != nil {
|
||||
return nil, lastErr
|
||||
}
|
||||
if order == hostLookupDNSFiles {
|
||||
addrs = goLookupIPFiles(name)
|
||||
}
|
||||
}
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,8 @@ type dnsConfig struct {
|
||||
timeout int // seconds before giving up on packet
|
||||
attempts int // lost packets before giving up on server
|
||||
rotate bool // round robin among servers
|
||||
unknownOpt bool // anything unknown was encountered
|
||||
lookup []string // OpenBSD top-level database "lookup" order
|
||||
}
|
||||
|
||||
// See resolv.conf(5) on a Linux machine.
|
||||
@ -32,6 +34,10 @@ func dnsReadConfig(filename string) (*dnsConfig, error) {
|
||||
attempts: 2,
|
||||
}
|
||||
for line, ok := file.readLine(); ok; line, ok = file.readLine() {
|
||||
if len(line) > 0 && (line[0] == ';' || line[0] == '#') {
|
||||
// comment.
|
||||
continue
|
||||
}
|
||||
f := getFields(line)
|
||||
if len(f) < 1 {
|
||||
continue
|
||||
@ -61,8 +67,7 @@ func dnsReadConfig(filename string) (*dnsConfig, error) {
|
||||
}
|
||||
|
||||
case "options": // magic options
|
||||
for i := 1; i < len(f); i++ {
|
||||
s := f[i]
|
||||
for _, s := range f[1:] {
|
||||
switch {
|
||||
case hasPrefix(s, "ndots:"):
|
||||
n, _, _ := dtoi(s, 6)
|
||||
@ -84,8 +89,19 @@ func dnsReadConfig(filename string) (*dnsConfig, error) {
|
||||
conf.attempts = n
|
||||
case s == "rotate":
|
||||
conf.rotate = true
|
||||
default:
|
||||
conf.unknownOpt = true
|
||||
}
|
||||
}
|
||||
|
||||
case "lookup":
|
||||
// OpenBSD option:
|
||||
// http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5
|
||||
// "the legal space-separated values are: bind, file, yp"
|
||||
conf.lookup = f[1:]
|
||||
|
||||
default:
|
||||
conf.unknownOpt = true
|
||||
}
|
||||
}
|
||||
return conf, nil
|
||||
|
@ -13,22 +13,23 @@ import (
|
||||
|
||||
var dnsReadConfigTests = []struct {
|
||||
name string
|
||||
conf dnsConfig
|
||||
want *dnsConfig
|
||||
}{
|
||||
{
|
||||
name: "testdata/resolv.conf",
|
||||
conf: dnsConfig{
|
||||
want: &dnsConfig{
|
||||
servers: []string{"8.8.8.8", "2001:4860:4860::8888", "fe80::1%lo0"},
|
||||
search: []string{"localdomain"},
|
||||
ndots: 5,
|
||||
timeout: 10,
|
||||
attempts: 3,
|
||||
rotate: true,
|
||||
unknownOpt: true, // the "options attempts 3" line
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "testdata/domain-resolv.conf",
|
||||
conf: dnsConfig{
|
||||
want: &dnsConfig{
|
||||
servers: []string{"8.8.8.8"},
|
||||
search: []string{"localdomain"},
|
||||
ndots: 1,
|
||||
@ -38,7 +39,7 @@ var dnsReadConfigTests = []struct {
|
||||
},
|
||||
{
|
||||
name: "testdata/search-resolv.conf",
|
||||
conf: dnsConfig{
|
||||
want: &dnsConfig{
|
||||
servers: []string{"8.8.8.8"},
|
||||
search: []string{"test", "invalid"},
|
||||
ndots: 1,
|
||||
@ -48,12 +49,23 @@ var dnsReadConfigTests = []struct {
|
||||
},
|
||||
{
|
||||
name: "testdata/empty-resolv.conf",
|
||||
conf: dnsConfig{
|
||||
want: &dnsConfig{
|
||||
ndots: 1,
|
||||
timeout: 5,
|
||||
attempts: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "testdata/openbsd-resolv.conf",
|
||||
want: &dnsConfig{
|
||||
ndots: 1,
|
||||
timeout: 5,
|
||||
attempts: 2,
|
||||
lookup: []string{"file", "bind"},
|
||||
servers: []string{"169.254.169.254", "10.240.0.1"},
|
||||
search: []string{"c.symbolic-datum-552.internal."},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestDNSReadConfig(t *testing.T) {
|
||||
@ -62,8 +74,8 @@ func TestDNSReadConfig(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(conf, &tt.conf) {
|
||||
t.Errorf("got %v; want %v", conf, &tt.conf)
|
||||
if !reflect.DeepEqual(conf, tt.want) {
|
||||
t.Errorf("%s:\n got: %+v\nwant: %+v", tt.name, conf, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,20 +49,28 @@ func lookupProtocol(name string) (int, error) {
|
||||
return proto, nil
|
||||
}
|
||||
|
||||
func lookupHost(host string) ([]string, error) {
|
||||
addrs, err, ok := cgoLookupHost(host)
|
||||
if !ok {
|
||||
addrs, err = goLookupHost(host)
|
||||
}
|
||||
func lookupHost(host string) (addrs []string, err error) {
|
||||
order := systemConf().hostLookupOrder(host)
|
||||
if order == hostLookupCgo {
|
||||
if addrs, err, ok := cgoLookupHost(host); ok {
|
||||
return addrs, err
|
||||
}
|
||||
// cgo not available (or netgo); fall back to Go's DNS resolver
|
||||
order = hostLookupFilesDNS
|
||||
}
|
||||
return goLookupHostOrder(host, order)
|
||||
}
|
||||
|
||||
func lookupIP(host string) ([]IPAddr, error) {
|
||||
addrs, err, ok := cgoLookupIP(host)
|
||||
if !ok {
|
||||
addrs, err = goLookupIP(host)
|
||||
}
|
||||
func lookupIP(host string) (addrs []IPAddr, err error) {
|
||||
order := systemConf().hostLookupOrder(host)
|
||||
if order == hostLookupCgo {
|
||||
if addrs, err, ok := cgoLookupIP(host); ok {
|
||||
return addrs, err
|
||||
}
|
||||
// cgo not available (or netgo); fall back to Go's DNS resolver
|
||||
order = hostLookupFilesDNS
|
||||
}
|
||||
return goLookupIPOrder(host, order)
|
||||
}
|
||||
|
||||
func lookupPort(network, service string) (int, error) {
|
||||
|
159
src/net/nss.go
Normal file
159
src/net/nss.go
Normal file
@ -0,0 +1,159 @@
|
||||
// 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 darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// nssConf represents the state of the machine's /etc/nsswitch.conf file.
|
||||
type nssConf struct {
|
||||
err error // any error encountered opening or parsing the file
|
||||
sources map[string][]nssSource // keyed by database (e.g. "hosts")
|
||||
}
|
||||
|
||||
type nssSource struct {
|
||||
source string // e.g. "compat", "files", "mdns4_minimal"
|
||||
criteria []nssCriterion
|
||||
}
|
||||
|
||||
// standardCriteria reports all specified criteria have the default
|
||||
// status actions.
|
||||
func (s nssSource) standardCriteria() bool {
|
||||
for i, crit := range s.criteria {
|
||||
if !crit.standardStatusAction(i == len(s.criteria)-1) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// nssCriterion is the parsed structure of one of the criteria in brackets
|
||||
// after an NSS source name.
|
||||
type nssCriterion struct {
|
||||
negate bool // if "!" was present
|
||||
status string // e.g. "success", "unavail" (lowercase)
|
||||
action string // e.g. "return", "continue" (lowercase)
|
||||
}
|
||||
|
||||
// standardStatusAction reports whether c is equivalent to not
|
||||
// specifying the criterion at all. last is whether this criteria is the
|
||||
// last in the list.
|
||||
func (c nssCriterion) standardStatusAction(last bool) bool {
|
||||
if c.negate {
|
||||
return false
|
||||
}
|
||||
var def string
|
||||
switch c.status {
|
||||
case "success":
|
||||
def = "return"
|
||||
case "notfound", "unavail", "tryagain":
|
||||
def = "continue"
|
||||
default:
|
||||
// Unknown status
|
||||
return false
|
||||
}
|
||||
if last && c.action == "return" {
|
||||
return true
|
||||
}
|
||||
return c.action == def
|
||||
}
|
||||
|
||||
func parseNSSConfFile(file string) *nssConf {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return &nssConf{err: err}
|
||||
}
|
||||
defer f.Close()
|
||||
return parseNSSConf(f)
|
||||
}
|
||||
|
||||
func parseNSSConf(r io.Reader) *nssConf {
|
||||
slurp, err := readFull(r)
|
||||
if err != nil {
|
||||
return &nssConf{err: err}
|
||||
}
|
||||
conf := new(nssConf)
|
||||
conf.err = foreachLine(slurp, func(line []byte) error {
|
||||
line = trimSpace(removeComment(line))
|
||||
if len(line) == 0 {
|
||||
return nil
|
||||
}
|
||||
colon := bytesIndexByte(line, ':')
|
||||
if colon == -1 {
|
||||
return errors.New("no colon on line")
|
||||
}
|
||||
db := string(trimSpace(line[:colon]))
|
||||
srcs := line[colon+1:]
|
||||
for {
|
||||
srcs = trimSpace(srcs)
|
||||
if len(srcs) == 0 {
|
||||
break
|
||||
}
|
||||
sp := bytesIndexByte(srcs, ' ')
|
||||
var src string
|
||||
if sp == -1 {
|
||||
src = string(srcs)
|
||||
srcs = nil // done
|
||||
} else {
|
||||
src = string(srcs[:sp])
|
||||
srcs = trimSpace(srcs[sp+1:])
|
||||
}
|
||||
var criteria []nssCriterion
|
||||
// See if there's a criteria block in brackets.
|
||||
if len(srcs) > 0 && srcs[0] == '[' {
|
||||
bclose := bytesIndexByte(srcs, ']')
|
||||
if bclose == -1 {
|
||||
return errors.New("unclosed criterion bracket")
|
||||
}
|
||||
var err error
|
||||
criteria, err = parseCriteria(srcs[1:bclose])
|
||||
if err != nil {
|
||||
return errors.New("invalid criteria: " + string(srcs[1:bclose]))
|
||||
}
|
||||
srcs = srcs[bclose+1:]
|
||||
}
|
||||
if conf.sources == nil {
|
||||
conf.sources = make(map[string][]nssSource)
|
||||
}
|
||||
conf.sources[db] = append(conf.sources[db], nssSource{
|
||||
source: src,
|
||||
criteria: criteria,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return conf
|
||||
}
|
||||
|
||||
// parses "foo=bar !foo=bar"
|
||||
func parseCriteria(x []byte) (c []nssCriterion, err error) {
|
||||
err = foreachField(x, func(f []byte) error {
|
||||
not := false
|
||||
if len(f) > 0 && f[0] == '!' {
|
||||
not = true
|
||||
f = f[1:]
|
||||
}
|
||||
if len(f) < 3 {
|
||||
return errors.New("criterion too short")
|
||||
}
|
||||
eq := bytesIndexByte(f, '=')
|
||||
if eq == -1 {
|
||||
return errors.New("criterion lacks equal sign")
|
||||
}
|
||||
lowerASCIIBytes(f)
|
||||
c = append(c, nssCriterion{
|
||||
negate: not,
|
||||
status: string(f[:eq]),
|
||||
action: string(f[eq+1:]),
|
||||
})
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
169
src/net/nss_test.go
Normal file
169
src/net/nss_test.go
Normal file
@ -0,0 +1,169 @@
|
||||
// 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 darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const ubuntuTrustyAvahi = `# /etc/nsswitch.conf
|
||||
#
|
||||
# Example configuration of GNU Name Service Switch functionality.
|
||||
# If you have the libc-doc-reference' and nfo' packages installed, try:
|
||||
# nfo libc "Name Service Switch"' for information about this file.
|
||||
|
||||
passwd: compat
|
||||
group: compat
|
||||
shadow: compat
|
||||
|
||||
hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4
|
||||
networks: files
|
||||
|
||||
protocols: db files
|
||||
services: db files
|
||||
ethers: db files
|
||||
rpc: db files
|
||||
|
||||
netgroup: nis
|
||||
`
|
||||
|
||||
func TestParseNSSConf(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
want *nssConf
|
||||
}{
|
||||
{
|
||||
name: "no_newline",
|
||||
in: "foo: a b",
|
||||
want: &nssConf{
|
||||
sources: map[string][]nssSource{
|
||||
"foo": {{source: "a"}, {source: "b"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newline",
|
||||
in: "foo: a b\n",
|
||||
want: &nssConf{
|
||||
sources: map[string][]nssSource{
|
||||
"foo": {{source: "a"}, {source: "b"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "whitespace",
|
||||
in: " foo:a b \n",
|
||||
want: &nssConf{
|
||||
sources: map[string][]nssSource{
|
||||
"foo": {{source: "a"}, {source: "b"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "comment1",
|
||||
in: " foo:a b#c\n",
|
||||
want: &nssConf{
|
||||
sources: map[string][]nssSource{
|
||||
"foo": {{source: "a"}, {source: "b"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "comment2",
|
||||
in: " foo:a b #c \n",
|
||||
want: &nssConf{
|
||||
sources: map[string][]nssSource{
|
||||
"foo": {{source: "a"}, {source: "b"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "crit",
|
||||
in: " foo:a b [!a=b X=Y ] c#d \n",
|
||||
want: &nssConf{
|
||||
sources: map[string][]nssSource{
|
||||
"foo": {
|
||||
{source: "a"},
|
||||
{
|
||||
source: "b",
|
||||
criteria: []nssCriterion{
|
||||
{
|
||||
negate: true,
|
||||
status: "a",
|
||||
action: "b",
|
||||
},
|
||||
{
|
||||
status: "x",
|
||||
action: "y",
|
||||
},
|
||||
},
|
||||
},
|
||||
{source: "c"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Ubuntu Trusty w/ avahi-daemon, libavahi-* etc installed.
|
||||
{
|
||||
name: "ubuntu_trusty_avahi",
|
||||
in: ubuntuTrustyAvahi,
|
||||
want: &nssConf{
|
||||
sources: map[string][]nssSource{
|
||||
"passwd": {{source: "compat"}},
|
||||
"group": {{source: "compat"}},
|
||||
"shadow": {{source: "compat"}},
|
||||
"hosts": {
|
||||
{source: "files"},
|
||||
{
|
||||
source: "mdns4_minimal",
|
||||
criteria: []nssCriterion{
|
||||
{
|
||||
negate: false,
|
||||
status: "notfound",
|
||||
action: "return",
|
||||
},
|
||||
},
|
||||
},
|
||||
{source: "dns"},
|
||||
{source: "mdns4"},
|
||||
},
|
||||
"networks": {{source: "files"}},
|
||||
"protocols": {
|
||||
{source: "db"},
|
||||
{source: "files"},
|
||||
},
|
||||
"services": {
|
||||
{source: "db"},
|
||||
{source: "files"},
|
||||
},
|
||||
"ethers": {
|
||||
{source: "db"},
|
||||
{source: "files"},
|
||||
},
|
||||
"rpc": {
|
||||
{source: "db"},
|
||||
{source: "files"},
|
||||
},
|
||||
"netgroup": {
|
||||
{source: "nis"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
gotConf := parseNSSConf(strings.NewReader(tt.in))
|
||||
if !reflect.DeepEqual(gotConf, tt.want) {
|
||||
t.Errorf("%s: mismatch\n got %#v\nwant %#v", tt.name, gotConf, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
129
src/net/parse.go
129
src/net/parse.go
@ -232,3 +232,132 @@ func last(s string, b byte) int {
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// lowerASCIIBytes makes x ASCII lowercase in-place.
|
||||
func lowerASCIIBytes(x []byte) {
|
||||
for i, b := range x {
|
||||
if 'A' <= b && b <= 'Z' {
|
||||
x[i] += 'a' - 'A'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lowerASCII returns the ASCII lowercase version of b.
|
||||
func lowerASCII(b byte) byte {
|
||||
if 'A' <= b && b <= 'Z' {
|
||||
return b + ('a' - 'A')
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// trimSpace returns x without any leading or trailing ASCII whitespace.
|
||||
func trimSpace(x []byte) []byte {
|
||||
for len(x) > 0 && isSpace(x[0]) {
|
||||
x = x[1:]
|
||||
}
|
||||
for len(x) > 0 && isSpace(x[len(x)-1]) {
|
||||
x = x[:len(x)-1]
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// isSpace reports whether b is an ASCII space character.
|
||||
func isSpace(b byte) bool {
|
||||
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
|
||||
}
|
||||
|
||||
// removeComment returns line, removing any '#' byte and any following
|
||||
// bytes.
|
||||
func removeComment(line []byte) []byte {
|
||||
if i := bytesIndexByte(line, '#'); i != -1 {
|
||||
return line[:i]
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
// foreachLine runs fn on each line of x.
|
||||
// Each line (except for possibly the last) ends in '\n'.
|
||||
// It returns the first non-nil error returned by fn.
|
||||
func foreachLine(x []byte, fn func(line []byte) error) error {
|
||||
for len(x) > 0 {
|
||||
nl := bytesIndexByte(x, '\n')
|
||||
if nl == -1 {
|
||||
return fn(x)
|
||||
}
|
||||
line := x[:nl+1]
|
||||
x = x[nl+1:]
|
||||
if err := fn(line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// foreachField runs fn on each non-empty run of non-space bytes in x.
|
||||
// It returns the first non-nil error returned by fn.
|
||||
func foreachField(x []byte, fn func(field []byte) error) error {
|
||||
x = trimSpace(x)
|
||||
for len(x) > 0 {
|
||||
sp := bytesIndexByte(x, ' ')
|
||||
if sp == -1 {
|
||||
return fn(x)
|
||||
}
|
||||
if field := trimSpace(x[:sp]); len(field) > 0 {
|
||||
if err := fn(field); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
x = trimSpace(x[sp+1:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bytesIndexByte is bytes.IndexByte. It returns the index of the
|
||||
// first instance of c in s, or -1 if c is not present in s.
|
||||
func bytesIndexByte(s []byte, c byte) int {
|
||||
for i, b := range s {
|
||||
if b == c {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// stringsHasSuffix is strings.HasSuffix. It reports whether s ends in
|
||||
// suffix.
|
||||
func stringsHasSuffix(s, suffix string) bool {
|
||||
return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
|
||||
}
|
||||
|
||||
// stringsHasSuffixFold reports whether s ends in suffix,
|
||||
// ASCII-case-insensitively.
|
||||
func stringsHasSuffixFold(s, suffix string) bool {
|
||||
if len(suffix) > len(s) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(suffix); i++ {
|
||||
if lowerASCII(suffix[i]) != lowerASCII(s[len(s)-len(suffix)+i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// stringsHasPrefix is strings.HasPrefix. It reports whether s begins with prefix.
|
||||
func stringsHasPrefix(s, prefix string) bool {
|
||||
return len(s) >= len(prefix) && s[:len(prefix)] == prefix
|
||||
}
|
||||
|
||||
func readFull(r io.Reader) (all []byte, err error) {
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := r.Read(buf)
|
||||
all = append(all, buf[:n]...)
|
||||
if err == io.EOF {
|
||||
return all, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
src/net/testdata/openbsd-resolv.conf
vendored
Normal file
5
src/net/testdata/openbsd-resolv.conf
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# Generated by vio0 dhclient
|
||||
search c.symbolic-datum-552.internal.
|
||||
nameserver 169.254.169.254
|
||||
nameserver 10.240.0.1
|
||||
lookup file bind
|
Loading…
Reference in New Issue
Block a user