- Exponential backoff retry logic (100ms, 200ms, 400ms... capped at 2s) replacing fixed 100ms delay between retries - Explicit REFUSED and NOTIMP response types (RespREFUSED, RespNOTIMPL) surfaced as terminal results with user-visible messages - CNAME loop detection: walking the ancestor referral chain before following a CNAME prevents infinite recursion; produces RespCNAMELoop - DNAME record support: synthesize CNAME target from DNAME mapping when the server omits the RFC 6672 synthesized CNAME record - ErrorMessage field on Response for surfacing error details to users - Fix resolveGlueViaSystem timeout bug: deadline.Sub(deadline) was always 0; replaced with time.Until(deadline) - DNAME records excluded from hasFinalAnswer so DNAME-only responses are correctly classified as RespCNAMEFollow - Text and JSON output updated with labels for all new response types - Tests: CNAME loop (2-step and direct), REFUSED, NOTIMP, graceful degradation (partial and total server failure), DNAME synthesis, IsNameInChain, backoffDelay, ResponseClassification strings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Copilot
multica-agent
parent
76f5010a5e
commit
368f200d23
@@ -2,6 +2,7 @@ package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
@@ -14,6 +15,8 @@ const (
|
||||
ResponseNODATA
|
||||
ResponseNXDOMAIN
|
||||
ResponseSERVFAIL
|
||||
ResponseREFUSED
|
||||
ResponseNOTIMPL
|
||||
ResponseOther
|
||||
)
|
||||
|
||||
@@ -29,6 +32,10 @@ func (rc ResponseClassification) String() string {
|
||||
return "nxdomain"
|
||||
case ResponseSERVFAIL:
|
||||
return "servfail"
|
||||
case ResponseREFUSED:
|
||||
return "refused"
|
||||
case ResponseNOTIMPL:
|
||||
return "notimp"
|
||||
default:
|
||||
return "other"
|
||||
}
|
||||
@@ -45,6 +52,13 @@ type DecodedResponse struct {
|
||||
Authority []dns.RR
|
||||
Additional []dns.RR
|
||||
CNAMEChain []string
|
||||
DNAMEMappings []DNAMEMapping
|
||||
}
|
||||
|
||||
// DNAMEMapping holds a DNAME record's owner and target for redirect synthesis.
|
||||
type DNAMEMapping struct {
|
||||
Owner string // e.g., "example.com."
|
||||
Target string // e.g., "example.net."
|
||||
}
|
||||
|
||||
func DecodeResponse(msg *dns.Msg) *DecodedResponse {
|
||||
@@ -62,6 +76,7 @@ func DecodeResponse(msg *dns.Msg) *DecodedResponse {
|
||||
Authority: msg.Ns,
|
||||
Additional: msg.Extra,
|
||||
CNAMEChain: extractCNAMEChain(msg),
|
||||
DNAMEMappings: extractDNAMEMappings(msg),
|
||||
}
|
||||
|
||||
d.Classification = classify(msg)
|
||||
@@ -75,6 +90,10 @@ func classify(msg *dns.Msg) ResponseClassification {
|
||||
return ResponseNXDOMAIN
|
||||
case dns.RcodeServerFailure:
|
||||
return ResponseSERVFAIL
|
||||
case dns.RcodeRefused:
|
||||
return ResponseREFUSED
|
||||
case dns.RcodeNotImplemented:
|
||||
return ResponseNOTIMPL
|
||||
case dns.RcodeSuccess:
|
||||
return classifySuccess(msg)
|
||||
default:
|
||||
@@ -125,6 +144,33 @@ func extractCNAMEChain(msg *dns.Msg) []string {
|
||||
return chain
|
||||
}
|
||||
|
||||
func extractDNAMEMappings(msg *dns.Msg) []DNAMEMapping {
|
||||
var mappings []DNAMEMapping
|
||||
for _, rr := range msg.Answer {
|
||||
if dname, ok := rr.(*dns.DNAME); ok {
|
||||
mappings = append(mappings, DNAMEMapping{
|
||||
Owner: dns.Fqdn(dname.Hdr.Name),
|
||||
Target: dns.Fqdn(dname.Target),
|
||||
})
|
||||
}
|
||||
}
|
||||
return mappings
|
||||
}
|
||||
|
||||
// SynthesizeCNAMEFromDNAME computes the CNAME target for queryName given a DNAME mapping.
|
||||
// Returns empty string if queryName is not a strict subdomain of dnameOwner.
|
||||
func SynthesizeCNAMEFromDNAME(queryName, dnameOwner, dnameTarget string) string {
|
||||
q := strings.ToLower(dns.Fqdn(queryName))
|
||||
owner := strings.ToLower(dns.Fqdn(dnameOwner))
|
||||
target := strings.ToLower(dns.Fqdn(dnameTarget))
|
||||
|
||||
if !dns.IsSubDomain(owner, q) || q == owner {
|
||||
return ""
|
||||
}
|
||||
prefix := strings.TrimSuffix(q, owner)
|
||||
return prefix + target
|
||||
}
|
||||
|
||||
func IsTruncated(msg *dns.Msg) bool {
|
||||
return msg != nil && msg.Truncated
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user