Fix DNS resolution: NS name bug, glue bypass, FormatRecord duplicate header, result deduplication #23
@@ -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(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user