伯克利包过滤器(BPF)
2023-07-01 20:19:17 阿炯

Berkeley Packet Filter,缩写 BPF,是类Unix系统上数据链路层的一种原始接口,提供原始链路层封包的收发。除此之外,如果网卡驱动支持混杂模式,那么它可以让网卡处于此种模式,这样可以收到网络上的所有包,不管他们的目的地是不是所在主机。另外其支持过滤数据包——用户态的进程可以提供一个过滤程序来声明它想收到哪些数据包。通过这种过滤可以避免从操作系统内核向用户态复制其他对用户态程序无用的数据包,从而极大地提高性能。

BPF有时也只表示过滤机制,而不是整个接口。一些系统,比如Linux和Tru64 Unix,提供了数据链路层的原始接口,而不是BPF的接口,但使用了BPF的过滤机制。BSD 内核实现例程如 bpf_mtap()和 bpf_tap(),以BPF_MTAP()和 BPF_TAP()等宏定义的形式进行包裹由网卡驱动(以及伪驱动pseudo-drivers) 向BPF机制发送进出的封包。原始的论文由Steven McCanne 和 Van Jacobson于1992年在劳伦斯伯克利国家实验室工作时编写,于1993年在San Diego举办的USENIX冬季会议上正式发表。

许多版本的Unix操作系统提供了用于捕获数据包的设施,使得监控当前网络情况成为了可能。但因为网络监控程序运行在用户态,数据包必须被拷贝来通过内核与用户态之间的边界。可以通过使用一种被称为“数据包过滤器”的内核代理来减少拷贝的数量,它会尽量早地丢弃不想要的数据包。早先的数据包过滤器被实现为基于栈的虚拟机,在RISC CPU上性能不佳。BPF使用了一种新的基于寄存器(Register)的虚拟机,在性能上有显著提升。传统的Unix BPF实现能够被用于用户态,尽管它是为内核态编写。这是通过编译时的条件预处理完成的。

BPF的过滤功能是以对于BPF虚拟机机器语言的一种解释器的形式实现的,使用这种语言编写的程序可以抓取数据包,对数据包中的数据采取算术操作,并将结果与常量或数据包中的数据或结果中的测试位比较,根据比较的结果决定接受还是拒绝封包。

包括FreeBSD和WinPcap在内的一些平台,使用即时编译(JIT)编译器来把BPF指令转换为原始字节码,以进一步提高性能。Linux有一个BPF JIT编译器,但被默认禁用。此虚拟机语言的内核态解释器则被用于其他操作系统的原始数据链路机制,例如Tru64 Unix系统,以及Linux内核中的套接字过滤器,和WinPcap数据包抓取机制。

用户态解释器由实现了pcap API的libpcap/WinPcap提供,因此在对此过滤机制没有内核态支持的系统上抓取数据包时,数据包可以在内核态过滤,使用pcap API的代码可以工作于此两种模式;在使用用户态过滤的系统上,所有数据包由内核态复制到用户态,包括将被过滤出去的封包。这种解释器也可以用于包含由pcap抓取的封包的文件。

2007年,Robert Watson与Christian Peron为FreeBSD操作系统中BPF的实现加入了zero-copy buffer extension,使得驱动程序中断处理器中的内核封包抓取能直接向用户内存写,以避免BPF设备收到的所有封包数据的两次复制需要,一份副本存在于用户进程的接收路径中,这保证了不同BPF设备调用者的独立性,同时使得只把封包头部放入BPF缓冲区,而不是复制整个封包数据。Will Drewry为seccomp(安全计算)系统调用策略添加了BPF过滤器,这也是BPF第一次在网络领域之外的使用。

eBPF

从3.18版本开始,Linux 内核提供了一种扩展的BPF虚拟机,被称为“extended BPF”,简称为eBPF。它能够被用于非网络相关的功能,比如附在不同的tracepoints上,从而获取当前内核运行的许多信息。传统的BPF,现在被称为cBPF(classical BPF)。

BPF extensibility and applicability to networking, tracing, security in the linux kernel and several user space implementations of BPF virtual machine led to a number of misunderstanding on what BPF actually is.

eBPF由Alexei Starovoitov在PluMgrid工作时设计,这家公司专注于研究新的方法来设计软件定义网络解决方案。在它只是一个提议时,Daniel Borkmann——Red Hat公司的内核工程师,帮助修改使得它能够进入内核代码并完全替代已有的BPF实现。这是二十年来BPF首次主要的更新,使得BPF成为了一个通用的虚拟机。eBPF现在被应用于网络、跟踪、内核优化、硬件建模等领域。其被Linux内核合并的事件线如下:
2014年3月,eBPF补丁被合并到Linux内核。
2014年6月,JIT组件被合并到内核3.15版本。
2014年12月,bpf系统调用被合并到内核3.18版本。
在后来的Linux 4.x系列版本中又添加了对于kprobes、uprobes、tracepoints以及perf_events的支持。

因为eBPF虚拟机使用的是类似于汇编语言的指令,对于程序编写来说直接使用难度非常大。和将C语言生成汇编语言类似,现在的编译器正在逐步完善从更高级的语言生成BPF虚拟机使用的指令。LLVM在3.7版本开始支持BPF作为后端输出。GCC 10也将会支持BPF作为后端。BCC是IOVisor项目下的编译器工具集,用于创建内核跟踪(tracing)工具。bpftrace是为eBPF设计的高级跟踪语言,在Linux内核(4.x)中提供。

eBPF config for kernel(5.15)

CONFIG_BPF=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT_DEFAULT_ON=y
CONFIG_BPF_UNPRIV_DEFAULT_OFF=y
CONFIG_BPF_LSM=y
CONFIG_CGROUP_BPF=y
CONFIG_NETFILTER_XT_MATCH_BPF=m
CONFIG_NET_CLS_BPF=m
CONFIG_NET_ACT_BPF=m
CONFIG_BPF_STREAM_PARSER=y
CONFIG_LWTUNNEL_BPF=y
CONFIG_BPF_EVENTS=y
CONFIG_BPF_KPROBE_OVERRIDE=y
CONFIG_DEBUG_INFO_BTF=y


Oracle基于BPF的Linux系统自动调优工具-bpftune

Oracle 于2023年6月开源了一个基于 BPF 的 Linux 参数自动调优工具bpftune,这是一个自动配置器,可以监控 Linux 系统的工作负载并自动设置正确的内核参数值。Linux 内核包含超过 1500 个可调参数,bpftune 会随着系统的状态不断地自动调整多项参数,一切参数的更改都是轻量级且完全实时,无需重新启动设备即可生效。其主要好处是:
1.使用 BPF(伯克利数据包过滤器)可观测性功能持续监控和调整系统行为。
2.由于可以使用 BPF 观察系统状态的更多细节,因此可以在细粒度级别调整系统行为。

目前 bpftune 可以自动调整的参数:
1.Congestion tuner: 自动调节拥塞控制算法的选择。
2.Neighbour table tuner: 在接近满载时通过增长表来自动调整 Neighbour table 的大小。
3.Route table tuner: 在接近满时通过增长表来自动调整路由表大小。
4.sysctl tuner: 监视 sysctl 设置,如果它与自动调整的 sysctl 值冲突,则禁用关联的调谐器。
5.TCP buffer tuner: 自动调整最大和初始缓冲区大小。
6.net buffer tuner: 自动调整与核心网络相关的可调参数。
7.netns tuner: 监控网络命名空间的添加和删除,有助于增强 bpftune 整体的命名空间感知能力。

Oracle Linux 用户可以使用 DNF 包管理器轻松安装 bpftune。其代码在 GPLv2 许可下开源,可从 GitHub 仓库获取。


针对Windows的eBPF实现:eBPF for Windows

此项目允许在 Windows 上使用 Linux 生态中熟悉的现有 eBPF 工具链和应用接口。也就是说,该项目将现有的 eBPF 项目作为子模块,并添加中间层,使其能在 Windows 上运行。下图显示了本项目的基本架构和相关组件:


现有的 eBPF 工具链(clang 等)可用于从各种语言的源代码生成 eBPF 字节码。字节码可以被任何应用程序使用,也可以通过 bpftool 或 Netsh 命令行工具使用。

eBPF 字节码会被发送到一个静态验证器(PREVAIL 验证器),该验证器托管在一个安全的用户模式环境中,如系统服务、飞地或可信虚拟机。如果 eBPF 程序通过了验证器的所有检查,就可以加载到内核模式执行上下文中。通常情况下,这是通过 JIT 编译器(通过 uBPF JIT 编译器)将程序编译成本地代码并传递给执行上下文来实现的。在调试构建中,字节码可直接加载到解释器(从内核模式执行上下文中的 uBPF),但解释器不会仅在调试模式中支持,不提供发布构建频道支持,因为它被认为安全性较低。

安装到内核模式执行上下文中的 eBPF 程序可以附加到各种钩子上,并调用 eBPF shim 公开的各种辅助 API,eBPF shim 内部封装了公共 Windows 内核 API,允许在现有版本的 Windows 上使用 eBPF。许多辅助程序已经存在,随着时间的推移,还将添加更多钩子和辅助程序。

此项目并不是 eBPF 的分支,eBPF for Windows 利用现有项目,包括 IOVisor uBPF 项目和 PREVAIL 校验器,通过为代码添加 Windows 特定的托管环境,将它们运行在 Windows 上。Linux 提供了许多钩子和辅助工具,其中有些是 Linux 特有的(例如,使用 Linux 内部数据结构),不适用于其它平台,而其它钩子和辅助工具则是通用的,目的是为 eBPF 程序提供支持。

eBPF 还可以与 HyperVisor-enforced Code Integrity(HyperVisor 强制代码完整性,HVCI)一起使用。启用 HVCI 后,eBPF 程序无法进行 JIT 编译,但可以以本地模式或解释模式运行。