用ebpf写包过滤程序
Write packet filtering program in ebpf
如果我想编写一个过滤 icmp 数据包的 cBPF 程序,我可以通过执行 tcpdump
和 -dd
选项来完成,其中
Dump packet-matching code as a C program fragment.
..请参阅下面的示例
如何使用 eBPF 指令编写相同的程序?
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
/* ... */
/* From the example above: tcpdump -i lo icmp -dd */
struct sock_filter code[] = {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 3, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 0, 1, 0x00000001 },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000000 },
};
struct sock_fprog bpf = {
.len = ARRAY_SIZE(code),
.filter = code,
};
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0)
/* ... bail out ... */
ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
if (ret < 0)
/* ... bail out ... */
/* ... */
close(sock);
您可以通过与您为 cBPF 程序所做的非常相似的方式传递 eBPF 指令:您可以像
struct bpf_insn ebpf_code[] = {
{ 0xb7, 0, 0, 0, 0 },
{ 0x95, 0, 0, 0, 0 },
};
(注意指令的长度与 cBPF 不同。)
但是目前没有工具可以像 tcpdump -dd
那样为您转储指令。这意味着您必须以另一种方式构建您的程序。
一种解决方案是自己编写 eBPF 指令。这很像汇编编程。你有一些 BPF in the kernel, or a list of the existing instructions and their syntax here.
的文档
因为手动编写单独的 eBPF 指令并不有趣[需要引用],eBPF 的典型工作流程有些不同。 clang/LLVM 有一个用于 eBPF 的 back-end,今天构建的大多数 eBPF 程序都依赖它。工作流程如下所示:
- 用 C 编写你的 BPF 程序。
- 用 clang/LLVM 将其编译成 object 文件 (ELF)。
- 使用工具或库(
ip link
, tc filter
, bpftool
, bcc, libbpf, gobpf,...)从您的 object 文件加载字节码。
- 将字节码注入内核并将其附加到钩子(例如套接字)(这通常由相同的工具或库完成)。
内核有一堆sample BPF programs用C写的,你可以看一下,看有没有适合你需要的。您需要实现的可能是:
- 检查您的数据包是否足够长以拥有完整的以太网 header。
- 检查以太网类型是否为 IPv4。
- 检查以太网 header 之后,您的数据包是否足够长以具有完整的 IPv4 header。
- 检查IP协议号是否为ICMP。
然后 return 与您要对该数据包执行的操作相关的操作(值取决于您将程序附加到哪个挂钩,socket/TC/XDP)。
如果我想编写一个过滤 icmp 数据包的 cBPF 程序,我可以通过执行 tcpdump
和 -dd
选项来完成,其中
Dump packet-matching code as a C program fragment.
..请参阅下面的示例
如何使用 eBPF 指令编写相同的程序?
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
/* ... */
/* From the example above: tcpdump -i lo icmp -dd */
struct sock_filter code[] = {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 3, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 0, 1, 0x00000001 },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000000 },
};
struct sock_fprog bpf = {
.len = ARRAY_SIZE(code),
.filter = code,
};
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0)
/* ... bail out ... */
ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
if (ret < 0)
/* ... bail out ... */
/* ... */
close(sock);
您可以通过与您为 cBPF 程序所做的非常相似的方式传递 eBPF 指令:您可以像
struct bpf_insn ebpf_code[] = {
{ 0xb7, 0, 0, 0, 0 },
{ 0x95, 0, 0, 0, 0 },
};
(注意指令的长度与 cBPF 不同。)
但是目前没有工具可以像 tcpdump -dd
那样为您转储指令。这意味着您必须以另一种方式构建您的程序。
一种解决方案是自己编写 eBPF 指令。这很像汇编编程。你有一些 BPF in the kernel, or a list of the existing instructions and their syntax here.
的文档因为手动编写单独的 eBPF 指令并不有趣[需要引用],eBPF 的典型工作流程有些不同。 clang/LLVM 有一个用于 eBPF 的 back-end,今天构建的大多数 eBPF 程序都依赖它。工作流程如下所示:
- 用 C 编写你的 BPF 程序。
- 用 clang/LLVM 将其编译成 object 文件 (ELF)。
- 使用工具或库(
ip link
,tc filter
,bpftool
, bcc, libbpf, gobpf,...)从您的 object 文件加载字节码。 - 将字节码注入内核并将其附加到钩子(例如套接字)(这通常由相同的工具或库完成)。
内核有一堆sample BPF programs用C写的,你可以看一下,看有没有适合你需要的。您需要实现的可能是:
- 检查您的数据包是否足够长以拥有完整的以太网 header。
- 检查以太网类型是否为 IPv4。
- 检查以太网 header 之后,您的数据包是否足够长以具有完整的 IPv4 header。
- 检查IP协议号是否为ICMP。
然后 return 与您要对该数据包执行的操作相关的操作(值取决于您将程序附加到哪个挂钩,socket/TC/XDP)。