AI摘要:

正在生成中……



这次测试 WireGuard 吞吐,其实最开始是由一台 FortiGate 防火墙引出来的。FortiGate 即使是入门级桌面型号,在开启 IPsec VPN 后依然可以做到数 Gbps 的吞吐,这很大程度上得益于 Fortinet 自研的SPU-CP-NP ASIC 硬件加速体系。

但另一方面,IPsec 本身的协议栈比较传统,配置复杂度也更高。相比之下,WireGuard 的设计明显更加现代与轻量,协议实现也更简洁。于是我产生了一个想法: 在没有专用 ASIC 硬件加速的情况下,现代轻量化的 WireGuard,能不能依靠通用 CPU的算力以及SIMD指令集等优化,把吞吐做到接近甚至追上 FortiGate 的 IPsec?

正好手头有一台基于 RK3568 的 OpenWrt 设备,于是决定直接拿现有设备做一次实际测试。测试环境为 Linux 6.6.8,RK3568 四核 Cortex-A55,2.5GE以太网接口由PCIE引出,网卡已开启多队列,RX 2 队列 TX 2 队列。
对端则是一台运行 Windows 11 Home 的游戏本,处理器为 Intel Core i9-14900HX,搭载瑞昱Realtek 2.5GE 网卡,同样已开启多队列,并正确安装 WireGuard-NT 内核驱动。

两端通过一根六类非屏蔽网线2.5GE 网络直联,后续所有测试均基于这套环境进行。

原本只是想看看这颗 SoC 到底能够把 WireGuard 跑到什么水平,但真正开始测试之后,很快就发现了一个比较反常的现象。当数据由 Peer 发送到 RK3568 时,WireGuard 接收方向可以达到接近 700Mbps。但反过来,由 RK3568 向同一个 Peer 发送数据时,吞吐却只有大约 400Mbps。

同一台设备 同一个 Peer 同一条网络链路,仅仅改变数据方向,吞吐就相差接近一倍。

继续观察 CPU 负载后,又发现了更明显的问题:发送测试过程中,并不是四个 CPU 核心一起接近满载,而是其中一个核心长时间处于高负载,其余几个核心仍然存在大量余量。

进一步查看系统负载和 /proc/interrupts 后,可以看到相当一部分 CPU 时间实际上消耗在网络 IRQ 和相关软中断处理上。

这时候问题就逐渐从RK3568 算力是不是不够,变成了另外一个方向:WireGuard 的发送路径为什么会死磕单个核心?网卡 IRQ 是否又进一步挤占了这个核心的处理能力?如果把这些中断负载重新分配到其他 CPU,能不能把 WireGuard 的发送吞吐继续往上推?

既然 WireGuard 两个方向的性能差距这么大,首先需要确认的并不是 WireGuard,而是底层网络本身有没有问题。为了确保底层链路不会成为限制,因此首先暂时绕过 WireGuard,直接使用两端的物理网卡地址进行 iperf3 测试。

可以看到,在不经过 WireGuard 的情况下,两端通过 2.5GbE 直连进行 iperf3 测试,实际吞吐可以达到 2.11 Gbit/s,这说明底层 2.5GbE 链路本身不存在明显性能瓶颈,网线 PCIe 网卡以及对端 2.5GbE 网卡都具备远高于当前 WireGuard 400~700Mbps 的实际传输能力。因此,WireGuard 发送方向只有约 400Mbps,显然不是由物理链路带宽不足导致的。既然底层网络没有问题,接下来就只能继续往 WireGuard 本身以及 Linux 网络处理路径上查。

首先观察 CPU 使用情况。在 Peer 向 RK3568 发送数据,也就是 RK3568 作为 WireGuard 接收端时,可以看到 WireGuard 的解密处理并不是简单地全部压在一个 CPU 上。数据包的密码学处理可以被分配到不同 CPU 的 worker 上执行,因此多个核心都会参与处理。

查询资料后得知,Linux WireGuard 的队列实现中,设备级加密/解密队列会通过 wg_cpumask_next_online() 在 CPU 之间选择处理核心,再通过 queue_work_on() 将密码学处理工作投递到对应 CPU。也就是说,WireGuard 的 ChaCha20-Poly1305 加解密本身并不是所谓的单线程实现。

这也和实际测试现象相符:在接收方向跑流量时,RK3568 的多个 CPU 都能看到比较明显的负载,最终 WireGuard RX 吞吐可以达到接近 700Mbps。

但当测试方向反过来,由 RK3568 向 Peer 发送数据时,情况就不一样了。虽然数据加密阶段同样可以利用多个 CPU,但继续观察可以发现,其中一个核心的负载明显高于其他核心,并且很快接近性能极限,而其他几个核心仍然存在大量空闲。

一开始这个现象看起来有些矛盾,既然 WireGuard 的加密可以分配到多个 CPU,为什么发送方向最后还是会出现明显的单核瓶颈?继续阅读WireGuard的实现文档后我们发现,WireGuard 除了设备级的并行密码学处理之外,还存在 per2peer 的发送处理阶段。同一个 Peer 的数据在完成加密之后,需要按照正确的顺序继续向下发送,因此这部分不能简单地让多个 CPU 各发各的。
在 Linux WireGuard 的实现中,发送完成后的 per-peer 工作会通过peer->serial_work_cpu,选择一个 CPU,然后使用queue_work_on(),把该 Peer 的发送工作投递到这个指定 CPU 上。本句由ChatGPT生成

也就是说,WireGuard 的加密可以多核并行,但同一个 Peer 的发送路径中仍然存在固定 CPU 上执行的串行阶段。WireGuard确实支持多核处理密码学任务,但对于单个 Peer 来说,发送路径中依然存在一个无法完全并行化的热点。

但答案没有这么简单,RK3568作为一颗A55架构的ARM SoC,其性能不太可能只有这么低,还有其他因素在影响隧道性能。

不过,问题显然还没有这么简单。仅仅存在一个串行处理阶段,并不足以完全解释当前只有约 400Mbps 的发送吞吐。RK3568 虽然采用的是 Cortex-A55 核心,单核性能并不算强,但从当前 CPU 负载和网络处理情况来看,限制吞吐的并不只是 WireGuard 本身。继续观察系统运行状态后可以发现,这个已经承担 WireGuard 串行发送任务的 CPU,同时还在处理大量网卡 IRQ、网络 softirq 以及协议栈相关工作。换句话说,WireGuard 的单 Peer 串行热点只是问题的一部分,真正进一步压缩发送性能的,还有集中在同一 CPU 上的大量网络中断处理开销。

继续对 /proc/interrupts 进行连续两次采样后,问题变得更加明显。
在 WireGuard 持续发送流量期间,eth1 实际活跃的几个 MSI-X 中断向量中,eth1-0、eth1-16 和 eth1-18 的新增中断几乎全部落到了 CPU2 上。两次采样之间,eth1-0 在 CPU2 上增加了 9,804 次中断,eth1-16 增加了 4,365 次,eth1-18 增加了 10,200 次;与此同时,CPU3 上的 eth1-1 仅增加了 12 次,而 CPU0、CPU1 没有观察到新增的 eth1 中断。也就是说,在这一采样区间内,eth1 一共新增了 24,381 次硬件中断,其中 24,369 次由 CPU2 处理,占比约 99.95%。这说明在当前 WireGuard TX 测试过程中,虽然网卡本身已经开启了多队列,但实际产生的硬件中断负载却高度集中在 CPU2,而没有均匀分散到四个 CPU 核心。这与前面观察到的 CPU 使用率现象高度吻合:WireGuard 单 Peer 的发送路径本身已经在 CPU2 上形成明显热点,而网卡 IRQ 又几乎全部落到了同一个核心,进一步加剧了 CPU2 的负载。
简单来说,WireGuard 单个 Peer 的发送路径里存在固定 CPU 上执行的串行处理阶段,而实际测试中,网卡 IRQ 和网络 softirq 又大量集中到了同一个核心。结果就是WireGuard 本身已经形成单核热点,网卡中断又继续挤占这个核心的处理时间。因此我们的优化思路并不是让 WireGuard 强行变成多线程TX,而是把可以迁移的硬件 IRQ 尽量分散到其他 CPU,让热点核心腾出更多资源给 WireGuard。

既然已经确认网卡 IRQ 和网络 softirq 明显集中在热点核心上,那么接下来就尝试使用 irqbalance 对硬件中断进行重新分配。

irqbalance 是 Linux 下用于在多核 CPU 之间自动平衡硬件 IRQ 的守护进程。OpenWrt 官方也提供了对应的软件包,安装后可以自动调整不同 IRQ 的 CPU affinity,从而避免大量中断长期集中在某一个核心上。
由于不同 Linux 发行版和 OpenWrt 分支使用的软件包管理器并不相同,这里不展开介绍 irqbalance 的安装过程,只说明如何启用。

在 OpenWrt 中,将 irqbalance 设置为启用:

uci set irqbalance.irqbalance.enabled='1'
uci commit irqbalance

然后启动服务并设置为开机自动启动,并使用命令确认是否生效,正常应该返回1

/etc/init.d/irqbalance start
/etc/init.d/irqbalance enable

uci get irqbalance.irqbalance.enabled

执行pgrep -a irqbalance命令,如果能够看到 irqbalance 进程,就说明服务已经正常启动。

如果系统带有 LuCI 对应管理页面,也可以直接在 Web 管理界面中启用 IRQ Balance。

启用完成后,继续使用之前完全相同的 WireGuard 和 iperf3 测试参数重新进行测速

开启 irqbalance 后,再次在 WireGuard 持续发送流量期间对 /proc/interrupts 进行连续采样,结果变化非常明显。

在两次采样之间,eth1 共新增约 4.57 万次硬件中断,其中 CPU0 承担约 44.9%,CPU3 承担约 55.1%,而之前的热点 CPU2 仅新增 17 次,已经几乎不再承担这部分网卡 IRQ。相比开启 irqbalance 前约 99.95% 的新增 eth1 IRQ 集中在 CPU2,开启后主要中断已经被重新分配到了 CPU0 和 CPU3。这说明 irqbalance 已经成功调整了网卡 MSI-X 中断的 CPU affinity,将原本与 WireGuard 热点核心竞争 CPU 时间的硬件 IRQ 转移到了其他空闲核心。随后重新进行相同条件下的 WireGuard TX 测试,发送吞吐也由原来的约 400Mbps 提升到了 600~700Mbps。

需要注意的是,irqbalance 并没有消除 WireGuard 单 Peer 发送路径本身的串行热点,它只是减少了热点核心还需要额外承担的网卡 IRQ,从而释放出更多 CPU 时间给 WireGuard。简单来说:WireGuard 还是那个 WireGuard,只是不再让它和网卡中断挤在同一颗核心上抢 CPU 了。

这次测试最初只是想看看 RK3568 这种通用 ARM SoC,在没有专用 ASIC 加速的情况下,WireGuard 实际能够跑到什么水平,结果却意外发现了明显的 TX/RX 吞吐差异。经过排查可以确认,问题并不在 2.5GbE 物理链路本身。裸网测试能够达到 2.11Gbit/s,而 WireGuard 发送方向却只有约 400Mbps。进一步观察后发现,WireGuard 虽然可以利用多个 CPU 进行密码学处理,但单个 Peer 的发送路径中依然存在明显的单核热点。与此同时,网卡 IRQ 和网络 softirq 又大量集中到了同一个 CPU,进一步挤占了这个核心的处理时间。

开启 irqbalance 后,网卡 MSI-X 中断被重新分配到了其他核心。测试中,开启前约 99.95% 的新增 eth1 IRQ 集中在 CPU2,而开启后主要中断已经转移到了 CPU0 和 CPU3。最终,WireGuard TTX 吞吐也从原来的约 400Mbps 提升到了 600~700Mbps。

需要注意的是,irqbalance 并没有改变 WireGuard 单 Peer 发送路径本身的调度机制,也没有让 WireGuard 真正变成完全多核发送。它解决的其实是另一个问题,不要让 WireGuard 的热点核心,同时还承担本可以交给其他 CPU 处理的大量网卡中断。

所以对于多核 ARM SoC、OpenWrt 软路由或者其他 Linux 网络设备,如果遇到 WireGuard 单方向吞吐异常偏低,并且同时出现“一颗核心跑满、其他核心很闲”的情况,可以先检查 /proc/interrupts 和 /proc/softirqs。

如果发现网卡 IRQ 明显集中在热点核心,那么启用 irqbalance 往往是一个成本很低、但可能非常有效的优化方式。

至少在这台 RK3568 上,WG吞吐可以达到700Mbps,我个人认为这是非常满意的结果了。

版权声明:转载时请以超链接形式标明文章原始出处和作者信息,来源孤影墨香
本文链接: https://www.iloli.xin/4577.html
访问时间:2026-10-04 12:33:48

赏

正因为知道可以在空中翱翔,才会畏惧展翅的那一刻而忘却疾风 努力学习ing