]> Cypherpunks repositories - gostls13.git/commitdiff
net: make go DNS use localhost if resolv.conf is missing or empty
authorAlex A Skinner <alex@lx.lc>
Sun, 26 Apr 2015 00:50:21 +0000 (20:50 -0400)
committerBrad Fitzpatrick <bradfitz@golang.org>
Thu, 30 Apr 2015 18:19:00 +0000 (18:19 +0000)
Per resolv.conf man page, "If this file does not exist, only the name
server on the local machine will be queried."

This behavior also occurs if file is present but unreadable,
or if no nameservers are listed.

Fixes #10566

Change-Id: Id5716da0eae534d5ebfafea111bbc657f302e307
Reviewed-on: https://go-review.googlesource.com/9380
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
src/net/conf.go
src/net/conf_test.go
src/net/dnsclient_unix.go
src/net/dnsclient_unix_test.go
src/net/dnsconfig_unix.go
src/net/dnsconfig_unix_test.go
src/net/error_test.go
src/net/net.go

index 9e8facac5bb5b2b7761889f7446fac3df2ad2ba5..010131c4892b532221e3f15e6ef77c175c1f9eb2 100644 (file)
@@ -68,9 +68,9 @@ func initConfVal() {
                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
@@ -85,7 +85,7 @@ func initConfVal() {
 
 // 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 {
@@ -100,7 +100,7 @@ func (c *conf) hostLookupOrder(hostname string) hostLookupOrder {
                // 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
@@ -135,9 +135,6 @@ func (c *conf) hostLookupOrder(hostname string) hostLookupOrder {
                        return hostLookupCgo
                }
        }
-       if c.resolv != nil && c.resolv.unknownOpt {
-               return hostLookupCgo
-       }
 
        hasDot := byteIndex(hostname, '.') != -1
 
index 46e91bc0a15902df2218a6d4546c3b59f0ec7ae7..43be546d07dbce696b0929ab12ad6f53cbb5c14d 100644 (file)
@@ -19,6 +19,15 @@ type nssHostTest struct {
 
 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
@@ -31,6 +40,7 @@ func TestConfHostLookupOrder(t *testing.T) {
                        c: &conf{
                                forceCgoLookupHost: true,
                                nss:                nssStr("foo: bar"),
+                               resolv:             defaultResolvConf,
                        },
                        hostTests: []nssHostTest{
                                {"foo.local", hostLookupCgo},
@@ -40,7 +50,8 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        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},
@@ -53,8 +64,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        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}},
                },
@@ -62,15 +74,17 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        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}},
                },
@@ -138,14 +152,18 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        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},
@@ -153,7 +171,10 @@ func TestConfHostLookupOrder(t *testing.T) {
                },
                {
                        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
@@ -164,6 +185,7 @@ func TestConfHostLookupOrder(t *testing.T) {
                        name: "mdns_allow",
                        c: &conf{
                                nss:          nssStr("hosts: files mdns dns"),
+                               resolv:       defaultResolvConf,
                                hasMDNSAllow: true,
                        },
                        hostTests: []nssHostTest{
@@ -173,7 +195,10 @@ func TestConfHostLookupOrder(t *testing.T) {
                },
                {
                        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},
@@ -182,7 +207,10 @@ func TestConfHostLookupOrder(t *testing.T) {
                },
                {
                        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},
@@ -191,14 +219,20 @@ func TestConfHostLookupOrder(t *testing.T) {
                },
                {
                        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},
@@ -206,7 +240,10 @@ func TestConfHostLookupOrder(t *testing.T) {
                },
                {
                        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},
@@ -218,7 +255,10 @@ func TestConfHostLookupOrder(t *testing.T) {
                // 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},
@@ -228,7 +268,7 @@ func TestConfHostLookupOrder(t *testing.T) {
                        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}},
                },
index 55647ebb21af2c28f73d31c64aaae60895c9d6fd..5a4411f5c7a9b9542015672aefbb546e631ea1fe 100644 (file)
@@ -216,10 +216,10 @@ func convertRR_AAAA(records []dnsRR) []IP {
 
 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
@@ -230,12 +230,12 @@ func loadDefaultConfig() {
 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)
@@ -258,14 +258,11 @@ func loadConfig(resolvConfPath string, reloadTime time.Duration, quit <-chan cha
                        }
                        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()
                }
        }()
 }
@@ -284,10 +281,6 @@ func lookup(name string, qtype uint16) (cname string, rrs []dnsRR, err error) {
        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] == '.'
index c85e147a0d57bfe7772a7b541a44ef135b967994..f46545dcac1b3f615ac7a060091e8b6ea7b84ed9 100644 (file)
@@ -171,23 +171,26 @@ func TestReloadResolvConfFail(t *testing.T) {
        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) {
@@ -198,19 +201,25 @@ 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
@@ -238,9 +247,7 @@ func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) {
        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
index abaef7b5e74665ead6fd35478dd93858b8f60727..6073fdb6d83943d13f5af03de97ed8bac4dba386 100644 (file)
@@ -8,6 +8,8 @@
 
 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
@@ -17,22 +19,25 @@ type dnsConfig struct {
        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.
@@ -104,7 +109,10 @@ func dnsReadConfig(filename string) (*dnsConfig, error) {
                        conf.unknownOpt = true
                }
        }
-       return conf, nil
+       if len(conf.servers) == 0 {
+               conf.servers = defaultNS
+       }
+       return conf
 }
 
 func hasPrefix(s, prefix string) bool {
index f4b118568adac2c9eb17640601bdbc866e549288..ef45f2d8b837bd9e4bc4846dc15b90c8a3bef820 100644 (file)
@@ -7,6 +7,7 @@
 package net
 
 import (
+       "os"
        "reflect"
        "testing"
 )
@@ -50,6 +51,7 @@ var dnsReadConfigTests = []struct {
        {
                name: "testdata/empty-resolv.conf",
                want: &dnsConfig{
+                       servers:  defaultNS,
                        ndots:    1,
                        timeout:  5,
                        attempts: 2,
@@ -70,12 +72,29 @@ var dnsReadConfigTests = []struct {
 
 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)
+       }
+}
index 356fad87d3250763fd44bd45b15530c288cf1ac5..9776a2a3b00b2e55c6523e724d3d5f91e583e769 100644 (file)
@@ -62,9 +62,6 @@ second:
        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
@@ -293,9 +290,6 @@ second:
        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
index 955d0185d22172feb4cbe35fa9ac2e0dee0ac18b..589f21f5827b14f6860d2d016580444ca91c91f6 100644 (file)
@@ -435,6 +435,7 @@ func (e InvalidAddrError) Timeout() bool   { return false }
 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
 }