如何在eBPF/libbpf中获取完整skb数据包内容而非地址?
解决libbpf中bpf_probe_read_kernel读取skb数据包内容的问题
你遇到的问题核心是BPF验证器的内存访问限制:skb->data是指向内核空间的指针,BPF程序处于沙箱环境,不能直接访问该指针指向的地址,直接传skb->data会触发验证器的内存访问错误;而&skb->data是取指针变量本身的地址,读取到的只是指针的值(数据包的内核地址),不是实际内容。
正确的做法是分两步操作:
先读取
skb->data的指针值
用bpf_probe_read_kernel读取skb->data成员的实际值(即数据包所在的内核地址),存到临时变量中:void *skb_data; bpf_probe_read_kernel(&skb_data, sizeof(skb_data), &skb->data);这里
&skb->data是struct sk_buff结构体成员的地址,属于BPF程序允许访问的范围,不会触发验证器错误。用读取到的指针值获取数据包内容
以第一步得到的skb_data作为源地址,再次调用bpf_probe_read_kernel读取实际的数据包内容到你的结构体中:bpf_probe_read_kernel(my_struct, 512, skb_data);
完整示例代码
#include <linux/skbuff.h> #include <bpf/bpf_helpers.h> #include <linux/if_ether.h> // 自定义数据包结构体,按需定义字段 struct my_packet { __u8 eth_dst[ETH_ALEN]; __u8 eth_src[ETH_ALEN]; __u16 eth_type; // 更多字段... }; SEC("kprobe/ip_rcv") int BPF_KPROBE(ip_rcv, struct sk_buff *skb) { struct my_packet pkt = {}; void *skb_data; __u32 skb_len; // 读取skb->data的指针值 bpf_probe_read_kernel(&skb_data, sizeof(skb_data), &skb->data); // 读取数据包实际长度,避免越界读取 bpf_probe_read_kernel(&skb_len, sizeof(skb_len), &skb->len); // 计算安全读取长度:取数据包实际长度和结构体大小的较小值 __u32 read_len = (skb_len > sizeof(pkt)) ? sizeof(pkt) : skb_len; // 读取数据包内容 bpf_probe_read_kernel(&pkt, read_len, skb_data); // 后续处理,比如打印以太网类型 bpf_printk("ETH type: %u", ntohs(pkt.eth_type)); return 0; } char _license[] SEC("license") = "GPL";
额外注意事项
- 确保
my_struct的内存合法(如栈上分配或bpf_map分配的内存),避免非法内存访问。 - 结合
skb->len控制读取长度,既符合BPF验证器的安全要求,也能避免读取超出数据包范围的无效内存。 - 若内核版本在5.10及以上,可考虑使用更高效的
bpf_copy_from_user类函数,但核心逻辑一致:先获取合法的内核地址,再通过安全读取函数获取内容。
内容的提问来源于stack exchange,提问作者Vic
相关产品推荐
相关产品推荐

