Linux BPF技术深度解析(1):BPF程序类型和案例

一、介绍

1.1 BPF程序分类

BPF(Berkeley Packet Filter)程序通过动态加载到内核或用户态,实现高效的事件处理和数据观测,根据其核心功能可分为跟踪类网络类两大类。以下是具体描述:

(1)跟踪类 BPF 程序(Tracing BPF)

跟踪类 BPF 程序的核心功能是监控系统事件、收集运行时数据,用于性能分析、调试、安全审计等场景。它们通过挂载到内核或用户态的 “钩子点”,捕获事件发生时的上下文信息(如函数参数、返回值、内存状态等)。

  1. kprobe/kretprobe

    • 挂载到内核函数的入口(kprobe)或返回处(kretprobe),用于跟踪内核函数的调用情况。
    • 应用:分析系统调用耗时(如sys_read)、定位内核漏洞(如空指针引用)、监控进程创建(do_fork)等。
  2. uprobe/uretprobe

    • 类似 kprobe,但作用于用户态程序的函数(如动态链接库函数、自定义程序函数)。
    • 应用:调试用户态程序逻辑(如跟踪malloc内存分配)、监控应用性能瓶颈(如数据库查询函数耗时)。
  3. tracepoint

    • 内核预先定义的静态跟踪点(由内核开发者在关键逻辑处埋点),比 kprobe 更稳定(无内核版本兼容问题),但覆盖范围有限。
    • 应用:跟踪进程调度(sched_switch)、文件系统操作(sys_enter_open)、网络包收发(net_dev_xmit)等。
  4. perf event

    • 与性能计数器(PMU,Performance Monitoring Unit)结合,跟踪硬件事件(如 CPU 缓存命中 / 失效)或软件事件(如进程切换)。
    • 应用:分析 CPU 使用率、缓存命中率、指令执行效率等底层性能指标。
  5. raw tracepoint

    • 比 tracepoint 更轻量,直接访问内核内部数据结构,不经过内核事件过滤逻辑,适合需要更高性能的场景,但兼容性较差(依赖内核内部实现)。
  6. cgroup 跟踪

    • 监控 cgroup(控制组)相关事件,如进程加入 / 退出 cgroup、资源限制触发(如内存阈值)。
    • 应用:容器资源监控(如 Docker 容器的 CPU / 内存使用趋势)。

(2)网络类 BPF 程序(Networking BPF)

网络类 BPF 程序专注于网络数据包的处理,包括过滤、转发、修改、监控等,运行在内核网络栈的关键节点,替代传统的 iptables、tc(流量控制)等工具,提供更高性能和灵活性。

  1. XDP(eXpress Data Path)

    • 运行在网络设备驱动的最早阶段(数据包刚进入内核时),是性能最高的网络 BPF 程序。
    • 功能:在数据包到达协议栈前进行过滤(丢弃恶意包)、转发(实现高性能负载均衡)、修改(如修改源 IP)。
    • 优势:绕过大部分内核网络栈,处理速率可达 100Gbps 以上,适合 DDoS 防护、高性能网关等场景。
  2. tc BPF(Traffic Control BPF)

    • 挂载到 tc(Linux 流量控制子系统)的钩子点(如cls_bpf用于分类,act_bpf用于动作),在网络栈的不同阶段处理数据包。
    • 应用:流量整形(限速)、基于数据包内容的路由(如根据 HTTP 头部转发)、流量统计(按端口 / 协议计数)。
  3. Socket Filter BPF

    • 作用于用户态 socket,过滤进入 socket 的数据包(仅保留感兴趣的包),替代传统的recvfrom+ 用户态过滤逻辑。
    • 应用:抓包工具(如tcpdump底层基于 BPF 过滤)、减少用户态与内核态的数据传输量(只传匹配的包)。
  4. LSM BPF(Linux Security Module BPF)

    • 虽属于安全类,但常涉及网络行为控制(如限制进程的网络连接),通过 LSM 钩子监控系统调用(如connectbind)。
    • 应用:防火墙规则(允许 / 禁止特定进程访问特定 IP)、网络访问审计(记录所有出站连接)。

(3)两类程序的核心区别

维度跟踪类 BPF网络类 BPF
核心目标事件监控与数据收集网络数据包处理与控制
钩子点位置内核 / 用户态函数、跟踪点网络栈(驱动、协议栈、socket)
典型工具bpftracebcc(分析工具)xdp-toolstc-bpf(网络工具)
输出内容事件日志、性能指标处理后的数据包、流量统计

1.2 BPF程序类

序号名称关键字说明
1套接字过滤器程序NPF_PROG_TYPE_SOCKET_FILTER用于过滤套接字上的网络数据包,通常用于数据包捕获(如 libpcap)。
2kprobe 程序kprobe/kretprobe用于动态跟踪内核函数调用,kprobe 跟踪函数入口,kretprobe 跟踪函数返回。
3跟踪点程序BPF_PROG_TPYE_TRACEPOINT基于内核预定义的静态跟踪点,用于性能分析或调试,无需修改内核代码。
4XDP 程序BPF_PROG_TPYE_XDP在网络驱动层早期处理数据包(eXpress Data Path),用于高性能数据包过滤、转发或流量控制。
5Perf 事件程序BPF_PROG_TPYE_PERF_EVENT用于监控内核或用户态的性能事件(如 CPU 周期、缓存命中率等),支持自定义计数器。
6cgroup 套接字程序BPF_PROG_TPYE_CGROUP_SKB在 cgroup 层级过滤套接字数据包,用于网络流量控制或安全策略。
7cgroup 打开套接字程序BPF_PROG_TPYE_CGROUP_SOCK在套接字创建时(socket() 系统调用)应用 cgroup 策略,用于限制或监控套接字行为。
8套接字选项程序BPF_PROG_TPYE_CGROUP_OPS拦截并修改套接字选项(如 setsockopt()/getsockopt()),用于自定义套接字行为。
9套接字映射程序BPF_PROG_TPYE_SK_SKB处理从套接字发送或接收的数据包(SKB),用于数据包修改或负载均衡。
10cgroup 设备程序BPF_PROG_TPYE_CGROUP_DEVICE控制 cgroup 内设备的访问权限(如允许/禁止读写特定设备文件)。
11套接字消息传递程序BPF_PROG_TPYE_SK_MSG处理套接字上的消息(如 sendmsg()/recvmsg()),用于消息过滤或修改。
12原始跟踪点程序BPF_PROG_TPYE_RAW_TRACE提供对原始跟踪点数据的低级访问,适用于需要精细控制跟踪数据的场景。
13cgroup 套接字地址程序BPF_PROG_TPYE_CGROUP_SOCK_ADDR过滤或修改套接字地址(如 bind()/connect() 的目标地址),用于网络地址控制。
14套接字重用端口程序SO_REUSEPORT允许多个套接字绑定到同一端口,结合 BPF 实现负载均衡或高并发服务(如 Web 服务器)。
15流量解析程序BPF_PROG_TPYE_FLOW_DISSECTOR解析网络流量中的协议字段(如 HTTP 头、DNS 查询),用于深度包检测(DPI)或流量分类。

 二、常用BPF程序类型案例

2.1 套接字过滤器程序

功能:过滤 TCP 数据包,仅打印目的端口为 80 的包。

代码 (sock_filter.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>

SEC("socket")
int socket_filter(struct __sk_buff *skb) {
    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;

    struct iphdr *ip = data + sizeof(*eth);
    if (data + sizeof(*eth) + sizeof(*ip) > data_end) return 0;

    if (ip->protocol != IPPROTO_TCP) return 0;

    struct tcphdr *tcp = data + sizeof(*eth) + sizeof(*ip);
    if (data + sizeof(*eth) + sizeof(*ip) + sizeof(*tcp) > data_end) return 0;

    if (tcp->dest == htons(80)) {
        bpf_printk("TCP packet to port 80\n");
    }

    return 0;
}

char _license[] SEC("license") = "GPL";

 编译 & 运行:

clang -O2 -target bpf -c sock_filter.c -o sock_filter.o
sudo bpftool prog load sock_filter.o /sys/fs/bpf/sock_filter
sudo bpftool net attach socket_filter id <prog_id>

 输出:

TCP packet to port 80

2.2 kprobe 程序

功能:跟踪 do_sys_open 系统调用。
代码 (kprobe_demo.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("kprobe/do_sys_open")
int kprobe__do_sys_open(struct pt_regs *ctx) {
    const char *filename = (const char *)PT_REGS_PARM1(ctx);
    bpf_printk("File opened: %s\n", filename);
    return 0;
}

char _license[] SEC("license") = "GPL";

编译 & 运行:

clang -O2 -target bpf -c kprobe_demo.c -o kprobe_demo.o
sudo bpftool prog load kprobe_demo.o /sys/fs/bpf/kprobe_demo
sudo bpftool perf attach pinned /sys/fs/bpf/kprobe_demo

输出:

File opened: /etc/passwd

2.3 跟踪点程序

功能:跟踪 syscalls:sys_enter_openat 跟踪点。
代码 (tracepoint_demo.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("tracepoint/syscalls/sys_enter_openat")
int tracepoint__sys_enter_openat(struct trace_event_raw_sys_enter *ctx) {
    const char *filename = (const char *)ctx->args[1];
    bpf_printk("openat called: %s\n", filename);
    return 0;
}

char _license[] SEC("license") = "GPL";

 编译 & 运行:

clang -O2 -target bpf -c tracepoint_demo.c -o tracepoint_demo.o
sudo bpftool prog load tracepoint_demo.o /sys/fs/bpf/tracepoint_demo
sudo bpftool perf attach pinned /sys/fs/bpf/tracepoint_demo

输出:

openat called: /proc/cpuinfo

2.4 XDP 程序

功能:丢弃所有 ICMP 数据包。
代码 (xdp_drop_icmp.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/icmp.h>

SEC("xdp")
int xdp_drop_icmp(struct xdp_md *ctx) {
    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;

    struct ethhdr *eth = data;
    if (data + sizeof(*eth) > data_end) return XDP_PASS;

    struct iphdr *ip = data + sizeof(*eth);
    if (data + sizeof(*eth) + sizeof(*ip) > data_end) return XDP_PASS;

    if (ip->protocol == IPPROTO_ICMP) {
        bpf_printk("Dropped ICMP packet\n");
        return XDP_DROP;
    }

    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

 编译 & 运行:

clang -O2 -target bpf -c xdp_drop_icmp.c -o xdp_drop_icmp.o
sudo bpftool net attach xdp id <prog_id> dev eth0

输出:

Dropped ICMP packet

2.5 Perf 事件程序

功能:统计 CPU 周期。
代码 (perf_event.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("perf_event")
int perf_event_handler(struct bpf_perf_event_data *ctx) {
    bpf_printk("CPU cycle event\n");
    return 0;
}

char _license[] SEC("license") = "GPL";

 编译 & 运行:

clang -O2 -target bpf -c perf_event.c -o perf_event.o
sudo bpftool prog load perf_event.o /sys/fs/bpf/perf_event
sudo bpftool perf attach pinned /sys/fs/bpf/perf_event

输出:

CPU cycle event

2.6 cgroup 套接字程序

功能:禁止 cgroup 内所有 TCP 流量。
代码 (cgroup_skb.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>

SEC("cgroup_skb/egress")
int cgroup_skb_egress(struct __sk_buff *skb) {
    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 1;

    struct iphdr *ip = data + sizeof(*eth);
    if (data + sizeof(*eth) + sizeof(*ip) > data_end) return 1;

    if (ip->protocol == IPPROTO_TCP) {
        bpf_printk("Blocking TCP packet\n");
        return 0; // DROP
    }

    return 1; // PASS
}

char _license[] SEC("license") = "GPL";

 编译 & 运行:

clang -O2 -target bpf -c cgroup_skb.c -o cgroup_skb.o
sudo bpftool prog load cgroup_skb.o /sys/fs/bpf/cgroup_skb
sudo bpftool cgroup attach /sys/fs/cgroup/unified/ id <prog_id>

输出:

Blocking TCP packet

2.7 cgroup 打开套接字程序

功能:禁止 cgroup 内创建新套接字。
代码 (cgroup_sock.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("cgroup/sock_create")
int cgroup_sock_create(struct bpf_sock *sk) {
    bpf_printk("Socket creation blocked\n");
    return 0; // BLOCK
}

char _license[] SEC("license") = "GPL";

 编译 & 运行:

clang -O2 -target bpf -c cgroup_sock.c -o cgroup_sock.o
sudo bpftool prog load cgroup_sock.o /sys/fs/bpf/cgroup_sock
sudo bpftool cgroup attach /sys/fs/cgroup/unified/ id <prog_id>

 输出:

Socket creation blocked

2.8 套接字选项程序

功能:修改 SO_REUSEADDR 选项。
代码 (cgroup_sockopt.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("cgroup/setsockopt")
int cgroup_setsockopt(struct bpf_sockopt *ctx) {
    if (ctx->level == SOL_SOCKET && ctx->optname == SO_REUSEADDR) {
        bpf_printk("Setting SO_REUSEADDR=1\n");
        *(int *)ctx->optval = 1;
    }
    return 0;
}

char _license[] SEC("license") = "GPL";

 编译 & 运行:

clang -O2 -target bpf -c cgroup_sockopt.c -o cgroup_sockopt.o
sudo bpftool prog load cgroup_sockopt.o /sys/fs/bpf/cgroup_sockopt
sudo bpftool cgroup attach /sys/fs/cgroup/unified/ id <prog_id>

 输出:

Setting SO_REUSEADDR=1

2.9 套接字映射程序

功能:修改套接字数据包。
代码 (sk_skb.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("sk_skb/stream_parser")
int sk_skb_parser(struct __sk_buff *skb) {
    bpf_printk("Processing socket data\n");
    return 0;
}

char _license[] SEC("license") = "GPL";

 编译 & 运行:

clang -O2 -target bpf -c sk_skb.c -o sk_skb.o
sudo bpftool prog load sk_skb.o /sys/fs/bpf/sk_skb
sudo bpftool net attach sk_skb id <prog_id>

 输出:

Processing socket data

2.10 cgroup 设备程序

功能:禁止访问 /dev/sda
代码 (cgroup_device.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("cgroup/device")
int cgroup_device(struct bpf_cgroup_dev_ctx *ctx) {
    if (ctx->major == 8 && ctx->minor == 0) { // /dev/sda
        bpf_printk("Blocking access to /dev/sda\n");
        return 0; // BLOCK
    }
    return 1; // ALLOW
}

char _license[] SEC("license") = "GPL";

 编译 & 运行:

clang -O2 -target bpf -c cgroup_device.c -o cgroup_device.o
sudo bpftool prog load cgroup_device.o /sys/fs/bpf/cgroup_device
sudo bpftool cgroup attach /sys/fs/cgroup/unified/ id <prog_id>

 输出:

Blocking access to /dev/sda

2.11 套接字消息传递程序

功能:过滤 sendmsg() 消息。
代码 (sk_msg.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("sk_msg")
int sk_msg_handler(struct sk_msg_md *msg) {
    bpf_printk("Intercepted sendmsg\n");
    return 0;
}

char _license[] SEC("license") = "GPL";

编译 & 运行:

clang -O2 -target bpf -c sk_msg.c -o sk_msg.o
sudo bpftool prog load sk_msg.o /sys/fs/bpf/sk_msg
sudo bpftool net attach sk_msg id <prog_id>

 输出:

Intercepted sendmsg

2.12 原始跟踪点程序

功能:跟踪 sys_enter 事件。
代码 (raw_tracepoint.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("raw_tracepoint/sys_enter")
int raw_tracepoint_handler(struct bpf_raw_tracepoint_args *ctx) {
    bpf_printk("Raw tracepoint triggered\n");
    return 0;
}

char _license[] SEC("license") = "GPL";

编译 & 运行:

clang -O2 -target bpf -c raw_tracepoint.c -o raw_tracepoint.o
sudo bpftool prog load raw_tracepoint.o /sys/fs/bpf/raw_tracepoint
sudo bpftool perf attach pinned /sys/fs/bpf/raw_tracepoint

输出:

 Raw tracepoint triggered

2.13 cgroup 套接字地址程序

功能:阻止绑定到 0.0.0.0:80
代码 (cgroup_sock_addr.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/in.h>

SEC("cgroup/bind4")
int cgroup_bind4(struct bpf_sock_addr *ctx) {
    if (ctx->user_port == htons(80) && ctx->user_ip4 == 0) {
        bpf_printk("Blocking bind to 0.0.0.0:80\n");
        return 0; // BLOCK
    }
    return 1; // ALLOW
}

char _license[] SEC("license") = "GPL";

 编译 & 运行:

clang -O2 -target bpf -c cgroup_sock_addr.c -o cgroup_sock_addr.o
sudo bpftool prog load cgroup_sock_addr.o /sys/fs/bpf/cgroup_sock_addr
sudo bpftool cgroup attach /sys/fs/cgroup/unified/ id <prog_id>

 输出:

Blocking bind to 0.0.0.0:80

2.14 套接字重用端口程序

功能:自定义 SO_REUSEPORT 负载均衡。
代码 (reuseport.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("sockops")
int sockops_handler(struct bpf_sock_ops *ctx) {
    if (ctx->op == BPF_SOCK_OPS_REUSEPORT_SELECT) {
        ctx->reuseport_index = 0; // Always select first socket
        bpf_printk("Reuseport selection\n");
    }
    return 0;
}

char _license[] SEC("license") = "GPL";

 编译 & 运行:

clang -O2 -target bpf -c reuseport.c -o reuseport.o
sudo bpftool prog load reuseport.o /sys/fs/bpf/reuseport
sudo bpftool net attach sockops id <prog_id>

 输出:

Reuseport selection

2.15 流量解析程序

功能:解析 HTTP 流量。
代码 (flow_dissector.c):

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("flow_dissector")
int flow_dissector_handler(struct __sk_buff *skb) {
    bpf_printk("Parsing network flow\n");
    return 0;
}

char _license[] SEC("license") = "GPL";

 编译 & 运行:

clang -O2 -target bpf -c flow_dissector.c -o flow_dissector.o
sudo bpftool prog load flow_dissector.o /sys/fs/bpf/flow_dissector
sudo bpftool net attach flow_dissector id <prog_id>

输出:

Parsing network flow

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

坏一点

您的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值