Phase 4.1: Error handling, edge cases, and robustness (#11)
CI / test (push) Failing after 2m36s

This commit was merged in pull request #11.
This commit is contained in:
2026-06-07 17:24:00 +00:00
parent 76f5010a5e
commit fe1afe2a97
12 changed files with 1118 additions and 22 deletions
+38 -7
View File
@@ -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
@@ -94,7 +121,11 @@ func (r *Response) classify() ResponseType {
func (r *Response) hasFinalAnswer() bool {
for _, rr := range r.Decoded.Answers {
if _, ok := rr.(*miekgdns.CNAME); ok {
switch rr.(type) {
case *miekgdns.CNAME, *miekgdns.DNAME, *miekgdns.RRSIG:
// CNAME and DNAME are redirect records, not final answers.
// RRSIG is a DNSSEC signature record — it covers the CNAME/DNAME
// but is not itself the answer to the original question type.
continue
}
return true
@@ -200,7 +231,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