OpenVPN DCO存在的问题

OpenVPN最初由James Yonan开发,于2001年5月13日首次发布。它支持许多常见平台(例如 FreeBSD、OpenBSD、Dragonfly、AIX 等)和一些不太常见的平台(macOS、Linux、Windows) 。它支持点对点和客户端-服务器模型,具有预共享密钥、证书或基于用户名/密码的身份验证。

存在的问题

虽然 OpenVPN 非常好,但显然肯定存在一个问题,那就是 OpenVPN 是作为单线程用户空间进程实现的。它使用 if_tun 将数据包注入网络堆栈,因此其性能未能跟上当前的连接速率。它使得利用现代多核硬件或加密卸载硬件变得困难。

OpenVPN性能的主要问题是其用户空间性质。传入流量由 NIC 接收,NIC 通常会将数据包通过 DMA 传输到内核内存中。然后由网络堆栈进一步处理,直到找出数据包属于哪个套接字,并将其传递到用户空间。该套接字可以是 UDP 或 TCP。

将数据包传递到用户空间涉及复制数据,此时用户空间 OpenVPN 进程验证并解密数据包,并使用 if_tun 将其重新注入到网络堆栈中。这意味着需要将纯文本数据包复制回内核以进行进一步处理。

这些上下文切换和来回复制不可避免地会对性能产生重大影响。这在当前的架构中,很难实现明显的性能改进。

什么是DCO

现在我们已经确定了问题所在,我们可以开始考虑解决方案。如果我们的问题是上下文切换到用户空间,那么一种可行的解决方案是将工作保留在内核内部,这就是 DCO(数据通道卸载)所做的。

DCO 将数据通道(即加密操作和流量隧道)移至内核中。它通过新的虚拟设备驱动程序 if_ovpn 来完成此操作。OpenVPN 用户空间进程仍然负责连接设置(包括身份验证和选项协商),并通过新的 ioctl 接口与 if_ovpn 驱动程序进行协调。

OpenVPN 项目认为 DCO 的引入是删除一些遗留功能并进行一些常规整理的好机会。作为其中的一部分,他们采用了亨利·福特的方法来选择加密算法。你可以拥有任何你喜欢的算法,只要你喜欢 AES-GCM 或 ChaCha20/Poly1305。DCO不支持压缩、第 2 层流量、非子网拓扑或流量整形

需要注意的是,DCO不会更改OpenVPN协议。客户端可以将它与不使用它的服务器一起使用,反之亦然。当然,当双方都使用它时,将获得最大的好处,但这不是必需的。

注意事项

在这一部分,我会谈论这一切有多么困难,所以你们都会对我真正做到了这一点印象深刻。但有几件事需要特别注意:

多路复用

第一个问题是OpenVPN使用单个连接来传输隧道数据和控制数据。隧道数据需要由内核处理,控制数据由OpenVPN用户空间进程处理。

你可以看到这个问题。该套接字最初由 OpenVPN 本身开放并完全拥有。它设置隧道并处理身份验证。一旦完成,它会将部分控制权移交给内核端(即 if_ovpn)。

这意味着通知 if_ovpn 文件描述符(内核使用它来查找内核结构套接字),以便它可以保存对其的引用。这可以确保套接字在内核使用它时不会消失。也许是因为 OpenVPN 进程被终止了,或者因为它今天心情不好而决定来惹我们。它是用户空间,它可能会做一些疯狂的事情。

对于那些想要了解内核代码的人来说,可以寻找ovpn_new_peer()函数。查找套接字后,我们可以通过安装过滤功能udp_set_kernel_tunneling()。过滤器ovpn_udp_input()查看指定套接字的所有传入数据包,并决定它是应处理的有效负载数据包,还是用户空间中的 OpenVPN 应处理的控制数据包。

这个隧道功能也是我必须对网络堆栈的其余部分进行的唯一更改。需要告知某些数据包将由内核处理,而其他数据包仍然可以传递到用户空间。

ovpn_udp_input()函数是接收路径的主要入口点。对于到达其所安装的套接字的任何 UDP 数据包,网络堆栈将数据包移交给此函数。

该函数首先检查内核驱动程序是否可以处理数据包。也就是说,该数据包是一个数据包,其目的地是已知的对等点 ID。如果不是这种情况,过滤器函数会告诉 UDP 代码通过正常流传递数据包,就好像没有过滤器功能一样。这意味着数据包将到达套接字并由 OpenVPN 的用户空间进程进行处理。

早期版本的 DCO 驱动程序具有单独的 ioctl 命令来读取和写入控制消息,但 Linux 和 FreeBSD 驱动程序都已改为使用套接字。这简化了控制数据包和新客户端的处理。

另一方面,如果数据包是已知对等点的数据包,则将其解密,验证其签名,然后将其传递到网络堆栈以进行进一步处理。

UDP协议

OpenVPN 可以在 UDP 和 TCP 上运行。虽然 UDP 是第 3 层 VPN 协议的优先选择,但一些用户需要通过 TCP 运行它来穿越防火墙。

FreeBSD 内核为 UDP 套接字提供了方便的过滤功能,但没有对应于 TCP 的过滤功能,因此 FreeBSD if_ovpn 目前仅支持 UDP,而不支持 TCP。Linux DCO 驱动程序已经实现对 TCP 支持。

硬件加密卸载

if_ovpn 依赖于内核中的 OpenCrypto 框架进行加密操作。这意味着它还可以利用系统中存在的任何加密卸载硬件。这可以进一步提高性能。

它已经通过英特尔 QuickAssist 技术 (QAT)、SafeXcel EIP-97 加密加速器和 AES-NI 的测试。

锁定设计

几乎每个现代 CPU 都有多个内核,如果能够使用多个内核,对性能的提升将会非常显著,事实证明这相当容易做到。整个方法基于区分对 if_ovpn 内部数据结构的读取和写入访问。允许不同的核心同时查找内容,但只允许一个核心更改内容(然后在进行更改时不允许任何读者)。事实证明这种方法效果很好,因为大多数时候我们不需要改变事情。

常见的情况是,当我们接收或发送数据包时,只需要查找密钥、目标地址和端口等相关信息。

只有当我们修改内容(即配置更改或重新输入密钥)时,我们才需要获取写锁,并且暂停数据通道。这足够简短,我们弱小的人类大脑不会注意到它,这让每个人都很高兴。

“我们不会更改过程数据”这一规则有一个例外,那就是数据包计数器。每个数据包都会被计数(甚至两次,一次用于数据包计数,一次用于字节计数),并且必须同时完成。在这里,我们也很幸运,因为内核的counter(9)框架正是针对这种情况而设计的。它保留每个 CPU 核心的总数,以便一个核心不会影响或减慢另一个核心的速度。只有当实际读取计数器时,它才会向每个核心询问其总数并将其相加。

控制接口

每个 OpenVPN DCO 平台都有自己独特的用户空间 OpenVPN 和内核模块之间通信的方式。在 Linux 上,这是通过 netlink 完成的,但 if_ovpn 工作是在 FreeBSD 的 netlink 实现准备好之前完成的。

if_ovpn 驱动程序是通过现有接口ioctl 路径配置的。具体来说,就是SIOCSDRVSPEC/SIOCGDRVSPEC调用。这些调用将struct ifdrv 传递给内核。ifd_cmd 字段用于传递命令,ifd_data 和 ifd_len 字段用于在内核和用户空间之间传递设备特定的结构。

if_ovpn 与既定方法有所不同,因为它传输序列化的 nvlist 而不是结构。这使得扩展接口变得更加容易。或者,更确切地说,这意味着我们可以在不破坏现有用户空间消费者的情况下扩展接口。如果将新字段添加到结构中,其布局会发生变化,这意味着现有代码将由于其大小不匹配而拒绝接受它7或变得非常困惑,因为字段不再具有它们以前的含义。

序列化的 nvlist 允许我们添加字段,而不会混淆另一方。任何未知字段都将被忽略。这使得添加新功能变得更加容易。

路由查找

您可能认为 if_ovpn 不需要担心路由决策。毕竟,当数据包到达网络驱动程序时,内核的网络堆栈已经做出了路由决策。你就错了。我会为此取笑你,但我也花了一段时间才弄清楚。

问题是给定的 if_ovpn 接口上可能存在多个对等点(例如,当它充当服务器并具有多个客户端时)。内核已经发现有问题的数据包需要发送到其中一个,但内核的运行假设所有这些客户端都位于单个广播域上。也就是说,在接口上发送的数据包对所有人来说都是可见的。这里的情况并非如此,因此 if_ovpn 需要计算出数据包必须发送到哪个客户端。

这是由 处理的ovpn_route_peer()。该函数首先查看对等点列表,查看是否有任何对等点的 VPN 地址与目标地址匹配。(由ovpn_find_peer_by_ip()或完成ovpn_find_peer_by_ip6(),具体取决于地址族)。如果找到匹配的对等点,则数据包将发送到该对等点。如果没有,ovpn_route_peer()则执行路由查找,并使用生成的网关地址重复对等查找。只有当 if_ovpn 找到将数据包发送到的对等方时,数据包才能被加密和传输。

密钥轮换

OpenVPN 会不时更改用于保护隧道安全的密钥。这是 if_ovpn 留给用户空间的艰巨工作之一,因此 OpenVPN 和 if_ovpn 之间需要进行一些协调。

OpenVPN 将使用 OVPN_NEW_KEY 命令安装新密钥。每个密钥都有一个 ID,每个数据包都包含用于加密它的密钥 ID。这意味着在密钥轮换期间,所有数据包仍然可以被解密,因为旧密钥和新密钥都是已知的并且在内核中保持活动状态。

安装新密钥后,可以使用 OVPN_SWAP_KEYS 命令将其激活。也就是说,新密钥将用于加密传出数据包。稍后可以使用 OVPN_DEL_KEY 命令删除旧密钥。

虚拟网络

是的,我们将不得不谈论 vnet。将 vnet 视为将监狱转变为具有自己的 IP 堆栈的虚拟机。这对于 pfSense 用例来说并不是严格要求的,但它使测试变得非常非常容易。这意味着我们可以在一台机器上进行测试,而不需要任何外部工具(除了 OpenVPN 本身,原因应该是非常明显的)。

测试表现

在Netgate 4100设备上,通过运行iperf3进行测试,得到了以下结果:

if_tun 207.3 Mbit/s
DCO Software 213.1 Mbit/s
DCO AES-NI 751.2 Mbit/s
DCO QAT 1,064.8 Mbit/s

“if_tun”是没有 DCO 的旧 OpenVPN 方法。值得注意的是,它在用户空间中使用了 AES-NI 指令,而“DCO 软件”设置则没有。尽管存在这种公然的作弊行为,DCO 的速度仍然稍快一些。

在公平的竞争环境中(即DCO 确实使用 AES-NI 指令),不存在竞争。DCO 的速度快了三倍多。

对于英特尔来说,QuickAssist 卸载引擎甚至比 AES-NI 还要快,使 OpenVPN 的速度比以前快五倍。

未来的工作

没有什么是不能改进的,但从某些方面来说,下一个增强是 DCO 设计成功的结果。在线 OpenVPN 协议使用 32 位初始化向量 (IV),出于加密原因,使用相同密钥重复使用 IV 是一个坏主意。

这意味着在我们达到这一点之前必须重新协商密钥。OpenVPN 的默认重新协商间隔为 3600 秒,并有 30% 的安全富裕,这将转换为 2^32 * 0.7 / 3600,即每秒约 835.000 个数据包。这将达到8 到 9 Gbit/s(假设数据包为 1300 字节)。

有了 DCO,现代硬件或多或少已经可以做到这一点。虽然这是一个好问题,但它仍然是一个问题,因此 OpenVPN 开发人员正在开发一种更新的数据包格式,该格式将使用 64 位 IV。

其他说明

if_ovpn 工作由 Rubicon Communications(以 Netgate 名义交易)赞助,用于其 pfSense 产品线。pfSense plus 22.05中开始提供OpenVPN DCO功能,这项工作已上游到 FreeBSD,并且是最近 14.0 版本的一部分。它需要 OpenVPN 2.6.0 或更高版本才能使用。很可惜的是,Netgate并未在pfSense CE中提供该功能。

相关文章:

原文地址

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇