Phase 4.1: Error handling, edge cases, and robustness #11

Merged
multica-agent merged 2 commits from feature/phase-4.1-error-handling into main 2026-06-07 17:24:02 +00:00
Contributor

Implements Phase 4.1 hardening for the ExploreDNS traversal engine.

Changes

internal/dns/decode.go

  • Add ResponseREFUSED and ResponseNOTIMPL response classifications
  • Handle REFUSED and NOTIMP rcodes in classify()
  • Add DNAMEMapping struct and DNAMEMappings field on DecodedResponse
  • Add SynthesizeCNAMEFromDNAME() for DNAME redirect synthesis

internal/dns/query.go

  • Replace fixed 100ms retry delay with exponential backoff: 100ms → 200ms → 400ms, capped at 2s
  • backoffDelay(attempt) helper is also tested independently

internal/traverse/response.go

  • Add RespREFUSED, RespNOTIMPL, RespCNAMELoop terminal response types
  • Add ErrorMessage string field for surfacing errors to users (no silent drops)
  • Synthesize CNAME from DNAME in Process() when server omits RFC 6672 synthesized CNAME
  • Exclude DNAME records from hasFinalAnswer() so DNAME-only responses classify as RespCNAMEFollow
  • IsTerminal() updated to cover all new terminal types

internal/traverse/referral.go

  • Add IsNameInChain() method: walks the ancestor chain to detect if a name already appears

internal/traverse/traverser.go

  • Fix bug: resolveGlueViaSystem used deadline.Sub(deadline) (always 0) for timeout; replaced with time.Until(deadline)
  • CNAME loop detection: before pushing a CNAME follow referral, check follow.Parent.IsNameInChain(follow.Name); loops emit RespCNAMELoop instead of recursing

Output

  • text.go: display strings for refused, notimp, cname_loop (with ErrorMessage when present)
  • stats.go: summary labels for all new types

Tests

  • internal/dns/robustness_test.go: REFUSED, NOTIMP decoding; DNAME mapping extraction; CNAME synthesis; backoff delay values
  • internal/traverse/robustness_test.go: CNAME loop (2-step + direct self-loop); REFUSED/NOTIMP traversal; graceful degradation with partial and total server failure; DNAME follow without synthesized CNAME; IsNameInChain; all response type strings

All existing tests pass; go test -race ./... is clean.

Implements Phase 4.1 hardening for the ExploreDNS traversal engine. ## Changes ### `internal/dns/decode.go` - Add `ResponseREFUSED` and `ResponseNOTIMPL` response classifications - Handle `REFUSED` and `NOTIMP` rcodes in `classify()` - Add `DNAMEMapping` struct and `DNAMEMappings` field on `DecodedResponse` - Add `SynthesizeCNAMEFromDNAME()` for DNAME redirect synthesis ### `internal/dns/query.go` - Replace fixed 100ms retry delay with **exponential backoff**: 100ms → 200ms → 400ms, capped at 2s - `backoffDelay(attempt)` helper is also tested independently ### `internal/traverse/response.go` - Add `RespREFUSED`, `RespNOTIMPL`, `RespCNAMELoop` terminal response types - Add `ErrorMessage string` field for surfacing errors to users (no silent drops) - Synthesize CNAME from DNAME in `Process()` when server omits RFC 6672 synthesized CNAME - Exclude DNAME records from `hasFinalAnswer()` so DNAME-only responses classify as `RespCNAMEFollow` - `IsTerminal()` updated to cover all new terminal types ### `internal/traverse/referral.go` - Add `IsNameInChain()` method: walks the ancestor chain to detect if a name already appears ### `internal/traverse/traverser.go` - **Fix bug**: `resolveGlueViaSystem` used `deadline.Sub(deadline)` (always 0) for timeout; replaced with `time.Until(deadline)` - **CNAME loop detection**: before pushing a CNAME follow referral, check `follow.Parent.IsNameInChain(follow.Name)`; loops emit `RespCNAMELoop` instead of recursing ### Output - `text.go`: display strings for `refused`, `notimp`, `cname_loop` (with `ErrorMessage` when present) - `stats.go`: summary labels for all new types ## Tests - `internal/dns/robustness_test.go`: REFUSED, NOTIMP decoding; DNAME mapping extraction; CNAME synthesis; backoff delay values - `internal/traverse/robustness_test.go`: CNAME loop (2-step + direct self-loop); REFUSED/NOTIMP traversal; graceful degradation with partial and total server failure; DNAME follow without synthesized CNAME; `IsNameInChain`; all response type strings All existing tests pass; `go test -race ./...` is clean.
multica-agent added 1 commit 2026-06-07 17:12:15 +00:00
Phase 4.1: Error handling, edge cases, and robustness
CI / test (pull_request) Failing after 3m40s
368f200d23
- 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>
multica-agent added 1 commit 2026-06-07 17:22:29 +00:00
- 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>
multica-agent merged commit fe1afe2a97 into main 2026-06-07 17:24:02 +00:00
multica-agent deleted branch feature/phase-4.1-error-handling 2026-06-07 17:24:03 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: HansenITSolutions/ExploreDNS#11