过滤和监控 socket 层的数据包
socket 类型的 eBPF 程序,返回值类型是 int,并且返回值用于决定如何处理捕获的数据包。返回 0 表示丢弃数据包,返回非零值表示接受数据包。
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <netinet/in.h>
#include <linux/virtio_net.h>
SEC("socket")
int socket_filter(struct __sk_buff *skb) {
// 定义以太网、IP 和 TCP 头部指针
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
struct ethhdr *eth = data;
// 检查以太网头部是否完整
if (data + sizeof(*eth) > data_end)
return 0; // 丢弃数据包
// 仅处理 IPv4 数据包
if (eth->h_proto != bpf_htons(ETH_P_IP))
return 0; // 丢弃数据包
struct iphdr *ip = data + sizeof(*eth);
// 检查 IP 头部是否完整
if ((void *)(ip + 1) > data_end)
return 0; // 丢弃数据包
// 仅处理 TCP 数据包
if (ip->protocol != IPPROTO_TCP)
return 0; // 丢弃数据包
struct tcphdr *tcp = (void *)ip + (ip->ihl * 4);
// 检查 TCP 头部是否完整
if ((void *)(tcp + 1) > data_end)
return 0; // 丢弃数据包
// 过滤 TCP 端口 80 的数据包
if (tcp->dest == bpf_htons(80)) {
bpf_printk("Captured TCP packet on port 80\n");
return -1; // 捕获数据包
}
return 0; // 丢弃数据包
}
char _license[] SEC("license") = "GPL";
kprobe 用于在内核函数调用之前插入探针。
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
// 定义一个 kretprobe 程序,用于跟踪 do_sys_open 函数的返回
SEC("kretprobe/do_sys_open")
int bpf_prog2(struct pt_regs *ctx) {
// 获取函数返回值
int ret = PT_REGS_RC(ctx);
bpf_printk("do_sys_open returned: %d\n", ret);
return 0;
}
char _license[] SEC("license") = "GPL";
kretprobe 用于在内核函数返回之后插入探针。
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
// 定义一个 kretprobe 程序,用于跟踪 do_sys_open 函数的返回
SEC("kretprobe/do_sys_open")
int bpf_prog2(struct pt_regs *ctx) {
// 获取函数返回值
int ret = PT_REGS_RC(ctx);
bpf_printk("do_sys_open returned: %d\n", ret);
return 0;
}
char _license[] SEC("license") = "GPL";
编译 eBPF 程序:
clang -O2 -target bpf -c kprobe_example.c -o kprobe_example.o
clang -O2 -target bpf -c kretprobe_example.c -o kretprobe_example.o
使用 bpftool 或者 bpftrace 工具加载 eBPF 程序:
# 使用 bpftool 加载 kprobe 程序
sudo bpftool prog load kprobe_example.o /sys/fs/bpf/kprobe_prog
sudo bpftool prog attach /sys/fs/bpf/kprobe_prog kprobe do_sys_open
# 使用 bpftool 加载 kretprobe 程序
sudo bpftool prog load kretprobe_example.o /sys/fs/bpf/kretprobe_prog
sudo bpftool prog attach /sys/fs/bpf/kretprobe_prog kretprobe do_sys_open
Tracepoint 名称规则
前缀:必须以 tracepoint 开头。
类别:接下来是 tracepoint 的类别,例如 kmem、sched、syscalls 等。
事件:最后是具体的事件名称,例如 kmalloc、sched_switch、sys_enter_openat 等。
以下方式查看系统中可用的 tracepoint:
bpftrace -l 'tracepoint:*'
内存分配
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/mm.h>
#include <stddef.h>
SEC("tracepoint/kmem/kmalloc")
int tracepoint_kmem_kmalloc(struct trace_event_raw_kmem_kmalloc *ctx) {
size_t size = ctx->bytes_alloc;
bpf_printk("Memory allocated: %zu bytes\n", size);
return 0;
}
char _license[] SEC("license") = "GPL";
进程切换
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/sched.h>
SEC("tracepoint/sched/sched_switch")
int tracepoint_sched_switch(struct trace_event_raw_sched_switch *ctx) {
char comm[TASK_COMM_LEN];
bpf_get_current_comm(&comm, sizeof(comm));
int pid = bpf_get_current_pid_tgid() >> 32;
bpf_printk("Process %s (PID: %d) is being scheduled out\n", comm, pid);
return 0;
}
char _license[] SEC("license") = "GPL";
Perf Event 是一种强大的工具,用于监控和分析系统性能事件。它可以捕获硬件和软件层面的各种性能数据,帮助开发者和系统管理员优化系统性能和排除故障。
监控cpu使用率:
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/sched.h>
#include <linux/types.h>
// 定义一个哈希表,用于存储每个进程的 CPU 使用时间
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, __u32);
__type(value, __u64);
} cpu_usage_map SEC(".maps");
// 捕获 CPU 上的进程切换事件
SEC("perf_event")
int on_cpu_event(struct bpf_perf_event_data *ctx) {
__u32 pid = bpf_get_current_pid_tgid() >> 32;
__u64 *usage, zero = 0;
// 获取当前进程的 CPU 使用时间
usage = bpf_map_lookup_elem(&cpu_usage_map, &pid);
if (!usage) {
bpf_map_update_elem(&cpu_usage_map, &pid, &zero, BPF_ANY);
usage = bpf_map_lookup_elem(&cpu_usage_map, &pid);
}
if (usage) {
*usage += ctx->sample_period;
}
return 0;
}
char LICENSE[] SEC("license") = "GPL";
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/perf_event.h>
#include <sys/syscall.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <linux/types.h>
#define PERF_EVENT_TYPE PERF_TYPE_HARDWARE
#define PERF_EVENT_CONFIG PERF_COUNT_HW_CPU_CYCLES
int main() {
struct bpf_object *obj;
int prog_fd, map_fd, cpu, err;
struct perf_event_attr attr = {
.type = PERF_EVENT_TYPE,
.config = PERF_EVENT_CONFIG,
.size = sizeof(struct perf_event_attr),
.sample_period = 1000,
.wakeup_events = 1,
};
// 加载 eBPF 程序
obj = bpf_object__open_file("cpu_usage.bpf.o", NULL);
if (libbpf_get_error(obj)) {
fprintf(stderr, "ERROR: opening BPF object file failed\n");
return 1;
}
err = bpf_object__load(obj);
if (err) {
fprintf(stderr, "ERROR: loading BPF object file failed\n");
return 1;
}
// 获取 eBPF 程序和映射文件描述符
prog_fd = bpf_program__fd(bpf_object__find_program_by_name(obj, "on_cpu_event"));
map_fd = bpf_map__fd(bpf_object__find_map_by_name(obj, "cpu_usage_map"));
// 附加 eBPF 程序到 perf event
for (cpu = 0; cpu < sysconf(_SC_NPROCESSORS_ONLN); cpu++) {
int perf_fd = syscall(__NR_perf_event_open, &attr, -1, cpu, -1, 0);
if (perf_fd < 0) {
fprintf(stderr, "ERROR: opening perf event failed\n");
return 1;
}
err = ioctl(perf_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);
if (err) {
fprintf(stderr, "ERROR: attaching BPF program to perf event failed\n");
return 1;
}
err = ioctl(perf_fd, PERF_EVENT_IOC_ENABLE, 0);
if (err) {
fprintf(stderr, "ERROR: enabling perf event failed\n");
return 1;
}
}
// 持续监控 CPU 使用情况
while (1) {
sleep(5);
__u32 pid;
__u64 usage;
printf("CPU Usage:\n");
for (int i = 0; i < 1024; i++) {
if (bpf_map_get_next_key(map_fd, &pid, &pid) == 0) {
if (bpf_map_lookup_elem(map_fd, &pid, &usage) == 0) {
printf("PID %d: %llu\n", pid, usage);
}
}
}
}
return 0;
}
perf 命令:
#于记录指定事件的性能数据。
perf record -e kmem:kmalloc -e kmem:kfree -a
#命令用于显示 perf record 捕获的性能数据
perf report
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。