@@ -24,6 +24,19 @@ const (
2424 colorWhite = "\033 [37m"
2525)
2626
27+ var stepDescriptions = map [string ]string {
28+ "ping" : "Testing ICMP reachability of resolvers" ,
29+ "resolve" : "Checking if resolvers can resolve standard domains" ,
30+ "nxdomain" : "Detecting DNS hijacking on non-existent domains" ,
31+ "edns" : "Testing EDNS0 support and buffer sizes" ,
32+ "resolve/tunnel" : "Verifying resolvers forward queries to your tunnel domain" ,
33+ "e2e/dnstt" : "Full tunnel connectivity test via DNSTT" ,
34+ "e2e/slipstream" : "Full tunnel connectivity test via Slipstream" ,
35+ "doh/resolve" : "Checking DoH resolver connectivity" ,
36+ "doh/resolve/tunnel" : "Verifying DoH resolvers forward to your tunnel domain" ,
37+ "doh/e2e" : "Full DoH tunnel connectivity test via DNSTT" ,
38+ }
39+
2740var scanCmd = & cobra.Command {
2841 Use : "scan" ,
2942 Short : "Full scan pipeline: ping -> resolve -> nxdomain -> tunnel -> e2e" ,
@@ -168,19 +181,20 @@ func runScan(cmd *cobra.Command, args []string) error {
168181 }
169182
170183 printBanner (len (ips ), dohMode , domain , steps )
184+ printPreFlight (len (ips ), domain , dnsttBin , slipstreamBin , steps )
171185
172186 ctx , stop := signal .NotifyContext (context .Background (), os .Interrupt )
173187 defer stop ()
174188
175189 scanStart := time .Now ()
176- report := scanner .RunChainQuietCtx (ctx , ips , workers , steps , newProgressFactoryWithTotal (len (steps )))
190+ report := scanner .RunChainQuietCtx (ctx , ips , workers , steps , newScanProgressFactory (len (steps ), stepDescriptions ))
177191 totalTime := time .Since (scanStart )
178192
179193 if ctx .Err () != nil {
180194 fmt .Fprintf (os .Stderr , "\n \n %s⚠ Interrupted — saving partial results to %s%s\n " , colorYellow , outputFile , colorReset )
181195 }
182196
183- printSummary (report , topN , totalTime )
197+ printSummary (report , topN , totalTime , domain )
184198
185199 return scanner .WriteChainReport (report , outputFile )
186200}
@@ -196,6 +210,29 @@ func hline(left, fill, right string, width int) string {
196210 return left + strings .Repeat (fill , width ) + right
197211}
198212
213+ func printPreFlight (ipCount int , domain , dnsttBin , slipstreamBin string , steps []scanner.Step ) {
214+ if ! isTTY () {
215+ return
216+ }
217+ w := os .Stderr
218+ fmt .Fprintf (w , " %sPre-flight:%s\n " , colorBold , colorReset )
219+ fmt .Fprintf (w , " %s\u2714 %s %d resolvers loaded\n " , colorGreen , colorReset , ipCount )
220+ fmt .Fprintf (w , " %s\u2714 %s %d workers\n " , colorGreen , colorReset , workers )
221+ fmt .Fprintf (w , " %s\u2714 %s %d scan steps configured\n " , colorGreen , colorReset , len (steps ))
222+ if dnsttBin != "" {
223+ fmt .Fprintf (w , " %s\u2714 %s dnstt-client: %s%s%s\n " , colorGreen , colorReset , colorDim , dnsttBin , colorReset )
224+ }
225+ if slipstreamBin != "" {
226+ fmt .Fprintf (w , " %s\u2714 %s slipstream-client: %s%s%s\n " , colorGreen , colorReset , colorDim , slipstreamBin , colorReset )
227+ }
228+ if domain != "" {
229+ fmt .Fprintf (w , " %s\u26a0 %s Domain: %s%s%s — %sverify NS delegation before scanning%s\n " ,
230+ colorYellow , colorReset , colorCyan , domain , colorReset , colorDim , colorReset )
231+ fmt .Fprintf (w , " %snslookup -type=NS %s 8.8.8.8%s\n " , colorDim , domain , colorReset )
232+ }
233+ fmt .Fprintf (w , "\n " )
234+ }
235+
199236func printBanner (count int , doh bool , domain string , steps []scanner.Step ) {
200237 mode := "UDP"
201238 if doh {
@@ -232,7 +269,7 @@ func printBanner(count int, doh bool, domain string, steps []scanner.Step) {
232269 fmt .Fprintf (w , "\n \n " )
233270}
234271
235- func printSummary (report scanner.ChainReport , topN int , totalTime time.Duration ) {
272+ func printSummary (report scanner.ChainReport , topN int , totalTime time.Duration , domain string ) {
236273 w := os .Stderr
237274
238275 fmt .Fprintf (w , "\n " )
@@ -295,6 +332,7 @@ func printSummary(report scanner.ChainReport, topN int, totalTime time.Duration)
295332 break // Only show hint for the first failing step
296333 }
297334 }
335+ fmt .Fprintf (w , "\n %sSee full guide: https://github.com/SamNet-dev/findns/blob/main/GUIDE.md%s\n " , colorDim , colorReset )
298336 fmt .Fprintln (w )
299337 return
300338 }
@@ -346,6 +384,28 @@ func printSummary(report scanner.ChainReport, topN int, totalTime time.Duration)
346384 fmt .Fprintf (w , " %s... and %d more in %s%s\n " , colorDim , len (report .Passed )- limit , outputFile , colorReset )
347385 }
348386
387+ // Next steps guidance
388+ fmt .Fprintf (w , "\n %sNext steps:%s\n " , colorBold , colorReset )
389+ fmt .Fprintf (w , " %s\u2022 %s Results saved to %s%s%s\n " , colorDim , colorReset , colorCyan , outputFile , colorReset )
390+ if domain != "" && len (report .Passed ) > 0 {
391+ topIP := report .Passed [0 ].IP
392+ fmt .Fprintf (w , " %s\u2022 %s Test top resolver: %snslookup %s %s%s\n " ,
393+ colorDim , colorReset , colorDim , domain , topIP , colorReset )
394+ }
395+ // Check if e2e was in pipeline
396+ hasE2E := false
397+ hasTunnel := false
398+ for _ , s := range report .Steps {
399+ if strings .Contains (s .Name , "e2e" ) {
400+ hasE2E = true
401+ }
402+ if strings .Contains (s .Name , "tunnel" ) {
403+ hasTunnel = true
404+ }
405+ }
406+ if hasTunnel && ! hasE2E && len (report .Passed ) > 0 {
407+ fmt .Fprintf (w , " %s\u2022 %s Run with --pubkey to test full tunnel connectivity (e2e)\n " , colorDim , colorReset )
408+ }
349409 fmt .Fprintln (w )
350410}
351411
0 commit comments