当我需要使用 捕获一些数据包tcpdump
时,我使用如下命令:
tcpdump -i eth0 "dst host 192.168.1.0"
我一直认为dst 主机 192.168.1.0部分称为 BPF,Berkeley Packet Filter。对我来说,这是一种过滤网络数据包的简单语言。但是今天我的室友告诉我,BPF 可以用来捕获性能信息。根据他的描述,它就像perfmon
Windows上的工具。这是真的吗?它与我在问题开头提到的 BPF 相同吗?
当我需要使用 捕获一些数据包tcpdump
时,我使用如下命令:
tcpdump -i eth0 "dst host 192.168.1.0"
我一直认为dst 主机 192.168.1.0部分称为 BPF,Berkeley Packet Filter。对我来说,这是一种过滤网络数据包的简单语言。但是今天我的室友告诉我,BPF 可以用来捕获性能信息。根据他的描述,它就像perfmon
Windows上的工具。这是真的吗?它与我在问题开头提到的 BPF 相同吗?
什么是 BPF?
BPF(或更常见的扩展版本,eBPF)是一种最初专门用于过滤数据包的语言,但它的功能远不止于此。正如您所指出的,在 Linux 上,它可以用于许多其他事情,包括用于安全性的系统调用过滤器和性能监控。虽然 Windows 确实添加了 eBPF 支持,但这不是 Windows
perfmon
实用程序使用的。Windows 仅添加了对依赖于操作系统对 eBPF 支持的非 Windows 实用程序的兼容性支持。eBPF 程序不在用户空间中执行。相反,应用程序创建一个 eBPF 程序并将其发送到内核,内核将执行它。它实际上是以解释器的形式在内核中实现的虚拟处理器的机器代码,尽管它也可以使用JIT 编译来显着提高性能。该程序可以访问内核中的一些基本接口,包括与性能和网络相关的接口。eBPF 程序然后与内核通信以向其提供计算结果(例如丢弃数据包)。
eBPF 程序的限制
为了防止拒绝服务攻击或意外崩溃,内核在编译之前首先验证代码。在运行之前,代码需要经过几个重要的检查:
对于非特权用户,该程序总共包含不超过4096条指令。
除了有界循环和函数调用外,不能发生向后跳转。
没有总是无法访问的指令。
结果是验证者必须能够证明 eBPF 程序停止。当然,它还没有找到解决停止问题的方法,这就是为什么它只接受它知道会停止的程序的原因。为此,它将程序表示为有向无环图。除此之外,它还试图通过防止指针的实际值被泄露,同时仍然允许对其执行有限的操作来防止信息泄漏和越界内存访问:
指针不能作为可检查的值进行比较、存储或返回。
指针运算只能针对标量(不是从指针派生的值)进行。
没有指针算法可以导致指向指定的内存映射之外。
验证器相当复杂,而且做得更多,尽管它本身就是严重 安全 漏洞的根源,至少在没有为非特权用户禁用
bpf(2)
系统调用的情况下。查看代码
该
dst host 192.168.1.0
命令的组件不是 BPF。这只是tcpdump
. 但是,您给它的命令用于生成 BPF 程序,然后将其发送到内核。请注意,在这种情况下使用的不是 eBPF,而是较旧的 cBPF。两者之间有几个重要的区别(尽管内核在内部将 cBPF 转换为 eBPF)。该-d
标志可用于查看要发送到内核的 cBPF 代码:更复杂的过滤器会导致更复杂的字节码。尝试手册页中的一些示例并附加
-d
标志以查看将哪些字节码加载到内核中。为了了解如何阅读反汇编,请查看BPF 过滤器文档。如果你正在阅读 eBPF 程序,你应该看看虚拟 CPU 的eBPF 指令集。理解代码
为简单起见,我假设您指定了 192.168.1.1 而不是 192.168.1.0 的目标 IP,并且只想匹配 IPv4,这会大大缩减代码,因为它不再需要处理 IPv6:
让我们来看看上面的字节码实际上做了什么。每次在指定的接口上接收到数据包时,都会运行 BPF 字节码。数据包内容(包括以太网标头,如果适用)被放入 BPF 代码可以访问的缓冲区中。如果数据包与过滤器匹配,代码将返回捕获缓冲区的大小(默认为 262144 字节),否则返回 0。
假设您正在运行此过滤器,并且它接收到一个数据包,该数据包从 192.168.1.142 向 192.168.1.1 发送带有空有效负载的 ICMP 消息。源 MAC 是 aa:aa:aa:aa:aa:aa,目标 MAC 是 bb:bb:bb:bb:bb:bb。以太网帧的十六进制内容为:
第一条指令是
ldh [12]
. 这会将位于数据包偏移 12 字节的半字(两个字节)加载到 A 寄存器中。这是值 0x0800(请记住,网络数据始终是大端的)。第二条指令是jeq #0x800
,它将立即数与 A 寄存器中的值进行比较。如果它们相等,它将跳转到指令 2,否则跳转到 5。以太网帧中该偏移处的值 0x800 指定 IPv4 协议。因为比较结果为真,所以代码现在跳转到指令 2。如果负载不是 IPv4,它会跳转到 5。指令 2(第三条)是
ld [30]
. 这会将偏移 30 处的整个 4 字节字加载到 A 寄存器中。在我们的以太网帧中,这是 0xc0a80101。下一条指令jeq #0xc0a80101
将立即数与 A 寄存器的内容进行比较,如果为真则跳转到 4,否则跳转到 5。该值是目标地址(0xc0a80101 是 192.168.1.1 的大端表示)。这些值确实匹配,因此程序计数器现在设置为 4。指令 4 是
ret #262144
。这将终止 BPF 程序并将整数 262144 返回给调用程序。这告诉调用程序,tcpdump
在这种情况下,数据包已被过滤器捕获,因此它向内核请求数据包的内容,对其进行更彻底的解码,并将信息写入您的终端。如果目标地址与过滤器查找的内容不匹配,或者协议类型不是 IPv4,则代码将跳转到指令 5,在那里它会遇到ret #0
. 这将在没有匹配的情况下终止过滤器。如果数据包中偏移量 12 处的半字是 0x800 并且偏移量 30 处的字是 0xc0a80101,则这只是返回 262144 的一种方式,否则返回 0。因为这一切都在内核中完成(可选地在被 JIT 引擎转换为本机代码之后),不需要昂贵的上下文切换或在内核空间和用户空间之间传递缓冲区,因此过滤器速度很快。
更高级的例子
BPF 代码不限于由
tcpdump
. 许多其他实用程序可以使用它。xt_bpf
您甚至可以使用该模块创建带有 BPF 过滤器的 iptables 规则!但是,在生成字节码时必须小心,tcpdump -ddd
因为它需要使用第 2 层标头,而 iptables 不会。要使它们兼容,您所要做的就是调整偏移量。此外,还提供了许多辅助函数,这些函数提供了通过读取原始数据包内容无法获得的信息,例如数据包长度、有效负载起始偏移量、接收数据包的 CPU、NetFilter 标记等。过滤器文档:
支持的 BPF 扩展有:
例如,要匹配在 CPU 3 上接收到的所有数据包,您可以执行以下操作:
请注意,这是使用与 兼容的 BPF 程序集语法
bpf_asm
,而此处的其他程序集清单正在使用tcpdump
语法。主要区别在于前者的语法使用命名标签,而后者的 BPF 语法用行号标记每条指令。此程序集转换为以下字节码(逗号分隔指令):这可以与
iptables
使用xt_bpf
模块一起使用:对于在该 CPU 上接收到的任何数据包,这将跳转到目标链
CPU3
。如果这看起来很强大,请记住这都是 cBPF。尽管 cBPF 在内部被翻译成 eBPF,但与原始 eBPF 所能做的相比,这一切都微不足道!
了解更多信息
我强烈建议您阅读这篇文章以了解如何
tcpdump
使用 cBPF。读完之后,请阅读这篇关于如何
tcpdump
将表达式转换为字节码的说明。如果您想了解有关它的所有内容,可以随时查看源代码!
为了补充@forest 的好答案,我们也许可以详细说明这些程序是如何执行的。
tcpdump 使用的 cBPF 有几个钩子:它可以附加到套接字,以便在数据包到达时运行(这就是 tcpdump 所做的,过滤在套接字上接收到的数据包,并仅将所需的数据包传递给用户空间),或者它们可以附加到seccomp 钩子上,以便对系统调用及其参数进行一些过滤。
eBPF的重要特性之一是它可以附加到内核中更广泛的钩子选择(尽管它不做 seccomp)。对于网络,有套接字,还有TC(流量控制)钩子、XDP(用于快速网络的驱动程序级钩子)或其他一些。关于您的问题:程序也可以附加到内核中的跟踪点(某些特定函数上的预定义挂钩,例如内核中的系统调用或“重要”函数),或内核探针(kprobes),使它们能够跟踪内核中的任何函数(前提是它在编译时没有内联)。然后是其他类型存在,例如用于安全用例的 LSM。
跟踪通常依靠跟踪点或 kprobes将 eBPF 程序附加到函数,并在内核中每次调用该函数时运行它。程序可以访问函数的参数或(如果它附加在出口处)返回值。通过使用映射、特殊的内核内存区域(例如数组或哈希映射),专用于在 eBPF 程序和/或用户空间之间共享数据,程序可以在连续运行之间收集指标或共享状态。
例如,来自 BCC 的opensnoop将附加到
open()
和openat()
系统调用的入口和出口处的跟踪点。在入口处,它收集正在打开的文件的路径,以及打开它的进程的 PID,并将其存储在哈希映射中。当系统调用退出时,第二个探测器收集返回值,并根据 PID 更新哈希映射中的相关条目。然后用户空间可以收集并转储哈希映射中的所有条目,以显示哪些文件已被哪些进程打开,以及返回值是什么。https://ebpf.io/是开始使用 eBPF 的好地方。