From 55c7e99f510b8a82e574b199e3df73e5e66d9260 Mon Sep 17 00:00:00 2001 From: phillip-stephens Date: Fri, 20 Sep 2024 12:49:38 -0400 Subject: [PATCH 1/3] add back authority fetching from cache, but with extra logic to hopefully prevent the issue seen before --- src/zdns/lookup.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++ src/zdns/util.go | 35 +++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/src/zdns/lookup.go b/src/zdns/lookup.go index 383e8ebe..58197d78 100644 --- a/src/zdns/lookup.go +++ b/src/zdns/lookup.go @@ -437,6 +437,71 @@ 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 + // Now, we check the authoritative: + name := strings.ToLower(q.Name) + layer = strings.ToLower(layer) + 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 { + 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, not the answers section. + 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...) + } + } + 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...) + } + } + return result, isCached, StatusNoError, 0, nil + } + } + } // 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) } From 83529ae1953727ed32489480902afe3ec6ae1a52 Mon Sep 17 00:00:00 2001 From: phillip-stephens Date: Fri, 20 Sep 2024 13:17:53 -0400 Subject: [PATCH 2/3] don't use cached authority if you don't have the auths or additionals --- src/zdns/lookup.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/zdns/lookup.go b/src/zdns/lookup.go index 58197d78..a7ef487d 100644 --- a/src/zdns/lookup.go +++ b/src/zdns/lookup.go @@ -439,6 +439,8 @@ func (r *Resolver) cachedRetryingLookup(ctx context.Context, q Question, nameSer } 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 // Now, we check the authoritative: name := strings.ToLower(q.Name) layer = strings.ToLower(layer) @@ -466,7 +468,7 @@ func (r *Resolver) cachedRetryingLookup(ctx context.Context, q Question, nameSer 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, not the answers section. + // the authority and additionals, and remove the NS answers for _, ans := range cachedResult.Answers { ns, ansOk := ans.(Answer) if !ansOk { @@ -498,7 +500,10 @@ func (r *Resolver) cachedRetryingLookup(ctx context.Context, q Question, nameSer result.Additional = append(result.Additional, cachedResult.Answers...) } } - return result, isCached, StatusNoError, 0, nil + // 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 + } } } } From a2b2d09e3539a53240b36ed0bc9667b03682e787 Mon Sep 17 00:00:00 2001 From: phillip-stephens Date: Fri, 20 Sep 2024 14:30:09 -0400 Subject: [PATCH 3/3] prettify comments --- src/zdns/lookup.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/zdns/lookup.go b/src/zdns/lookup.go index a7ef487d..8b766b90 100644 --- a/src/zdns/lookup.go +++ b/src/zdns/lookup.go @@ -439,17 +439,18 @@ func (r *Resolver) cachedRetryingLookup(ctx context.Context, q Question, nameSer } 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 - // Now, we check the authoritative: + // 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) @@ -466,9 +467,10 @@ func (r *Resolver) cachedRetryingLookup(ctx context.Context, q Question, nameSer 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 + // 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 { @@ -490,6 +492,7 @@ func (r *Resolver) cachedRetryingLookup(ctx context.Context, q Question, nameSer 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, ".") @@ -504,6 +507,7 @@ func (r *Resolver) cachedRetryingLookup(ctx context.Context, q Question, nameSer 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 } } }