mirror of https://github.com/golang/go.git
net: auto-reload the /etc/nsswitch.conf on unix systems
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: 82842c1274
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>
This commit is contained in:
parent
fcecf3e1fa
commit
79950a4162
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue