Fix DNS resolution: NS name bug, glue bypass, FormatRecord duplicate header, result deduplication #23

Merged
gary merged 1 commits from agent/go-expert-developer/da16f960 into main 2026-06-08 10:21:10 +00:00
5 changed files with 77 additions and 22 deletions
Showing only changes of commit 3da28da9a2 - Show all commits
+1 -9
View File
@@ -1,7 +1,6 @@
package dns package dns
import ( import (
"fmt"
"strings" "strings"
"github.com/miekg/dns" "github.com/miekg/dns"
@@ -234,12 +233,5 @@ func FormatRecord(rr dns.RR) string {
if rr == nil { if rr == nil {
return "" return ""
} }
header := rr.Header() return rr.String()
return fmt.Sprintf("%s %d %s %s %s",
header.Name,
header.Ttl,
dns.ClassToString[header.Class],
QNameType(header.Rrtype),
rr.String(),
)
} }
+51
View File
@@ -196,3 +196,54 @@ func containsString(items []string, target string) bool {
} }
return false return false
} }
// DeduplicateResults collapses terminal results that represent the same
// outcome from the same server into a single entry with summed probability.
// This prevents the same nameserver failure (or answer) from appearing once
// per delegation path when several parent servers all refer to the same child.
func DeduplicateResults(results []traverse.TraversalResult) []traverse.TraversalResult {
type entry struct {
result traverse.TraversalResult
prob float64
}
keys := make(map[string]*entry)
var order []string
for _, r := range results {
if r.Response == nil || r.Referral == nil {
continue
}
key := resultDeduplicationKey(r)
if e, ok := keys[key]; ok {
e.prob += r.Referral.Prob
} else {
keys[key] = &entry{result: r, prob: r.Referral.Prob}
order = append(order, key)
}
}
deduped := make([]traverse.TraversalResult, 0, len(order))
for _, key := range order {
e := keys[key]
refCopy := *e.result.Referral
refCopy.Prob = e.prob
deduped = append(deduped, traverse.TraversalResult{
Referral: &refCopy,
Response: e.result.Response,
})
}
return deduped
}
func resultDeduplicationKey(r traverse.TraversalResult) string {
bailiwick := strings.TrimSuffix(r.Referral.Bailiwick, ".")
switch r.Response.Type {
case traverse.RespAnswer:
key, _ := answerKey(r.Response)
return "answer:" + bailiwick + ":" + key
case traverse.RespNSResolutionFailed:
return "ns_error:" + r.Response.ErrorMessage
default:
return r.Response.Type.String() + ":" + bailiwick + ":" + r.Response.ErrorMessage
}
}
+7 -5
View File
@@ -120,7 +120,8 @@ func (f *textFormatter) writeResults(results []traverse.TraversalResult) error {
} }
terminal := terminalResults(results) terminal := terminalResults(results)
for _, result := range terminal { deduped := DeduplicateResults(terminal)
for _, result := range deduped {
prefix := strings.Repeat(" ", result.Referral.Depth+1) prefix := strings.Repeat(" ", result.Referral.Depth+1)
line := prefix + f.formatResultLine(result) line := prefix + f.formatResultLine(result)
if _, err := fmt.Fprintln(f.w, line); err != nil { if _, err := fmt.Fprintln(f.w, line); err != nil {
@@ -196,14 +197,15 @@ func (f *textFormatter) formatResultLine(result traverse.TraversalResult) string
prob := formatProbability(result.Referral.Prob) prob := formatProbability(result.Referral.Prob)
switch result.Response.Type { switch result.Response.Type {
case traverse.RespAnswer: case traverse.RespAnswer:
key, rrs := answerKey(result.Response) key, _ := answerKey(result.Response)
if key == "" { if key == "" {
return fmt.Sprintf("%s resulted in answer", prob) return fmt.Sprintf("%s resulted in answer", prob)
} }
if len(rrs) == 1 { nsLabel := ""
return f.colorize(fmt.Sprintf("%s answered with %s", prob, rrs[0]), colorGreen) if result.Referral.Bailiwick != "" && result.Referral.Bailiwick != "." {
nsLabel = trimDomain(result.Referral.Bailiwick) + " "
} }
return f.colorize(fmt.Sprintf("%s answered with %s", prob, strings.Join(rrs, " / ")), colorGreen) return f.colorize(fmt.Sprintf("%s %sanswered with %s", prob, nsLabel, key), colorGreen)
case traverse.RespNODATA: case traverse.RespNODATA:
return fmt.Sprintf("%s found no such record", prob) return fmt.Sprintf("%s found no such record", prob)
case traverse.RespNXDOMAIN: case traverse.RespNXDOMAIN:
+4 -1
View File
@@ -230,7 +230,10 @@ func (r *Referral) Resolve(ctx context.Context, traverser *Traverser, cache *Inf
if resp.Type == RespReferral { if resp.Type == RespReferral {
children := resp.ChildReferrals() children := resp.ChildReferrals()
for _, child := range children { for _, child := range children {
if visited != nil && visited[child.Name] { // Only skip visited names when they have no addresses; if glue
// was included in the referral response we still need to query
// that child to get the authoritative answer.
if visited != nil && visited[child.Name] && !child.HasAddresses() {
continue continue
} }
if !stack.Push(child) { if !stack.Push(child) {
+14 -7
View File
@@ -227,15 +227,19 @@ func (t *Traverser) processReferral(ctx context.Context, ref *Referral, cache *I
} }
t.mu.Unlock() t.mu.Unlock()
ref.Addresses = t.resolveGlueViaSystem(ctx, ref.Name, cache) // Resolve the nameserver's IP address. The NS hostname is stored in
// Bailiwick; ref.Name is the domain being queried (not the NS name).
nsToResolve := ref.Bailiwick
if nsToResolve == "" || nsToResolve == "." {
nsToResolve = ref.Name
}
nsName := strings.TrimSuffix(nsToResolve, ".")
ref.Addresses = t.resolveGlueViaSystem(ctx, nsToResolve, cache)
if len(ref.Addresses) > 0 { if len(ref.Addresses) > 0 {
ref.State = StateResolved ref.State = StateResolved
} else { } else {
addrs, err := t.ResolveNS(ctx, ref.Name, cache, visitedCopy, t.depth) addrs, err := t.ResolveNS(ctx, nsToResolve, cache, visitedCopy, t.depth)
nsName := strings.TrimSuffix(ref.Bailiwick, ".")
if nsName == "" || nsName == "." {
nsName = strings.TrimSuffix(ref.Name, ".")
}
if err != nil { if err != nil {
return &Response{ return &Response{
Referral: ref, Referral: ref,
@@ -375,7 +379,10 @@ func (t *Traverser) ResolveNS(ctx context.Context, nsName string, cache *InfoCac
if resp.Type == RespReferral { if resp.Type == RespReferral {
children := resp.ChildReferrals() children := resp.ChildReferrals()
for _, child := range children { for _, child := range children {
if visited != nil && visited[child.Name] { // Only skip visited names when they have no addresses; if glue
// was included in the referral response we still need to query
// that child to get the authoritative answer.
if visited != nil && visited[child.Name] && !child.HasAddresses() {
continue continue
} }
if !stack.Push(child) { if !stack.Push(child) {