diff --git a/src/zdns/lookup.go b/src/zdns/lookup.go index 383e8ebe..8b766b90 100644 --- a/src/zdns/lookup.go +++ b/src/zdns/lookup.go @@ -437,6 +437,80 @@ func (r *Resolver) cachedRetryingLookup(ctx context.Context, q Question, nameSer return r, isCached, StatusBlacklist, 0, nil } } + if !requestIteration { + // We're performing our own iteration, let's try checking the cache for the next authority + // For example, if we query yahoo.com and google.com, we don't need to go to the root servers for the gTLD + // servers twice, they'll be identical + name := strings.ToLower(q.Name) + layer = strings.ToLower(layer) + // get the next authority to query + authName, err := nextAuthority(name, layer) + if err != nil { + r.verboseLog(depth+2, err) + return SingleQueryResult{}, isCached, StatusAuthFail, 0, errors.Wrap(err, "could not get next authority with name: "+name+" and layer: "+layer) + } + if name != layer && authName != layer { + // we have a valid authority to check the cache for + var result SingleQueryResult + result.Answers = make([]interface{}, 0) + result.Additional = make([]interface{}, 0) + result.Authorities = make([]interface{}, 0) + if authName == "" { + r.verboseLog(depth+2, "Can't parse name to authority properly. name: ", name, ", layer: ", layer) + return SingleQueryResult{}, isCached, StatusAuthFail, 0, nil + } + r.verboseLog(depth+2, "Cache auth check for ", authName) + var qAuth Question + qAuth.Name = authName + qAuth.Type = dns.TypeNS + qAuth.Class = dns.ClassINET + cachedResult, ok = r.cache.GetCachedResult(qAuth, nil, depth+2) + if ok { + isCached = true + // we have a cache hit on the NS record for the authorities. Let's construct a response with the NS + // record. However, the caller is going to expect an answer as if they'd queried the root server, so we + // need to populate the authority and additionals, and remove the NS answers since the caller didn't + // perform an NS lookup. + for _, ans := range cachedResult.Answers { + ns, ansOk := ans.(Answer) + if !ansOk { + continue + } + if ns.Type == "NS" { + result.Authorities = append(result.Authorities, ns) + } + } + // populate the additionals from the cache + // starting with A records + for _, ans := range cachedResult.Answers { + var qAdd Question + qAdd.Name = strings.TrimSuffix(ans.(Answer).Answer, ".") + qAdd.Type = dns.TypeA + qAdd.Class = dns.ClassINET + cachedResult, ok = r.cache.GetCachedResult(qAdd, nil, depth+2) + if ok { + result.Additional = append(result.Additional, cachedResult.Answers...) + } + } + // now AAAA records + for _, ans := range cachedResult.Answers { + var qAdd Question + qAdd.Name = strings.TrimSuffix(ans.(Answer).Answer, ".") + qAdd.Type = dns.TypeAAAA + qAdd.Class = dns.ClassINET + cachedResult, ok = r.cache.GetCachedResult(qAdd, nil, depth+2) + if ok { + result.Additional = append(result.Additional, cachedResult.Answers...) + } + } + // only want to return if we actually have additionals and authorities from the cache for the caller + if len(result.Additional) > 0 && len(result.Authorities) > 0 { + return result, isCached, StatusNoError, 0, nil + } + // unsuccessful in retrieving from the cache, we'll continue to the wire + } + } + } // Alright, we're not sure what to do, go to the wire. result, status, try, err := r.retryingLookup(ctx, q, nameServer, requestIteration) diff --git a/src/zdns/util.go b/src/zdns/util.go index 2840145d..bbb93995 100644 --- a/src/zdns/util.go +++ b/src/zdns/util.go @@ -19,6 +19,8 @@ import ( "net" "strings" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" "github.com/zmap/dns" @@ -108,6 +110,39 @@ func checkGlueHelper(server, ansType string, result SingleQueryResult) (SingleQu return SingleQueryResult{}, StatusError } +// nextAuthority returns the next authority to query based on the current name and layer +// Example: nextAuthority("www.google.com", ".") -> "com" +func nextAuthority(name, layer string) (string, error) { + // We are our own authority for PTRs + // (This is dealt with elsewhere) + if strings.HasSuffix(name, "in-addr.arpa") && layer == "." { + return "in-addr.arpa", nil + } + + idx := strings.LastIndex(name, ".") + if idx < 0 || (idx+1) >= len(name) { + return name, nil + } + if layer == "." { + return name[idx+1:], nil + } + + if !strings.HasSuffix(name, layer) { + return "", errors.New("Server did not provide appropriate resolvers to continue recursion") + } + + // Limit the search space to the prefix of the string that isn't layer + idx = strings.LastIndex(name, layer) - 1 + if idx < 0 || (idx+1) >= len(name) { + // Out of bounds. We are our own authority + return name, nil + } + // Find the next step in the layer + idx = strings.LastIndex(name[0:idx], ".") + next := name[idx+1:] + return next, nil +} + func makeVerbosePrefix(depth int) string { return fmt.Sprintf("DEPTH %02d", depth) + ":" + strings.Repeat(" ", 2*depth) }