从 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 就已经被运营商识别。
实际上,它只能证明以下几件事:
- FakeHTTP 收到了需要处理的握手报文。
- 程序构造了带 payload 的 TCP 报文。
- 本机内核接受了 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,而是下面这套排障顺序:
- 先验证测试对象。 拨号、前缀和服务地址变化后,历史 URL 不能直接当作当前证据。
- 分开机械健康与业务效果。 进程、NFQUEUE、计数器、
FAKE(*)和零丢包只能证明注入链工作。 - 始终保留无注入基线。 同一来源、同一目标、同一对象、同一时间窗口,才有可比较性。
- 一次只改变一个变量。 先地址,再 Hop Limit,再 payload,再持久化配置。
- 使用真实流量样本。 明文 SNI 相同,不代表 TLS 指纹相同,更不代表分类结果相同。
- 扩大测试对象并重复。 headers、几 KB 或单次短文件都不足以证明进入高速档。
- 运行时证据优先。 现场抓包和实际传输结果高于历史配置、注释和工具名称。
- 部署必须可回滚。 新旧 payload 分开保存,init 先备份,候选队列与生产队列隔离。
- 拓扑变化必须重审规则。 IPv6 路由前缀与 IPv4 NAT 的 hook、FIB 和地址语义并不相同。
- 分类样本有生命周期。 运营商策略和业务依赖会变化,曾经有效不代表永久有效。
最初的问题看起来像“FakeHTTP 为什么突然坏了”,最终答案却跨越了地址漂移、验证方法、Hop Limit、TLS 指纹和运营商策略变化。只有把每一层都变成可重复的实验,才能区分“程序发了包”和“限速真的被解除”。


