(g *globalFlags)
| 31 | } |
| 32 | |
| 33 | func newScanSNICmd(g *globalFlags) *cobra.Command { |
| 34 | var ( |
| 35 | targetIP string |
| 36 | port uint16 |
| 37 | conc int |
| 38 | jsonOut bool |
| 39 | topN int |
| 40 | timeout time.Duration |
| 41 | limit int |
| 42 | ) |
| 43 | cmd := &cobra.Command{ |
| 44 | Use: "sni", |
| 45 | Short: "Probe candidate SNIs against a target IP", |
| 46 | Long: "Probes a curated list of Cloudflare-hosted SNIs by sending a real TLS\n" + |
| 47 | "ClientHello and observing whether the handshake succeeds, is RST'd, or\n" + |
| 48 | "times out. Ranks by TLS handshake time. Use the top results to populate\n" + |
| 49 | "your profile's sni_pool.", |
| 50 | RunE: func(cmd *cobra.Command, args []string) error { |
| 51 | ctx, cancel := installSignalCancel(cmd.Context()) |
| 52 | defer cancel() |
| 53 | |
| 54 | ip, err := netip.ParseAddr(targetIP) |
| 55 | if err != nil { |
| 56 | return fmt.Errorf("invalid --target %q: %w", targetIP, err) |
| 57 | } |
| 58 | |
| 59 | snis := scanner.DefaultSNICandidates |
| 60 | if limit > 0 && limit < len(snis) { |
| 61 | snis = snis[:limit] |
| 62 | } |
| 63 | |
| 64 | fmt.Fprintf(cmd.OutOrStdout(), |
| 65 | "snix scan sni: probing %d candidates against %s:%d (conc=%d, timeout=%s)\n", |
| 66 | len(snis), ip, port, conc, timeout) |
| 67 | |
| 68 | var done atomic.Int32 |
| 69 | total := len(snis) |
| 70 | start := time.Now() |
| 71 | onResult := func(r scanner.Result) { |
| 72 | n := done.Add(1) |
| 73 | var mark string |
| 74 | switch r.Outcome { |
| 75 | case scanner.OutcomeOK: |
| 76 | mark = "✓" |
| 77 | case scanner.OutcomeDPIReset: |
| 78 | mark = "✗" |
| 79 | case scanner.OutcomeTimeout: |
| 80 | mark = "t" |
| 81 | default: |
| 82 | mark = "-" |
| 83 | } |
| 84 | if !jsonOut { |
| 85 | fmt.Fprintf(cmd.ErrOrStderr(), |
| 86 | "\r[%3d/%3d] %s %-40s %s %10s", |
| 87 | n, total, mark, r.SNI, r.Outcome, r.Handshake.Round(time.Millisecond)) |
| 88 | } |
| 89 | } |
| 90 |
no test coverage detected