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) {
+ confVal.resolv = dnsReadConfig("/etc/resolv.conf")
+ if confVal.resolv.err != nil && !os.IsNotExist(confVal.resolv.err) &&
+ !os.IsPermission(confVal.resolv.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
// hostLookupOrder determines which strategy to use to resolve hostname.
func (c *conf) hostLookupOrder(hostname string) hostLookupOrder {
- if c.forceCgoLookupHost {
+ if c.forceCgoLookupHost || c.resolv.unknownOpt {
return hostLookupCgo
}
if byteIndex(hostname, '\\') != -1 || byteIndex(hostname, '%') != -1 {
// 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 {
+ if os.IsNotExist(c.resolv.err) {
return hostLookupFiles
}
lookup := c.resolv.lookup
return hostLookupCgo
}
}
- if c.resolv != nil && c.resolv.unknownOpt {
- return hostLookupCgo
- }
hasDot := byteIndex(hostname, '.') != -1
func nssStr(s string) *nssConf { return parseNSSConf(strings.NewReader(s)) }
+// represents a dnsConfig returned by parsing a nonexistent resolv.conf
+var defaultResolvConf = &dnsConfig{
+ servers: defaultNS,
+ ndots: 1,
+ timeout: 5,
+ attempts: 2,
+ err: os.ErrNotExist,
+}
+
func TestConfHostLookupOrder(t *testing.T) {
tests := []struct {
name string
c: &conf{
forceCgoLookupHost: true,
nss: nssStr("foo: bar"),
+ resolv: defaultResolvConf,
},
hostTests: []nssHostTest{
{"foo.local", hostLookupCgo},
{
name: "ubuntu_trusty_avahi",
c: &conf{
- nss: nssStr("hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4"),
+ nss: nssStr("hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4"),
+ resolv: defaultResolvConf,
},
hostTests: []nssHostTest{
{"foo.local", hostLookupCgo},
{
name: "freebsdlinux_no_resolv_conf",
c: &conf{
- goos: "freebsd",
- nss: nssStr("foo: bar"),
+ goos: "freebsd",
+ nss: nssStr("foo: bar"),
+ resolv: defaultResolvConf,
},
hostTests: []nssHostTest{{"google.com", hostLookupFilesDNS}},
},
{
name: "openbsd_no_resolv_conf",
c: &conf{
- goos: "openbsd",
+ goos: "openbsd",
+ resolv: defaultResolvConf,
},
hostTests: []nssHostTest{{"google.com", hostLookupFiles}},
},
{
name: "solaris_no_nsswitch",
c: &conf{
- goos: "solaris",
- nss: &nssConf{err: os.ErrNotExist},
+ goos: "solaris",
+ nss: &nssConf{err: os.ErrNotExist},
+ resolv: defaultResolvConf,
},
hostTests: []nssHostTest{{"google.com", hostLookupCgo}},
},
{
name: "linux_no_nsswitch.conf",
c: &conf{
- goos: "linux",
- nss: &nssConf{err: os.ErrNotExist},
+ goos: "linux",
+ nss: &nssConf{err: os.ErrNotExist},
+ resolv: defaultResolvConf,
},
hostTests: []nssHostTest{{"google.com", hostLookupDNSFiles}},
},
{
name: "files_mdns_dns",
- c: &conf{nss: nssStr("hosts: files mdns dns")},
+ c: &conf{
+ nss: nssStr("hosts: files mdns dns"),
+ resolv: defaultResolvConf,
+ },
hostTests: []nssHostTest{
{"x.com", hostLookupFilesDNS},
{"x.local", hostLookupCgo},
},
{
name: "dns_special_hostnames",
- c: &conf{nss: nssStr("hosts: dns")},
+ c: &conf{
+ nss: nssStr("hosts: dns"),
+ resolv: defaultResolvConf,
+ },
hostTests: []nssHostTest{
{"x.com", hostLookupDNS},
{"x\\.com", hostLookupCgo}, // punt on weird glibc escape
name: "mdns_allow",
c: &conf{
nss: nssStr("hosts: files mdns dns"),
+ resolv: defaultResolvConf,
hasMDNSAllow: true,
},
hostTests: []nssHostTest{
},
{
name: "files_dns",
- c: &conf{nss: nssStr("hosts: files dns")},
+ c: &conf{
+ nss: nssStr("hosts: files dns"),
+ resolv: defaultResolvConf,
+ },
hostTests: []nssHostTest{
{"x.com", hostLookupFilesDNS},
{"x", hostLookupFilesDNS},
},
{
name: "dns_files",
- c: &conf{nss: nssStr("hosts: dns files")},
+ c: &conf{
+ nss: nssStr("hosts: dns files"),
+ resolv: defaultResolvConf,
+ },
hostTests: []nssHostTest{
{"x.com", hostLookupDNSFiles},
{"x", hostLookupDNSFiles},
},
{
name: "something_custom",
- c: &conf{nss: nssStr("hosts: dns files something_custom")},
+ c: &conf{
+ nss: nssStr("hosts: dns files something_custom"),
+ resolv: defaultResolvConf,
+ },
hostTests: []nssHostTest{
{"x.com", hostLookupCgo},
},
},
{
name: "myhostname",
- c: &conf{nss: nssStr("hosts: files dns myhostname")},
+ c: &conf{
+ nss: nssStr("hosts: files dns myhostname"),
+ resolv: defaultResolvConf,
+ },
hostTests: []nssHostTest{
{"x.com", hostLookupFilesDNS},
{"somehostname", hostLookupCgo},
},
{
name: "ubuntu14.04.02",
- c: &conf{nss: nssStr("hosts: files myhostname mdns4_minimal [NOTFOUND=return] dns mdns4")},
+ c: &conf{
+ nss: nssStr("hosts: files myhostname mdns4_minimal [NOTFOUND=return] dns mdns4"),
+ resolv: defaultResolvConf,
+ },
hostTests: []nssHostTest{
{"x.com", hostLookupFilesDNS},
{"somehostname", hostLookupCgo},
// files.
{
name: "debian_squeeze",
- c: &conf{nss: nssStr("hosts: dns [success=return notfound=continue unavail=continue tryagain=continue] files [notfound=return]")},
+ c: &conf{
+ nss: nssStr("hosts: dns [success=return notfound=continue unavail=continue tryagain=continue] files [notfound=return]"),
+ resolv: defaultResolvConf,
+ },
hostTests: []nssHostTest{
{"x.com", hostLookupDNSFiles},
{"somehostname", hostLookupDNSFiles},
name: "resolv.conf-unknown",
c: &conf{
nss: nssStr("foo: bar"),
- resolv: &dnsConfig{unknownOpt: true},
+ resolv: &dnsConfig{servers: defaultNS, ndots: 1, timeout: 5, attempts: 2, unknownOpt: true},
},
hostTests: []nssHostTest{{"google.com", hostLookupCgo}},
},
var cfg struct {
ch chan struct{}
- mu sync.RWMutex // protects dnsConfig and dnserr
+ mu sync.RWMutex // protects dnsConfig
dnsConfig *dnsConfig
- dnserr error
}
+
var onceLoadConfig sync.Once
// Assume dns config file is /etc/resolv.conf here
func loadConfig(resolvConfPath string, reloadTime time.Duration, quit <-chan chan struct{}) {
var mtime time.Time
cfg.ch = make(chan struct{}, 1)
- if fi, err := os.Stat(resolvConfPath); err != nil {
- cfg.dnserr = err
- } else {
+ if fi, err := os.Stat(resolvConfPath); err == nil {
mtime = fi.ModTime()
- cfg.dnsConfig, cfg.dnserr = dnsReadConfig(resolvConfPath)
}
+
+ cfg.dnsConfig = dnsReadConfig(resolvConfPath)
+
go func() {
for {
time.Sleep(reloadTime)
}
mtime = m
// In case of error, we keep the previous config
- ncfg, err := dnsReadConfig(resolvConfPath)
- if err != nil || len(ncfg.servers) == 0 {
- continue
+ if ncfg := dnsReadConfig(resolvConfPath); ncfg.err == nil {
+ cfg.mu.Lock()
+ cfg.dnsConfig = ncfg
+ cfg.mu.Unlock()
}
- cfg.mu.Lock()
- cfg.dnsConfig = ncfg
- cfg.dnserr = nil
- cfg.mu.Unlock()
}
}()
}
cfg.mu.RLock()
defer cfg.mu.RUnlock()
- if cfg.dnserr != nil || cfg.dnsConfig == nil {
- err = cfg.dnserr
- return
- }
// If name is rooted (trailing dot) or has enough dots,
// try it by itself first.
rooted := len(name) > 0 && name[len(name)-1] == '.'
r := newResolvConfTest(t)
defer r.Close()
- // resolv.conf.tmp does not exist yet
r.Start()
- if _, err := goLookupIP("golang.org"); err == nil {
- t.Fatal("goLookupIP(missing) succeeded")
- }
-
r.SetConf("nameserver 8.8.8.8")
+
if _, err := goLookupIP("golang.org"); err != nil {
t.Fatalf("goLookupIP(missing; good) failed: %v", err)
}
- // Using a bad resolv.conf while we had a good
- // one before should not update the config
+ // Using an empty resolv.conf should use localhost as servers
r.SetConf("")
- if _, err := goLookupIP("golang.org"); err != nil {
- t.Fatalf("goLookupIP(missing; good; bad) failed: %v", err)
+
+ if len(cfg.dnsConfig.servers) != len(defaultNS) {
+ t.Fatalf("goLookupIP(missing; good; bad) failed: servers=%v, want: %v", cfg.dnsConfig.servers, defaultNS)
}
+
+ for i := range cfg.dnsConfig.servers {
+ if cfg.dnsConfig.servers[i] != defaultNS[i] {
+ t.Fatalf("goLookupIP(missing; good; bad) failed: servers=%v, want: %v", cfg.dnsConfig.servers, defaultNS)
+ }
+ }
+
}
func TestReloadResolvConfChange(t *testing.T) {
r := newResolvConfTest(t)
defer r.Close()
- r.SetConf("nameserver 8.8.8.8")
r.Start()
+ r.SetConf("nameserver 8.8.8.8")
if _, err := goLookupIP("golang.org"); err != nil {
t.Fatalf("goLookupIP(good) failed: %v", err)
}
r.WantServers([]string{"8.8.8.8"})
- // Using a bad resolv.conf when we had a good one
- // before should not update the config
+ // Using an empty resolv.conf should use localhost as servers
r.SetConf("")
- if _, err := goLookupIP("golang.org"); err != nil {
- t.Fatalf("goLookupIP(good; bad) failed: %v", err)
+
+ if len(cfg.dnsConfig.servers) != len(defaultNS) {
+ t.Fatalf("goLookupIP(missing; good; bad) failed: servers=%v, want: %v", cfg.dnsConfig.servers, defaultNS)
+ }
+
+ for i := range cfg.dnsConfig.servers {
+ if cfg.dnsConfig.servers[i] != defaultNS[i] {
+ t.Fatalf("goLookupIP(missing; good; bad) failed: servers=%v, want: %v", cfg.dnsConfig.servers, defaultNS)
+ }
}
// A new good config should get picked up
testHookUninstaller.Do(func() { uninstallTestHooks() })
onceLoadConfig.Do(loadDefaultConfig)
- if cfg.dnserr != nil || cfg.dnsConfig == nil {
- b.Fatalf("loadConfig failed: %v", cfg.dnserr)
- }
+
// This looks ugly but it's safe as long as benchmarks are run
// sequentially in package testing.
orig := cfg.dnsConfig
package net
+var defaultNS = []string{"127.0.0.1", "::1"}
+
type dnsConfig struct {
servers []string // servers to use
search []string // suffixes to append to local name
rotate bool // round robin among servers
unknownOpt bool // anything unknown was encountered
lookup []string // OpenBSD top-level database "lookup" order
+ err error // any error that occurs during open of resolv.conf
}
// See resolv.conf(5) on a Linux machine.
// TODO(rsc): Supposed to call uname() and chop the beginning
// of the host name to get the default search domain.
-func dnsReadConfig(filename string) (*dnsConfig, error) {
- file, err := open(filename)
- if err != nil {
- return nil, &DNSConfigError{err}
- }
- defer file.close()
+func dnsReadConfig(filename string) *dnsConfig {
conf := &dnsConfig{
ndots: 1,
timeout: 5,
attempts: 2,
}
+ file, err := open(filename)
+ if err != nil {
+ conf.servers = defaultNS
+ conf.err = err
+ return conf
+ }
+ defer file.close()
for line, ok := file.readLine(); ok; line, ok = file.readLine() {
if len(line) > 0 && (line[0] == ';' || line[0] == '#') {
// comment.
conf.unknownOpt = true
}
}
- return conf, nil
+ if len(conf.servers) == 0 {
+ conf.servers = defaultNS
+ }
+ return conf
}
func hasPrefix(s, prefix string) bool {
package net
import (
+ "os"
"reflect"
"testing"
)
{
name: "testdata/empty-resolv.conf",
want: &dnsConfig{
+ servers: defaultNS,
ndots: 1,
timeout: 5,
attempts: 2,
func TestDNSReadConfig(t *testing.T) {
for _, tt := range dnsReadConfigTests {
- conf, err := dnsReadConfig(tt.name)
- if err != nil {
- t.Fatal(err)
+ conf := dnsReadConfig(tt.name)
+ if conf.err != nil {
+ t.Fatal(conf.err)
}
if !reflect.DeepEqual(conf, tt.want) {
t.Errorf("%s:\n got: %+v\nwant: %+v", tt.name, conf, tt.want)
}
}
}
+
+func TestDNSReadMissingFile(t *testing.T) {
+ conf := dnsReadConfig("a-nonexistent-file")
+ if !os.IsNotExist(conf.err) {
+ t.Errorf("Missing resolv.conf:\n got: %v\nwant: %v", conf.err, os.ErrNotExist)
+ }
+ conf.err = nil
+ want := &dnsConfig{
+ servers: defaultNS,
+ ndots: 1,
+ timeout: 5,
+ attempts: 2,
+ }
+ if !reflect.DeepEqual(conf, want) {
+ t.Errorf("Missing resolv.conf:\n got: %+v\nwant: %+v", conf, want)
+ }
+}
switch err := nestedErr.(type) {
case *AddrError, addrinfoErrno, *DNSError, InvalidAddrError, *ParseError, *timeoutError, UnknownNetworkError:
return nil
- case *DNSConfigError:
- nestedErr = err.Err
- goto third
case *os.SyscallError:
nestedErr = err.Err
goto third
switch err := nestedErr.(type) {
case *AddrError, addrinfoErrno, *DNSError, InvalidAddrError, *ParseError, *timeoutError, UnknownNetworkError:
return nil
- case *DNSConfigError:
- nestedErr = err.Err
- goto third
case *os.SyscallError:
nestedErr = err.Err
goto third
func (e InvalidAddrError) Temporary() bool { return false }
// DNSConfigError represents an error reading the machine's DNS configuration.
+// (No longer used; kept for compatibility.)
type DNSConfigError struct {
Err error
}