Merge pull request 'Fix DNS resolution: NS name bug, glue bypass, FormatRecord duplicate header, result deduplication' (#23) from agent/go-expert-developer/da16f960 into main
Reviewed-on: http://gitea.hansenits.com.au/hits/ExploreDNS/pulls/23
This commit was merged in pull request #23.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
@@ -234,12 +233,5 @@ func FormatRecord(rr dns.RR) string {
|
||||
if rr == nil {
|
||||
return ""
|
||||
}
|
||||
header := rr.Header()
|
||||
return fmt.Sprintf("%s %d %s %s %s",
|
||||
header.Name,
|
||||
header.Ttl,
|
||||
dns.ClassToString[header.Class],
|
||||
QNameType(header.Rrtype),
|
||||
rr.String(),
|
||||
)
|
||||
return rr.String()
|
||||
}
|
||||
|
||||
@@ -196,3 +196,54 @@ func containsString(items []string, target string) bool {
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,8 @@ func (f *textFormatter) writeResults(results []traverse.TraversalResult) error {
|
||||
}
|
||||
|
||||
terminal := terminalResults(results)
|
||||
for _, result := range terminal {
|
||||
deduped := DeduplicateResults(terminal)
|
||||
for _, result := range deduped {
|
||||
prefix := strings.Repeat(" ", result.Referral.Depth+1)
|
||||
line := prefix + f.formatResultLine(result)
|
||||
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)
|
||||
switch result.Response.Type {
|
||||
case traverse.RespAnswer:
|
||||
key, rrs := answerKey(result.Response)
|
||||
key, _ := answerKey(result.Response)
|
||||
if key == "" {
|
||||
return fmt.Sprintf("%s resulted in answer", prob)
|
||||
}
|
||||
if len(rrs) == 1 {
|
||||
return f.colorize(fmt.Sprintf("%s answered with %s", prob, rrs[0]), colorGreen)
|
||||
nsLabel := ""
|
||||
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:
|
||||
return fmt.Sprintf("%s found no such record", prob)
|
||||
case traverse.RespNXDOMAIN:
|
||||
|
||||
@@ -230,7 +230,10 @@ func (r *Referral) Resolve(ctx context.Context, traverser *Traverser, cache *Inf
|
||||
if resp.Type == RespReferral {
|
||||
children := resp.ChildReferrals()
|
||||
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
|
||||
}
|
||||
if !stack.Push(child) {
|
||||
|
||||
@@ -227,15 +227,19 @@ func (t *Traverser) processReferral(ctx context.Context, ref *Referral, cache *I
|
||||
}
|
||||
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 {
|
||||
ref.State = StateResolved
|
||||
} else {
|
||||
addrs, err := t.ResolveNS(ctx, ref.Name, cache, visitedCopy, t.depth)
|
||||
nsName := strings.TrimSuffix(ref.Bailiwick, ".")
|
||||
if nsName == "" || nsName == "." {
|
||||
nsName = strings.TrimSuffix(ref.Name, ".")
|
||||
}
|
||||
addrs, err := t.ResolveNS(ctx, nsToResolve, cache, visitedCopy, t.depth)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
Referral: ref,
|
||||
@@ -375,7 +379,10 @@ func (t *Traverser) ResolveNS(ctx context.Context, nsName string, cache *InfoCac
|
||||
if resp.Type == RespReferral {
|
||||
children := resp.ChildReferrals()
|
||||
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
|
||||
}
|
||||
if !stack.Push(child) {
|
||||
|
||||
Reference in New Issue
Block a user