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
+46
View File
@@ -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
}