過濾和監控 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