排查日期:2026-11-03 ~ 2026-11-04

状态:已解决。根因是联发科补丁的内存重叠 Bug;修复已在路由器上持久生效,并复测通过。

先看结论

路由器固件(基于香蕉派官方仓库自编译的 BPI-R4 Pro OpenWrt)中,联发科的「DSCP 学习」补丁给连接跟踪(conntrack)加了一个 QoS 扩展(开关 net.netfilter.nf_conntrack_qos,默认为 1),却忘了在内核里登记这个扩展的结构体大小。

由于它实际只被分配了 0 字节,导致和随后加入的 NAT 扩展共用了同一块内存。路由器每转发一个包,都会把包的 TOS 字节写进这块内存,正好覆盖掉 NAT 记录的出口网卡编号 masq_index:

  • TOS = 0x00:网卡编号被覆盖成 0,主线内核 NAT 的「出口网卡是否变更」检查刚好跳过 0 值,数据包正常通过;

  • TOS = 0x02(ECN 的 ECT(0))或 0x20(DSCP):网卡编号变成 2 或 32,与隧道口 ipip6-wan 的真实编号 20 对不上。NAT 误以为出口网卡发生了变化,于是直接删掉这条连接跟踪记录并静默丢弃当前数据包,不留任何计数,也不报任何错。

苹果设备默认对 TCP 开启 ECN,发出的上行数据包 TOS 几乎都是 0x02,导致每隔几个包就会触发一次「删连接 + 丢包」。苹果登录服务器因迟迟收不全请求而重置连接,iPad 上便弹出「无法验证」。

修复方案:将 net.netfilter.nf_conntrack_qos 设为 0 并写入 /etc/sysctl.conf。关闭后 ECN 和 DSCP 均恢复正常,硬件加速与下载速度不受影响。


环境信息

项目

配置

异常设备

iPad Pro(Wi-Fi 接入)

对照设备

Windows 11(有线);WSL2(Ubuntu 24.04,NAT 模式)

路由器

Banana Pi BPI-R4 Pro;MediaTek MT7988A / Filogic

固件

自编译 OpenWrt 24.10-SNAPSHOT;内核 6.6.93

构建版本

r0-56e0e77a;target mediatek/filogic

外网接入

IPv6 IPoE;IPv4 走 ipip6 隧道,MTU 1460

防火墙与加速

fw4;软件卸载、硬件卸载均开启

固件来源

因为需要 v6plus / MAP-E 等隧道支持,固件基于香蕉派官方 BPI-R4 Pro OpenWrt 开发仓库 构建。刷写与编译背景见前文:《Banana Pi BPI-R4 Pro OpenWrt固件刷写指南》。

与本次问题有关的配置

  • IPv4-over-IPv6 隧道使用 ip6tnl 连接运营商 BR,fw4 在隧道口执行 masquerade(SNAT)。

  • 卸载配置为 flow_offloading=1、flow_offloading_hw=1;SDK 另带 /etc/flowtable.conf 自定义表。

  • 已加载 br_netfilter,bridge-nf-call-iptables=1;开机脚本 pppq-ebl 默认设置 qos_toggle=2。

  • 异常线索:/proc/net/nf_conntrack 的每个方向都多出了主线内核没有的 tos= 字段。


症状表现

  • iPad 连接家里 Wi-Fi 时,退出、登录、验证 Apple 账户均告失败,提示「无法验证」。系统日志报错 NSURLErrorDomain -1005(网络连接已中断)和 AKAuthenticationError -7005。

  • 同一台 iPad 切换到手机热点后,验证一切正常。

  • 家里的 Windows 台式机访问同样的苹果服务一直正常。

  • 早期排查 Discord 语音断流问题时曾发现:iPad 发出的带 ECN 标记的 IPv4 包得不到及时确认,当时如果在路由器上强行清掉 ECN 标记就能恢复正常(后来为了排查根因撤掉了该临时规则)。


排查过程

1. 抓取 iPad 日志:协商了 ECN,带标记的包大量丢失

使用 pymobiledevice3 抓取 iPad 的 syslog,观察 akd(Apple 账户守护进程)访问苹果认证服务器(gsa.apple.com,仅支持 IPv4)的一条连接记录:

01.153  tcp_output flags=[SEC]  (SYN,带 ECE+CWR,请求开启 ECN)
01.307  tcp_input  flags=[S.E]  (SYN-ACK,服务器同意 ECN)
01.308  写出 ClientHello(1534 字节)
02.558  才收到 ServerHello          ← 间隔长达 1.25 秒,说明首个 ClientHello 丢了,靠超时重传才成功
02.577  TLS 握手完成,开始发 HTTP 请求(约 16 KB)
04.157  tcp_input flags=[R] ack=0   ← 收到 RST,连接被重置

连接结束时 iPad 内核打印的 TCP 统计摘要非常关键:

ECN: 0/1/1                                     ← ECN 已成功协商
bytes in/out: 5789/22814, retransmitted bytes: 9936
ecn packets sent/acked/marked/lost: 11/142947/0/8   ← 发出的 11 个带 ECN 标记的包里,丢了 8 个!

另一条并发连接则是发出 8 个丢了 5 个。随后 CFNetwork 报错 HTTP load failed, 16249/0 bytes (error code: -1005)——客户端发出了 16 KB 的请求体,但连一个字节的响应都没收到。

此外还有一个细节:收到的 RST 包只带 R 标志、不带 ACK,且序号正好等于客户端的 rcv_nxt。这是对端 TCP 栈在「本地根本没有这条连接状态」时的标准回应。

2. 为什么 Windows 电脑没事:默认不开 ECN

在 Windows 台式机上检查 TCP 全局参数:

netsh int tcp show global  →  ECN 功能 : disabled

Windows 默认关闭 TCP ECN,发出的包永远不带 ECN 标记;而 Linux 默认 tcp_ecn=2(仅在作为服务端且对方请求时才被动启用),作为客户端主动发起连接时也不会开启。唯独苹果设备默认主动开启 ECN,因此成了家里的「唯一受害者」。

3. 排除运营商问题:带 ECN 标记的 ICMP 全部正常

在 WSL 里用 ping -Q 发送 TOS 分别为 0、1、2、3 的 ICMP 包,测试目标包括路由器、8.8.8.8、1.1.1.1 以及苹果服务器 17.179.252.2:全部 0% 丢包。宿主机网卡抓包确认这些 ECN 标记确实发到了线上,回包也全部正常。

这说明运营商和 ipip6 隧道并不会无脑丢弃带 ECN 标记的包,问题只出在经过状态跟踪的 TCP(以及 UDP)流量上。

4. 在电脑上稳定复现:WSL 开启 ECN

在 WSL 里临时修改 net.ipv4.tcp_ecn,用 curl 向 https://gsa.apple.com/grandslam/GsService2 发送 20 KB 的随机 POST 数据:

ECN 设置

结果

TLS 握手

总耗时

默认 2

稳定返回 HTTP 401

约 0.23 s

约 0.47 s

主动开启 1

高概率连接重置

约 0.60 s

1.1 ~ 1.5 s

默认设置 2 不主动开启 ECN;HTTP 401 表示随机 POST 数据被拒绝,属于预期结果。主动开启 ECN 后,curl 的完整报错为:

curl: (56) Recv failure: Connection reset by peer

现象与 iPad 分毫不差:开启 ECN 后,TLS 握手多出来的约 0.36 秒正是 ClientHello 丢失后触发超时重传的时间。这同时证明了有线网络同样会复现,彻底排除了 Wi-Fi 驱动或无线干扰的嫌疑。

注:gsa.apple.com 的证书链根证书为苹果自家的「Apple Root CA」,不在 Ubuntu 默认信任库中,因此测试时加了 -k 参数(已提前通过 openssl 校验过证书指纹,排除中间人干扰)。

5. 路由器三点同步抓包:数据包在路由器内部蒸发

SSH 登录路由器,在三个关键接口同时用 tcpdump 抓包(抓包统计显示内核缓冲区 dropped by kernel 均为 0):

  • br-lan:LAN 侧网桥,数据包刚进入路由器;

  • ipip6-wan:隧道虚拟接口,NAT 转换之后、IPv6 封装之前;

  • br-wan:WAN 侧接口,IPv6 封装之后。

观察一条开启 ECN 的连接中,上行数据包的流向(✗ 表示在 br-lan 抓到了,但没能走到 ipip6-wan 隧道口):

时间(s)  数据包                    ECN标记   是否到达隧道口
0.117    ClientHello (517B)       ECT(0)    ✗ (在路由器内部蒸发)
0.477    ClientHello 重传          无        ✓
0.607    Finished (80B)           ECT(0)    ✗ (在路由器内部蒸发)
0.607    后续 5 个数据段            ECT(0)    ✓
0.723    数据段 (2456B, GRO合并)   ECT(0)    ✗ (在路由器内部蒸发)
0.723    Finished 重传             无        ✓
0.723    数据段                    ECT(0)    ✓
0.724    数据段 (2456B)            ECT(0)    ✗ (在路由器内部蒸发)
...

规律非常明显:带 ECN 标记(ECT(0))的数据包有一部分在 br-lan 到 ipip6-wan 之间被路由器自己丢掉了(往往是一连串突发数据里的第一个包);而超时重传时因为内核自动退避去掉了 ECN 标记,重传包 100% 能够通过。另外,成功穿过的 ECT(0) 包在 WAN 口的外层 IPv6 头里也正常保留了 ECN 标记,实锤问题出在路由器内核内部。

同时,既然所有被丢的包都能在 br-lan 上被 tcpdump 看到,说明它们当时还没进入 flowtable 硬件/软件快速路径,走的是标准的 Linux 内核网络栈转发路径。

6. 常规计数器全部为零:诡异的静默丢包

在测试前后对比路由器的所有常规丢包计数器(网卡 rx/tx_dropped、/proc/net/snmp、/proc/net/netstat、conntrack 统计、nftables 规则计数):

  • 网卡与 IP 协议栈丢包计数:变化为 0;

  • nftables 中所有带 counter 的 drop 规则(包括 fw4 的 NAT 泄漏防护规则):变化为 0;

  • conntrack invalid 计数:仅在连接末尾增加 3 个(属于连接异常关闭阶段的附带现象)。

明明丢了十几个数据包,常规监控指标却一片祥和。这意味着大概率是某个内核 Netfilter 钩子函数直接返回了 NF_DROP。顺带一提,排查时也确认了路由器上没有残留的 legacy iptables 规则、没有启用任何透明代理插件,也没有配置复杂的策略路由。

7. 内核 Tracepoint 追踪:锁定 POSTROUTING 阶段的 Netfilter 钩子

启用内核自带的 skb:kfree_skb 跟踪点(可记录丢包原因 reason),先空跑 6 秒采集基线,再在 ECN 复现测试期间采集 6 秒:

统计 nf_hook_slow 触发 NETFILTER_DROP 的次数:

采样阶段

丢包次数

空闲基线(6 秒)

5

ECN 测试(6 秒)

29

接着为该跟踪点挂载调用栈触发器(stacktrace if reason == NETFILTER_DROP),抓到了多出来的那部分丢包的完整调用栈:

kfree_skb_reason < nf_hook_slow < ip_output < ip_forward_finish

真相渐近:转发的 IPv4 数据包是在 POSTROUTING(ip_output)阶段被某个 Netfilter 钩子函数直接丢弃的。而在 POSTROUTING 阶段挂载的只有 NAT(masquerade)、MSS 钳制(仅处理 SYN 包)以及连接跟踪确认(nf_conntrack_confirm)。

8. Kprobe 挂载 NAT 函数:NAT 亲手删了连接并丢了包

利用 kprobe / kretprobe 动态探针挂载到 NAT 核心函数上进行验证:

探针 / 条件

观测次数

nf_nat_inet_fn 返回 NF_DROP

11 ~ 24(每次测试)

nf_nat_packet 返回 NF_DROP(改写包头失败)

0

nf_nat_masquerade_ipv4 返回 NF_DROP

0

nf_ct_delete 删除连接跟踪记录(下述调用栈)

23

nf_nat_inet_fn 返回丢包的次数与实际丢包数完全吻合。nf_ct_delete 的上述 23 次调用均来自:

nf_nat_inet_fn < nf_nat_ipv4_out < nf_hook_slow < ip_output

结论让人惊讶:正是 NAT 模块自己在出口阶段调用了 nf_ct_delete 删掉了这条连接的跟踪记录,并返回 NF_DROP 丢弃了当前包。

在主线 Linux 内核里,nf_nat_inet_fn 只有在检测到「masquerade 绑定的出口网卡发生变化(nf_nat_oif_changed)」时才会走这条删连接并丢包的分支。但我的隧道接口 ipip6-wan 全程根本没有变动过,为什么会触发这个分支?

9. 观察连接跟踪表:开 ECN 的连接在半路被反复「杀死重建」

在测试期间以 0.1 秒的间隔高频轮询 /proc/net/nf_conntrack:

【不开 ECN 的正常连接】
  SYN_SENT → ESTABLISHED [ASSURED] pkts=3 → FIN_WAIT pkts=18 → TIME_WAIT pkts=19

【开启 ECN 的异常连接】
  SYN_SENT → ESTABLISHED tos=20/0 [UNREPLIED] orig_pkts=1 → ESTABLISHED [ASSURED] pkts=3 ...
                          ↑ 注意这里:明明已经握手完成,却突然变成 UNREPLIED 且包计数归 1!

当旧的连接跟踪记录被 NAT 误杀后,紧接着到达的下一个 TCP 数据包会因为内核默认开启了 nf_conntrack_tcp_loose=1(允许半路拾起已建立的 TCP 连接)而重新创建一条新的 conntrack 记录。这就是为什么连接没有当场暴毙,而是表现为「每发一阵数据就得献祭一个包用来删表重建」。

这里我还注意到一个极其反常的细节:线上抓包确认 SYN 包的 TOS 明明是 0x00,但连接跟踪表里刚建立连接时却显示 tos=20!当时百思不得其解,直到最后看懂了内存布局才恍然大悟(详见第 13 节):20 根本不是什么 TOS 值,而是隧道网卡 ipip6-wan 的 ifindex(网卡接口编号)!

10. 不只是 ECN:中途修改 DSCP 同样会触发

为了验证这个 Bug 针对的是整个 8 位 TOS 字节(而不仅仅是低 2 位的 ECN),我写了一个完全不用 ECN 的测试脚本:在 TLS 握手完成后,通过 setsockopt(IP_TOS) 动态修改 socket 的 DSCP 值再发送 HTTP 请求,测量收到响应的耗时:

TOS(握手 → 请求)

第 1 次

第 2 次

0x00 → 0x00

239 ms

256 ms

0x00 → 0x20

602 ms

603 ms

0x20 → 0x20

602 ms

573 ms

0x20 → 0x00

234 ms

236 ms

结果非常清晰:只要发送请求时的 TOS 不是 0x00(无论是 0x00 → 0x20 还是全程 0x20),耗时都会精准增加约 360 ms(恰好是一次超时重传的时间),同时连接跟踪表里也能看到连接被删掉重建。也就是说,任何上行 IPv4 流量只要 TOS 非零,经过 masquerade 时都会中招,ECN 只是日常最容易触发它的场景。

11. 顺藤摸瓜找到内核开关:net.netfilter.nf_conntrack_qos

在路由器上检索与 tos / qos 相关的 sysctl 参数和内核符号表(/proc/kallsyms):

sysctl 参数:
  net.netfilter.nf_conntrack_qos = 1        ← 主线内核根本没有这个参数!

内核符号:
  nf_conntrack_qos_pernet_init              ← conntrack 的 QoS 扩展(正是 tos= 字段的来源)
  mtk_foe_entry_set_dscp                    ← 向联发科硬件 PPE 表项写入 DSCP
  mtk_ppe_internal_debugfs_{open,read,write}_qos   ← 对应 debugfs 下的 qos_toggle

起初我怀疑是联发科这个扩展故意设计成「检测到 TOS 变化就删掉连接重学」,但随后的源码比对推翻了这个猜测——不是设计逻辑蠢,而是写出了一个教科书级别的 C 语言内存重叠 Bug。

12. 开关对照实验:立竿见影

将 net.netfilter.nf_conntrack_qos 临时设为 0 进行对比测试:

开关为 net.netfilter.nf_conntrack_qos;DSCP 测试在连接中途将 TOS 改为 0x20。

开关状态

ECN 连接

DSCP 请求

1(默认)

TLS 0.61 s;100% Reset

约 600 ms;重传

0(关闭)

TLS 0.23 s;0 次 Reset

约 220 ~ 270 ms;无丢包

恢复 1

TLS 0.61 s;全部 Reset

—

关闭扩展后,ECN 连接的 TLS 握手耗时与不开 ECN 时完全一致;重新开启,问题立即复现。

13. 补丁溯源:内存重叠是如何发生的?

顺着源码树排查我在《Banana Pi BPI-R4 Pro OpenWrt固件刷写指南》中编译固件所用的官方仓库 BPI-SINOVOIP/BPI-R4PRO-8X-OPENWRT-V24.10.0-Master-Devel,在 target/linux/mediatek/patches-6.6/ 目录下找到了罪魁祸首:

  • 999-3004-netfilter-add-DSCP-learning-flow-to-xt_FLOWOFFLOAD.patch

  • 999-3016-netfilter-add-DSCP-learning-flow-to-nft_flow_offload.patch

这两个联发科 SDK 补丁新增了 nf_conn_qos 连接跟踪扩展(也就是 /proc/net/nf_conntrack 里 tos= 的来源)和 nf_conntrack_qos sysctl 开关,本意是在 nft_flow_offload 的 flow add 阶段记录双向流量的 TOS 值,等攒够 30 个包后将学习到的 DSCP 写入硬件 PPE 表项(mtk_foe_entry_set_dscp)。

补丁本身既不比较 TOS,也绝不会主动删连接。 真正致命的缺陷在于:

  1. 漏登记扩展大小:补丁定义了 NF_CT_EXT_QOS 扩展,却忘了在内核的 nf_ct_ext_type_len[] 数组里登记 struct nf_conn_qos 的长度!

  2. 分配 0 字节导致内存重叠:由于登记长度为 0,内核在给连接分配 NF_CT_EXT_QOS 扩展时,指针偏移量没有推进(只分了 0 字节)。紧接着,当数据包走到 masquerade 阶段分配 NF_CT_EXT_NAT(struct nf_conn_nat)扩展时,NAT 扩展被严丝合缝地分配到了与 QoS 扩展完全相同的内存起始地址上!

  3. 字段精准踩踏:这台路由器的内核默认未开启 PPTP(# CONFIG_NF_CONNTRACK_PPTP is not set),因此 struct nf_conn_nat 结构体里的第一个成员就是 int masq_index(用于记录 masquerade 出口网卡的 ifindex)。而它的内存位置,恰好与 struct nf_conn_qos 的「原方向 TOS」字段完全重叠!

我在路由器上核对了网卡编号,ip link 显示隧道口 ipip6-wan 的 ifindex 恰好就是 20:

  • 当 SYN 包建立连接并经过 masquerade 时,NAT 把出口网卡编号 20 写进了 nat->masq_index;所以打印 /proc/net/nf_conntrack 时,共用同一块内存的 QoS 扩展就莫名其妙地打印出了 tos=20!

  • 当连接建立完成(ESTABLISHED 且 ASSURED),后续转发包开始进入 flow add 逻辑,QoS 扩展把数据包的 TOS 字节写进这块内存,当场把 nat->masq_index 覆盖掉了!

  • 紧接着数据包在 POSTROUTING 阶段走到主线内核的 nf_nat_oif_changed(),其检查逻辑为:

    if (nat->masq_index && nat->masq_index != out->ifindex) {
        nf_ct_kill_acct(ct, ctinfo, skb);
        return NF_DROP;
    }
    
    • 如果数据包 TOS 为 0:masq_index 被覆盖成 0,if (nat->masq_index ...) 条件为假,检查被直接跳过,数据包侥幸存活!

    • 如果数据包 TOS 为 0x02(ECN)或 0x20(DSCP):masq_index 被覆盖成 2 或 32,既不为 0、又不等于 ipip6-wan 的真实编号 20。NAT 立即判定「出口网卡变了」,调用 nf_ct_kill_acct() 杀掉连接并返回 NF_DROP!

这完美解释了第 5 节和第 8 节里观测到的所有现象:

  • 为什么握手和重传包不丢?因为连接刚重建、还没拿到对端回包变成 ASSURED 之前,flow add 会提前返回,不去写 TOS;一旦连接变成 ASSURED,下一个带 ECN 的包立刻写坏 masq_index 并自爆;

  • 为什么结尾会有几个 conntrack invalid?因为连接在最后阶段被误杀后,尾随其后的 FIN 包无法通过 tcp_loose 新建连接,被当成无主包丢弃。

巧合的是,社区里已经有开发者独立踩中并定位了完全相同的 Bug,开源在 yearsyan/mediatek-conntrack-qos-fix 仓库中(其排查的基线 Commit 56e0e77a 正好也是我当时编译固件所用的版本),并提供了修复补丁 999-3035-netfilter-fix-conntrack-qos-extension-size.patch。

补充:为什么最后会收到苹果服务器的 RST?

  • 已排除原因:并非 NAT 删表重建后改变了源端口。对比 LAN 侧和隧道口的抓包,重建前后的 TCP 源端口完全一致。

  • 合理推断:抓包确认 RST 确实由远端服务器发出(TTL 与正常回包一致)。由于客户端在发送 16 KB 的认证请求体期间反复遭遇丢包与超时重传,耗时被大幅拉长,苹果前端负载均衡或服务端因超时提前关闭了连接状态;当客户端重传的后续分段姗姗来迟时,对端已无该连接记录,便直接回了不带 ACK 的 RST。


修复方案与复测结果

1. 运行时持久化修复(无需重刷固件)

直接在路由器上关闭 nf_conntrack_qos,并将其写入 /etc/sysctl.conf(OpenWrt 在升级固件时默认也会保留该配置文件):

sysctl -w net.netfilter.nf_conntrack_qos=0
echo 'net.netfilter.nf_conntrack_qos=0' >> /etc/sysctl.conf

关闭该开关后,新建的连接不再分配 NF_CT_EXT_QOS 扩展,内存重叠问题自然消失。唯一的影响是硬件 PPE 表项不再学习 DSCP(保持为 0),且流量在连接建立后会立即下发给硬件加速,无需再苦等 30 个数据包,反而加快了硬件卸载的生效速度。

2. 修复前后复测

以下对比中,修复前为 nf_conntrack_qos=1,修复后为 nf_conntrack_qos=0。

测试项目

修复前

修复后

ECN HTTPS 请求

TLS 0.61 s;频繁 Reset

TLS 0.24 s;连续测试 0 次 Reset

修改 DSCP(0x20)

约 600 ms;必现重传

230 ~ 270 ms;正常

IPv4 多线程下载

1.55 ~ 1.79 Gbit/s

1.54 ~ 1.78 Gbit/s

关闭扩展后,ECN、DSCP 均恢复正常,下载性能无损。

应用修复后,家里 iPad 的 Apple 账户登录验证、Discord 语音等服务全部恢复丝滑。

3. 源码级根治方案(重编固件)

如果后续重新编译固件,并且确实需要硬件 PPE 的 DSCP 学习功能,可以在源码树的 target/linux/mediatek/patches-6.6/ 中合入 yearsyan/mediatek-conntrack-qos-fix 提供的 999-3035-netfilter-fix-conntrack-qos-extension-size.patch,补齐 nf_ct_ext_type_len 的大小注册即可。


附:排查走过的弯路(被排除的假设)

  • 隧道 MTU / MSS 钳制:路由器已有 MSS clamping;将 WSL 网卡 MTU 手动降至 1280 后依然复现。

  • 运营商或 BR 丢弃 ECN 包:ping -Q 测试全部 0% 丢包;三点抓包证实包丢在路由器内部,成功转发的包在外层 IPv6 头中也正常保留 ECN。

  • Wi-Fi 驱动或无线干扰:纯有线连接的 WSL 开启 tcp_ecn=1 后,仍可 100% 复现。

  • Flowtable 软硬件加速出错:被丢的包都能在 br-lan 抓到,尚未进入快速路径;当时 PPE 硬件表项也处于未绑定状态。

  • 防火墙或连接状态误判:丢包发生时,nft 规则计数器和 conntrack invalid 计数均无增长。

  • 隧道接口闪断:kprobe 确认出口始终是 ipip6-wan,ifindex=20 未变。

  • NAT 重建时更换源端口:对比 LAN 侧和隧道侧抓包,源端口全程一致。


参考资料


附录:排查用到的关键命令备忘

1. 在 Linux / WSL 下复现 ECN 丢包

测试完成后,将 tcp_ecn 恢复为 2。

sysctl -w net.ipv4.tcp_ecn=1      # 主动发起 ECN 协商(默认 2)
head -c 15000 /dev/urandom | base64 -w0 > /tmp/body.txt
curl -4 -sSk -o /dev/null --data-binary @/tmp/body.txt \
  -w 'code=%{http_code} tls=%{time_appconnect} total=%{time_total}\n' \
  https://gsa.apple.com/grandslam/GsService2
sysctl -w net.ipv4.tcp_ecn=2

2. 发送带 ECN 标记的 Ping

ping -c 5 -Q 2 8.8.8.8     # 0=Not-ECT, 1=ECT(1), 2=ECT(0), 3=CE

3. 在 WAN 口抓取隧道封装后的数据包

抓取 ipip6 数据包,按内层目的 IP 17.157.64.66(0x119d4042)过滤:

tcpdump -ni br-wan -w /tmp/wan.pcap 'ip6 proto 4 and (ip6[56:4] = 0x119d4042 or ip6[52:4] = 0x119d4042)'

4. 开启内核丢包原因与调用栈追踪

使用 kfree_skb 跟踪点,用完务必关闭。

T=/sys/kernel/debug/tracing
echo 'protocol == 2048' > $T/events/skb/kfree_skb/filter
echo 'stacktrace if reason == 8' > $T/events/skb/kfree_skb/trigger   # 8 对应 NETFILTER_DROP(具体数值以同目录 format 文件为准)
echo 1 > $T/events/skb/kfree_skb/enable
# ……执行复现测试……
echo 0 > $T/events/skb/kfree_skb/enable
echo '!stacktrace if reason == 8' > $T/events/skb/kfree_skb/trigger

5. 挂载 NAT kprobe 探针

以下命令适用于 ARM64,用完清理探针。

T=/sys/kernel/debug/tracing
echo 'r:ecnnatret nf_nat_inet_fn ret=$retval:u32' >> $T/kprobe_events
echo 'ret == 0' > $T/events/kprobes/ecnnatret/filter
echo 'p:ecndel nf_ct_delete' >> $T/kprobe_events
echo stacktrace > $T/events/kprobes/ecndel/trigger
# ……执行复现测试,查看 $T/trace……
# 清理探针:先 disable 并移除 trigger,再执行:
echo '-:ecnnatret' >> $T/kprobe_events
echo '-:ecndel' >> $T/kprobe_events

文章作者: Lien
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 彼方!
喜欢就支持一下吧