AI摘要:正在生成中……
# 为什么突然想到要测试WireGuard吞吐?
这次测试 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。
#使用 irqbalance 分散硬件中断负载
既然已经确认网卡 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

















Comments | NOTHING