过滤和监控 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


putao
8 声望1 粉丝

推动世界向前发展,改善民生。