feat: complete Phase 4.1 error handling and robustness (HAN-385)
CI / test (pull_request) Failing after 2m41s

- Fast mode cache isolation: TraverserConfig.Fast=false gives each
  referral its own independent InfoCache with no cross-branch glue
  inheritance; Fast=true (default) retains the shared root cache so
  earlier branch discoveries are reused
- Wire cfg.Fast from CLI config into TraverserConfig in main.go
- IDN/Punycode: NewReferral now converts unicode domain labels to their
  ACE/punycode form via golang.org/x/net/idna before querying, with a
  graceful fallback when conversion fails
- DNSSEC: hasFinalAnswer() now skips RRSIG records alongside CNAME so
  a signed referral does not prevent CNAME following
- Tests: DNSSEC RRSIG does not block CNAME follow, fast/non-fast cache
  isolation, 12-NS referral, IDN conversion, wildcard answer, long CNAME
  chain depth limit, partial branch failure with graceful degradation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
Gary Hansen
2026-06-08 03:22:21 +10:00
co-authored by Copilot multica-agent
parent 368f200d23
commit 6e9aefc47e
7 changed files with 451 additions and 11 deletions
+26 -2
View File
@@ -8,6 +8,7 @@ import (
"github.com/hits/ExploreDNS/internal/dns"
miekgdns "github.com/miekg/dns"
"golang.org/x/net/idna"
)
type ResolutionState int
@@ -46,12 +47,35 @@ type Referral struct {
Prob float64
}
// idnaLookup is the IDN lookup profile used to convert internationalised domain
// names (unicode labels) to their ACE/punycode equivalents before querying.
var idnaLookup = idna.New(
idna.MapForLookup(),
idna.BidiRule(),
idna.StrictDomainName(false),
)
// toASCII converts a domain name that may contain unicode labels to its
// punycode (ACE) representation. Pure-ASCII names are returned unchanged.
// On conversion errors the original name is returned so the caller can still
// attempt a query (the server will reject it if truly invalid).
func toASCII(name string) string {
if name == "" || name == "." {
return name
}
ascii, err := idnaLookup.ToASCII(name)
if err != nil {
return name
}
return ascii
}
func NewReferral(name string, qtype uint16, bailiwick string, depth int, prob float64, parent *Referral) *Referral {
return &Referral{
Name: miekgdns.Fqdn(strings.ToLower(name)),
Name: miekgdns.Fqdn(strings.ToLower(toASCII(name))),
Qtype: qtype,
Qclass: miekgdns.ClassINET,
Bailiwick: miekgdns.Fqdn(strings.ToLower(bailiwick)),
Bailiwick: miekgdns.Fqdn(strings.ToLower(toASCII(bailiwick))),
Depth: depth,
Prob: prob,
Parent: parent,