summaryrefslogtreecommitdiff
path: root/libgo/go/net/dnsclient_unix.go
diff options
context:
space:
mode:
authorIan Lance Taylor <ian@gcc.gnu.org>2015-10-31 00:59:47 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2015-10-31 00:59:47 +0000
commitaf146490bb04205107cb23e301ec7a8ff927b5fc (patch)
tree13beeaed3698c61903fe93fb1ce70bd9b18d4e7f /libgo/go/net/dnsclient_unix.go
parent725e1be3406315d9bcc8195d7eef0a7082b3c7cc (diff)
runtime: Remove now unnecessary pad field from ParFor.
It is not needed due to the removal of the ctx field. Reviewed-on: https://go-review.googlesource.com/16525 From-SVN: r229616
Diffstat (limited to 'libgo/go/net/dnsclient_unix.go')
-rw-r--r--libgo/go/net/dnsclient_unix.go403
1 files changed, 252 insertions, 151 deletions
diff --git a/libgo/go/net/dnsclient_unix.go b/libgo/go/net/dnsclient_unix.go
index 7511083f795..c03c1b1159f 100644
--- a/libgo/go/net/dnsclient_unix.go
+++ b/libgo/go/net/dnsclient_unix.go
@@ -20,6 +20,7 @@ import (
"io"
"math/rand"
"os"
+ "strconv"
"sync"
"time"
)
@@ -184,9 +185,9 @@ func tryOneName(cfg *dnsConfig, name string, qtype uint16) (string, []dnsRR, err
}
continue
}
- cname, addrs, err := answer(name, server, msg, qtype)
- if err == nil || err.(*DNSError).Err == noSuchHost {
- return cname, addrs, err
+ cname, rrs, err := answer(name, server, msg, qtype)
+ if err == nil || msg.rcode == dnsRcodeSuccess || msg.rcode == dnsRcodeNameError && msg.recursion_available {
+ return cname, rrs, err
}
lastErr = err
}
@@ -194,144 +195,188 @@ func tryOneName(cfg *dnsConfig, name string, qtype uint16) (string, []dnsRR, err
return "", nil, lastErr
}
-func convertRR_A(records []dnsRR) []IP {
- addrs := make([]IP, len(records))
- for i, rr := range records {
- a := rr.(*dnsRR_A).A
- addrs[i] = IPv4(byte(a>>24), byte(a>>16), byte(a>>8), byte(a))
+// addrRecordList converts and returns a list of IP addresses from DNS
+// address records (both A and AAAA). Other record types are ignored.
+func addrRecordList(rrs []dnsRR) []IPAddr {
+ addrs := make([]IPAddr, 0, 4)
+ for _, rr := range rrs {
+ switch rr := rr.(type) {
+ case *dnsRR_A:
+ addrs = append(addrs, IPAddr{IP: IPv4(byte(rr.A>>24), byte(rr.A>>16), byte(rr.A>>8), byte(rr.A))})
+ case *dnsRR_AAAA:
+ ip := make(IP, IPv6len)
+ copy(ip, rr.AAAA[:])
+ addrs = append(addrs, IPAddr{IP: ip})
+ }
}
return addrs
}
-func convertRR_AAAA(records []dnsRR) []IP {
- addrs := make([]IP, len(records))
- for i, rr := range records {
- a := make(IP, IPv6len)
- copy(a, rr.(*dnsRR_AAAA).AAAA[:])
- addrs[i] = a
- }
- return addrs
-}
+// A resolverConfig represents a DNS stub resolver configuration.
+type resolverConfig struct {
+ initOnce sync.Once // guards init of resolverConfig
-var cfg struct {
- ch chan struct{}
- mu sync.RWMutex // protects dnsConfig and dnserr
- dnsConfig *dnsConfig
- dnserr error
-}
-var onceLoadConfig sync.Once
+ // ch is used as a semaphore that only allows one lookup at a
+ // time to recheck resolv.conf.
+ ch chan struct{} // guards lastChecked and modTime
+ lastChecked time.Time // last time resolv.conf was checked
+ modTime time.Time // time of resolv.conf modification
-// Assume dns config file is /etc/resolv.conf here
-func loadDefaultConfig() {
- loadConfig("/etc/resolv.conf", 5*time.Second, nil)
+ mu sync.RWMutex // protects dnsConfig
+ dnsConfig *dnsConfig // parsed resolv.conf structure used in lookups
}
-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 {
- mtime = fi.ModTime()
- cfg.dnsConfig, cfg.dnserr = dnsReadConfig(resolvConfPath)
- }
- go func() {
- for {
- time.Sleep(reloadTime)
- select {
- case qresp := <-quit:
- qresp <- struct{}{}
- return
- case <-cfg.ch:
- }
+var resolvConf resolverConfig
- // 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
- ncfg, err := dnsReadConfig(resolvConfPath)
- if err != nil || len(ncfg.servers) == 0 {
- continue
- }
- cfg.mu.Lock()
- cfg.dnsConfig = ncfg
- cfg.dnserr = nil
- cfg.mu.Unlock()
- }
- }()
-}
-
-func lookup(name string, qtype uint16) (cname string, addrs []dnsRR, err error) {
- if !isDomainName(name) {
- return name, nil, &DNSError{Err: "invalid domain name", Name: name}
+// init initializes conf and is only called via conf.initOnce.
+func (conf *resolverConfig) init() {
+ // Set dnsConfig, modTime, and lastChecked so we don't parse
+ // resolv.conf twice the first time.
+ conf.dnsConfig = systemConf().resolv
+ if conf.dnsConfig == nil {
+ conf.dnsConfig = dnsReadConfig("/etc/resolv.conf")
}
- onceLoadConfig.Do(loadDefaultConfig)
- select {
- case cfg.ch <- struct{}{}:
- default:
+ if fi, err := os.Stat("/etc/resolv.conf"); err == nil {
+ conf.modTime = fi.ModTime()
}
+ conf.lastChecked = time.Now()
+
+ // Prepare ch so that only one update of resolverConfig may
+ // run at once.
+ conf.ch = make(chan struct{}, 1)
+}
- cfg.mu.RLock()
- defer cfg.mu.RUnlock()
+// tryUpdate tries to update conf with the named resolv.conf file.
+// The name variable only exists for testing. It is otherwise always
+// "/etc/resolv.conf".
+func (conf *resolverConfig) tryUpdate(name string) {
+ conf.initOnce.Do(conf.init)
- if cfg.dnserr != nil || cfg.dnsConfig == nil {
- err = cfg.dnserr
+ // Ensure only one update at a time checks resolv.conf.
+ if !conf.tryAcquireSema() {
return
}
- // 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 {
- rname := name
- if !rooted {
- rname += "."
- }
- // Can try as ordinary name.
- cname, addrs, err = tryOneName(cfg.dnsConfig, rname, qtype)
- if rooted || err == nil {
- return
- }
+ defer conf.releaseSema()
+
+ now := time.Now()
+ if conf.lastChecked.After(now.Add(-5 * time.Second)) {
+ return
}
+ conf.lastChecked = now
- // Otherwise, try suffixes.
- for i := 0; i < len(cfg.dnsConfig.search); i++ {
- rname := name + "." + cfg.dnsConfig.search[i]
- if rname[len(rname)-1] != '.' {
- rname += "."
+ if fi, err := os.Stat(name); err == nil {
+ if fi.ModTime().Equal(conf.modTime) {
+ return
}
- cname, addrs, err = tryOneName(cfg.dnsConfig, rname, qtype)
- if err == nil {
+ conf.modTime = fi.ModTime()
+ } else {
+ // If modTime wasn't set prior, assume nothing has changed.
+ if conf.modTime.IsZero() {
return
}
+ conf.modTime = time.Time{}
}
- // 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, addrs, err = tryOneName(cfg.dnsConfig, name+".", qtype)
+ dnsConf := dnsReadConfig(name)
+ conf.mu.Lock()
+ conf.dnsConfig = dnsConf
+ conf.mu.Unlock()
+}
+
+func (conf *resolverConfig) tryAcquireSema() bool {
+ select {
+ case conf.ch <- struct{}{}:
+ return true
+ default:
+ return false
+ }
+}
+
+func (conf *resolverConfig) releaseSema() {
+ <-conf.ch
+}
+
+func lookup(name string, qtype uint16) (cname string, rrs []dnsRR, err error) {
+ if !isDomainName(name) {
+ return "", nil, &DNSError{Err: "invalid domain name", Name: name}
+ }
+ resolvConf.tryUpdate("/etc/resolv.conf")
+ resolvConf.mu.RLock()
+ conf := resolvConf.dnsConfig
+ resolvConf.mu.RUnlock()
+ for _, fqdn := range conf.nameList(name) {
+ cname, rrs, err = tryOneName(conf, fqdn, qtype)
if err == nil {
- return
+ break
}
}
-
- if e, ok := err.(*DNSError); ok {
+ if err, ok := err.(*DNSError); ok {
// Show original name passed to lookup, not suffixed one.
// In general we might have tried many suffixes; showing
// just one is misleading. See also golang.org/issue/6324.
- e.Name = name
+ err.Name = name
}
return
}
+// nameList returns a list of names for sequential DNS queries.
+func (conf *dnsConfig) nameList(name string) []string {
+ // If name is rooted (trailing dot), try only that name.
+ rooted := len(name) > 0 && name[len(name)-1] == '.'
+ if rooted {
+ return []string{name}
+ }
+ // Build list of search choices.
+ names := make([]string, 0, 1+len(conf.search))
+ // If name has enough dots, try unsuffixed first.
+ if count(name, '.') >= conf.ndots {
+ names = append(names, name+".")
+ }
+ // Try suffixes.
+ for _, suffix := range conf.search {
+ suffixed := name + "." + suffix
+ if suffixed[len(suffixed)-1] != '.' {
+ suffixed += "."
+ }
+ names = append(names, suffixed)
+ }
+ // Try unsuffixed, if not tried first above.
+ if count(name, '.') < conf.ndots {
+ names = append(names, name+".")
+ }
+ return names
+}
+
+// hostLookupOrder specifies the order of LookupHost lookup strategies.
+// It is basically a simplified representation of nsswitch.conf.
+// "files" means /etc/hosts.
+type hostLookupOrder int
+
+const (
+ // hostLookupCgo means defer to cgo.
+ hostLookupCgo hostLookupOrder = iota
+ hostLookupFilesDNS // files first
+ hostLookupDNSFiles // dns first
+ hostLookupFiles // only files
+ hostLookupDNS // only DNS
+)
+
+var lookupOrderName = map[hostLookupOrder]string{
+ hostLookupCgo: "cgo",
+ hostLookupFilesDNS: "files,dns",
+ hostLookupDNSFiles: "dns,files",
+ hostLookupFiles: "files",
+ hostLookupDNS: "dns",
+}
+
+func (o hostLookupOrder) String() string {
+ if s, ok := lookupOrderName[o]; ok {
+ return s
+ }
+ return "hostLookupOrder=" + strconv.Itoa(int(o)) + "??"
+}
+
// goLookupHost is the native Go implementation of LookupHost.
// Used only if cgoLookupHost refuses to handle the request
// (that is, only if cgoLookupHost is the stub in cgo_stub.go).
@@ -339,12 +384,18 @@ func lookup(name string, qtype uint16) (cname string, addrs []dnsRR, err error)
// depending on our lookup code, so that Go and C get the same
// answers.
func goLookupHost(name string) (addrs []string, err error) {
- // Use entries from /etc/hosts if they match.
- addrs = lookupStaticHost(name)
- if len(addrs) > 0 {
- return
+ return goLookupHostOrder(name, hostLookupFilesDNS)
+}
+
+func goLookupHostOrder(name string, order hostLookupOrder) (addrs []string, err error) {
+ if order == hostLookupFilesDNS || order == hostLookupFiles {
+ // Use entries from /etc/hosts if they match.
+ addrs = lookupStaticHost(name)
+ if len(addrs) > 0 || order == hostLookupFiles {
+ return
+ }
}
- ips, err := goLookupIP(name)
+ ips, err := goLookupIPOrder(name, order)
if err != nil {
return
}
@@ -355,54 +406,79 @@ func goLookupHost(name string) (addrs []string, err error) {
return
}
-// goLookupIP is the native Go implementation of LookupIP.
-// Used only if cgoLookupIP refuses to handle the request
-// (that is, only if cgoLookupIP is the stub in cgo_stub.go).
-// Normally we let cgo use the C library resolver instead of
-// depending on our lookup code, so that Go and C get the same
-// answers.
-func goLookupIP(name string) (addrs []IP, err error) {
- // Use entries from /etc/hosts if possible.
- haddrs := lookupStaticHost(name)
- if len(haddrs) > 0 {
- for _, haddr := range haddrs {
- if ip := ParseIP(haddr); ip != nil {
- addrs = append(addrs, ip)
- }
+// lookup entries from /etc/hosts
+func goLookupIPFiles(name string) (addrs []IPAddr) {
+ for _, haddr := range lookupStaticHost(name) {
+ haddr, zone := splitHostZone(haddr)
+ if ip := ParseIP(haddr); ip != nil {
+ addr := IPAddr{IP: ip, Zone: zone}
+ addrs = append(addrs, addr)
}
- if len(addrs) > 0 {
- return
+ }
+ sortByRFC6724(addrs)
+ return
+}
+
+// goLookupIP is the native Go implementation of LookupIP.
+// The libc versions are in cgo_*.go.
+func goLookupIP(name string) (addrs []IPAddr, err error) {
+ return goLookupIPOrder(name, hostLookupFilesDNS)
+}
+
+func goLookupIPOrder(name string, order hostLookupOrder) (addrs []IPAddr, err error) {
+ if order == hostLookupFilesDNS || order == hostLookupFiles {
+ addrs = goLookupIPFiles(name)
+ if len(addrs) > 0 || order == hostLookupFiles {
+ return addrs, nil
}
}
+ if !isDomainName(name) {
+ return nil, &DNSError{Err: "invalid domain name", Name: name}
+ }
+ resolvConf.tryUpdate("/etc/resolv.conf")
+ resolvConf.mu.RLock()
+ conf := resolvConf.dnsConfig
+ resolvConf.mu.RUnlock()
type racer struct {
- qtype uint16
- rrs []dnsRR
+ rrs []dnsRR
error
}
lane := make(chan racer, 1)
qtypes := [...]uint16{dnsTypeA, dnsTypeAAAA}
- for _, qtype := range qtypes {
- go func(qtype uint16) {
- _, rrs, err := lookup(name, qtype)
- lane <- racer{qtype, rrs, err}
- }(qtype)
- }
var lastErr error
- for range qtypes {
- racer := <-lane
- if racer.error != nil {
- lastErr = racer.error
- continue
+ for _, fqdn := range conf.nameList(name) {
+ for _, qtype := range qtypes {
+ go func(qtype uint16) {
+ _, rrs, err := tryOneName(conf, fqdn, qtype)
+ lane <- racer{rrs, err}
+ }(qtype)
+ }
+ for range qtypes {
+ racer := <-lane
+ if racer.error != nil {
+ lastErr = racer.error
+ continue
+ }
+ addrs = append(addrs, addrRecordList(racer.rrs)...)
}
- switch racer.qtype {
- case dnsTypeA:
- addrs = append(addrs, convertRR_A(racer.rrs)...)
- case dnsTypeAAAA:
- addrs = append(addrs, convertRR_AAAA(racer.rrs)...)
+ if len(addrs) > 0 {
+ break
}
}
- if len(addrs) == 0 && lastErr != nil {
- return nil, lastErr
+ if lastErr, ok := lastErr.(*DNSError); ok {
+ // Show original name passed to lookup, not suffixed one.
+ // In general we might have tried many suffixes; showing
+ // just one is misleading. See also golang.org/issue/6324.
+ lastErr.Name = name
+ }
+ sortByRFC6724(addrs)
+ if len(addrs) == 0 {
+ if lastErr != nil {
+ return nil, lastErr
+ }
+ if order == hostLookupDNSFiles {
+ addrs = goLookupIPFiles(name)
+ }
}
return addrs, nil
}
@@ -414,10 +490,35 @@ func goLookupIP(name string) (addrs []IP, err error) {
// depending on our lookup code, so that Go and C get the same
// answers.
func goLookupCNAME(name string) (cname string, err error) {
- _, rr, err := lookup(name, dnsTypeCNAME)
+ _, rrs, err := lookup(name, dnsTypeCNAME)
if err != nil {
return
}
- cname = rr[0].(*dnsRR_CNAME).Cname
+ cname = rrs[0].(*dnsRR_CNAME).Cname
return
}
+
+// goLookupPTR is the native Go implementation of LookupAddr.
+// Used only if cgoLookupPTR refuses to handle the request (that is,
+// only if cgoLookupPTR is the stub in cgo_stub.go).
+// Normally we let cgo use the C library resolver instead of depending
+// on our lookup code, so that Go and C get the same answers.
+func goLookupPTR(addr string) ([]string, error) {
+ names := lookupStaticAddr(addr)
+ if len(names) > 0 {
+ return names, nil
+ }
+ arpa, err := reverseaddr(addr)
+ if err != nil {
+ return nil, err
+ }
+ _, rrs, err := lookup(arpa, dnsTypePTR)
+ if err != nil {
+ return nil, err
+ }
+ ptrs := make([]string, len(rrs))
+ for i, rr := range rrs {
+ ptrs[i] = rr.(*dnsRR_PTR).Ptr
+ }
+ return ptrs, nil
+}