如何编写一个简单的 eBPF 程序,用于跟踪系统调用.
如何使用 eBPF 开发最简单的Hello World程序 ?
接下来就是围绕这个问题来开发咯.
要开发 eBPF 程序,需要安装必要的软件,以下是当前使用的机器的配置:
-
操作系统: Ubuntu 24.04.1 LTS
-
内核: 6.8.0-45-generic
-
Go: 1.22.4 linux/amd64
-
eBPF 包: v0.16.0
-
gopacket 包: v1.1.19
-
netlink 包: v1.3.0
-
bpftool: v7.4.0
-
LLVM: 18.0
-
Clang: 18.1.3 x86_64-pc-linux-gnu
-
GCC: 13.2.0
-
eunomia-bpf(ecc): v1.0.27
-
culium/ebpf: v0.16.0
eBPF 程序主要由两部分构成:内核态部分和用户态部分. 内核态部分包含 eBPF 程序的实际逻辑,用户态部分负责加载、运行和监控内核态程序. 当然有时候也可以使用 ip 命令加载和卸载 eBPF 内核态的程序.
这里只会介绍如何使用 eunomia-bpf、cilium/ebpf 两种模式加载 eBPF 程序.
选择了合适的开发框架后,如 BCC(BPF Compiler Collection)、libbpf、cilium/ebpf 或 eunomia-bpf等,就可以开始进行用户态和内核态程序的开发. 首先以 eunomia-bpf 为例,这里将介绍 eBPF 程序的基本开发流程:
下载 ecli 工具,用于运行 eBPF 程序:
wget https://aka.pw/bpf-ecli -O ecli && chmod +x ./ecli
./ecli -h
ecli subcommands, including run, push, pull
Usage: ecli [COMMAND_LINE]... [COMMAND]
Commands:
run run ebpf program
client Client operations
push Operations about pushing image to registry
pull Operations about pulling image from registry
help Print this message or the help of the given subcommand(s)
Arguments:
[COMMAND_LINE]... Not preferred. Only for compatibility to older versions. Command line to run. The executable could either be a local path or URL or `-` (read from stdin). The following arguments will be passed to the program
Options:
-h, --help Print help
下载编译器工具链,用于将 eBPF 内核代码编译为 config 文件或 WASM 模块:
wget https://github.com/eunomia-bpf/eunomia-bpf/releases/latest/download/ecc && chmod +x ./ecc
./ecc -h
eunomia-bpf compiler
Usage: ecc [OPTIONS] <SOURCE_PATH> [EXPORT_EVENT_HEADER]
Arguments:
<SOURCE_PATH> path of the bpf.c file to compile
[EXPORT_EVENT_HEADER] path of the bpf.h header for defining event struct [default: ]
......也可以使用 docker 镜像进行编译:
# for x86_64 and aarch64
docker run -it -v `pwd`/:/src/ ghcr.io/eunomia-bpf/ecc-`uname -m`:latest # compile with docker. `pwd` should contains *.bpf.c files and *.h files.总体上来说,使用 eunomia-bpf 编译和加载程序非常方便,而且实际使用中发现:
-
编译自动化,较少遇到编译问题
-
运行傻瓜化. 编译的时候生成了元数据信息,所以运行的时候只需要这些元数据信息就可以运行了,甚至不需要写用户态的代码
-
安装必要的依赖
-
安装
LLVM和Clang: 这些工具用于编译eBPF程序 -
安装
Go: 用于编写用户空间程序 -
安装 bpf2go: 用于生成
Go绑定代码go get github.com/cilium/ebpf/cmd/bpf2go
-
-
编写
eBPF内核态程序**
创建一个 .c 文件,编写 eBPF 程序逻辑:
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(void *ctx) {
char msg[] = "Hello, eBPF!";
bpf_trace_printk(msg, sizeof(msg));
return 0;
}
char LICENSE[] SEC("license") = "Dual BSD/GPL";- 使用
bpf2go生成Go绑定代码
在 Go 文件中添加 bpf2go 指令:
//go:generate bpf2go -cc clang -cflags "-O2 -g -Wall -Werror" bpf hello.bpf.c
package main
import (
"log"
"C"
// 其他必要的导入
)
func main() {
// 主程序逻辑
}- 编写用户空间程序
在 main.go 中实现加载和附加 eBPF 程序的逻辑:
func main() {
objs := bpfObjects{}
if err := loadBpfObjects(&objs, nil); err != nil {
log.Fatalf("loading objects: %v", err)
}
defer objs.Close()
// 附加 eBPF 程序到 tracepoint
// 读取 trace pipe 或执行其他操作
}- 生成 Go 绑定代码
运行 go generate 命令生成绑定代码
- 编译和运行程序
-
使用
go build编译程序 -
使用
root权限运行编译后的程序
- 测试和调试
-
使用
bpftool或其他工具查看加载的 eBPF 程序 -
检查
trace pipe输出或其他预期结果
首先编写一个内核态的 ebpf 程序 hello.c,这是当前 ebpf 程序的核心. 目前还是以 C 为主要开发语言:
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ // SPDX 许可证标识符,指定双重许可选项:LGPL-2.1 或 BSD-2-Clause
// 定义宏以禁用 BPF 程序中的全局数据使用
#define BPF_NO_GLOBAL_DATA
// 包含 Linux BPF 头文件,用于 BPF 程序定义和辅助函数
#include <linux/bpf.h>
// 包含 BPF 框架提供的辅助函数
#include <bpf/bpf_helpers.h>
// 包含 BPF 跟踪辅助函数,用于附加到跟踪点
#include <bpf/bpf_tracing.h>
typedef unsigned int u32; // 定义无符号整数类型别名 u32
typedef int pid_t; // 定义整数类型别名 pid_t
const pid_t pid_filter = 0; // 定义用于 PID 过滤的常量,0 表示不进行过滤
char LICENSE[] SEC("license") = "Dual BSD/GPL"; // 指定 BPF 程序的许可证,允许双重 BSD/GPL 许可
// 定义一个 BPF map 来存储上次输出的时间
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, __u64);
} last_output SEC(".maps");
// 定义 sys_enter_write 跟踪点的 BPF 程序部分
SEC("tp/syscalls/sys_enter_write")
// 定义处理该跟踪点的 BPF 程序函数
int handle_tp(void *ctx)
{
pid_t pid = bpf_get_current_pid_tgid() >> 32;
if (pid_filter && pid != pid_filter)
return 0;
__u64 ts = bpf_ktime_get_ns();
u32 key = 0;
__u64 *last_ts = bpf_map_lookup_elem(&last_output, &key);
if (!last_ts) {
// 如果是第一次运行,直接输出并更新时间戳
bpf_map_update_elem(&last_output, &key, &ts, BPF_ANY);
bpf_printk("BPF triggered sys_enter_write from PID %d.\n", pid);
} else if (ts - *last_ts >= 1000000000) { // 1秒 = 1,000,000,000 纳秒
// 如果距离上次输出已经过了至少1秒,则输出并更新时间戳
bpf_map_update_elem(&last_output, &key, &ts, BPF_ANY);
bpf_printk("BPF triggered sys_enter_write from PID %d.\n", pid);
}
return 0;
}这段程序通过定义一个 handle_tp 函数并使用 SEC 宏把它附加到 sys_enter_write tracepoint(即在进入 write 系统调用时执行). 该函数通过使用 bpf_get_current_pid_tgid 和 bpf_printk 函数获取调用 write 系统调用的进程 ID,并在内核日志中打印出来. 同时这里为了防止打印过快,这里加了一个限速.
-
bpf_printk(): 一种将信息输出到trace_pipe(/sys/kernel/debug/tracing/trace_pipe)简单机制. 在一些简单用例中这样使用没有问题,但是它也有一些限制:最多 3 个参数; 第一个参数必须是%s(即字符串);同时trace_pipe在内核中全局共享,其他并行使用trace_pipe的程序有可能会将trace_pipe的输出扰乱. 一个更好的方式是通过BPF_PERF_OUTPUT(), 稍后将会讲到. -
void *ctx:ctx 本来是具体类型的参数, 但是由于我们这里没有使用这个参数,因此就将其写成 void *类型. -
return 0;:必须这样,返回 0 (如果要知道 why, 参考 iovisor/bcc#139[2]).
要编译和运行这段程序,可以使用 ecc 工具和 ecli 命令:
./ecc ./eBPF/helloworld/hello.c
INFO [ecc_rs::bpf_compiler] Compiling bpf object...
INFO [ecc_rs::bpf_compiler] Generating package json..
INFO [ecc_rs::bpf_compiler] Packing ebpf object and config into ./eBPF/helloworld/package.json...然后使用 ecli 运行编译后的程序:
./ecli run ./eBPF/helloworld/package.json
INFO [faerie::elf] strtab: 0x354 symtab 0x390 relocs 0x3d8 sh_offset 0x3d8
INFO [bpf_loader_lib::skeleton::preload::section_loader] User didn't specify custom value for variable pid_filter, use the default one in ELF
INFO [bpf_loader_lib::skeleton::poller] Running ebpf program...运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出:
sudo cat /sys/kernel/debug/tracing/trace_pipe | grep "BPF triggered sys_enter_write"
......
grep-375177 [005] ...21 23247.070739: bpf_trace_printk: BPF triggered sys_enter_write from PID 375177.
cat-375181 [000] ...21 23247.070741: bpf_trace_printk: BPF triggered sys_enter_write from PID 375181.
grep-375177 [005] ...21 23247.070751: bpf_trace_printk: BPF triggered sys_enter_write from PID 375177.
cat-375181 [000] ...21 23247.070753: bpf_trace_printk: BPF triggered sys_enter_write from PID 375181.
grep-375177 [005] ...21 23247.070760: bpf_trace_printk: BPF triggered sys_enter_write from PID 375177.
cat-375181 [000] ...21 23247.070768: bpf_trace_printk: BPF triggered sys_enter_write from PID 375181.
grep-375177 [005] ...21 23247.070772: bpf_trace_printk: BPF triggered sys_enter_write from PID 375177.
cat-375181 [000] ...21 23247.070781: bpf_trace_printk: BPF triggered sys_enter_write from PID 375181.
grep-375177 [005] ...21 23247.070781: bpf_trace_printk: BPF triggered sys_enter_write from PID 375177.
......
按 Ctrl+C 停止 ecli 进程之后,可以看到对应的输出也停止.
注意:如果正在使用的 Linux 发行版(例如 Ubuntu )默认情况下没有启用跟踪子系统可能看不到任何输出,使用以下指令打开这个功能:
sudo su
echo 1 > /sys/kernel/debug/tracing/tracing_on接下来再介绍如何使用 cilium/ebpf 开发 eBPF 程序. 还是以 hello.c 为例.
首先我们新建一个minimal.go文件,先写好它的骨架:
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go --go-package minimal -output-dir minimal Minimal hello.c -- -I/usr/include/bpf -I/usr/include/linux
package main
import (
"log"
"os"
"os/signal"
"syscall"
"C"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/rlimit"
minimal "github.com/767829413/advanced-go/eBPF/helloworld/minimal"
)
func main() {
}主要是通过 bpf2go 生成辅助代码. 注意这里 Minimal 第一个字母使用了大写字母,所以生成的代码中的对象是 exported,可以在包外使用.
然后运行go generate生成绑定代码:
go generate它会在 helloworld 文件夹下生成绑定代码,然后我们就可以完成 minimal.go ,实现读取和加载这个 ebpf 程序:
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go --go-package minimal -output-dir minimal Minimal hello.c -- -I/usr/include/bpf -I/usr/include/linux
package main
import (
"log"
"os"
"os/signal"
"syscall"
"C"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/rlimit"
minimal "github.com/767829413/advanced-go/eBPF/helloworld/minimal"
)
func main() {
// 允许当前进程锁定内存以用于 eBPF 资源.
if err := rlimit.RemoveMemlock(); err != nil {
log.Fatalf("failed to remove memlock: %v", err)
}
// 加载 eBPF 程序.
objs := minimal.MinimalObjects{}
if err := minimal.LoadMinimalObjects(&objs, nil); err != nil {
log.Fatalf("loading objects: %v", err)
}
defer objs.Close()
// 将 eBPF 程序附加到 sys_enter_write tracepoint.
tp, err := link.Tracepoint("syscalls", "sys_enter_write", objs.HandleTp, nil)
if err != nil {
log.Fatalf("opening tracepoint: %v", err)
}
defer tp.Close()
log.Println("eBPF program loaded and attached. Press Ctrl+C to exit.")
// 创建一个通道,用于接收信号
sigs := make(chan os.Signal, 1)
// 注册信号处理器,监听 SIGINT 和 SIGTERM 信号
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs
}这里主要是 RemoveMemlock, 加载 ebpf 内核程序,然后附加到 sys_enter_write 跟踪点上. 还有就是执行需要 root 权限.
同样也可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出:
sudo cat /sys/kernel/debug/tracing/trace_pipe | grep "BPF triggered sys_enter_write"
<...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: write system call from PID 3840345.
<...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: write system call from PID 3840345.后面差不多是一样的讨论,不会再详细介绍每个步骤了.
如上所述, eBPF 程序的基本框架包括:
-
包含头文件:需要包含
<linux/bpf.h>和<bpf/bpf_helpers.h>等头文件. -
定义许可证:需要定义许可证,通常使用 "Dual BSD/GPL".
-
定义
BPF函数:需要定义一个BPF函数,例如其名称为handle_tp,其参数为void *ctx,返回值为int. 通常用 C 语言编写. -
使用
BPF助手函数:在例如 BPF 函数中,可以使用 BPF 助手函数bpf_get_current_pid_tgid()和bpf_printk(). -
返回值
跟踪点(tracepoints)是内核静态插桩技术,在技术上只是放置在内核源代码中的跟踪函数,实际上就是在源码中插入的一些带有控制条件的探测点,这些探测点允许事后再添加处理函数. 比如在内核中,最常见的静态跟踪方法就是 printk,即输出日志. 又比如:在系统调用、调度程序事件、文件系统操作和磁盘 I/O 的开始和结束时都有跟踪点. 跟踪点于 2009 年在 Linux 2.6.32 版本中首次提供. 跟踪点是一种稳定的 API,数量有限.
在 Linux 系统上,可以通过以下几种方法查看所有可用的 tracepoints 列表:
Tracepoints 信息通常位于 /sys/kernel/debug/tracing 文件系统中. 你可以通过以下命令查看所有可用的 tracepoints:
sudo cat /sys/kernel/debug/tracing/available_events
这个文件列出了系统中所有可用的 tracepoints.
perf 工具是一个强大的性能分析工具,支持查看和使用 tracepoints. 你可以使用以下命令列出所有可用的 tracepoints:
sudo perf list tracepoint
这个命令会列出所有可用的 tracepoints 以及它们所属的子系统.
bpftrace 是一个高级的 BPF 工具,可以用来动态分析系统行为. 你可以使用 bpftrace 来列出所有可用的 tracepoints:
sudo bpftrace -l 'tracepoint:*'
这个命令会列出所有 tracepoints.
你也可以直接浏览 /sys/kernel/debug/tracing/events/ 目录,查看所有可用的 tracepoints. 每个子目录代表一个 tracepoint 类别,子目录中的文件表示具体的 tracepoints. 例如:
ls /sys/kernel/debug/tracing/events/
你可以进一步进入具体的子目录查看详细信息:
ls /sys/kernel/debug/tracing/events/syscalls/
这些方法可以帮助你查看系统中所有可用的 tracepoints. 根据你的需求,可以选择其中一种方法来获取所需的 tracepoints 列表.
在 BPF 程序中,SEC 宏用于将特定的代码段标记为属于某个 BPF 子系统. 不同的子系统和用途有各自特定的段名称. 以下是一些常见的 SEC 段名称及其用途:
- Tracepoints (
tp):
-
SEC("tp/syscalls/sys_enter_write"): 系统调用进入write时的跟踪点. -
SEC("tp/syscalls/sys_exit_write"): 系统调用退出write时的跟踪点.
- Kprobes and Kretprobes:
-
SEC("kprobe/do_sys_open"): 内核函数do_sys_open的入口探测点. -
SEC("kretprobe/do_sys_open"): 内核函数do_sys_open的返回探测点.
- Uprobes and Uretprobes:
-
SEC("uprobe/your_app:func"): 用户空间程序your_app中函数func的入口探测点. -
SEC("uretprobe/your_app:func"): 用户空间程序your_app中函数func的返回探测点.
- XDP (eXpress Data Path):
SEC("xdp"): 用于 XDP 程序,该程序处理网络数据包.
- Socket Filtering:
SEC("socket"): 用于 BPF socket 过滤器,用于过滤数据包.
- TC (Traffic Control):
-
SEC("classifier"): 用于流量控制的分类器. -
SEC("action"): 用于流量控制的动作.
- cgroup:
-
SEC("cgroup/skb"): 用于 cgroup 绑定的 skb 程序. -
SEC("cgroup/sock"): 用于 cgroup 绑定的 sock 程序. -
SEC("cgroup/dev"): 用于 cgroup 设备管理.
- Perf Events:
SEC("perf_event"): 用于 perf 事件的 BPF 程序.
- Raw Tracepoints (
raw_tp):
SEC("raw_tp/sys_enter"): 原始跟踪点事件,例如系统调用进入.
- LSM (Linux Security Module):
SEC("lsm/bprm_check_security"): 用于 LSM 的安全检查点.
这些只是常见的一些 SEC 段名称,BPF 生态系统不断发展,也可能会引入新的段名称以支持更多的功能和子系统. 每个段名称对应的具体功能可以在 BPF 文档和内核源码中找到详细解释.
以下是一些常用的 eBPF CO-RE 宏及其简要说明,包括之前提到的以及新增的获取当前进程和 TCP/IP 相关的宏:
-
BPF_CORE_READ(dst, src, field)-
用于安全地读取结构体字段
-
即使字段在不同内核版本中的偏移量不同,也能正确读取
-
-
BPF_CORE_READ_STR_INTO(dst, size, src, field)- 用于读取字符串类型的结构体字段到目标缓冲区
-
BPF_PROBE_READ(dst, size, src)- 从任意内存位置安全地读取数据
-
BPF_PROBE_READ_STR(dst, size, src)- 从任意内存位置安全地读取字符串
-
bpf_core_type_exists(type)- 检查指定的类型是否存在
-
bpf_core_field_exists(field)- 检查指定的字段是否存在于结构体中
-
bpf_core_enum_value_exists(enum_type, enum_value)- 检查指定的枚举值是否存在
-
bpf_core_field_size(field)- 获取结构体字段的大小
-
bpf_core_field_offset(field)- 获取结构体字段的偏移量
-
bpf_core_type_size(type)- 获取指定类型的大小
-
BPF_CORE_READ_INTO(dst, src, field)- 将结构体字段的内容读取到目标变量中
-
BPF_PROG(name, args...)- 定义 BPF 程序入口点
-
SEC("section_name")- 指定 BPF 程序或映射的 ELF section
-
BPF_CURRENT_TASK()- 获取当前进程的
task_struct指针
- 获取当前进程的
-
BPF_CURRENT_PID_TGID()- 获取当前进程的 PID 和 TGID
-
BPF_GET_CURRENT_TASK_BTF()- 获取当前进程的
task_struct,使用 BTF(BPF Type Format)
- 获取当前进程的
-
BPF_GET_CURRENT_COMM(comm)- 获取当前进程的名称(comm)
-
BPF_CORE_READ_BITFIELD(src, field)- 读取位字段,常用于网络协议头部的标志位
-
BPF_SOCK_OPS(name)- 定义一个 socket 操作的 BPF 程序
-
BPF_SK_LOOKUP(name)- 定义一个用于 socket 查找的 BPF 程序
-
BPF_CORE_READ_BYTE(src, idx)- 从内存中读取单个字节,常用于解析网络包
-
BPF_CORE_READ_BITFIELD_PROBED(src, field)- 与
BPF_CORE_READ_BITFIELD类似,但会在运行时探测字段是否存在
- 与
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include <linux/tcp.h>
SEC("socket")
int socket_prog(struct __sk_buff *skb)
{
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
__u32 pid_tgid = bpf_get_current_pid_tgid();
__u32 pid = pid_tgid >> 32;
char comm[16];
BPF_CORE_READ_STR_INTO(comm, sizeof(comm), task, comm);
// 读取 TCP 头部
struct tcphdr *tcp_header;
tcp_header = bpf_skb_load_bytes(skb, ETH_HLEN + sizeof(struct iphdr), sizeof(struct tcphdr));
if (!tcp_header)
return 0;
// 读取 TCP 标志位
__u8 tcp_flags = BPF_CORE_READ_BITFIELD(tcp_header, flags);
bpf_printk("Process %s (PID %d) sent a packet with TCP flags: %x", comm, pid, tcp_flags);
return 0;
}
char LICENSE[] SEC("license") = "GPL";这些宏大大简化了跨内核版本的 eBPF 程序开发,特别是在处理进程信息和网络数据时. 它们使得程序能够适应不同版本内核中的结构体变化,同时提供了方便的方法来访问常用的系统和网络信息.
除了之前提到的宏,以下是一些额外的重要 eBPF CO-RE 宏:
-
BPF_PROBE_READ_KERNEL(dst, size, src)- 从内核内存安全地读取数据
-
BPF_PROBE_READ_USER(dst, size, src)- 从用户空间内存安全地读取数据
-
BPF_KPTR_XCHG(map, key, ptr, flags)- 原子地交换 BPF map 中的内核指针
-
BPF_PROG_KPROBE(func, struct pt_regs *ctx)- 定义一个 kprobe BPF 程序
-
BPF_PROG_TRACEPOINT(category, name, args...)- 定义一个 tracepoint BPF 程序
-
BPF_PROG_RAW_TRACEPOINT(name, args...)- 定义一个 raw tracepoint BPF 程序
-
bpf_core_type_id_local(type)- 获取本地类型的 ID
-
bpf_core_type_id_kernel(type)- 获取内核中类型的 ID
-
bpf_core_field_byte_offset(field)- 获取字段在结构体中的字节偏移量
-
BPF_TRACE_PRINTK(...)- 向 trace pipe 打印调试信息
-
BPF_SNPRINTF(out, out_size, fmt, args...)- 格式化字符串到缓冲区
-
BPF_SEQ_PRINTF(seq, fmt, args...)- 向 seq_file 写入格式化字符串
-
BPF_CORE_READ_SYMBOL(name)- 读取内核符号的地址
-
BPF_PROG_SCHED_CLS(name)- 定义一个用于流量分类的 BPF 程序
-
BPF_PROG_XDP(name)- 定义一个 XDP (eXpress Data Path) BPF 程序
-
BPF_PROG_CGROUP_SKB(name)- 定义一个 cgroup skb BPF 程序
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
// XDP程序示例
SEC("xdp")
int xdp_prog(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if (data + sizeof(struct ethhdr) > data_end)
return XDP_PASS;
if (bpf_core_enum_value_exists(enum xdp_action, XDP_DROP)) {
if (eth->h_proto == bpf_htons(ETH_P_IP)) {
struct iphdr *ip = data + sizeof(struct ethhdr);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
if (ip->protocol == IPPROTO_TCP) {
struct tcphdr *tcp = (void *)ip + sizeof(struct iphdr);
if ((void *)(tcp + 1) > data_end)
return XDP_PASS;
__u16 src_port;
BPF_CORE_READ_INTO(&src_port, tcp, source);
if (src_port == bpf_htons(80))
return XDP_DROP;
}
}
}
return XDP_PASS;
}
char LICENSE[] SEC("license") = "GPL";这些宏进一步扩展了 eBPF 程序的功能和灵活性,使得开发者能够更精细地控制程序行为,并更好地适应不同的内核版本和配置.