☰
用SNI指纹揪出TSPU的DPI封锁:rkn-block-checker TLS握手探测实现原理
2026/10/11 11:46:17 网站建设 项目流程

【免费下载链接】rkn-block-checker

Diagnose RKN/TSPU internet blocks layer by layer (DNS, TCP, TLS, HTTP)

项目地址:https://gitcode.com/gh_mirrors/rk/rkn-block-checker
点击查看免费下载

当网站打不开时,你只知道"失败了",却不知道在哪一层失败。rkn-block-checker 是一个网络封锁诊断工具,它逐层探测 DNS → TCP → TLS → HTTP,专门用 TLS 握手中的 SNI 指纹来识别 TSPU 的 DPI(深度包检测)封锁。本文带你读懂它的 TLS 探测到底是怎么实现的,以及为什么"TCP 通、TLS 断"就是 DPI 的典型签名。

为什么"网站打不开"分不出封锁类型

不同封锁手段会留下完全不同的"指纹":

封锁方式发生位置典型表现
DNS 投毒解析层ISP 的 DNS 返回错误 IP 或拒绝解析
TCP 重置IP 层三次握手被 RST 掐断
TLS DPI on SNI加密层TCP 正常,握手刚开始就被掐断
HTTP 拦截页应用层返回 200 或 451,内容是运营商拦截页

其中TLS DPI on SNI 是现代 TSPU 的标志性手段:中间设备不会直接断掉 TCP 连接,而是先放你连上,读取 ClientHello 里的 SNI 扩展(明文携带的目标域名),一旦发现是被封锁的主机名,就立刻发 RST 或者干脆停止响应。

这意味着:你必须真的发起一次 TLS 握手,才能看到它。只测 TCP 永远发现不了这种封锁。

SNI 是什么:为什么它成了"指纹"

TLS 握手的第一步,客户端会发送一个ClientHello报文。由于 SNI(Server Name Indication)扩展在加密开始前就要发送,它实际上是明文可见的。TSPU 正是盯着这个字段做过滤——域名成了暴露你访问意图的"指纹"。

rkn-block-checker 的探测思路因此非常直接:

  1. 先做一次裸 TCP 连接,确认 443 端口可达;
  2. 紧接着用同一个目标主机名发起 TLS 握手,server_hostname参数会自动写进 SNI 扩展;
  3. 观察握手结果——完成,还是被 RST / 超时。

两步的对比结果就是诊断依据。

核心实现:check_tls 的 TLS 握手探测

整个探测逻辑位于 network.py 的check_tls函数中,核心流程如下:

ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE # 只关心握手能否完成,不校验证书 sock = _open_socket(host, port, timeout) with ctx.wrap_socket(sock, server_hostname=host) as ssock: ... # server_hostname 会被写入 SNI 扩展

实现上有几个值得注意的细节:

  • SNI 由server_hostname自动携带。调用ctx.wrap_socket(sock, server_hostname=host)时,目标域名会作为 SNI 扩展发送出去——这正是让 DPI 设备"看到"域名、从而触发封锁的关键一步。
  • 关闭证书校验(check_hostname = False、verify_mode = ssl.CERT_NONE)。诊断目的不是验证站点身份,而是确认握手能否走到完成,证书问题会干扰判断。
  • 异常即信号。握手过程中的每种失败都被归类为可判读的错误文本(见 check_tls):
异常归类含义提示
socket.timeouttimeout握手被静默丢弃,符合 DPI 过滤特征
ConnectionAbortedError/ConnectionResetErrorconnection reset during TLSClientHello 之后被 RST,典型 SNI DPI 签名
ssl.SSLErrorSSLError: ...握手层面的协议错误

同时用time.monotonic()记录握手耗时,成功时还会顺手提取证书 CN,写入结果供报告展示。

判定逻辑:TCP 通、TLS 断 = TLS_BLOCK

逐层编排发生在 core.py 的check_url中。对每个目标,流程严格串行:先check_tcp,TCP 成功后才执行check_tls(见 check_url):

  • TCP 失败→ 判TIMEOUT/TCP_RESET/DOWN,不再往下走;
  • TCP 成功但 TLS 被 RST→ 判TLS_BLOCK,置信度MEDIUM,备注写明"ClientHello 之后被重置,符合基于 SNI 的 DPI 过滤特征(典型 TSPU/RKN 签名),但不是确证";
  • TLS 成功→ 继续 HTTP 层,检查 451 状态码和拦截页标记。

所有判定值和置信度等级定义在 models.py 中。这里的设计哲学是宁可降一档:单个 TLS 重置信号无法完全排除服务器端故障,所以只给MEDIUM而非HIGH;对比之下,"系统 DNS 失败而 DoH 成功"这种两个独立信号互相印证的情况才给HIGH。

测试侧同样围绕这套信号展开:tests/test_network.py 通过 mock socket 分别模拟ConnectionAbortedError和超时,断言check_tls返回的ok is False且错误文本包含reset或等于timeout——保证判定逻辑不依赖真实网络。

输出长什么样:一眼认出 DPI 封锁

运行rkn-check后,被 SNI DPI 拦截的站点会呈现这样的特征(节选自 docs/sample-output.json 对应的报告):

Blacklist (RKN-restricted) name verdict TCP TLS PLT status -------------------------------------------------------------------- instagram ~ LIKELY TLS DPI 22ms - - - └ TLS reset right after ClientHello - consistent with SNI-based DPI twitter/x ~ LIKELY TLS DPI 24ms - - - └ TLS handshake silently dropped - consistent with DPI filtering

JSON 模式下(--json),指纹信息是机器可判读的:"tcp_ok": true配"tls_ok": false,再配"tls_error": "connection reset during TLS"——这就是 DPI-on-SNI 的完整签名。任何下游脚本都可以用这三个字段做二次分析。

上手三步:安装并验证你的连接

# 方式一:直接从 PyPI 安装 pip install rkn-block-checker # 方式二:从源码安装 git clone https://gitcode.com/gh_mirrors/rk/rkn-block-checker cd rkn-block-checker pip install -e .

然后只需一条命令:

rkn-check

工具会用内置的 ~21 个对照站点和 ~15 个受限站点做一轮完整扫描,给出每个站点的层级判定和总结论。只想验证单个站点时:

rkn-check --url https://example.com

一个小提示:工具默认发送通用的 Chrome User-Agent 以融入正常流量,避免在沿途日志里留下独特的工具指纹;如果你是探测自己掌控的基础设施,加上--identify会切换到自我标识的 UA。

总结:一张表看懂四层探测

层探测动作失败意味着
DNS系统解析 vs Cloudflare DoH 全地址集比对投毒 / 透明改写
TCP443 端口裸握手RST 注入(少见)
TLS带 SNI 的真实握手TSPU DPI 的典型签名
HTTP握手后 GET,查 451 与拦截页标记运营商拦截页

理解了这个原理,你就不难明白 rkn-block-checker 的价值所在:它不回答"能不能访问",而是回答"卡在哪一层、是什么手段"——而 SNI 指纹正是区分 DPI 封锁与一般网络故障的那把钥匙。

【免费下载链接】rkn-block-checker

Diagnose RKN/TSPU internet blocks layer by layer (DNS, TCP, TLS, HTTP)

项目地址:https://gitcode.com/gh_mirrors/rk/rkn-block-checker
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询