func (r *Resolver) Lookup(sname string, stype msg.QType, sclass rr.Class, rd bool) (answer, redirects rr.RRs, result LookupResult, err error) {
	defer func() {
		if e := recover(); e != nil {
			if r.log.Level >= dns.LOG_ERRORS {
				r.log.Log("FAIL Lookup error: %s", e.(error))
			}
		}
	}()
	var slist *srvlist
	var reply *msg.Message
	var srv server  
	var ip net.IP   
	retry := 0    
	iserver := 0  
	sname = dns.RootedName(strings.ToLower(sname))
	aliases := map[string]bool{strings.ToLower(sname): true}  
step1:
	bestmatch := -2  
	nodata, nxdomain, sname0 := false, false, sname
	answer = r.cached(sname,
		func(rec *rr.RR) bool {
			switch {
			case sclass != rec.Class:
				return false
			case stype == msg.QTYPE_STAR:
				return true
			case rec.Type == rr.TYPE_NXDOMAIN:
				nxdomain = true
				return false
			case rec.Type == rr.TYPE_NODATA && rr.Type(stype) == rec.RData.(*rr.NODATA).Type:
				nodata = true
				return false
			case rec.Type == rr.TYPE_CNAME && stype != msg.QTYPE_CNAME:
				cname := strings.ToLower(rec.RData.(*rr.CNAME).Name)
				if aliases[cname] {
					result = LookupAliasLoop
					return false
				}
				redirects = append(redirects, rec)
				sname = cname
				aliases[sname] = true
				result = LookupAliased
				return false
			default:
				return rr.Type(stype) == rec.Type
			}
			panic("unreachable")
		})
	switch {
	case result == LookupAliasLoop:
		return
	case len(answer) != 0:
		return
	case sname != sname0:
		goto step1
	case nxdomain:  
		switch result {
		case LookupAliased:
			result = LookupAliasError
		default:
			result = LookupNameError
		}
		return
	case nodata:  
		result = LookupDataNotFound
		return
	}
step2:
	var slabels []string
	if slabels, err = dns.Labels(sname); err != nil {
		return
	}
	slist = &srvlist{conf: r.getQueryConf()}
	srvmap := map[string]bool{}
	for len(slabels) != 0 {
		q := strings.Join(slabels, ".")
		if nss := r.cached(q,
			func(rec *rr.RR) bool {
				if rec.Class == sclass && rec.Type == rr.TYPE_NS {
					nm := strings.ToLower(rec.RData.(*rr.NS).NSDName)
					if !srvmap[nm] {
						srvmap[nm] = true
						return true
					}  
				}
				return false
			}); nss != nil {
			for _, ns := range nss {
				var matchcount int
				if matchcount, err = dns.MatchCount(sname, ns.Name); err != nil {
					if r.log.Level >= dns.LOG_ERRORS {
						r.log.Log("Matchcount: %s", err)
					}
					err = nil  
					continue
				}
				if matchcount <= bestmatch {  
					continue
				}
				nsdname := ns.RData.(*rr.NS).NSDName
				if as := r.cached(nsdname,
					func(r *rr.RR) bool {
						return r.Class == sclass && (r.Type == rr.TYPE_A || r.Type == rr.TYPE_AAAA)
					}); as != nil {
					srv := server{name: nsdname, zone: ns.Name, matchcount: matchcount, attempts: int(slist.conf.Conf.Opt.Attempts)}
					for _, a := range as {
						var ip net.IP
						switch x := a.RData.(type) {
						default:
							log.Fatalf("FAIL internal error %T", x)
						case *rr.A:
							ip = x.Address
						case *rr.AAAA:
							ip = x.Address
						}
						srv.ips = append(srv.ips, ip)
					}
					slist.servers = append(slist.servers, srv)
					continue
				}
				r.needNSAdr(nsdname)
				retry++
			}
		}
		slabels = slabels[1:]  
	}
	if len(slist.servers) == 0 {
		slist = r.sbelt()
		if r.log.Level >= dns.LOG_DEBUG {
			r.log.Log("%q: using sbelt", sname)
		}
	}
	sort.Sort(slist)
	iserver = 0
step3:
	if r.log.Level >= dns.LOG_DEBUG {
		r.log.Log("slist servers %d", len(slist.servers))
	}
	rxbuf := make([]byte, 4500)
asking:
	for {
		if len(slist.servers) == 0 {
			if r.log.Level >= dns.LOG_DEBUG {
				r.log.Log("Lookup %q fail, no servers to ask", sname)
			}
			result = LookupFail
			return
		}
		if iserver >= len(slist.servers) {
			if r.log.Level >= dns.LOG_DEBUG {
				r.log.Log("Lookup %q giving up without getting a valid response", sname)
			}
			result = LookupFail
			return
		}
		srv = slist.servers[iserver]
		if srv.matchcount <= bestmatch {
			if retry == 0 {
				if r.log.Level >= dns.LOG_DEBUG {
					r.log.Log("Lookup %q giving up due to no progress in matching", sname)
				}
				result = LookupFail
				return
			}
			<-time.After(1e9 / 2)
			retry >>= 1
			goto step2
		}
		const qmark = "------------------------------------------------------------------------------"
		const rmark = "=============================================================================="
		const (
			attemptUDP = iota
			attemptENDS
		)
		for attempts := 0; attempts < srv.attempts; attempts++ {
			for _, ip = range srv.ips {
				attempting := attemptUDP
			reAttempt:
				m := msg.New()
				m.Question.Append(sname, stype, sclass)
				if attempting == attemptENDS {
					m.Additional = rr.RRs{optRR}
				}
				m.Header.RD = rd  
				if r.log.Level >= dns.LOG_TRACE {
					if r.log.Level >= dns.LOG_DEBUG {
						r.log.Log("\n%s(QUERY MSG for %q @ %s)\n%s\n%s\n", qmark, srv.name, ip, m, qmark)
					} else {
						r.log.Log("asking %q @ %s, Q: %s", srv.name, ip, m.Question)
					}
				}
				adr, err := net.ResolveUDPAddr("udp", ip.String()+":53")
				if err != nil {
					if r.log.Level >= dns.LOG_ERRORS {
						r.log.Log("FAIL net.ResolveUDPAddr: %s", err)
					}
					continue
				}
				c, err := net.DialUDP("udp", nil, adr)
				if err != nil {
					if r.log.Level >= dns.LOG_ERRORS {
						r.log.Log("FAIL net.DialUDP: %s", err)
					}
					continue
				}
				defer c.Close()
				c.SetDeadline(time.Now().Add(time.Duration(slist.conf.Conf.Opt.TimeoutSecs) * time.Second))
				var rxbytes int
				if rxbytes, reply, err = m.ExchangeBuf(c, rxbuf); err != nil {
					if r.log.Level >= dns.LOG_ERRORS {
						r.log.Log("FAIL ExchangeBuf: %s", err)
					}
					continue
				}
				if r.log.Level >= dns.LOG_TRACE {
					if r.log.Level >= dns.LOG_DEBUG {
						r.log.Log("\n%s(REPLY MSG from %q @ %s)\n%s\nWire:\n%s\n%s\n", rmark, srv.name, ip, reply, hex.Dump(rxbuf[:rxbytes]), rmark)
					} else {
						r.log.Log("got a response for %q from %q @ %s", sname, srv.name, ip)
					}
				}
				h := &reply.Header
				if h.TC {
					switch attempting {
					case attemptUDP:
						attempting = attemptENDS
						goto reAttempt
					}
				}
				reject := h.ID != m.Header.ID ||
					!h.QR ||
					h.Opcode != m.Header.Opcode ||
					h.TC ||
					h.Z ||
					h.QDCOUNT != m.Header.QDCOUNT
				if reject {
					continue
				}
				break asking  
			}
		}
		iserver++
	}
	if srv.matchcount <= bestmatch {
		log.Fatalf("FAIL internal error %d <= %d", srv.matchcount, bestmatch)
	}
	if r.log.Level > dns.LOG_DEBUG {
		r.log.Log("%q bestmatch %d -> %d", sname, bestmatch, srv.matchcount)
	}
	bestmatch = srv.matchcount
	other := rr.RRs{}
	cnames := rr.RRs{}  
	soa := (*rr.RR)(nil)
	soadata := (*rr.SOA)(nil)
	answer, other = reply.Answer.Filter(func(r *rr.RR) bool {
		return sclass == r.Class && (stype == msg.QTYPE_STAR || r.Type == rr.Type(stype)) && strings.ToLower(r.Name) == sname
	})
	cnames, other = other.Filter(func(r *rr.RR) bool {
		return sclass == r.Class && r.Type == rr.TYPE_CNAME && strings.ToLower(r.Name) == sname
	})
	soas, ns := reply.Authority.Filter(func(r *rr.RR) bool {
		return sclass == r.Class && r.Type == rr.TYPE_SOA
	})
	ns, _ = ns.Filter(func(rec *rr.RR) (y bool) {
		mc, _ := dns.MatchCount(sname, rec.Name)
		return mc > bestmatch
	})
	if len(soas) == 1 {
		soa = soas[0]
		soadata = soa.RData.(*rr.SOA)
	}
	switch {
	case reply.RCODE == msg.RC_NO_ERROR && len(answer) != 0:
		r.cache.Add(reply.Answer, soas, ns, reply.Additional)
		answer.Unique()  
		return
	case reply.RCODE == msg.RC_NAME_ERROR:
		r.cache.Add(reply.Answer, soas, ns, reply.Additional)
		if reply.AA && len(soas) == 1 {
			ttl := soa.TTL
			if ttl2 := int32(soadata.Minimum); ttl2 < ttl {
				ttl = ttl2
			}
			r.cache.Add(rr.RRs{&rr.RR{sname, rr.TYPE_NXDOMAIN, sclass, ttl, &rr.NXDOMAIN{}}})
		}
		switch result {
		case LookupAliased:
			result = LookupAliasError
		default:
			result = LookupNameError
		}
		return
	case reply.RCODE == msg.RC_NO_ERROR && reply.ANCOUNT == 0 && (len(soas) == 1 || len(ns) == 0):
		r.cache.Add(reply.Answer, soas, ns, reply.Additional)
		if reply.AA {
			ttl := soa.TTL
			if ttl2 := int32(soadata.Minimum); ttl2 < ttl {
				ttl = ttl2
			}
			r.cache.Add(rr.RRs{&rr.RR{sname, rr.TYPE_NODATA, sclass, ttl, &rr.NODATA{rr.Type(stype)}}})
		}
		result = LookupDataNotFound
		return
	case reply.RCODE == msg.RC_NO_ERROR && len(cnames) == 1:
		r.cache.Add(reply.Answer, soas, ns, reply.Additional)
		cn := cnames[0]
		cname := cn.RData.(*rr.CNAME)
		for chain := true; chain; {
			sname = strings.ToLower(cname.Name)  
			if aliases[sname] {
				result = LookupAliasLoop
				return
			}
			aliases[sname] = true
			redirects = append(redirects, cn)
			chain = false
			for _, cn = range other {
				if cn.Type == rr.TYPE_CNAME && strings.ToLower(cn.Name) == sname {
					cname, chain = cn.RData.(*rr.CNAME), true
					break
				}
			}
		}
		result = LookupAliased
		goto step1
	case reply.RCODE == msg.RC_NO_ERROR && len(ns) != 0:
		r.cache.Add(soas, ns, reply.Additional)
		goto step2
	default:
		goto step3  
	}
	panic("unreachable")
}
Lookup is a general DNS lookup function (rfc1034/p.30). It attempts to
retrieve arbitrary information from the DNS. The caller supplies a sname,
stype and sclass, and wants all of the matching RRs. Lookup should normally
report "DNS lookup error" results via the return result variable.  A non-nil
Error is returned for any non-lookup error event. The rd parameter is the
msg.Messsage.Header "Recursion Desired" flag. Lookup CNAMEs chain walked, if
any, is returned in redirects.
package resolver
import (
	"encoding/hex"
	"fmt"
	"github.com/cznic/dns"
	"github.com/cznic/dns/cache"
	"github.com/cznic/dns/hosts"
	"github.com/cznic/dns/msg"
	"github.com/cznic/dns/resolv"
	"github.com/cznic/dns/rr"
	"log"
	"net"
	"os"
	"sort"
	"strings"
	"sync"
	"time"
)
type queryConf struct {
	hosts map[string][]net.IP
	ips   map[string][]string
	*resolv.Conf
	*Resolver
}
func (q *queryConf) hostsChanged(f *hosts.File) (err error) {
	q.hosts = map[string][]net.IP{}
	q.ips = map[string][]string{}
	add := func(name string, ip net.IP) {
		q.hosts[name] = append(q.hosts[name], ip)
	}
	for _, item := range *f {
		name := strings.ToLower(item.CanonicalName)
		ip := item.IP
		sip := string([]byte(ip))
		if _, ok := q.ips[sip]; ok {
			return fmt.Errorf("duplicate ip entry %q", ip.String())
		}
		q.ips[sip] = append(append([]string{}, name), item.Aliases...)
		add(strings.ToLower(item.CanonicalName), ip)
		for _, name := range item.Aliases {
			add(strings.ToLower(name), ip)
		}
	}
	return
}
func (q *queryConf) qhosts(name string) (y []net.IP, ok bool) {
	y, ok = q.hosts[name]
	return
}
func (q *queryConf) qips(ip net.IP) (y []string, ok bool) {
	y, ok = q.ips[string([]byte(ip))]
	return
}
func (q *queryConf) hostqlist(name string) (list []string) {
	labels, err := dns.Labels(name)
	if err != nil {
		if q.Resolver.log.Level >= dns.LOG_ERRORS {
			q.Resolver.log.Log("FAIL %s: %s", name, err)
		}
		return
	}
	dots := len(labels) - 1
	ndots := int(q.Conf.Opt.Ndots)
	domain := q.Conf.Domain
	if domain == "" {
		domain = dns.Host2domain(q.Resolver.hostName)
	}
	rooted := dns.IsRooted(name)
	if rooted || dots >= ndots {
		list = append(list, name)  
	}
	if !rooted && dots < ndots {
		rname := dns.RootedName(name)
		if domain != "" {
			list = append(list, rname+domain)
		}
		list = append(list, name)
		for _, s := range q.Conf.Search {
			list = append(list, rname+s)
		}
	}
	return
}
type goStrMapBool struct {
	m    map[string]bool
	lock sync.Mutex
}
func newGoStrMapBool() *goStrMapBool {
	return &goStrMapBool{m: map[string]bool{}}
}
type LookupResult int
const (
	LookupOK           LookupResult = iota  
	LookupAliased                           
	LookupNameError                         
	LookupDataNotFound                      
	LookupFail                              
	LookupAliasLoop                         
	LookupAliasError                        
)
var LookupResultStr = map[LookupResult]string{
	LookupOK:           "Sucess",
	LookupAliased:      "Sucess. QNAME is an alias and the result is for its canonical name",
	LookupNameError:    "QNAME doesn't exist",
	LookupDataNotFound: "QNAME does exist, but no data for the combination of QTYPE and QCLASS were found",
	LookupFail:         "Lookup fail. Could be also wrong resolver configuration or network communication error.",
	LookupAliasLoop:    "Detected a cycle in the aliases chain",
	LookupAliasError:   "QNAME is an alias to a non existing canonical name",
}
type Resolver struct {
	cache                 *cache.Cache
	hostName              string
	log                   *dns.Logger
	getQueryConf          func() *queryConf
	pendingA, pendingAAAA *goStrMapBool  
}
func New(hostsFName, resolvFName string, logger *dns.Logger) (r *Resolver, err error) {
	if logger == nil {
		logger = dns.NoLogger
	}
	r = &Resolver{cache: cache.New(), log: logger, pendingA: newGoStrMapBool(), pendingAAAA: newGoStrMapBool()}
	defer func() {
		if e := recover(); e != nil {
			if r.log.Level >= dns.LOG_ERRORS {
				r.log.Log("FAIL New: %s", e.(error))
			}
			r = nil
		}
	}()
	if r.log.Level >= dns.LOG_TRACE {
		r.log.Log("New(hosts %q resolv %q)", hostsFName, resolvFName)
	}
	if hostsFName == "" {
		hostsFName = hosts.Sys
	}
	r.hostName, err = os.Hostname()
	if err != nil {
		panic(err)
	}
	var hostsCfg *hosts.Cfg
	hostsCfg, err = hosts.NewCfg(hostsFName, logger)
	if err != nil {
		panic(err)
	}
	if resolvFName == "" {
		resolvFName = resolv.Sys
	}
	var resolvCfg *resolv.Cfg
	resolvCfg, err = resolv.NewCfg(resolvFName, logger)
	if err != nil {
		panic(err)
	}
	hostsCfg.SetChanged()
	resolvCfg.SetChanged()
	r.getQueryConf = func() *queryConf {  
		v := &queryConf{Resolver: r}
		hfile, _, herr := hostsCfg.File()
		if herr != nil {
			if r.log.Level >= dns.LOG_ERRORS {
				r.log.Log("FAIL hostsCfg.File: %s", herr)
			}
		}
		if err := v.hostsChanged(hfile); err != nil {
			if r.log.Level >= dns.LOG_ERRORS {
				r.log.Log("FAIL hostsChanged: %s", err)
			}
		}
		cfile, _, cerr := resolvCfg.Conf()
		if cerr != nil {
			if r.log.Level >= dns.LOG_ERRORS {
				r.log.Log("FAIL resolvCfg.File: %s", cerr)
			}
		}
		v.Conf = cfile
		return v
	}
	if r.log.Level >= dns.LOG_TRACE {
		r.log.Log("New() OK")
	}
	return
}
func (r *Resolver) Cache() *cache.Cache {
	return r.cache
}
func (r *Resolver) Logger() *dns.Logger {
	return r.log
}
func (r *Resolver) getHostByName(name string, qtype msg.QType) (ipList []net.IP, redirects rr.RRs, err error) {
	qc := r.getQueryConf()
	qlist := qc.hostqlist(strings.ToLower(strings.TrimSpace(name)))
	for _, q := range qlist {
		if ipList, _ = qc.qhosts(q); ipList != nil {  
			return
		}
	}
	for i, q := range qlist {
		rrs, cnames, result, e := r.Lookup(q, qtype, rr.CLASS_IN, false)
		if e != nil {
			err = e
			return
		}
		redirects = cnames
		switch result {
		case LookupNameError:
			if i+1 < len(qlist) {
				continue
			}
			fallthrough
		default:
			err = fmt.Errorf(LookupResultStr[result])
			return
		case LookupOK, LookupAliased:
			for _, rec := range rrs {
				switch x := rec.RData.(type) {
				default:
					log.Fatalf("FAIL %T", x)
				case *rr.A:
					ipList = append(ipList, x.Address)
				case *rr.AAAA:
					ipList = append(ipList, x.Address)
				}
			}
			return
		}
		panic("unreachable")
	}
	panic("unreachable")
}
func (r *Resolver) GetHostByNameIPv4(name string) (ipList []net.IP, redirects rr.RRs, err error) {
	return r.getHostByName(name, msg.QTYPE_A)
}
func (r *Resolver) GetHostByNameIPv6(name string) (ipList []net.IP, redirects rr.RRs, err error) {
	return r.getHostByName(name, msg.QTYPE_AAAA)
}
func (r *Resolver) GetHostByName(name string, preferIPv6 bool) (ipList []net.IP, redirects rr.RRs, err error) {
	a, b := (*Resolver).GetHostByNameIPv4, (*Resolver).GetHostByNameIPv6
	if preferIPv6 {
		a, b = b, a
	}
	if ipList, redirects, err = a(r, name); len(ipList) != 0 {
		return
	}
	return b(r, name)
}
func (r *Resolver) GetHostByAddr(ip net.IP) (hosts []string, err error) {
	qc := r.getQueryConf()
	if hosts, _ = qc.qips(ip); hosts != nil {
		return  
	}
	name := dns.RevLookupName(ip)
	if name == "" {
		return nil, fmt.Errorf("GetHostByAddr:invalid ip '% x'", ip)
	}
	var rrs rr.RRs
	var rslt LookupResult
	if rrs, _, rslt, err = r.Lookup(name, msg.QTYPE_PTR, rr.CLASS_IN, false); err != nil {
		return
	}
	switch rslt {
	default:
		err = fmt.Errorf("GetHostByAddr: %s", LookupResultStr[rslt])
	case LookupOK, LookupAliased:
		for _, v := range rrs {
			hosts = append(hosts, v.RData.(*rr.PTR).PTRDName)
		}
	}
	return
}
func (r *Resolver) sbelt() (s *srvlist) {
	s = &srvlist{conf: r.getQueryConf()}
	servers := s.conf.Conf.Nameserver
	if len(servers) == 0 {
		s.servers = []server{{zone: "DefaultLocalNameServer.", name: "DefaultLocalNameServer.", attempts: int(s.conf.Conf.Opt.Attempts), ips: []net.IP{dns.DefaultLocalNameServer()}, matchcount: -1}}
		return
	}
	s.servers = make([]server, len(servers))
	for i, srv := range servers {
		n := fmt.Sprintf("%d.SBELT.", i)
		s.servers[i] = server{zone: n, name: n, attempts: int(s.conf.Conf.Opt.Attempts), ips: []net.IP{srv}, matchcount: -1}
	}
	return
}
func (r *Resolver) cached(name string, want func(*rr.RR) bool) (wanted rr.RRs) {
	if rrs, hit := r.cache.Get(name); hit {
		wanted, _ = rrs.Filter(want)
	}
	return
}
func (r *Resolver) needNSAdr(name string) {
	const retry = 60e9  
	f := func(p *goStrMapBool, q msg.QType) {
		p.lock.Lock()          
		defer p.lock.Unlock()  
		if p.m[name] {         
			return
		}
		p.m[name] = true  
		p.lock.Unlock()   
		r.Lookup(name, q, rr.CLASS_IN, true)  
		<-time.After(retry)
		p.lock.Lock()      
		delete(p.m, name)  
	}
	go f(r.pendingA, msg.QTYPE_A)
	go f(r.pendingAAAA, msg.QTYPE_AAAA)
}
var optRR = &rr.RR{".", rr.TYPE_OPT, rr.Class(4096), (&rr.EXT_RCODE{}).ToTTL(), &rr.OPT{}}