--- /dev/null
+// 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
+}
--- /dev/null
+// 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)
+ }
+ }
+ }
+
+}
"io"
"math/rand"
"os"
+ "strconv"
"sync"
"time"
)
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).
// depending on our lookup code, so that Go and C get the same
// answers.
func goLookupHost(name string) (addrs []string, err error) {
- // Use entries from /etc/hosts if they match.
- addrs = lookupStaticHost(name)
- if len(addrs) > 0 {
- return
+ 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 || order == hostLookupFiles {
+ return
+ }
}
- ips, err := goLookupIP(name)
+ ips, err := goLookupIPOrder(name, order)
if err != nil {
return
}
return
}
+// 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)
+ }
+ }
+ 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 {
- 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
+ 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 {
}
}
}
- if len(addrs) == 0 && lastErr != nil {
- return nil, lastErr
+ if len(addrs) == 0 {
+ if lastErr != nil {
+ return nil, lastErr
+ }
+ if order == hostLookupDNSFiles {
+ addrs = goLookupIPFiles(name)
+ }
}
return addrs, nil
}
package net
type dnsConfig struct {
- servers []string // servers to use
- search []string // suffixes to append to local name
- ndots int // number of dots in name to trigger absolute lookup
- timeout int // seconds before giving up on packet
- attempts int // lost packets before giving up on server
- rotate bool // round robin among servers
+ servers []string // servers to use
+ search []string // suffixes to append to local name
+ ndots int // number of dots in name to trigger absolute lookup
+ 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.
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
}
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)
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
var dnsReadConfigTests = []struct {
name string
- conf dnsConfig
+ want *dnsConfig
}{
{
name: "testdata/resolv.conf",
- conf: 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,
+ 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,
},
{
name: "testdata/search-resolv.conf",
- conf: dnsConfig{
+ want: &dnsConfig{
servers: []string{"8.8.8.8"},
search: []string{"test", "invalid"},
ndots: 1,
},
{
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."},
},
},
}
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)
}
}
}
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 addrs, err
+ 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 addrs, err
+ return goLookupIPOrder(host, order)
}
func lookupPort(network, service string) (int, error) {
--- /dev/null
+// 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
+}
--- /dev/null
+// 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)
+ }
+ }
+}
}
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
+ }
+ }
+}
--- /dev/null
+# Generated by vio0 dhclient
+search c.symbolic-datum-552.internal.
+nameserver 169.254.169.254
+nameserver 10.240.0.1
+lookup file bind