scanner

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 18, 2026 License: GPL-3.0 Imports: 12 Imported by: 0

Documentation

Overview

Package scanner probes candidate SNIs and CDN IPs to discover which combination works on the user's current network. This directly addresses the #1 user request upstream (patterniha/SNI-Spoofing Issue #8): "please tell me which SNIs work on my ISP".

Design rationale:

  • We do NOT use the bypass engine during scanning. We send real TLS ClientHellos directly to a target IP and observe outcomes. This faithfully reflects what the user's ISP+DPI would do to a normal TLS connection using that SNI.
  • Results are classified into discrete Outcomes so the caller can rank and act on them without parsing ad-hoc error strings.

Index

Constants

This section is empty.

Variables

View Source
var DefaultCloudflareIPs = []string{
	"1.1.1.1",
	"1.0.0.1",
	"104.16.132.229",
	"104.17.2.81",
	"104.18.24.17",
	"104.19.90.17",
	"104.20.64.1",
	"104.21.2.17",
	"104.22.38.200",
	"104.26.10.39",
	"172.64.41.3",
	"172.65.250.78",
	"172.66.40.17",
	"172.67.160.80",
	"188.114.96.7",
	"188.114.97.7",
	"188.114.98.7",
	"188.114.99.7",
	"162.159.140.229",
	"162.159.61.3",
}

DefaultCloudflareIPs is a small set of Cloudflare anycast IPs that actually serve TLS on :443 (verified handshakes). We avoid the .0 of each /24 because those are network-address placeholders, not edge servers.

These IPs were picked from:

  • 1.1.1.1 / 1.0.0.1 (Cloudflare DNS, universally reachable)
  • Sampled /32s from Cloudflare's public /12 ranges (cloudflare.com/ips)

Expanded lists belong elsewhere so they can be refreshed without touching the scanner.

View Source
var DefaultSNICandidates = []string{

	"auth.vercel.com",
	"cdn.segment.io",
	"static.cloudflareinsights.com",
	"ajax.cloudflare.com",
	"challenges.cloudflare.com",
	"cf-assets.www.cloudflare.com",
	"www.cloudflare.com",
	"developers.cloudflare.com",

	"api.github.com",
	"raw.githubusercontent.com",
	"gist.github.com",
	"github.githubassets.com",
	"objects.githubusercontent.com",
	"api.stripe.com",
	"js.stripe.com",
	"checkout.stripe.com",
	"api.notion.com",
	"www.notion.so",
	"api.linear.app",
	"cdn.linear.app",
	"api.figma.com",
	"www.figma.com",
	"cdn.sanity.io",
	"www.sanity.io",
	"api.openai.com",
	"chat.openai.com",
	"api.anthropic.com",

	"cdn.jsdelivr.net",
	"unpkg.com",
	"cdnjs.cloudflare.com",
	"fonts.googleapis.com",
	"fonts.gstatic.com",

	"vercel.app",
	"nextjs.org",
	"netlify.app",
	"app.netlify.com",
	"fly.io",
	"render.com",
	"railway.app",
	"pages.dev",
	"workers.dev",

	"plausible.io",
	"app.posthog.com",
	"eu.posthog.com",
	"api.amplitude.com",
	"api.mixpanel.com",
	"cdn.amplitude.com",

	"developer.mozilla.org",
	"stackoverflow.com",
	"www.npmjs.com",
	"registry.npmjs.org",
	"pypi.org",
	"files.pythonhosted.org",

	"www.cloudflare.tv",
	"stream.cloudflare.com",
}

DefaultSNICandidates is a curated list of domain names that are:

  • Hosted on Cloudflare or other major CDNs that the bypass targets
  • Unlikely to be on any national blocklist (benign SaaS / tooling)
  • Stable over time (core infrastructure, not trending domains)

The scanner probes each against the user's chosen target IP. SNIs that work populate a profile's sni_pool for bypass rotation.

Keep this list small (~60 entries). A large list makes scans slow without improving the result: the top-ranked handful is what goes into the pool.

Functions

This section is empty.

Types

type IPResult

type IPResult struct {
	IP        netip.Addr
	Port      uint16
	Reachable bool
	RTT       time.Duration
	Err       string
}

IPResult is one IP probe's outcome.

func ProbeIP

func ProbeIP(ctx context.Context, ip netip.Addr, port uint16, timeout time.Duration) IPResult

ProbeIP dials ip:port with cfg.ConnectTimeout and measures RTT. This is a plain TCP connect; no TLS. Returns Reachable=true only on successful three-way handshake.

func ProbeIPs

func ProbeIPs(ctx context.Context, ips []string, port uint16, timeout time.Duration,
	concurrency int, onResult func(IPResult)) []IPResult

ProbeIPs runs ProbeIP concurrently. Output order matches input.

func RankIPs

func RankIPs(in []IPResult) []IPResult

RankIPs sorts reachable IPs by RTT ascending. Unreachable are dropped.

type Outcome

type Outcome int

Outcome classifies what happened when we tried to open a TLS connection with a given SNI. The ordering encodes "worst to best" for sorting.

const (
	// OutcomeUnknown is the zero value; only seen when a probe never ran.
	OutcomeUnknown Outcome = iota
	// OutcomeDPIReset: TCP RST before or during TLS handshake, typical
	// DPI-based SNI block.
	OutcomeDPIReset
	// OutcomeTimeout: DPI silently dropped our ClientHello.
	OutcomeTimeout
	// OutcomeTCPRefused: TCP couldn't even connect — address/route problem.
	OutcomeTCPRefused
	// OutcomeTLSError: TLS handshake failed for a non-network reason
	// (bad_certificate, unknown_ca, unrecognized_name). Target-side reject.
	OutcomeTLSError
	// OutcomeOK: TLS handshake completed.
	OutcomeOK
)

func (Outcome) String

func (o Outcome) String() string

String returns a short label for reports.

type ProbeConfig

type ProbeConfig struct {
	// TargetIP is the IP we connect to. If zero, the scanner resolves each
	// SNI via DNS instead (useful for testing SNI against its own host).
	TargetIP netip.Addr
	// TargetPort is typically 443.
	TargetPort uint16
	// ConnectTimeout bounds the TCP connect phase.
	ConnectTimeout time.Duration
	// HandshakeTimeout bounds the TLS handshake from TCP accept onwards.
	HandshakeTimeout time.Duration
	// Concurrency is the max in-flight probes. <=0 means 16.
	Concurrency int
}

ProbeConfig tunes a single scan.

type Result

type Result struct {
	SNI       string
	TargetIP  netip.Addr
	Outcome   Outcome
	RTT       time.Duration // TCP connect time
	Handshake time.Duration // TCP + TLS time (zero on non-OK)
	Err       string        // human-readable error, "" on success
}

Result is a single SNI probe's output.

func ProbeSNI

func ProbeSNI(ctx context.Context, sni string, cfg ProbeConfig) Result

ProbeSNI runs a single TLS handshake attempt against cfg.TargetIP using sni as the SNI. Returns a classified Result; Err is always non-empty for non-OK outcomes and always empty for OK.

func ProbeSNIs

func ProbeSNIs(ctx context.Context, snis []string, cfg ProbeConfig, onResult func(Result)) []Result

ProbeSNIs runs ProbeSNI for every sni in the slice concurrently bounded by cfg.Concurrency. Results are returned in the same order as input.

func RankSNIs

func RankSNIs(in []Result) []Result

RankSNIs keeps only OK outcomes and sorts by handshake time ascending.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL