PAYATHON 2026

SK_SKB 程序如何读取 data 和 data_end?

数据小陈

结论

SK_SKB 的 parser 和 verdict 程序可以通过 skb->data、skb->data_end 直接访问数据。data_end - data 是当前 skb 中连续线性可访问的数据长度,skb->len 则是 skb 的总长度,两者不一定相等。即使 skb->len 大于 0,当前没有线性可访问的数据时,data_end - data 也可能为 0。bpf_skb_pull_data 可用于 BPF_PROG_TYPE_SK_SKB,包括 verdict 程序。调用成功后,需要重新取得指针并检查边界。SK_SKB 程序说明 bpf_skb_pull_data 说明

为什么长度会不同

示例中的 skb->len 表示 skb 的总长度,而 data 到 data_end 之间只有线性区域。skb 的部分数据可能存放在非线性片段中,因此不能根据 skb->len 推断 data_end - data 的值。直接访问只能读取 [data, data_end) 范围内的数据。需要读取范围以外的数据时,可以使用 bpf_skb_load_bytes,也可以先调用 bpf_skb_pull_data,将所需字节拉入线性区域。

调试时,可将指针转换为字节指针,并显式转换打印值,避免 void * 指针相减时出现类型歧义:

char *data = (char *)(long)skb->data;
char *data_end = (char *)(long)skb->data_end;
int linear_len = data_end - data;

bpf_printk("len: %u linear_len: %d", skb->len, linear_len);

linear_len 仍可能小于 skb->len,甚至为 0;这本身不能证明字段不可用。

使用 bpf_skb_pull_data

如果程序需要直接访问当前 skb 中线性区域以外的字节,可以请求将所需范围拉入线性区域。helper 返回 0 表示成功,返回负值表示失败。调用可能改变底层数据缓冲区,所以调用前取得的指针和基于这些指针进行的边界判断都会失效。

if (bpf_skb_pull_data(skb, skb->len) < 0)
    return SK_DROP;

char *data = (char *)(long)skb->data;
char *data_end = (char *)(long)skb->data_end;

if (data + skb->len > data_end)
    return SK_DROP;

/* 在边界检查通过后访问所需字节 */

这里传入的 skb->len 应与当前 skb 中实际需要访问的长度相符。如果 parser 已将一条完整消息交给 verdict,且该 skb 的长度覆盖整条消息,就可以按这个长度请求。helper 只处理当前 skb,不会等待或拼接尚未到达、也未包含在当前 skb 中的数据。应结合 parser 的分帧逻辑和实际传入 verdict 的 skb,判断其是否完整覆盖消息。

如果只需读取少量字节,使用 bpf_skb_load_bytes 通常更直接。调用 bpf_skb_pull_data 后若要继续直接访问数据,必须重新读取 data 和 data_end,并再次检查边界。Linux helper 文档

备注:内容仅供参考。