- 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
@@ -16,6 +16,9 @@ const (
|
||||
RespNODATA
|
||||
RespNXDOMAIN
|
||||
RespSERVFAIL
|
||||
RespREFUSED
|
||||
RespNOTIMPL
|
||||
RespCNAMELoop
|
||||
RespError
|
||||
)
|
||||
|
||||
@@ -33,6 +36,12 @@ func (rt ResponseType) String() string {
|
||||
return "nxdomain"
|
||||
case RespSERVFAIL:
|
||||
return "servfail"
|
||||
case RespREFUSED:
|
||||
return "refused"
|
||||
case RespNOTIMPL:
|
||||
return "notimp"
|
||||
case RespCNAMELoop:
|
||||
return "cname_loop"
|
||||
case RespError:
|
||||
return "error"
|
||||
default:
|
||||
@@ -41,11 +50,12 @@ func (rt ResponseType) String() string {
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Referral *Referral
|
||||
Server net.IP
|
||||
Cache *InfoCache
|
||||
Decoded *dns.DecodedResponse
|
||||
Type ResponseType
|
||||
Referral *Referral
|
||||
Server net.IP
|
||||
Cache *InfoCache
|
||||
Decoded *dns.DecodedResponse
|
||||
Type ResponseType
|
||||
ErrorMessage string
|
||||
}
|
||||
|
||||
func NewResponse(ref *Referral, server net.IP, cache *InfoCache) *Response {
|
||||
@@ -59,15 +69,28 @@ func NewResponse(ref *Referral, server net.IP, cache *InfoCache) *Response {
|
||||
func (r *Response) Process(msg *miekgdns.Msg) *Response {
|
||||
if msg == nil {
|
||||
r.Type = RespError
|
||||
r.ErrorMessage = "nil DNS response"
|
||||
return r
|
||||
}
|
||||
|
||||
r.Decoded = dns.DecodeResponse(msg)
|
||||
if r.Decoded == nil {
|
||||
r.Type = RespError
|
||||
r.ErrorMessage = "failed to decode DNS response"
|
||||
return r
|
||||
}
|
||||
|
||||
// Synthesize CNAME from DNAME when the server didn't include a synthesized CNAME record.
|
||||
if len(r.Decoded.CNAMEChain) == 0 && r.Referral != nil && len(r.Decoded.DNAMEMappings) > 0 {
|
||||
for _, dm := range r.Decoded.DNAMEMappings {
|
||||
synthesized := dns.SynthesizeCNAMEFromDNAME(r.Referral.Name, dm.Owner, dm.Target)
|
||||
if synthesized != "" {
|
||||
r.Decoded.CNAMEChain = append(r.Decoded.CNAMEChain, synthesized)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.Type = r.classify()
|
||||
return r
|
||||
}
|
||||
@@ -78,6 +101,10 @@ func (r *Response) classify() ResponseType {
|
||||
return RespNXDOMAIN
|
||||
case dns.ResponseSERVFAIL:
|
||||
return RespSERVFAIL
|
||||
case dns.ResponseREFUSED:
|
||||
return RespREFUSED
|
||||
case dns.ResponseNOTIMPL:
|
||||
return RespNOTIMPL
|
||||
case dns.ResponseAnswer:
|
||||
if len(r.Decoded.CNAMEChain) > 0 && !r.hasFinalAnswer() {
|
||||
return RespCNAMEFollow
|
||||
@@ -97,6 +124,9 @@ func (r *Response) hasFinalAnswer() bool {
|
||||
if _, ok := rr.(*miekgdns.CNAME); ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := rr.(*miekgdns.DNAME); ok {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -200,7 +230,7 @@ func (r *Response) resolveGlue(child *Referral) {
|
||||
|
||||
func (r *Response) IsTerminal() bool {
|
||||
switch r.Type {
|
||||
case RespAnswer, RespNODATA, RespNXDOMAIN, RespSERVFAIL, RespError:
|
||||
case RespAnswer, RespNODATA, RespNXDOMAIN, RespSERVFAIL, RespREFUSED, RespNOTIMPL, RespCNAMELoop, RespError:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user