예제 #1
0
파일: resolver.go 프로젝트: cznic/dns
// Build a query task list from a hostname.
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) // initial absolute query
	}
	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
}
예제 #2
0
파일: resolver.go 프로젝트: cznic/dns
// 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.
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 // current server asked
	var ip net.IP  // current IP asked

	retry := 0   // number of requests sent for missing addresses of known nameservers
	iserver := 0 // index into slist servers
	sname = dns.RootedName(strings.ToLower(sname))
	aliases := map[string]bool{strings.ToLower(sname): true} // CNAME loop detection

	// rfc1034/5.3.3
	// The top level algorithm has four steps:

step1:
	//=================================================================
	//   1. See if the answer is in local information, and if so return
	//      it to the client.

	bestmatch := -2 // sbelt has -1
	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: // NXDOMAIN resolved from cache
		switch result {
		case LookupAliased:
			result = LookupAliasError
		default:
			result = LookupNameError
		}
		return
	case nodata: // NODATA resolved from cache
		result = LookupDataNotFound
		return
	}

step2:
	//=================================================================
	//   2. Find the best servers to ask.
	// Step 2 looks for a name server to ask for the required data.  The
	// general strategy is to look for locally-available name server RRs,
	// starting at SNAME, then the parent domain name of SNAME, the
	// grandparent, and so on toward the root.  Thus if SNAME were
	// Mockapetris.ISI.EDU, this step would look for NS RRs for
	// Mockapetris.ISI.EDU, then ISI.EDU, then EDU, and then . (the root).
	// These NS RRs list the names of hosts for a zone at or above SNAME.
	// Copy the names into SLIST.  Set up their addresses using local data.
	// It may be the case that the addresses are not available.  The
	// resolver has many choices here; the best is to start parallel
	// resolver processes looking for the addresses while continuing onward
	// with the addresses which are available.

	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)
					// Name servers for a domain are
					// themselves generally anywhere in the
					// DNS tree and the same NS may serve
					// otherwise unrelated parts of the DNS
					// tree (i.e. separated zones). Thus we
					// can see the same one(s) again while
					// walking the slabels towards root.
					// Avoid adding a same NS more than
					// once to the SLIST.
					if !srvmap[nm] {
						srvmap[nm] = true
						return true
					} // else rejecting duplicate nameserver nm
				}
				return false

			}); nss != nil {

			for _, ns := range nss {
				// check ns NS
				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 // not fatal, but ignore the NS RR
					continue
				}

				if matchcount <= bestmatch { // ignore
					continue
				}

				// matchcount > bestmatch => chance
				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 {

					// got len(as) addresses for ns
					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
				}

				// We have a nice candidate NS but have no A
				// nor AAAA RRs for it.  Could be due to
				// missing glue record(s) or their expired
				// TTLs.  Enter emergency panic mode for the
				// missing address(es).
				r.needNSAdr(nsdname)
				retry++

			}
		}
		slabels = slabels[1:] // level up
	}

	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:
	//=================================================================
	//   3. Send them queries until one returns a response.

	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
			}

			// retry due to pending NS addresses requests
			<-time.After(1e9 / 2)
			retry >>= 1
			goto step2

		}

		const qmark = "------------------------------------------------------------------------------"
		const rmark = "=============================================================================="

		const (
			attemptUDP = iota
			attemptENDS
		)

		// try server srv
		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 // Recursion Desired
				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
				}

				// got a response
				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 // response accepted

			}
		}

		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

	//step4:

	other := rr.RRs{}
	cnames := rr.RRs{} // only those matching sname
	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
	})
	// Authority NSs sanity check
	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)
	}

	//=================================================================
	//   4. Analyze the response, either:

	switch {

	//-----------------------------------------------------------------
	//       4.a. if the response answers the question or contains a name
	//            error, cache the data as well as returning it back to
	//            the client.
	case reply.RCODE == msg.RC_NO_ERROR && len(answer) != 0:
		r.cache.Add(reply.Answer, soas, ns, reply.Additional)
		answer.Unique() // improve some bad configured server responses
		return

	case reply.RCODE == msg.RC_NAME_ERROR:
		r.cache.Add(reply.Answer, soas, ns, reply.Additional)

		//   rfc2038/5 cache NXDOMAIN
		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

	//-----------------------------------------------------------------
	//   rfc2038/2.2 - No Data
	//
	//   NODATA is indicated by an answer with the RCODE set to NOERROR and
	//   no relevant answers in the answer section.  The authority section
	//   will contain an SOA record, or there will be no NS records there.
	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)

		//   rfc2038/5 cache NODATA
		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

	//-----------------------------------------------------------------
	//       4.c. if the response shows a CNAME and that is not the
	//            answer itself, cache the CNAME, change the SNAME to the
	//            canonical name in the CNAME RR and go to step 1.
	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) // next name in chain

			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

	//-----------------------------------------------------------------
	//       4.b. if the response contains a better delegation to other
	//            servers, cache the delegation information, and go to
	//            step 2.
	case reply.RCODE == msg.RC_NO_ERROR && len(ns) != 0:
		r.cache.Add(soas, ns, reply.Additional)
		goto step2

	//-----------------------------------------------------------------
	//       4.d. if the response shows a servers failure or other
	//            bizarre contents, delete the server from the SLIST and
	//            go back to step 3.
	default:
		goto step3 // "delete from the SLIST" is performed by iserver incrementing in step 3
	}

	panic("unreachable")
}