两个标签常被当成同一件事:硬件加速与内核卸载。前者描述库里的 AES 例程执行哪些指令,后者描述数据包处理发生在哪里。一个 VPN 可以只有前者没有后者,也可以只有后者没有前者,或两者兼有。任何性能决策都应先说明你真正改变的是这两者中的哪一个。

两条轴,而不是一个分数

第一条轴是实现:执行 AES-256-GCM 的代码是走使用处理器专用指令的版本,还是走把指令当作普通算术执行的通用例程。这个选择存在于加密库内部,在库初始化时决定。

第二条轴是位置:数据包经过内核的转换路径,还是用户态守护进程从隧道设备读取、加密再写回。

两条轴互不限制。带有 AES 指令的处理器上,用户态 VPN 可以一直使用这些指令;没有这些指令的处理器上,内核路径会退回软件实现。厂商资料里的标签很少做这个区分,因此在两种场景之间照搬的调优建议往往毫无作用。

第一条轴:哪段代码做加密

OpenSSL 在初始化时探测处理器特性,并用结果选择优化过的代码路径,x86 能力文档对此有说明。AES-NI 是这条分发链背后被记录的若干特性之一。这些都不要求把库变成内核组件。

在假定走快路径之前,先确认该特性对进程可见。在 Linux 上来源是 /proc/cpuinfo 中的 flags 字段,而在虚拟机内部它反映的是管理程序暴露的内容,不是物理主机的型号名称。

grep -m1 '^flags' /proc/cpuinfo | grep -o '\baes\b'
openssl list -providers -verbose

第二条命令列出该构建中加载的提供者。它不会告诉你 VPN 服务链接的是哪个库,一台主机也可能装有两套 OpenSSL,而正在运行的进程只用其中一套。

第二条轴:数据包路径在哪里运行

放进内核意味着转换发生内核自身的 IPsec 机制里,或内核内的隧道驱动里,于是数据包不必为每一批都经过用户态进程往返。这能减少拷贝和上下文切换,并以单线程守护进程做不到的方式把工作分摊到多个核心。它同时也把你在吞吐下降时要读的计数器和跟踪挪到了你无法掌控的边界另一侧。把这看作调试成本,而不只是性能决策。

它不会创造指令支持,不会配置算法,也不会让已有路径开始工作。

虚拟化在这条轴上也多了一道弯。虚拟机可能拿到被遮蔽的处理器型号,隐藏主机具备的特性,嵌套虚拟机看到的更少。要从 VPN 真正运行的环境内部去读能力视图,而不是从管理程序控制台。

每种选择买到什么,又买不到什么

实际问题在于时间花在哪里。如果吞吐受限于一个忙碌的线程、反复的缓冲区拷贝或每个包一次系统调用,那么把转换搬到能消除这些开销的路径上可能有帮助,前提是内核实现了你需要的转换。

如果瓶颈是拥塞的上行链路、有丢包的路径、很长的往返时延、已饱和的对端服务,或绑定在单个核心上的单个线程,位置并不会改变什么要紧的东西。指令选择同理:专用硬件上的 AES 修不好缺失的路由,也不会在隧道因域名解析失败而空转时帮上忙。

两者都适用的地方,它们会叠加。内核路径可以在内核加密 API 内部使用同一套指令级分发,而选了合适库的用户态路径已经在用它了。决策不是哪个抽象地更快,而是你对哪一项成本有证据。

改动之前先写下四个事实

写下服务的二进制文件和它链接的加密库,因为它们决定第一条轴。然后记录协商好的数据保护,因为没有专用指令路径的算法行为与有的不同,而套件由双方共同选择。

接着是从运行环境内部看到的能力视图,最后是带着证据的活动数据包路径,证明你以为开启的模式确实开着。加载了模块并不证明某个配置文件在用它,配置里的一行也不证明内核接受了它。

只改一件事,然后用相同端点、相同传输方向的稳定负载做对比。你没有测量过的速度说法,价值低于一句后端从来没有可疑之处的备注。

相关文章