使用 eBPF 在 Linux 内核中过滤数据包简介

了解如何使用扩展 Berkeley 数据包过滤器 (eBPF),它是原始 Berkeley 数据包过滤器的增强版,用于在 Linux 内核中过滤数据包。
380 位读者喜欢这篇文章。
The revenge of Linux

Opensource.com

1992 年,劳伦斯伯克利实验室的 Steven McCanne 和 Van Jacobson 为 BSD Unix 系统提出了一个解决方案,通过实现内核数据包过滤器(称为 Berkeley 数据包过滤器 (BPF))来最大限度地减少不必要的网络数据包副本到用户空间。1997 年,它被引入 Linux 内核版本 2.1.75。

BPF 的目的是尽早过滤掉所有不需要的数据包,因此过滤机制必须从 tcpdump 等用户空间实用程序转移到内核虚拟机中。它通过系统调用 bpf() 发送一组类似汇编的指令,用于从用户空间到内核过滤必要的数据包。内核在加载程序之前对其进行静态分析,并确保它们不会挂起或损害正在运行的系统。

BPF 机器

BPF 机器抽象 包括 一个累加器、一个索引寄存器 (x)、一个暂存内存存储和一个隐式程序计数器。它有一小组算术、逻辑和跳转指令。累加器用于算术运算,而索引寄存器提供数据包或暂存内存区域的偏移量。这是一个用 BPF 字节码编写的小型 BPF 程序示例

    ldh    [12]
    jeq    #ETHERTYPE_IP, l1, l2
    l1:    ret    #TRUE
    l2:    ret    #0

ldh 指令从以太网数据包中偏移量为 12 的位置(即以太网类型字段)将半字(16 位)值加载到累加器中。如果它不是 IP 数据包,则将返回 0,并且该数据包将被拒绝。

BPF 即时编译器

2011 年,内核中引入了即时 (JIT) 编译器,以加速 BPF 字节码的执行。此编译器将 BPF 字节码转换为主机系统的汇编代码。x86-64、SPARC、PowerPC、ARM、ARM64、MIPS 和 System 390 都有这样的编译器,可以通过 CONFIG_BPF_JIT 启用。

eBPF 机器

扩展 BPF (eBPF) 是对 BPF(现在称为 cBPF,代表经典 BPF)的增强,它具有更多资源,例如 10 个寄存器和 1-8 字节的加载/存储指令。BPF 只有向前跳转,而 eBPF 既有向后跳转,也有向前跳转,因此可以存在循环,当然,内核会确保循环正确终止。它还包括一个名为 maps 的全局数据存储,并且此 maps 状态在事件之间持续存在。因此,eBPF 也可用于聚合事件的统计信息。此外,eBPF 程序可以用类似 C 的函数编写,可以使用 GNU 编译器集合 (GCC)/LLVM 编译器进行编译。eBPF 被设计为使用一对一映射进行 JIT 编译,因此它可以生成非常优化的代码,其性能与本机编译的代码一样快。

eBPF 和跟踪回顾

上游内核开发

Linux 中的传统内置跟踪器以后处理方式使用,它们转储固定事件的详细信息,然后 perftrace-cmd 等用户空间工具可以进行后处理以获取所需的信息(例如,perf stat);但是,eBPF 可以在内核上下文中准备用户信息,并且仅将需要的信息传输到用户空间。到目前为止,上游内核中已经实现了使用 eBPF 过滤 kprobestracepointsperf_events 的支持。它们已在 Arch x86-64AArch64S390xPowerPC 64SPARC64 中得到支持。

有关更多信息,请查看以下 Linux 内核文件

用户空间开发

用户空间工具已针对内核树内和内核树外开发。查看以下文件和目录,了解有关上游内核中 eBPF 使用的更多信息

BCC 是另一个内核树外工具,它具有用于特定用途的高效内核跟踪程序(例如,funccount,它计算与模式匹配的函数)。

Perf 也有一个 BPF 接口,可用于将 eBPF 对象加载到内核中。

eBPF 跟踪:用户空间到内核空间流程

BPF 系统调用和 BPF maps 是可以与 eBPF 内核交互的两个有用实体。

BPF 系统调用

用户可以使用 bpf() 系统调用与 eBPF 内核交互,其原型为

int bpf(int cmd, union bpf_attr *attr, unsigned int size);

以下是这些参数的摘要;有关更多详细信息,请参阅 man 手册 BPF

  • cmd 可以是任何定义的 enum bpf_cmd,它告诉内核关于 map 区域的管理(例如,创建、更新、删除、查找元素、附加或分离程序等)。
  • attr 可以是用户定义的结构,可以由其各自的命令使用。
  • sizeattr 的大小。

BPF maps

eBPF 跟踪在内核域本身中计算统计信息。我们将需要在内核中某种类型的内存/数据结构来创建此类统计信息。Maps 是一种通用数据结构,以键值对的形式存储不同类型的数据。它们允许在 eBPF 内核程序之间以及内核和用户空间应用程序之间共享数据。

Maps 的重要属性包括

  • 类型 (map_type)
  • 最大元素数 (max_entries)
  • 键大小(以字节为单位)(key_size)
  • 值大小(以字节为单位)(value_size)

有不同类型的 maps(例如,哈希、数组、程序数组等),这些 maps 基于用途或需求选择。例如,如果键是字符串(或不是来自整数序列),则可以使用哈希 map 来加快查找速度;但是,如果键像索引一样,则数组 map 将提供最快的查找方法。

键不能大于 key_size,它不能存储大于 value_size 的值,并且 max_entries 是可以在 map 中存储的最大键值对数。

以下是两个重要的命令,请注意

  • BPF_PROG_LOAD: 以下是此程序的重要属性

prog_type:对跟踪有用的程序类型

BPF_PROG_TYPE_KPROBE
BPF_PROG_TYPE_TRACEPOINT
BPF_PROG_TYPE_PERF_EVENT

insns:指向 struct bpf_insn 的指针,其中包含由内核 BPF 虚拟机执行的 BPF 指令

insn_cntinsns 中存在的指令总数

license:string,它必须与 GPL 兼容才能调用标记为 gpl_only 的辅助函数

kern_version:内核树的版本

  • BPF_MAP_CREATE: 接受 BPF maps 部分中讨论的属性,创建一个新 map,然后返回一个引用该 map 的新文件描述符。返回的 map_fd 可用于查找或更新 map 元素,命令如 BPF_MAP_LOOKUP_ELEMBPF_MAP_UPDATE_ELEMBPF_MAP_DELETE_ELEMBPF_MAP_GET_NEXT_KEY。这些 map 操作命令接受具有 map_fd、键和值的属性。

要了解其工作原理,请查看 GitHub 上的这个 独立的 eBPF 演示;它不需要任何其他 eBPF 库代码。它有一个小型库来加载 BPF 内核代码的不同部分(bpf_load.c),然后在 bpf() 系统调用(bpf.c)之上使用包装器函数来操作 map 和加载内核 BPF 代码。当我们编译此代码时,我们得到两个可执行文件:memcpy_kprobememcpy_stat

  • memcpy_kprobe: 对于每个应用程序,我们都有一个 _kern 文件和另一个 _user 文件。_kern 文件有一个函数,int bpf_prog1(struct pt_regs *ctx)。此函数在内核中执行,因此它可以访问内核变量和函数。Memcpy_kprobe_kern.c 分别具有程序、许可证和版本的三个节映射。来自这些节的数据是系统调用 bpf(BPF_PROG_LOAD,...) 的属性的一部分,然后内核根据 prog_type 属性执行加载的 BPF 指令。因此,当在内核 memcpy() 的入口处检测到 kprobe 时,将执行 memcpy_kprobe_kern.c 中的 BPF 代码。当执行此 BPF 代码时,它将读取 memcpy() 的第三个参数,例如复制的大小,然后将在跟踪缓冲区中打印一个 memcpy size 语句。Memcpy_kprobe_user.c 加载内核程序并持续读取跟踪缓冲区以显示内核 eBPF 程序正在写入的内容。
  • memcpy_stat: 这会在内核本身中准备 memcpy() 复制大小的统计信息。Memcpy_stat_kern.c 还有一个节,作为 maps. bpf_prog1() 读取 memcpy() 大小并更新 map 表。相应的用户空间程序 memcpy_stat_user.c 每两秒读取一次 map 表,并在控制台上打印统计信息。

这些简单的示例解释了如何编写用于内核跟踪和统计信息准备的内核 eBPF 代码。

如果您使用过 eBPF 并有建议分享,请在评论中留言。

标签
User profile image.
Pratyush 在 Red Hat 担任 Linux 内核专家。他主要负责处理 Red Hat 产品和上游遇到的多个 kexec/kdump 问题。他还处理 Red Hat 支持的 ARM64 平台周围的其他内核调试/跟踪/性能问题。除了 Linux 内核之外,他还为上游 kexec-tools 和 makedumpfile 项目做出了贡献。

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.