]> Cypherpunks repositories - gostls13.git/commitdiff
net: auto-reload the /etc/nsswitch.conf on unix systems
authorMateusz Poliwczak <mpoliwczak34@gmail.com>
Thu, 10 Nov 2022 18:52:22 +0000 (18:52 +0000)
committerGopher Robot <gobot@golang.org>
Thu, 10 Nov 2022 21:11:02 +0000 (21:11 +0000)
This change is made to align with the current (recently changed) glibc behaviour, it will allow the hostLookupOrder method to change its decisions on runtime (based on /etc/nsswitch.conf changes).

Fixes #56515

Change-Id: I241d67f053b6d2111eebcd67744adee02829166e
GitHub-Last-Rev: 82842c127474d5d225d2e9b68568387ee6b0ba04
GitHub-Pull-Request: golang/go#56588
Reviewed-on: https://go-review.googlesource.com/c/go/+/448075
Run-TryBot: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
src/net/conf.go
src/net/conf_test.go
src/net/nss.go

index b08bbc7d7a1c818f4eca4238ea24f424aa91add0..6854f466581659b613b404f7ebe8b73e17bacf94 100644 (file)
@@ -29,7 +29,6 @@ type conf struct {
        goos          string // the runtime.GOOS, to ease testing
        dnsDebugLevel int
 
-       nss    *nssConf
        resolv *dnsConfig
 }
 
@@ -112,10 +111,6 @@ func initConfVal() {
                return
        }
 
-       if runtime.GOOS != "openbsd" {
-               confVal.nss = parseNSSConfFile("/etc/nsswitch.conf")
-       }
-
        confVal.resolv = dnsReadConfig("/etc/resolv.conf")
        if confVal.resolv.err != nil && !os.IsNotExist(confVal.resolv.err) &&
                !os.IsPermission(confVal.resolv.err) {
@@ -224,7 +219,7 @@ func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrde
                return fallbackOrder
        }
 
-       nss := c.nss
+       nss := getSystemNSS()
        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.
index 5ae705508601979982d871e074f19af3ba69c823..86fc4797b95e197852e03c6d016944b4e0353680 100644 (file)
@@ -10,6 +10,7 @@ import (
        "io/fs"
        "strings"
        "testing"
+       "time"
 )
 
 type nssHostTest struct {
@@ -33,6 +34,7 @@ func TestConfHostLookupOrder(t *testing.T) {
        tests := []struct {
                name      string
                c         *conf
+               nss       *nssConf
                resolver  *Resolver
                hostTests []nssHostTest
        }{
@@ -40,9 +42,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                        name: "force",
                        c: &conf{
                                forceCgoLookupHost: true,
-                               nss:                nssStr("foo: bar"),
                                resolv:             defaultResolvConf,
                        },
+                       nss: nssStr("foo: bar"),
                        hostTests: []nssHostTest{
                                {"foo.local", "myhostname", hostLookupCgo},
                                {"google.com", "myhostname", hostLookupCgo},
@@ -52,9 +54,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                        name: "netgo_dns_before_files",
                        c: &conf{
                                netGo:  true,
-                               nss:    nssStr("hosts: dns files"),
                                resolv: defaultResolvConf,
                        },
+                       nss: nssStr("hosts: dns files"),
                        hostTests: []nssHostTest{
                                {"x.com", "myhostname", hostLookupDNSFiles},
                        },
@@ -63,9 +65,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                        name: "netgo_fallback_on_cgo",
                        c: &conf{
                                netGo:  true,
-                               nss:    nssStr("hosts: dns files something_custom"),
                                resolv: defaultResolvConf,
                        },
+                       nss: nssStr("hosts: dns files something_custom"),
                        hostTests: []nssHostTest{
                                {"x.com", "myhostname", hostLookupFilesDNS},
                        },
@@ -73,9 +75,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        name: "ubuntu_trusty_avahi",
                        c: &conf{
-                               nss:    nssStr("hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4"),
                                resolv: defaultResolvConf,
                        },
+                       nss: nssStr("hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4"),
                        hostTests: []nssHostTest{
                                {"foo.local", "myhostname", hostLookupCgo},
                                {"foo.local.", "myhostname", hostLookupCgo},
@@ -88,9 +90,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                        name: "freebsdlinux_no_resolv_conf",
                        c: &conf{
                                goos:   "freebsd",
-                               nss:    nssStr("foo: bar"),
                                resolv: defaultResolvConf,
                        },
+                       nss:       nssStr("foo: bar"),
                        hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
                },
                // On OpenBSD, no resolv.conf means no DNS.
@@ -106,9 +108,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                        name: "solaris_no_nsswitch",
                        c: &conf{
                                goos:   "solaris",
-                               nss:    &nssConf{err: fs.ErrNotExist},
                                resolv: defaultResolvConf,
                        },
+                       nss:       &nssConf{err: fs.ErrNotExist},
                        hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
                },
                {
@@ -174,26 +176,26 @@ func TestConfHostLookupOrder(t *testing.T) {
                        name: "linux_no_nsswitch.conf",
                        c: &conf{
                                goos:   "linux",
-                               nss:    &nssConf{err: fs.ErrNotExist},
                                resolv: defaultResolvConf,
                        },
+                       nss:       &nssConf{err: fs.ErrNotExist},
                        hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
                },
                {
                        name: "linux_empty_nsswitch.conf",
                        c: &conf{
                                goos:   "linux",
-                               nss:    nssStr(""),
                                resolv: defaultResolvConf,
                        },
+                       nss:       nssStr(""),
                        hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
                },
                {
                        name: "files_mdns_dns",
                        c: &conf{
-                               nss:    nssStr("hosts: files mdns dns"),
                                resolv: defaultResolvConf,
                        },
+                       nss: nssStr("hosts: files mdns dns"),
                        hostTests: []nssHostTest{
                                {"x.com", "myhostname", hostLookupFilesDNS},
                                {"x.local", "myhostname", hostLookupCgo},
@@ -202,9 +204,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        name: "dns_special_hostnames",
                        c: &conf{
-                               nss:    nssStr("hosts: dns"),
                                resolv: defaultResolvConf,
                        },
+                       nss: nssStr("hosts: dns"),
                        hostTests: []nssHostTest{
                                {"x.com", "myhostname", hostLookupDNS},
                                {"x\\.com", "myhostname", hostLookupCgo},     // punt on weird glibc escape
@@ -214,10 +216,10 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        name: "mdns_allow",
                        c: &conf{
-                               nss:          nssStr("hosts: files mdns dns"),
                                resolv:       defaultResolvConf,
                                hasMDNSAllow: true,
                        },
+                       nss: nssStr("hosts: files mdns dns"),
                        hostTests: []nssHostTest{
                                {"x.com", "myhostname", hostLookupCgo},
                                {"x.local", "myhostname", hostLookupCgo},
@@ -226,9 +228,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        name: "files_dns",
                        c: &conf{
-                               nss:    nssStr("hosts: files dns"),
                                resolv: defaultResolvConf,
                        },
+                       nss: nssStr("hosts: files dns"),
                        hostTests: []nssHostTest{
                                {"x.com", "myhostname", hostLookupFilesDNS},
                                {"x", "myhostname", hostLookupFilesDNS},
@@ -238,9 +240,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        name: "dns_files",
                        c: &conf{
-                               nss:    nssStr("hosts: dns files"),
                                resolv: defaultResolvConf,
                        },
+                       nss: nssStr("hosts: dns files"),
                        hostTests: []nssHostTest{
                                {"x.com", "myhostname", hostLookupDNSFiles},
                                {"x", "myhostname", hostLookupDNSFiles},
@@ -250,9 +252,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        name: "something_custom",
                        c: &conf{
-                               nss:    nssStr("hosts: dns files something_custom"),
                                resolv: defaultResolvConf,
                        },
+                       nss: nssStr("hosts: dns files something_custom"),
                        hostTests: []nssHostTest{
                                {"x.com", "myhostname", hostLookupCgo},
                        },
@@ -260,9 +262,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        name: "myhostname",
                        c: &conf{
-                               nss:    nssStr("hosts: files dns myhostname"),
                                resolv: defaultResolvConf,
                        },
+                       nss: nssStr("hosts: files dns myhostname"),
                        hostTests: []nssHostTest{
                                {"x.com", "myhostname", hostLookupFilesDNS},
                                {"myhostname", "myhostname", hostLookupCgo},
@@ -286,9 +288,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        name: "ubuntu14.04.02",
                        c: &conf{
-                               nss:    nssStr("hosts: files myhostname mdns4_minimal [NOTFOUND=return] dns mdns4"),
                                resolv: defaultResolvConf,
                        },
+                       nss: nssStr("hosts: files myhostname mdns4_minimal [NOTFOUND=return] dns mdns4"),
                        hostTests: []nssHostTest{
                                {"x.com", "myhostname", hostLookupFilesDNS},
                                {"somehostname", "myhostname", hostLookupFilesDNS},
@@ -302,9 +304,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        name: "debian_squeeze",
                        c: &conf{
-                               nss:    nssStr("hosts: dns [success=return notfound=continue unavail=continue tryagain=continue] files [notfound=return]"),
                                resolv: defaultResolvConf,
                        },
+                       nss: nssStr("hosts: dns [success=return notfound=continue unavail=continue tryagain=continue] files [notfound=return]"),
                        hostTests: []nssHostTest{
                                {"x.com", "myhostname", hostLookupDNSFiles},
                                {"somehostname", "myhostname", hostLookupDNSFiles},
@@ -313,9 +315,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                {
                        name: "resolv.conf-unknown",
                        c: &conf{
-                               nss:    nssStr("foo: bar"),
                                resolv: &dnsConfig{servers: defaultNS, ndots: 1, timeout: 5, attempts: 2, unknownOpt: true},
                        },
+                       nss:       nssStr("foo: bar"),
                        hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
                },
                // Android should always use cgo.
@@ -323,9 +325,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                        name: "android",
                        c: &conf{
                                goos:   "android",
-                               nss:    nssStr(""),
                                resolv: defaultResolvConf,
                        },
+                       nss: nssStr(""),
                        hostTests: []nssHostTest{
                                {"x.com", "myhostname", hostLookupCgo},
                        },
@@ -338,9 +340,9 @@ func TestConfHostLookupOrder(t *testing.T) {
                                goos:               "darwin",
                                forceCgoLookupHost: true, // always true for darwin
                                resolv:             defaultResolvConf,
-                               nss:                nssStr(""),
                                netCgo:             true,
                        },
+                       nss: nssStr(""),
                        hostTests: []nssHostTest{
                                {"localhost", "myhostname", hostLookupFilesDNS},
                        },
@@ -349,10 +351,13 @@ func TestConfHostLookupOrder(t *testing.T) {
 
        origGetHostname := getHostname
        defer func() { getHostname = origGetHostname }()
+       defer setSystemNSS(getSystemNSS(), 0)
 
        for _, tt := range tests {
+
                for _, ht := range tt.hostTests {
                        getHostname = func() (string, error) { return ht.localhost, nil }
+                       setSystemNSS(tt.nss, time.Hour)
 
                        gotOrder := tt.c.hostLookupOrder(tt.resolver, ht.host)
                        if gotOrder != ht.want {
@@ -360,7 +365,15 @@ func TestConfHostLookupOrder(t *testing.T) {
                        }
                }
        }
+}
 
+func setSystemNSS(nss *nssConf, addDur time.Duration) {
+       nssConfig.mu.Lock()
+       nssConfig.nssConf = nss
+       nssConfig.mu.Unlock()
+       nssConfig.acquireSema()
+       nssConfig.lastChecked = time.Now().Add(addDur)
+       nssConfig.releaseSema()
 }
 
 func TestSystemConf(t *testing.T) {
index c4c608fb61864760be09654e17c4df961fcd6cdc..ad4c18145e07224683d4ea7cf7716d7d298914ff 100644 (file)
@@ -9,10 +9,93 @@ import (
        "internal/bytealg"
        "io"
        "os"
+       "sync"
+       "time"
 )
 
+const (
+       nssConfigPath = "/etc/nsswitch.conf"
+)
+
+var nssConfig nsswitchConfig
+
+type nsswitchConfig struct {
+       initOnce sync.Once // guards init of nsswitchConfig
+
+       // ch is used as a semaphore that only allows one lookup at a
+       // time to recheck nsswitch.conf
+       ch          chan struct{} // guards lastChecked and modTime
+       lastChecked time.Time     // last time nsswitch.conf was checked
+
+       mu      sync.Mutex // protects nssConf
+       nssConf *nssConf
+}
+
+func getSystemNSS() *nssConf {
+       nssConfig.tryUpdate()
+       nssConfig.mu.Lock()
+       conf := nssConfig.nssConf
+       nssConfig.mu.Unlock()
+       return conf
+}
+
+// init initializes conf and is only called via conf.initOnce.
+func (conf *nsswitchConfig) init() {
+       conf.nssConf = parseNSSConfFile("/etc/nsswitch.conf")
+       conf.lastChecked = time.Now()
+       conf.ch = make(chan struct{}, 1)
+}
+
+// tryUpdate tries to update conf.
+func (conf *nsswitchConfig) tryUpdate() {
+       conf.initOnce.Do(conf.init)
+
+       // Ensure only one update at a time checks nsswitch.conf
+       if !conf.tryAcquireSema() {
+               return
+       }
+       defer conf.releaseSema()
+
+       now := time.Now()
+       if conf.lastChecked.After(now.Add(-5 * time.Second)) {
+               return
+       }
+       conf.lastChecked = now
+
+       var mtime time.Time
+       if fi, err := os.Stat(nssConfigPath); err == nil {
+               mtime = fi.ModTime()
+       }
+       if mtime.Equal(conf.nssConf.mtime) {
+               return
+       }
+
+       nssConf := parseNSSConfFile(nssConfigPath)
+       conf.mu.Lock()
+       conf.nssConf = nssConf
+       conf.mu.Unlock()
+}
+
+func (conf *nsswitchConfig) acquireSema() {
+       conf.ch <- struct{}{}
+}
+
+func (conf *nsswitchConfig) tryAcquireSema() bool {
+       select {
+       case conf.ch <- struct{}{}:
+               return true
+       default:
+               return false
+       }
+}
+
+func (conf *nsswitchConfig) releaseSema() {
+       <-conf.ch
+}
+
 // nssConf represents the state of the machine's /etc/nsswitch.conf file.
 type nssConf struct {
+       mtime   time.Time              // time of nsswitch.conf modification
        err     error                  // any error encountered opening or parsing the file
        sources map[string][]nssSource // keyed by database (e.g. "hosts")
 }
@@ -70,7 +153,14 @@ func parseNSSConfFile(file string) *nssConf {
                return &nssConf{err: err}
        }
        defer f.Close()
-       return parseNSSConf(f)
+       stat, err := f.Stat()
+       if err != nil {
+               return &nssConf{err: err}
+       }
+
+       conf := parseNSSConf(f)
+       conf.mtime = stat.ModTime()
+       return conf
 }
 
 func parseNSSConf(r io.Reader) *nssConf {