记绕过上海电信限速一事

从 5 Mbps 到 410 Mbps:一次 FakeHTTP 分类信号失效的排障实录

这次故障最容易误判的地方,是所有组件看起来都正常:进程存在、NFQUEUE 有计数,日志也出现了伪造包发送记录,但真实 TCP 吞吐仍停留在约 5 Mbps。

最终修复依靠的不是反复重启,也不是盲目刷新 TLS 随机字段,而是重新建立可信的测试基线,逐层定位运营商分类节点,再从真实业务连接中提取新的 TLS ClientHello,完成严格的 A/B 验证。

本文记录的是我在自有实验环境中的排障过程。为了避免把当前分类样本变成可直接复制的公开配置,文中对业务目标与管理信息做了匿名化,只保留方法、证据与测量结果。

故障现象:UDP 正常,TCP 失效

路由器上同时运行着两套早期报文注入逻辑:

  • FakeSIP 处理 UDP,通过真实 DTLS/SNI 样本影响 UDP 五元组的分类。
  • FakeHTTP 处理 TCP,在握手早期注入真实 TLS ClientHello 样本。

故障出现时,UDP 路径仍能进入高速档,TCP 路径却不再提速。第一反应很容易是:FakeSIP 是否抢占了 FakeHTTP 的流量,或者两个程序是否共用了同一个 NFQUEUE?

现场检查很快排除了这个方向。两套服务使用不同的队列,FakeSIP 的 nftables 规则只匹配 UDP,FakeHTTP 的规则只匹配 TCP;各队列深度为 0,内核与用户态丢包也都是 0。临时收窄 FakeSIP 的 marked-packet 规则后,TCP 结果没有变化。

因此,UDP 服务工作正常与 TCP 服务失效只是同时发生,并不存在直接的队列争用关系。

第一条弯路:健康日志不等于破解成功

FakeHTTP 的日志能够看到类似下面的事件顺序:

client ===SYN===> server
client <===FAKE(*)=== server
client <===SYN-ACK=== server

这很容易让人产生一种错觉:既然 FAKE(*) 已经出现,payload 就已经被运营商识别。

实际上,它只能证明以下几件事:

  1. FakeHTTP 收到了需要处理的握手报文。
  2. 程序构造了带 payload 的 TCP 报文。
  3. 本机内核接受了 raw socket 的发送请求。

它不能证明报文真的经过了目标分类设备,不能证明分类设备接受了内容,更不能证明业务流进入了高速档。NFQUEUE 计数上涨和 drop 为 0,同样只是在证明处理链路健康。

这次排障中最重要的判断标准始终只有一个:同一条真实业务流在严格 A/B 条件下,实际传输了多少字节。

第二条弯路:拨号后的旧地址

最初的测速地址来自一份历史成功记录。问题在于,线路后来经历过多次重新拨号,委派前缀已经改变。旧地址上的超时既可能来自 payload 失效,也可能只是地址本身不再有效。

我重新从测试服务器读取当前地址,并检查实际监听进程。服务端本机读取 1 MiB 数据只需要约 0.02 秒,说明文件服务和本机转发没有性能问题。

这一步非常关键:如果测试对象本身已经失效,后续所有 payload、TTL 和队列实验都会得到看似合理但没有意义的结果。

重新确认后的测试拓扑如下:

外部验证机
    |
运营商分类节点
    |
OpenWrt 路由器
  - nftables early-handshake selector
  - NFQUEUE 512
  - FakeHTTP + captured ClientHello
    |
测试服务器

重新建立可信的 5 Mbps 基线

为了得到不受 FakeHTTP 影响的基线,我只让当前测试地址和 HTTPS 测试端口临时绕过生产队列,其他 TCP 流量保持原状。

外部验证机在 20 秒内收到 12,500,974 字节,平均约 625 KB/s,换算后正好接近 5 Mbps。这同时证明了三件事:

  • 当前地址能够从外部访问。
  • 测试服务器确实能够发送文件。
  • 这条路径的低速档可以稳定复现。

从这一刻开始,后面的每个实验都只改变一个变量,并使用同一个地址、同一个端口、同一种 Range 下载方式。

用 Hop Limit 定位分类节点

FakeHTTP 的基本思路是让分类设备看到伪造的 TLS 样本,同时让低 Hop Limit 报文在到达真实客户端前过期。历史配置使用的跳数在旧拓扑中有效,但线路和路由变化后,分类设备的位置也可能相对改变。

我先使用旧业务信号,只调整 Hop Limit:

注入信号 Hop Limit 结果
不注入 HTTP 正常,约 5 Mbps
旧业务信号 1 HTTP 正常,仍约 5 Mbps
旧业务信号 2 TLS 超时
新抓取的旧业务信号 2 TLS 超时
生成的候选模板 2 HTTP 正常,仍约 5 Mbps
真实的新信任域名 ClientHello 2 进入高速档

Hop Limit 为 1 时,报文没有到达分类节点,因此连接保持正常,但仍处于 5 Mbps 档位。Hop Limit 为 2 时,旧信号开始改变上游行为,却没有进入高速档。

外部验证机上的抓包进一步排除了“伪造 TLS 数据到达客户端并污染连接”的猜测:客户端只看到了真实 SYN-ACK,随后发出了自己的 ClientHello;服务端也确认收到了这段真实数据,但后续 TLS 数据没有正常到达客户端。

这说明 Hop Limit 为 2 已经足以触达当前分类位置,问题在于分类信号本身,而不是 FakeHTTP 没有发包。

旧信号失效,而不是随机字段过期

历史 payload 来自一个真实的外层 TLS 连接。TLS ClientHello 中包含随机数、会话 ID、key share 和 binder 等每次连接都会变化的字段。因此,一个自然的假设是:旧 payload 只是“太旧了”,重新抓取相同业务域名的当前 ClientHello 就能恢复。

我按照历史 handoff 的方法重新触发真实连接:先启动限时 tcpdump,再向本地入口发送一个使用新源端口的小数据报,让工作进程建立新的外层 TLS 连接。提取器从 PCAP 中选择包含目标 SNI 的完整 ClientHello record,并去除 Ethernet、IP 和 TCP 头。

新旧记录长度相同,但大量会话相关字节已经不同。新的真实 ClientHello 仍然在 Hop Limit 为 2 时导致 TLS 超时,没有恢复速度。

这一步排除了“刷新随机字段即可修复”的假设。失效的是运营商当前接受的分类信号,而不是某个 TLS 会话字段。

从真实业务链路寻找新候选

既然旧业务域名已经不能进入高速档,下一步就不是继续微调相同 payload,而是寻找当前业务实际使用的依赖域名。

我读取了官方 Web 入口当前加载的资源与 API 依赖,得到一组候选业务域名。第一轮使用 FakeHTTP 内置生成器构造 ClientHello,只改变 SNI,其余测试条件完全一致。

结果很有意思:这些生成模板不再触发 TLS 黑洞,但也没有进入高速档,所有结果都稳定在约 5 Mbps。这说明分类设备不只是读取一个明文域名字符串。

随后,我对每个候选业务域名发起真实 curl TLS 连接,在网卡上抓取完整握手,并用确定性提取器分别保存 ClientHello。捕获文件中每个候选只有一个完整匹配,且都小于 FakeHTTP 的 payload 长度上限。

生成模板为什么不够

一个 TLS ClientHello 的识别特征远不止 SNI,还包括:

  • cipher suites 及排列顺序;
  • extension 类型与顺序;
  • supported groups;
  • signature algorithms;
  • ALPN;
  • supported versions;
  • key share;
  • 会话与 padding 形态。

FakeHTTP 内置生成器能够构造语法上合理的 ClientHello,但它不是当前真实客户端产生的指纹。实测也证明,同一个候选业务域名,生成模板只能维持 5 Mbps,而从真实连接抓到的数百字节 ClientHello 可以触发完全不同的分类结果。

因此,payload 不是一段只要包含域名就能长期复用的文本,也不是 TLS 凭据。它更接近某个时间、某条线路和某类客户端共同形成的分类样本。

决定性 A/B:从 5 Mbps 到高速档

所有真实候选依次放入独立的 NFQUEUE 进行测试。规则只接管当前测试地址和端口,生产 FakeHTTP 仍由原队列处理其他流量。

其中一个新信任域名的真实 ClientHello 出现了决定性变化:16 MiB 在 0.486 秒内完成。为了排除短文件、缓存和偶然分类,我继续扩大对象并重复测试:

测试 结果
基线 约 5 Mbps
首次 16 MiB 候选 0.486 秒
64 MiB 候选重复 1 1.338 秒
64 MiB 候选重复 2 1.350 秒
64 MiB 候选重复 3 1.663 秒
生产 64 MiB 重复测试 约 301-442 Mbps
完整 1 GiB 20.919 秒,约 410.6 Mbps
清理后 64 MiB 1.733 秒,约 309.7 Mbps

完整 1 GiB 传输是最重要的一项:它证明高速结果不是握手阶段的短时突发,也不是只完成了 HTTP headers。清理候选队列、临时 nftables 表和精确旁路后再次获得约 309.7 Mbps,则证明最终生产状态仍然有效。

持久化到 OpenWrt

生产服务继续使用 NFQUEUE 512,由 procd 管理。命令结构如下,具体 payload 文件和设备名使用通用参数名表示:

fakehttp -i <WAN_DEVICE> -0 -1 -6 -f -s \
  -b <PAYLOAD_FILE> -t 2 -w <LOG_FILE>

参数含义:

  • -0 -1 同时处理外部发起和内部发起的 TCP 握手。
  • -6 处理 IPv6 TCP。
  • -f 跳过 FakeHTTP 自己生成防火墙规则,由 init 脚本统一拥有 nftables 表。
  • -s 使用静默生产模式。
  • -b 读取经过验证的真实 ClientHello。
  • -t 2 固定本次路径上已经验证的 Hop Limit。

nftables 需要同时观察两个方向:prerouting 看进入 WAN 的 SYN 与早期 ACK,postrouting 看从 LAN 发往 WAN 的 SYN、SYN-ACK 与早期 ACK。FakeHTTP 生成的报文使用专用 mark,两个 chain 的第一条规则必须让 marked packet 直接 return,防止程序处理自己生成的报文。

队列使用 flags bypass。即使用户态进程暂时退出,普通网络报文也会继续转发,只是暂时没有分类信号。

常用验证命令同样不需要包含任何业务域名:

/etc/init.d/fakehttp-router-payload status
/etc/init.d/fakehttp-router-payload enabled
cat /proc/net/netfilter/nfnetlink_queue
nft -a list table <FAMILY> fakehttp_router_payload

部署后我还主动结束了一次 FakeHTTP PID。procd 在等待窗口内启动了新 PID,重新占有队列 512,随后完整下载仍然成功。旧 init 和旧 payload 没有删除,可随时作为回滚证据。

迁移到 IPv4 NAT 路由器

另一台 OpenWrt 使用的是 IPv4 NAT,而不是带公网委派前缀的 IPv6 PPPoE。直接复制原来的 ip6 + pppoe 规则,即使服务显示 running,也不会匹配真实数据面。

IPv6 路由前缀环境中,入站可以通过路由结果判断目标是否应转发到 LAN;IPv4 NAT 环境则要考虑 DNAT/SNAT 的执行顺序:

  • prerouting 阶段的回包尚未完成 DNAT,应使用 fib daddr type local 匹配当前 WAN 本地地址。
  • postrouting 阶段使用 LAN 到 WAN 的接口组合匹配经 NAT 发出的握手。
  • FakeHTTP 进程改为 -4,nftables family 改为 ip
  • payload、Hop Limit、队列隔离和 procd 监督原则保持不变。

迁移后的队列在 5 秒内处理了数百个真实握手事件,入站 SYN、出站 SYN 和 early ACK 计数持续增长,drop 保持为 0。主动结束进程后,procd 也成功重新拉起服务。

但这台路由器没有可执行命令的 LAN 测速主机,本机到公网测试端也不可达,因此这里只能称为部署与数据面验证完成,不能声称已经获得与第一台路由器相同的吞吐。运行状态、队列计数和自动恢复仍然不能代替真实 A/B 速度测试。

这次排障留下的原则

这次从约 5 Mbps 恢复到 410.6 Mbps,真正可复用的不是某个域名或某段 payload,而是下面这套排障顺序:

  1. 先验证测试对象。 拨号、前缀和服务地址变化后,历史 URL 不能直接当作当前证据。
  2. 分开机械健康与业务效果。 进程、NFQUEUE、计数器、FAKE(*) 和零丢包只能证明注入链工作。
  3. 始终保留无注入基线。 同一来源、同一目标、同一对象、同一时间窗口,才有可比较性。
  4. 一次只改变一个变量。 先地址,再 Hop Limit,再 payload,再持久化配置。
  5. 使用真实流量样本。 明文 SNI 相同,不代表 TLS 指纹相同,更不代表分类结果相同。
  6. 扩大测试对象并重复。 headers、几 KB 或单次短文件都不足以证明进入高速档。
  7. 运行时证据优先。 现场抓包和实际传输结果高于历史配置、注释和工具名称。
  8. 部署必须可回滚。 新旧 payload 分开保存,init 先备份,候选队列与生产队列隔离。
  9. 拓扑变化必须重审规则。 IPv6 路由前缀与 IPv4 NAT 的 hook、FIB 和地址语义并不相同。
  10. 分类样本有生命周期。 运营商策略和业务依赖会变化,曾经有效不代表永久有效。

最初的问题看起来像“FakeHTTP 为什么突然坏了”,最终答案却跨越了地址漂移、验证方法、Hop Limit、TLS 指纹和运营商策略变化。只有把每一层都变成可重复的实验,才能区分“程序发了包”和“限速真的被解除”。

请登录后发表评论