如何使用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头。
- TCP:从TCP头的
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
相关产品推荐
相关产品推荐

