mirror of
https://github.com/golang/go.git
synced 2025-11-10 05:31:03 +00:00
net: redo resolv.conf recheck implementation
The previous implementation spawned an extra goroutine to handle rechecking resolv.conf for changes. This change eliminates the extra goroutine, and has rechecking done as part of a lookup. A side effect of this change is that the first lookup after a resolv.conf change will now succeed, whereas previously it would have failed. It also fixes rechecking logic to ignore resolv.conf parsing errors as it should. Fixes #8652 Fixes #10576 Fixes #10649 Fixes #10650 Fixes #10845 Change-Id: I502b587c445fa8eca5207ca4f2c8ec8c339fec7f Reviewed-on: https://go-review.googlesource.com/9991 Run-TryBot: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com> Reviewed-by: Mikio Hara <mikioh.mikioh@gmail.com> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
40fad6c286
commit
ef7e108565
2 changed files with 74 additions and 83 deletions
|
|
@ -214,95 +214,106 @@ func convertRR_AAAA(records []dnsRR) []IP {
|
|||
return addrs
|
||||
}
|
||||
|
||||
// cfg is used for the storage and reparsing of /etc/resolv.conf
|
||||
var cfg struct {
|
||||
ch chan struct{}
|
||||
// ch is used as a semaphore that only allows one lookup at a time to
|
||||
// recheck resolv.conf. It acts as guard for lastChecked and modTime.
|
||||
ch chan struct{}
|
||||
lastChecked time.Time // last time resolv.conf was checked
|
||||
modTime time.Time // time of resolv.conf modification
|
||||
|
||||
mu sync.RWMutex // protects dnsConfig
|
||||
dnsConfig *dnsConfig
|
||||
dnsConfig *dnsConfig // parsed resolv.conf structure used in lookups
|
||||
}
|
||||
|
||||
var onceLoadConfig sync.Once
|
||||
|
||||
// Assume dns config file is /etc/resolv.conf here
|
||||
func loadDefaultConfig() {
|
||||
loadConfig("/etc/resolv.conf", 5*time.Second, nil)
|
||||
}
|
||||
|
||||
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 {
|
||||
mtime = fi.ModTime()
|
||||
func initCfg() {
|
||||
// Set dnsConfig, modTime, and lastChecked so we don't parse
|
||||
// resolv.conf twice the first time.
|
||||
cfg.dnsConfig = systemConf().resolv
|
||||
if cfg.dnsConfig == nil {
|
||||
cfg.dnsConfig = dnsReadConfig("/etc/resolv.conf")
|
||||
}
|
||||
|
||||
cfg.dnsConfig = dnsReadConfig(resolvConfPath)
|
||||
if fi, err := os.Stat("/etc/resolv.conf"); err == nil {
|
||||
cfg.modTime = fi.ModTime()
|
||||
}
|
||||
cfg.lastChecked = time.Now()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(reloadTime)
|
||||
select {
|
||||
case qresp := <-quit:
|
||||
qresp <- struct{}{}
|
||||
return
|
||||
case <-cfg.ch:
|
||||
}
|
||||
// Prepare ch so that only one loadConfig may run at once
|
||||
cfg.ch = make(chan struct{}, 1)
|
||||
cfg.ch <- struct{}{}
|
||||
}
|
||||
|
||||
// In case of error, we keep the previous config
|
||||
fi, err := os.Stat(resolvConfPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// If the resolv.conf mtime didn't change, do not reload
|
||||
m := fi.ModTime()
|
||||
if m.Equal(mtime) {
|
||||
continue
|
||||
}
|
||||
mtime = m
|
||||
// In case of error, we keep the previous config
|
||||
if ncfg := dnsReadConfig(resolvConfPath); ncfg.err == nil {
|
||||
cfg.mu.Lock()
|
||||
cfg.dnsConfig = ncfg
|
||||
cfg.mu.Unlock()
|
||||
}
|
||||
func loadConfig(resolvConfPath string) {
|
||||
onceLoadConfig.Do(initCfg)
|
||||
|
||||
// ensure only one loadConfig at a time checks /etc/resolv.conf
|
||||
select {
|
||||
case <-cfg.ch:
|
||||
defer func() { cfg.ch <- struct{}{} }()
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if cfg.lastChecked.After(now.Add(-5 * time.Second)) {
|
||||
return
|
||||
}
|
||||
cfg.lastChecked = now
|
||||
|
||||
if fi, err := os.Stat(resolvConfPath); err == nil {
|
||||
if fi.ModTime().Equal(cfg.modTime) {
|
||||
return
|
||||
}
|
||||
}()
|
||||
cfg.modTime = fi.ModTime()
|
||||
} else {
|
||||
// If modTime wasn't set prior, assume nothing has changed.
|
||||
if cfg.modTime.IsZero() {
|
||||
return
|
||||
}
|
||||
cfg.modTime = time.Time{}
|
||||
}
|
||||
|
||||
ncfg := dnsReadConfig(resolvConfPath)
|
||||
cfg.mu.Lock()
|
||||
cfg.dnsConfig = ncfg
|
||||
cfg.mu.Unlock()
|
||||
}
|
||||
|
||||
func lookup(name string, qtype uint16) (cname string, rrs []dnsRR, err error) {
|
||||
if !isDomainName(name) {
|
||||
return name, nil, &DNSError{Err: "invalid domain name", Name: name}
|
||||
}
|
||||
onceLoadConfig.Do(loadDefaultConfig)
|
||||
|
||||
select {
|
||||
case cfg.ch <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
|
||||
loadConfig("/etc/resolv.conf")
|
||||
cfg.mu.RLock()
|
||||
defer cfg.mu.RUnlock()
|
||||
resolv := cfg.dnsConfig
|
||||
cfg.mu.RUnlock()
|
||||
|
||||
// If name is rooted (trailing dot) or has enough dots,
|
||||
// try it by itself first.
|
||||
rooted := len(name) > 0 && name[len(name)-1] == '.'
|
||||
if rooted || count(name, '.') >= cfg.dnsConfig.ndots {
|
||||
if rooted || count(name, '.') >= resolv.ndots {
|
||||
rname := name
|
||||
if !rooted {
|
||||
rname += "."
|
||||
}
|
||||
// Can try as ordinary name.
|
||||
cname, rrs, err = tryOneName(cfg.dnsConfig, rname, qtype)
|
||||
cname, rrs, err = tryOneName(resolv, rname, qtype)
|
||||
if rooted || err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, try suffixes.
|
||||
for i := 0; i < len(cfg.dnsConfig.search); i++ {
|
||||
rname := name + "." + cfg.dnsConfig.search[i]
|
||||
for _, suffix := range resolv.search {
|
||||
rname := name + "." + suffix
|
||||
if rname[len(rname)-1] != '.' {
|
||||
rname += "."
|
||||
}
|
||||
cname, rrs, err = tryOneName(cfg.dnsConfig, rname, qtype)
|
||||
cname, rrs, err = tryOneName(resolv, rname, qtype)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -310,8 +321,8 @@ func lookup(name string, qtype uint16) (cname string, rrs []dnsRR, err error) {
|
|||
|
||||
// Last ditch effort: try unsuffixed only if we haven't already,
|
||||
// that is, name is not rooted and has less than ndots dots.
|
||||
if count(name, '.') < cfg.dnsConfig.ndots {
|
||||
cname, rrs, err = tryOneName(cfg.dnsConfig, name+".", qtype)
|
||||
if count(name, '.') < resolv.ndots {
|
||||
cname, rrs, err = tryOneName(resolv, name+".", qtype)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue