You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用bpf_skb_load_bytes()读取任意长度的数据包负载?

解决eBPF中获取数据包负载长度并调用bpf_skb_load_bytes的问题

核心问题分析

你遇到的两个关键点:一是必须传入准确的负载长度给bpf_skb_load_bytes,否则会返回-14(EFAULT);二是BPF验证器不认可你计算payload_len的方式,认为可能出现负值。本质原因是缺少对各层头部长度的合法性校验,导致验证器无法确认total_hlen不会超过skb->len。

解决方案步骤

1. 正确计算各层头部长度(必须做边界校验)

要计算payload长度,首先得确保各层头部长度的计算合法,且skb有足够空间容纳这些头部:

  • 以太网头部:普通以太网帧头部固定14字节;如果是带VLAN的帧,需额外加4字节(可通过eth_type判断是否为0x8100来处理)。
  • IP头部:从IP头的ihl字段获取,该字段以4字节为单位,所以ip_hlen = iph->ihl * 4。必须先检查skb->len >= eth_hlen + sizeof(struct iphdr)确保能完整读取IP头,同时iph->ihl不能小于5(最小IP头长度为20字节)。
  • 传输层头部:
    • TCP:从TCP头的doff字段获取,tp_hlen = th->doff *4,需检查doff >=5,且skb->len >= eth_hlen + ip_hlen + sizeof(struct tcphdr)。
    • UDP:固定8字节,只需检查skb长度足够容纳UDP头。

2. 让BPF验证器认可payload_len的合法性

验证器对数值边界检查极严,需通过多层条件判断,明确告知验证器total_hlen不会超过skb->len:

修正后的代码示例:

__u32 eth_hlen = 14; // 默认以太网头部长度,可根据VLAN调整
struct ethhdr *eth = bpf_hdr_pointer(skb);
if (!eth) return 0;

// 处理VLAN(可选)
if (eth->h_proto == bpf_htons(ETH_P_8021Q)) {
    eth_hlen += 4;
}

// 检查是否为IP包
if (eth->h_proto != bpf_htons(ETH_P_IP)) return 0;

// 读取IP头并校验
struct iphdr *iph = (struct iphdr *)((char *)eth + eth_hlen);
if (skb->len < eth_hlen + sizeof(struct iphdr)) return 0;
if (iph->ihl < 5) return 0; // 最小IP头长度是20字节(5*4)
__u32 ip_hlen = iph->ihl * 4;

// 检查传输层协议(以TCP为例)
if (iph->protocol != IPPROTO_TCP) return 0;

// 读取TCP头并校验
struct tcphdr *th = (struct tcphdr *)((char *)iph + ip_hlen);
if (skb->len < eth_hlen + ip_hlen + sizeof(struct tcphdr)) return 0;
if (th->doff <5) return 0; // 最小TCP头长度是20字节(5*4)
__u32 tp_hlen = th->doff *4;

__u32 total_hlen = eth_hlen + ip_hlen + tp_hlen;
char payload[MAX_PAYLOAD_SIZE];

// 关键:通过多重校验确保total_hlen <= skb->len
if (total_hlen > skb->len) return 0;
__u32 payload_len = skb->len - total_hlen;
if (payload_len ==0) return 0;

// 限制读取长度不超过缓冲区大小
if (payload_len > sizeof(payload)) {
    payload_len = sizeof(payload);
}

long err = bpf_skb_load_bytes(skb, total_hlen, payload, payload_len);
if (!err) {
    bpf_printk("负载长度:%d,内容:%s", payload_len, payload);
} else {
    bpf_printk("读取失败,错误码:%ld", err);
}

3. 额外注意事项

  • 如果你用的是Socket Filter,skb的头部偏移可能和TC不同(Socket Filter的skb数据起始可能是IP头而非以太网头),调整eth_hlen初始值即可。
  • 对于UDP包,直接将tp_hlen设为8字节,跳过doff校验即可。
  • bpf_skb_load_bytes的第三个参数是用户缓冲区,必须确保其大小足够容纳payload_len(或像代码里那样截断)。

内容的提问来源于stack exchange,提问作者someone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 16:01:11