Phase 4.1: Error handling, edge cases, and robustness
CI / test (pull_request) Failing after 3m40s

- 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:
Gary Hansen
2026-06-08 03:11:46 +10:00
co-authored by Copilot multica-agent
parent 76f5010a5e
commit 368f200d23
9 changed files with 670 additions and 14 deletions
+6
View File
@@ -138,6 +138,12 @@ func summaryTypeLabel(respType string) string {
return "name does not exist"
case "servfail":
return "resulted in SERVFAIL"
case "refused":
return "query refused by server"
case "notimp":
return "query type not implemented by server"
case "cname_loop":
return "resulted in a CNAME loop"
case "error":
return "resulted in an error"
case "referral":
+15 -1
View File
@@ -210,8 +210,22 @@ func (f *textFormatter) formatResultLine(result traverse.TraversalResult) string
return f.colorize(fmt.Sprintf("%s name does not exist", prob), colorYellow)
case traverse.RespSERVFAIL:
return f.colorize(fmt.Sprintf("%s resulted in SERVFAIL", prob), colorRed)
case traverse.RespREFUSED:
return f.colorize(fmt.Sprintf("%s query refused by server", prob), colorRed)
case traverse.RespNOTIMPL:
return f.colorize(fmt.Sprintf("%s query type not implemented by server", prob), colorRed)
case traverse.RespCNAMELoop:
msg := "CNAME loop detected"
if result.Response.ErrorMessage != "" {
msg = result.Response.ErrorMessage
}
return f.colorize(fmt.Sprintf("%s %s", prob, msg), colorRed)
case traverse.RespError:
return f.colorize(fmt.Sprintf("%s resulted in an error", prob), colorRed)
msg := "resulted in an error"
if result.Response.ErrorMessage != "" {
msg = result.Response.ErrorMessage
}
return f.colorize(fmt.Sprintf("%s %s", prob, msg), colorRed)
default:
return fmt.Sprintf("%s %s", prob, result.Response.Type)
}