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 ¶
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.
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 ¶
IPResult is one IP probe's outcome.
func ProbeIP ¶
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.
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 )
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.