eBPF 内核数据平面(技术预览版)
总结 瞻博网络云原生 Contrail 网络 (CN2) 23.3 版支持用于 Linux 内核的扩展伯克利数据包过滤器 (eBPF) 数据平面。基于 eBPF 的数据平面使程序能够加载到高性能应用程序的内核中。
Linux 内核概述
Linux 内核内存大致分为以下两个领域:
-
内核空间:作系统的核心在内核空间中运行。该作系统可以不受限制地访问所有主机资源,包括:内存、存储和 CPU。内核空间严格保留用于运行特权作系统内核、内核扩展和主机的大多数设备驱动程序。内核空间受到保护,只运行受信任的代码。
-
用户空间库:非内核进程,例如常规应用程序,在用户空间内运行。在用户空间中运行的代码对硬件资源的访问有限,并且通常依靠内核空间来执行特权作(如磁盘或网络 I/O)。用户空间应用程序通过 API 或系统调用从内核请求服务。系统调用在进程和作系统之间提供接口,以允许用户空间进程请求作系统的服务。
在某些情况下,开发人员可能需要比通过系统调用接口分配的更多的内核性能。自定义系统调用、对新硬件的支持和新文件系统都可能需要额外的内核灵活性。像这样的情况需要在不添加源代码的情况下对内核进行增强。Linux 内核模块 (LKM) 扩展了基本内核,而无需添加或更改其源代码。与系统调用不同,LKM 在运行时直接加载到内核中。这意味着不需要在每次需要新的 LKM 时重新编译和重新启动内核。尽管 LKM 扩展了内核,但当涉及到高性能数据包处理等用例时,LKM 仍然受到内核本身的开销和网络限制的限制。此外,LKM 通常存在需要解决的内核版本兼容性问题,而非上游 LKM 可能存在信任问题。
有关更多信息,请参阅 瞻博网络 CN2 技术预览(技术预览) 或联系 瞻博网络支持。
eBPF 概述
Berkeley 数据包过滤器 (BPF) 提供了一种高效过滤数据包的方法,可避免从内核到用户空间的无用数据包副本。eBPF 是一种编写在内核空间中执行的代码的机制。开发人员使用受限制的 C 语言编写 eBPF 程序。这段代码称为字节码,由 Clang/LLVM 等编译器工具链编译和生成。
BPF 的一个安全优势是要求 BPF 程序在内核中处于活动状态之前必须经过验证。BPF 程序在内核的虚拟机 (VM) 中运行。验证过程可确保 BPF 程序在 VM 中运行到完成,而不会循环。验证过程还包括检查有效寄存器状态、程序大小和越界跳转。验证过程结束后,程序在内核中处于活动状态。
eBPF 内核数据平面
与传统 LKM 相比,eBPF 内核数据平面使您能够更安全、更高效地扩展和自定义内核的行为。与 LKM 一样,内核不会重新加载,内核的代码也不会更改。内核数据平面中的 eBPF 意味着用户空间应用程序可以在数据流经内核时对其进行处理。换言之,利用 eBPF 的应用在内核中编写和处理,大大减少了典型内核进程的网络层开销。高性能数据包处理、数据包过滤、跟踪和安全监控等任务都受益于 eBPF 提供的额外内核性能。
了解 eBPF 程序
事件和挂钩
eBPF 程序在事件驱动的环境中执行。事件是被监视、跟踪或分析的特定情况或条件。例如,到达某个接口的网络数据包就是一个事件。eBPF 程序附加到此事件,可以分析入口网络数据包或跟踪数据包的路径。
挂钩是指将 eBPF 程序附加到内核流中的特定执行点的过程。当钩子触发时,执行 eBPF 程序。eBPF 程序可以修改事件行为或记录与事件相关的数据。例如,系统调用可能充当挂钩。启动系统调用时,将触发钩子,使 eBPF 程序能够监视系统进程。
钩子的其他示例包括:
帮助程序函数
eBPF 程序调用助手函数,有效地使 eBPF 程序功能丰富。帮助程序函数执行以下作:
- 搜索、更新和删除键值对
- 生成伪随机数
- 获取和设置隧道元数据
- 将 eBPF 程序链接在一起(尾部调用)
- 使用套接字执行绑定、检索 Cookie 和重定向数据包等任务
内核定义了帮助函数,这意味着内核将 eBPF 程序进行的调用范围列入白名单。
eBPF 图
映射是 eBPF 程序使用的主要数据结构。映射允许在内核和用户空间之间进行数据通信。映射是一种键值存储,其中值被视为任意数据的二进制 blob。当不再需要映射时,将通过关闭关联的文件描述符将其移除。
每个映射都具有以下四个属性:其类型、可以包含的最大元素数量、其值的大小(以字节为单位)以及其键的大小(以字节为单位)。有多种地图类型可供选择,每种类型都提供不同的行为和权衡。通过使用 bpf_map_lookup_elem() 和 bpf_map_update_elem() 函数,可以从 eBPF 程序和用户空间程序访问和作所有映射。
执行 eBPF 程序
内核期望所有 eBPF 程序都以字节码的形式加载。因此,必须使用工具链将 eBPF 程序编译为字节码。编译完成后,eBPF 程序在部署到指定的挂钩点之前,会在内核中进行验证。eBPF 支持现代架构,这意味着它被升级为 64 位编码,总共有 11 个寄存器。这将 eBPF 与 x86_64、ARM 和 arm64 架构等的硬件紧密对应。
最重要的是,eBPF 解锁了对内核级事件的访问,避免了与直接更改内核代码相关的典型约束。总之,eBPF 的工作原理是:
-
将 eBPF 程序编译为字节码
-
验证程序是否在 VM 加载中的挂钩点安全执行
-
将程序附加到内核中由指定事件触发的挂钩点
-
在运行时编译为本机字节码以提高可移植性
-
调用帮助程序函数以在程序运行时作数据
-
使用映射(键值对)在用户空间和内核空间之间共享数据并维护状态
图 1:eBPF 程序执行
XDP概览
eXpress 数据路径 (XDP) 是内核网络堆栈中较新的可编程层。XDP 是连接和执行 eBPF 字节码的较新挂钩点之一。XDP 目标文件加载在多个内核和架构上,无需像传统 eBPF 程序那样进行重新编译。尽管与内核绕过解决方案相比,XDP程序缩小了性能差距,但这些程序不会绕过内核。以下是定义XDP的一些关键特征:
-
不打算比内核绕过更快
-
直接对数据包缓冲区进行作
-
减少每个数据包执行的指令数
内核网络堆栈专为套接字交付用例而设计。因此,它始终将所有传入数据包转换为套接字缓冲区 (SKB)。XDP支持在数据包转换为SKB之前对其进行处理。XDP与现有内核堆栈一起运行,所有这些都不需要修改内核。这使得 XDP 成为提供更大灵活性的内核替代方案。
XDP 和流量控制挂钩
所有 eBPF 字节码加载、映射创建、更新和读取都通过 BPF 系统调用进行处理。请务必注意,XDP 驱动程序挂钩仅在入口上可用。如果必须处理出口数据包,则可能需要使用替代挂钩,如 TC(流量控制)。在更广泛的层面上,XDP BPF 计划和 TC BPF 计划之间存在一些差异:
-
XDP挂钩发生得更早,从而提高了性能。
-
TC 钩子发生得更晚。因此,他们可以访问套接字缓冲区 (
sk_buff
) 结构和字段。这是导致 XDP 和 TC 挂钩之间性能差异的重要因素,sk_buff
因为数据结构包含元数据和信息,使内核的网络堆栈能够有效地处理和路由数据包。访问sk_buff
结构及其属性会产生一定的开销,因为内核堆栈必须分配、提取元数据和管理数据包,直到它到达 TC 钩子。
-
-
TC 钩子可以实现更好的数据包处理。
-
XDP 更适合完整的数据包重写。
-
该
sk_buff
用例包含大量特定于协议的详细信息,例如与 GSO(通用分段卸载)相关的状态信息。这使得仅通过重写数据包数据来切换协议变得具有挑战性。因此,需要通过 BPF 帮助程序函数完成进一步的转换。这些帮助程序函数可确保正确转换的sk_buff
内部组件。相比之下,用
xdp_buff
例不会遇到这些问题。它在数据包处理的早期阶段运行,甚至在内核分配sk_buff
.因此,在这种情况下进行任何类型的数据包重写都非常简单。
-
-
TC、eBPF 和 XDP 作为补充计划。
-
在用例需要数据包重写和数据复杂处理的情况下,可以通过运行两种类型的互补程序来克服每种程序类型的限制。例如,入口点的 XDP 程序可以执行完整的数据包重写,并将自定义元数据从 XDP BPF 传输到 TC BPF。然后,TC BPF 程序可以利用 XDP 元数据和
sk_buff
字段来执行复杂的数据包处理。
-
-
TC BPF 不需要更改硬件驱动程序。XDP 使用本机驱动程序模式以获得最佳性能。
图 2:XDP 和 TC 挂钩
XDP 控制和数据平面
使用 XDP,数据平面驻留在内核中,将其分为两个部分:内核核心和内核内 eBPF 程序。控制平面驻留在用户空间中,与内核空间一起运行以执行基本任务,例如将 BPF 程序加载到内核中以及通过使用映射管理程序行为。
XDP作和行为
在XDP挂钩点上运行的eBPF程序会根据程序处理数据包的方式返回以下作:
- 将入口数据包重定向到其他接口或用户空间。
-
将接收到的数据包通过到达的同一接口发回。
-
将数据包发送到内核堆栈进行处理。
-
丢弃数据包。
XDP 和 eBPF
XDP用作内核网络堆栈的软件卸载层。理想的用例是与 Linux 内核的网络堆栈结合使用以提高性能。例如,在 IP 路由用例中,内核处理路由表管理和邻居查找,而 XDP 通过使用助手功能高效访问这些路由表,从而加速数据包处理。查找完成并确定下一跃点后,将相应修改数据包标头。如果不需要修改,则数据包将按原样转发至内核。
需要注意的是,XDP对RX内存型号施加了以下限制:
- 目前不支持所有流行驱动程序的巨型帧,尽管 i40e、Veth 和 virtio 支持巨型帧。
-
内核空间中存在组播支持
利用基于 eBPF XDP 驱动程序模式的方法比专有内核模块(如 vRouter 内核模块)具有优势。其中一些优势包括改进的生命周期管理 (LCM)、安全性和性能。与集成到 Linux 上游树中的内核模块相比,在内核版本之间维护替代的 eBPF XDP 内核程序更有效。这是因为内核依赖项仅限于一小组 eBPF 辅助函数。
需要注意的是,如果控制器/磁带库崩溃,XDP 或 TC 程序仍会转发流量。重新连接或更换 XDP 或 TC 程序不会中断网络流量。
内核到用户空间通信
AF_XDP套接字用于促进在 XDP 钩子上作的内核空间程序与用户空间库之间的通信。AF_XDP是在 Linux 4.1.8 中引入的。AF_XDP 是一种新的套接字类型,用于将原始数据包快速传送到用户空间应用程序。AF_XDP利用 Linux 内核驱动程序实现高性能和可扩展性。值得注意的是,它还提供了一些功能,例如支持零副本数据传输、最大限度地减少对中断的依赖以及网络驱动程序优化,类似于数据平面开发套件 (DPDK) 和矢量数据包处理 (VPP) 所提供的功能。
流经AF_XDP套接字的数据包路径如下:
特定于 NIC 的驱动程序访问 NIC 上的传入数据包。驱动程序通过在绑定到相应接口的 XDP 钩子上运行的 eBPF 程序拦截和处理它们。XDP程序单独处理每个数据包,完成后,它会生成一个作,如XDP作和合作部分所述。通过利用XDP_REDIRECT,数据包通过与该接口关联的AF_XDP套接字定向到用户空间。这样可以直接从 NIC 接收原始数据包。通过绕过内核空间,避免了内核开销,数据包直接访问用户空间库。
启用 eBPF 数据平面
您可以通过在 vRouter 的规范中指定 agentModeType: xdp
来启用 eBPF 数据平面功能。由于并非所有 XDP 驱动程序当前都支持巨型帧,因此 CN2 部署程序会设置交换矩阵、环路和 veth 接口的最大传输单元 (MTU) 值。
以下示例是启用了 XDP-eBPF 的 vRouter 资源。
apiVersion: dataplane.juniper.net/v1 kind: Vrouter metadata: name: contrail-vrouter-masters namespace: contrail spec: agent: default: collectors: - localhost:6700 xmppAuthEnable: true sandesh: introspectSslEnable: true sandeshSslEnable: true agentModeType: xdp common: containers: - name: contrail-vrouter-agent image: contrail-vrouter-agent - name: contrail-watcher image: contrail-init - name: contrail-vrouter-telemetry-exporter image: contrail-vrouter-telemetry-exporter initContainers: - name: contrail-init image: contrail-init - name: contrail-cni-init image: contrail-cni-init
记下该 name: contrail-vrouter-masters
字段。由于此 vRouter 被指定为主节点,因此所有可用的主节点都设置为 agentModeType: xdp
应用部署程序时。此逻辑也适用于工作节点;如果在部署程序中为工作节点资源指定“xdp”,则所有工作器节点都将配置为 XDP-eBPF。
XDP 支持的驱动程序
下表列出了具有 eBPF-XDP 数据路径的经 CN2 验证的 XDP 驱动程序。
驱动程序名称 | 基础平台 |
---|---|
ENA | AWS |
维特 | Pod 连接 |
Virtio | CN2-On-CN2 |
i40e | 裸机 |
如果您的集群对 NIC 接口使用 AWS ENA 底层驱动程序,则部署程序会为物理接口设置 4 个接收 (RX) 和 4 个传输 (TX) 的队列配置。发生此更改的原因是 XDP 不支持 ENA 驱动程序的默认 RX 和 TX 队列计数。
eBPF 数据平面设计和 CN2 实现
eBPF 内核数据平面由两个核心组件组成:内核 XDP 程序和补充的用户空间库。这个由 eBPF 提供支持的数据平面是 vRouter 的第三个数据平面选项,另外两个是基于内核模块的 vRouter 和 DPDK。以下各节将介绍此技术提供的各种数据平面功能的设计和实现细节。
eBPF 数据平面组件
如前所述,eBPF 数据平面基于 XDP。对于需要数据包源自默认命名空间的功能,可能还需要出口 TC 挂钩。这是由于出口路径上没有 XDP 挂钩。TC 程序还引入了新的解析函数,因为 TC 钩子与 SKB 一起运行。

eBPF 数据平面不打算作为单独的守护程序;相反,它将作为库集成在控制器中(如果是 CN2,则为 vRouter 代理)。此实施分为两个组件:
-
用户空间库:用户空间库充当内核 eBPF 程序和在用户空间中运行的控制器(vRouter 代理)之间的接口。它公开 vRouter 代理用于对数据路径进行编程的 API。此组件执行以下功能:
-
将 eBPF 程序加载和卸载到内核中
-
将 eBPF 程序绑定到接口
-
配置和读取映射条目
-
转发、修改和过滤用户空间数据路径中的数据包
-
-
内核空间程序:这些 eBPF 程序构成了在内核核心中运行的主要数据路径。该程序加载到接口的 XDP 钩子上,并处理这些接口的所有传入数据包。TC 也用于环路接口的出口。如前所述,用户空间 eBPF 库负责将 eBPF 程序加载到内核核心中。内核空间程序执行以下作:
-
转发、修改和过滤内核中的数据包
-
使用助手函数与内核的其余部分进行交互
-
创建、更新和读取映射(映射是用户空间库和内核空间程序之间共享的数据结构)
-
vRouter 代理通过用户空间库通过以下几种方式与 eBPF 数据平面进行交互:
-
控制器(vRouter 代理)直接调用用户空间 eBPF 库公开的 API。
-
库公开的公共 API 取代了用于其他数据路径的 ksync/Sandesh 层提供的功能
-
代理在用户空间库中注册回调,以查找流量未命中并接收关注的数据包
-
- 该库还侦听 netlink 套接字事件;用户空间库可以监听来自内核的路由、ARP 和地址更新
- 该库还会在内核中查询其他信息