Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: lazy query A/AAAA of NSes when iterating on authorities #477

Merged
merged 2 commits into from
Nov 20, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 45 additions & 36 deletions src/zdns/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -893,34 +893,41 @@ func constructSingleQueryResultFromDNSMsg(res *SingleQueryResult, r *dns.Msg) (*
return res, r, StatusNoError, nil
}

// iterateOnAuthorities takes the authorities from the referrals of a nameserver, shuffles them, and iteratively tries to do a lookup against them.
// If one succeeds, we return without trying the others. If one fails, we iterate to the next.
func (r *Resolver) iterateOnAuthorities(ctx context.Context, qWithMeta *QuestionWithMetadata, depth int, result *SingleQueryResult, layer string, trace Trace) (*SingleQueryResult, Trace, Status, error) {
if len(result.Authorities) == 0 {
developStorm marked this conversation as resolved.
Show resolved Hide resolved
return nil, trace, StatusNoAuth, nil
}
var newLayer string
newTrace := trace
nameServers := make([]NameServer, 0, len(result.Authorities))
for i, elem := range result.Authorities {
// Skip DS and RRSIG records as they are not nameservers but may present in the section
// https://datatracker.ietf.org/doc/html/rfc4035#section-3.1.4

// Shuffle authorities to try them in random order
authorities := make([]interface{}, len(result.Authorities))
copy(authorities, result.Authorities)
rand.Shuffle(len(authorities), func(i, j int) {
authorities[i], authorities[j] = authorities[j], authorities[i]
})

for _, elem := range authorities {
// Skip DS and RRSIG records
switch elem.(type) {
case DSAnswer, RRSIGAnswer:
continue
}

if util.HasCtxExpired(&ctx) {
return &SingleQueryResult{}, newTrace, StatusTimeout, nil
return &SingleQueryResult{}, trace, StatusTimeout, nil
}
var ns *NameServer
var nsStatus Status
var nextLayer string

r.verboseLog(depth+1, "Trying Authority: ", elem)
ns, nsStatus, nextLayer, newTrace = r.extractAuthority(ctx, elem, layer, depth, result, newTrace)

// Extract authority details
ns, nsStatus, nextLayer, newTrace := r.extractAuthority(ctx, elem, layer, depth, result, trace)
trace = newTrace
r.verboseLog(depth+1, "Output from extract authorities: ", ns.String())

if nsStatus == StatusIterTimeout {
r.verboseLog(depth+2, "--> Hit iterative timeout: ")
return &SingleQueryResult{}, newTrace, StatusIterTimeout, nil
r.verboseLog(depth+2, "--> Hit iterative timeout")
return &SingleQueryResult{}, trace, StatusIterTimeout, nil
}

if nsStatus != StatusNoError {
Expand All @@ -931,33 +938,29 @@ func (r *Resolver) iterateOnAuthorities(ctx context.Context, qWithMeta *Question
} else {
r.verboseLog(depth+2, "--> Auth find failed for name ", qWithMeta.Q.Name, " with status: ", newStatus)
}
if i+1 == len(result.Authorities) {
r.verboseLog(depth+2, "--> No more authorities to try for name ", qWithMeta.Q.Name, ", terminating: ", nsStatus)
}
} else {
// We have a valid nameserver
newLayer = nextLayer
nameServers = append(nameServers, *ns)
continue
}
}

if len(nameServers) == 0 {
r.verboseLog(depth+1, fmt.Sprintf("--> Auth found no valid nameservers for name: %s Terminating", qWithMeta.Q.Name))
return &SingleQueryResult{}, newTrace, StatusServFail, errors.New("no valid nameservers found")
}
// Try iterative lookup immediately with this nameserver
iterateResult, newTrace, status, err := r.iterativeLookup(ctx, qWithMeta, []NameServer{*ns}, depth+1, nextLayer, trace)
trace = newTrace

iterateResult, newTrace, status, err := r.iterativeLookup(ctx, qWithMeta, nameServers, depth+1, newLayer, newTrace)
if status == StatusNoNeededGlue {
r.verboseLog((depth + 2), "--> Auth resolution of ", nameServers, " was unsuccessful. No glue to follow", status)
return iterateResult, newTrace, status, err
} else if isStatusAnswer(status) {
r.verboseLog((depth + 1), "--> Auth Resolution of ", nameServers, " success: ", status)
return iterateResult, newTrace, status, err
} else {
// We don't allow the continue fall through in order to report the last auth failure code, not STATUS_ERROR
r.verboseLog((depth + 2), "--> Iterative resolution of ", qWithMeta.Q.Name, " at ", nameServers, " Failed. Last auth. Terminating: ", status)
return iterateResult, newTrace, status, err
if status == StatusNoNeededGlue {
r.verboseLog(depth+2, "--> Auth resolution of ", ns, " was unsuccessful. No glue to follow")
continue
}

if isStatusAnswer(status) {
r.verboseLog(depth+1, "--> Auth Resolution of ", ns, " success: ", status)
return iterateResult, trace, status, err
}

r.verboseLog(depth+2, "--> Iterative resolution of ", qWithMeta.Q.Name, " at ", ns, " Failed: ", status)
}

// If we get here, all authorities failed
r.verboseLog(depth+2, "--> No more authorities to try for name ", qWithMeta.Q.Name, ", terminating")
return &SingleQueryResult{}, trace, StatusServFail, errors.New("no valid nameservers found or all lookups failed")
}

func (r *Resolver) extractAuthority(ctx context.Context, authority interface{}, layer string, depth int, result *SingleQueryResult, trace Trace) (*NameServer, Status, string, Trace) {
Expand Down Expand Up @@ -995,7 +998,13 @@ func (r *Resolver) extractAuthority(ctx context.Context, authority interface{},
q.Q.Type = dns.TypeA
}
q.RetriesRemaining = &r.retriesRemaining

// A/AAAA records for NSes are not on the chain of trust, so we don't need to validate DNSSEC
// Doing this to save us some time (this can propogate A LOT of queries in certain cases)
prevSecValue := r.shouldValidateDNSSEC
r.shouldValidateDNSSEC = false
res, trace, status, _ = r.iterativeLookup(ctx, &q, r.rootNameServers, depth+1, ".", trace)
r.shouldValidateDNSSEC = prevSecValue
}
if status == StatusIterTimeout || status == StatusNoNeededGlue {
return nil, status, "", trace
Expand Down