diff --git a/internal/output/stats.go b/internal/output/stats.go index a6b0456..b7c1aa2 100644 --- a/internal/output/stats.go +++ b/internal/output/stats.go @@ -144,6 +144,8 @@ func summaryTypeLabel(respType string) string { return "query type not implemented by server" case "cname_loop": return "resulted in a CNAME loop" + case "ns_error": + return "nameserver lookup failed" case "error": return "resulted in an error" case "referral": diff --git a/internal/output/stats_test.go b/internal/output/stats_test.go index 90f9875..25ca90a 100644 --- a/internal/output/stats_test.go +++ b/internal/output/stats_test.go @@ -88,6 +88,7 @@ func TestSummaryTypeLabelAllTypes(t *testing.T) { "refused": "query refused by server", "notimp": "query type not implemented by server", "cname_loop": "resulted in a CNAME loop", + "ns_error": "nameserver lookup failed", "error": "resulted in an error", "referral": "resulted in a referral", "unknown_type": "unknown_type", diff --git a/internal/output/text.go b/internal/output/text.go index dd43c0c..53b657a 100644 --- a/internal/output/text.go +++ b/internal/output/text.go @@ -220,6 +220,12 @@ func (f *textFormatter) formatResultLine(result traverse.TraversalResult) string msg = result.Response.ErrorMessage } return f.colorize(fmt.Sprintf("%s %s", prob, msg), colorRed) + case traverse.RespNSResolutionFailed: + msg := "nameserver lookup failed" + if result.Response.ErrorMessage != "" { + msg = result.Response.ErrorMessage + } + return f.colorize(fmt.Sprintf("%s %s", prob, msg), colorYellow) case traverse.RespError: msg := "resulted in an error" if result.Response.ErrorMessage != "" { diff --git a/internal/output/text_test.go b/internal/output/text_test.go index 6cc95e5..d760599 100644 --- a/internal/output/text_test.go +++ b/internal/output/text_test.go @@ -121,6 +121,7 @@ Answers: []miekgdns.RR{ {"refused", traverse.RespREFUSED, nil, ""}, {"notimp", traverse.RespNOTIMPL, nil, ""}, {"cname_loop", traverse.RespCNAMELoop, nil, "loop detected"}, +{"ns_error", traverse.RespNSResolutionFailed, nil, "nameserver ns1.example.com could not be resolved"}, {"error", traverse.RespError, nil, "something went wrong"}, } diff --git a/internal/traverse/response.go b/internal/traverse/response.go index 81620f4..6803777 100644 --- a/internal/traverse/response.go +++ b/internal/traverse/response.go @@ -20,6 +20,11 @@ const ( RespNOTIMPL RespCNAMELoop RespError + // RespNSResolutionFailed indicates that the traversal could not resolve the + // IP address of an in-bailiwick nameserver. The domain may still be + // reachable in practice (e.g. via glue records held by the registry), but + // the iterative traversal could not complete that path. + RespNSResolutionFailed ) func (rt ResponseType) String() string { @@ -44,6 +49,8 @@ func (rt ResponseType) String() string { return "cname_loop" case RespError: return "error" + case RespNSResolutionFailed: + return "ns_error" default: return "unknown" } @@ -231,7 +238,7 @@ func (r *Response) resolveGlue(child *Referral) { func (r *Response) IsTerminal() bool { switch r.Type { - case RespAnswer, RespNODATA, RespNXDOMAIN, RespSERVFAIL, RespREFUSED, RespNOTIMPL, RespCNAMELoop, RespError: + case RespAnswer, RespNODATA, RespNXDOMAIN, RespSERVFAIL, RespREFUSED, RespNOTIMPL, RespCNAMELoop, RespError, RespNSResolutionFailed: return true default: return false diff --git a/internal/traverse/response_test.go b/internal/traverse/response_test.go index efdcc69..a7bfac1 100644 --- a/internal/traverse/response_test.go +++ b/internal/traverse/response_test.go @@ -246,6 +246,7 @@ func TestResponseIsTerminal(t *testing.T) { {RespNXDOMAIN, true}, {RespSERVFAIL, true}, {RespError, true}, + {RespNSResolutionFailed, true}, {RespReferral, false}, {RespCNAMEFollow, false}, } @@ -272,6 +273,7 @@ func TestResponseTypeString(t *testing.T) { {RespNXDOMAIN, "nxdomain"}, {RespSERVFAIL, "servfail"}, {RespError, "error"}, + {RespNSResolutionFailed, "ns_error"}, } for _, tt := range tests { diff --git a/internal/traverse/traverser.go b/internal/traverse/traverser.go index b6baea2..e139db1 100644 --- a/internal/traverse/traverser.go +++ b/internal/traverse/traverser.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "strings" "sync" "time" @@ -231,10 +232,15 @@ func (t *Traverser) processReferral(ctx context.Context, ref *Referral, cache *I ref.State = StateResolved } else { addrs, err := t.ResolveNS(ctx, ref.Name, cache, visitedCopy, t.depth) + nsName := strings.TrimSuffix(ref.Bailiwick, ".") + if nsName == "" || nsName == "." { + nsName = strings.TrimSuffix(ref.Name, ".") + } if err != nil { return &Response{ - Referral: ref, - Type: RespError, + Referral: ref, + Type: RespNSResolutionFailed, + ErrorMessage: fmt.Sprintf("nameserver %s could not be resolved", nsName), } } if len(addrs) > 0 { @@ -242,8 +248,9 @@ func (t *Traverser) processReferral(ctx context.Context, ref *Referral, cache *I ref.State = StateResolved } else { return &Response{ - Referral: ref, - Type: RespError, + Referral: ref, + Type: RespNSResolutionFailed, + ErrorMessage: fmt.Sprintf("nameserver %s could not be resolved", nsName), } } } @@ -360,7 +367,7 @@ func (t *Traverser) ResolveNS(ctx context.Context, nsName string, cache *InfoCac break } - if resp.Type == RespSERVFAIL || resp.Type == RespError { + if resp.Type == RespSERVFAIL || resp.Type == RespError || resp.Type == RespNSResolutionFailed { lastErr = fmt.Errorf("server error resolving %s: %s", nsName, resp.Type) continue }