BPF和XDP参考指南

说明:

内容来自cilium官方文档 BPF and XDP Reference Guide 。因为原文实在太长,只翻译了其中少量部分。

BPF是Linux内核中一种高度灵活和高效的虚拟机类构造,允许以安全的方式在各个挂钩点执行字节码。 它用于许多Linux内核子系统,最突出的是网络,跟踪和安全性(例如沙盒)。

尽管BPF自1992年以来一直存在,但本文档涵盖了扩展的Berkeley Packet Filter (eBPF)版本,该版本最初出现在内核3.18中,并且现在被称为“经典”BPF(cBPF)的原始版本大多已经过时。许多人都知道cBPFtcpdump使用的包过滤语言。如今,Linux内核仅运行eBPF,并且加载的cBPF字节码会在程序执行之前被透明地转换为内核中的eBPF表示。 除非指出eBPF和cBPF之间存在明显差异,否则本文档通常会引用术语BPF。

尽管Berkeley Packet Filter这个名称暗示了数据包过滤的特定用途,但是这些指令集足够通用而灵活,除了网络之外还有许多BPF用例。有关使用BPF的项目列表,请参阅Further Reading

Cilium在其数据路径中重度使用BPF,有关详细信息,请参阅 概念。 本章的目标是提供BPF参考指南,以便了解BPF,其网络特定用途,包括使用tc(traffic control)和XDP(eXpress Data Path)加载BPF程序,以及帮助开发Cilium的BPF模板。

BPF架构

BPF不仅通过提供其指令集来定义自己,而且还通过提供更多的周边基础设施来定义自己,例如充当有效键/值存储的map,与内核功能交互并利用内核功能的辅助函数,调用其他BPF程序的尾调用 ,安全加固原语,用于固定对象(maps,programs)的伪文件系统,以及允许将BPF卸载到比如网卡的基础设置。

LLVM提供BPF后端,因此像clang这样的工具可以用来将C编译成BPF目标文件,然后可以将其加载到内核中。BPF与Linux内核密切相关,可在不牺牲原生内核性能的情况下实现完全可编程性。

最后但同样重要的是,使用BPF的内核子系统也是BPF基础设施的一部分。本文档中讨论的两个主要子系统是tc和XDP,这些可以附加BPF程序。XDP BPF程序在最早的网络驱动程序阶段附加,并在数据包接收时触发BPF程序的运行。根据定义,这实现了最佳的包处理性能,因为包不能在软件中更早的时间点处理。但是,由于此处理在网络堆栈中如此早地发生,因此堆栈尚未从数据包中提取元数据。另一方面,tc BPF程序稍后在内核堆栈中执行,因此它们可以访问更多元数据和核心内核功能。除了tc和XDP程序之外,还有各种其他内核子系统,它们使用BPF,例如跟踪(kprobes,uprobes,tracepoints等)。

以下小节提供了有关BPF架构各个方面的更多详细信息。

指令集

BPF是一个通用的RISC指令集,最初设计用于在C的子集中编写程序,可以通过编译器后端(例如LLVM)将其编译为BPF指令,以便内核可以在以后通过内核中的JIT编译器映射它们到原生操作码中,以在内核中实现最佳执行性能。

将这些指令推送到内核中的优点包括:

  • 使内核可编程而无需跨越内核/用户空间边界。例如,与网络相关的BPF程序,比如Cilium,可以实现灵活的容器策略,负载均衡和其他方法,而无需将数据包移动到用户空间并返回内核。BPF程序和内核/用户空间之间的状态仍然可以在需要时通过maps共享。
  • 考虑到可编程数据路径的灵活性,程序可以通过编译掉用例不需要的功能来极大优化性能。例如,如果容器不需要IPv4,则可以构建BPF程序以仅处理IPv6以便在快速路径中节省资源。
  • 在网络(如tc和XDP)的情况下,BPF程序可以原子方式更新,而不必重新启动内核,系统服务或容器,并且不会出现流量中断。此外,还可以通过BPF maps在整个更新过程中维护任何程序状态。
  • BPF为用户空间提供稳定的ABI,不需要任何第三方内核模块。BPF是Linux内核的核心部分,随处可见,并保证现有BPF程序继续运行在较新的内核版本上。此保证与内核提供用户空间应用程序的系统调用的保证相同。此外,BPF程序可跨不同架构移植。
  • BPF程序与内核协同工作,它们利用现有的内核基础结构(例如驱动程序,网络设备,隧道,协议栈,套接字)和工具(例如iproute2)以及内核提供的安全保证。与内核模块不同,BPF程序通过内核验证程序进行验证,以确保它们不会崩溃内核,始终终止等。例如,XDP程序重用现有的内核内驱动程序并对提供的包含数据包帧的DMA缓冲区进行操作,而不像在其他模型中那样将他们或者整个驱动程序暴露给用户空间。此外,XDP程序重用现有的堆栈而不是绕过它。BPF可以被认为是内核工具的通用“粘合代码”,用于制作解决特定用例的程序。

在内核中执行BPF程序始终是事件驱动的! 例如,在ingress路径上附加了BPF程序的网络设备将在接收到数据包后触发程序的执行,具有附加BPF程序的kprobes的内核地址将在该地址处的代码获得执行后捕获(trap),然后调用kprobes回调函数进行检测,随后触发执行附加到它的BPF程序。

备注:此处指令集的细节内容忽略,请见原文。后续也会有大段内容忽略,不再提示。

辅助函数

辅助函数是一个概念,它使BPF程序能够查询核心内核定义的函数调用集,以便从/向内核检索/推送数据。对于每种BPF程序类型,可用的辅助函数可能不同,例如,与附加到tc层的BPF程序相比,附加到套接字的BPF程序仅允许调用辅助程序子集。用于轻量级隧道的封装和解封装助手构成了仅可用于较低tc层的功能的示例,而用于将通知推送到用户空间的事件输出助手可用于tc和XDP程序。

Maps

map是驻留在内核空间中的高效键/值存储。 可以从BPF程序访问它们,以便在多个BPF程序调用之间保持状态。它们也可以通过用户空间的文件描述符进行访问,并且可以与其他BPF程序或用户空间应用程序任意共享。

彼此共享mac的BPF程序不需要具有相同的程序类型,例如,跟踪程序可以与网络程序共享map。 单个BPF程序目前可以直接访问多达64个不同的map。

map实现由核心内核提供。 存在可以读/写任意数据的具有每CPU和非每CPU风格的通用map,但是也存在一些与辅助函数一起使用的非泛型map。

对象固定

。。。。。。

编程类型

在撰写本文时,有18种不同的BPF程序类型,网络的两种主要类型在下面的小节中进一步说明,即XDP BPF程序以及tc BPF程序。 LLVM,iproute2或其他工具的这两种程序类型的大量使用示例遍布整个工具链部分,此处不再介绍。 相反,本节重点介绍其架构,概念和用例。

XDP

XDP代表eXpress Data Path,为BPF提供框架,支持Linux内核中的高性能可编程数据包处理。它在软件中尽可能早地运行BPF程序,即在网络驱动程序接收数据包时。

此时,在快速路径中,驱动程序只是从其接收环中拾取数据包,而没有进行任何昂贵的操作,例如分配skb以便将数据包进一步推送到网络堆栈,也无需将数据包推入GRO引擎。因此,XDP BPF程序在CPU可用于处理的最早时刻执行。

XDP与Linux内核及其基础设施协同工作,这意味着内核不会像仅在用户空间中运行的各种网络框架那样被绕过。将数据包保留在内核空间中有几个主要优点:

  • XDP能够重用所有上游开发的内核网络驱动程序,用户空间工具,甚至其他可用的内核基础设施,如路由表,套接字等。
  • 驻留在内核空间中,XDP与内核的其余部分具有相同的安全模型,用于访问硬件。
  • 不需要跨越内核/用户空间边界,因为已处理的数据包已经驻留在内核中,因此可以灵活地将数据包转发到其他内核实体,例如容器或内核的网络堆栈本身使用的命名空间。这在Meltdown和Spectre时期特别相关。
  • 将数据包从XDP发送到内核的强大,广泛使用且高效的TCP/IP堆栈是非常可能的,允许完全重用,并且不需要像用户空间框架那样维护单独的TCP/IP堆栈。
  • BPF的使用允许完全可编程性,保持稳定的ABI与内核的系统调用ABI具有相同的“永不中断用户空间”保证,并且与模块相比,它还提供安全措施,这要归功于BPF验证器确保内核操作的稳定性。
  • XDP通常允许在运行时自动交换程序,而不会出现任何网络流量中断甚至内核/系统重启。
  • XDP允许灵活地构建集成到内核中的工作负载。 例如,它可以在“busy polling”或“interrupt driven”模式下操作。不需要将CPU显式专用于XDP。 没有特殊的硬件要求,也不依赖于大页面。
  • XDP不需要任何第三方内核模块或许可。它是一个长期的架构解决方案,是Linux内核的核心部分,由内核社区开发。
  • 在运行4.8或更高的内核的主要发行版中,已经带有XDP并启用,并支持大多数主要的10G或更高版本的网络驱动程序。

XDP的用例

本小节介绍了XDP的一些主要用例。 该列表并非详尽无遗,并且考虑到XDP和BPF的可编程性和效率,可以轻松地对其进行调整以解决非常具体的用例。

TBD