output: distinguish nameserver resolution failures from domain errors

When an in-bailiwick nameserver cannot be resolved (no glue records and
iterative resolution fails), the traversal previously returned a generic
RespError. This made the Results section show many identical '6.7%
resulted in an error' lines and the Summary show '100% resulted in an
error' — even though the domain itself may be perfectly reachable in
practice via glue held by the registry.

Add a new RespNSResolutionFailed response type (string: ns_error)
for this specific case. Changes:

- traverse/response.go: add RespNSResolutionFailed, include it in
  IsTerminal() and String()
- traverse/traverser.go: use RespNSResolutionFailed with a descriptive
  ErrorMessage when ResolveNS fails in processReferral
- output/stats.go: map ns_error to nameserver lookup failed in
  summaryTypeLabel
- output/text.go: handle RespNSResolutionFailed in formatResultLine

Before:
  Results:     6.7% resulted in an error (x15)
  Summary:   100% resulted in an error

After:
  Results:     6.7% nameserver ns-a.hansenits.com.au could not be resolved (x15)
  Summary:   100% nameserver lookup failed

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 15:43:47 +10:00
co-authored by Copilot multica-agent
parent d6063eb636
commit beefaf44e0
7 changed files with 35 additions and 5 deletions
+15 -4
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net"
"strings"
"sync"
"time"
@@ -232,18 +233,28 @@ func (t *Traverser) processReferral(ctx context.Context, ref *Referral, cache *I
} else {
addrs, err := t.ResolveNS(ctx, ref.Name, cache, visitedCopy, t.depth)
if err != nil {
nsName := strings.TrimSuffix(ref.Bailiwick, ".")
if nsName == "" || nsName == "." {
nsName = strings.TrimSuffix(ref.Name, ".")
}
return &Response{
Referral: ref,
Type: RespError,
Referral: ref,
Type: RespNSResolutionFailed,
ErrorMessage: fmt.Sprintf("nameserver %s could not be resolved", nsName),
}
}
if len(addrs) > 0 {
ref.Addresses = addrs
ref.State = StateResolved
} else {
nsName := strings.TrimSuffix(ref.Bailiwick, ".")
if nsName == "" || nsName == "." {
nsName = strings.TrimSuffix(ref.Name, ".")
}
return &Response{
Referral: ref,
Type: RespError,
Referral: ref,
Type: RespNSResolutionFailed,
ErrorMessage: fmt.Sprintf("nameserver %s could not be resolved", nsName),
}
}
}