Linux bpf_xdp_adjust_head XDP 头部调整与数据移位
一、XDP 数据包的内存布局
XDP BPF 程序运行在网卡驱动层的早期路径,此时数据包位于 rx ring buffer 的 page 中。struct xdp_buff 描述了 XDP 程序可操作的数据区域:
struct xdp_buff {
void *data; /* 数据包起始地址 */
void *data_end; /* 数据包结束地址 */
void *data_meta; /* metadata 区域起始地址 */
void *data_hard_start; /* page 起始地址(硬边界) */
struct xdp_rxq_info *rxq; /* 接收队列信息 */
};
page 的内存布局:
data_hard_start data_end
| |
v v
+----------+-------------------+-------+
| headroom | packet | tail |
| (192 B) | (ethernet+IP++) | |
+----------+-------------------+-------+
^ ^
data data_end
默认 headroom 为 XDP_PACKET_HEADROOM(192 字节),data 指向以太网头起始。
二、bpf_xdp_adjust_head helper 实现
核心代码在 net/core/filter.c:
BPF_CALL_2(bpf_xdp_adjust_head, struct xdp_buff *, xdp, int, offset)
{
void *xdp_frame_start = xdp->data_hard_start;
unsigned long metalen = xdp->data - xdp->data_meta;
void *new_data = xdp->data + offset;
/* 限制1:不能越过 data_hard_start(向前移过头) */
if (unlikely(new_data < xdp_frame_start))
return -EINVAL;
/* 限制2:不能越过 data_end(向后移过头) */
if (unlikely(new_data > xdp->data_end))
return -EINVAL;
/* 限制3:metadata 区域不能非法 */
if (unlikely(metalen > 0 &&
new_data < xdp->data_meta + sizeof(u32)))
return -EINVAL;
/* 限制4:不能将 data_meta 推出 data_hard_start */
if (unlikely(new_data - metalen < xdp_frame_start))
return -EINVAL;
/* 更新指针 */
xdp->data = new_data;
return 0;
}
当 offset > 0 时,data 后移,缩小数据区(去掉头部)。
当 offset < 0 时,data 前移,扩大数据区(增加头部空间)。
三、数据移位实现:XDP_TX 时的 memmove
调整头部后,发送数据前必须将负偏移增加的头空间填充内容。XDP 在 xdp_frame 层面处理移位:
struct xdp_frame *xdp_convert_to_xdp_frame(struct xdp_buff *xdp)
{
struct xdp_frame *xdp_frame;
int metasize;
int headroom;
/* 计算调整量 */
if (xdp->data < xdp->data_hard_start + XDP_PACKET_HEADROOM) {
/* 头部扩大了,需要 memmove */
u32 shift = xdp->data_hard_start + XDP_PACKET_HEADROOM - xdp->data;
void *dst = xdp->data_hard_start + XDP_PACKET_HEADROOM;
memmove(dst, xdp->data, xdp->data_end - xdp->data);
xdp->data = dst;
if (xdp->data_meta)
xdp->data_meta += shift; /* meta 指针同步偏移 */
}
}
xdp_frame 的实际发送路径(ndo_xdp_xmit)要求 xdp_frame->data 至少从 page 的 headroom 位置开始。
四、XDP 程序中调整头部并封装新协议
以下示例展示 XDP 程序剥离 VLAN 头或添加 IPIP 封装头:
int xdp_encap_example(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
struct iphdr *ip;
int offset;
/* 需要封装 IPv4-in-IPv4,先调整 head 腾出空间 */
/* IPIP 封装需要:外层的 eth(14) + ip(20) = 34 字节 */
offset = -(int)sizeof(struct iphdr); /* 向后移动 20 字节 */
if (bpf_xdp_adjust_head(ctx, offset) != 0)
return XDP_ABORTED;
/* 重新获取指针,data/data_end 已更新 */
data = (void *)(long)ctx->data;
data_end = (void *)(long)ctx->data_end;
/* 现在 data 指向原来 data + 20 的位置,前面有 20 字节空位 */
/* 写入外部以太网头 */
eth = data;
memcpy(eth->h_dest, orig_eth->h_dest, ETH_ALEN);
memcpy(eth->h_source, orig_eth->h_source, ETH_ALEN);
eth->h_proto = htons(ETH_P_IP);
/* 写入外部 IP 头 */
ip = data + sizeof(struct ethhdr);
if (ip + 1 > (struct iphdr *)data_end)
return XDP_ABORTED;
ip->version = 4;
ip->ihl = 5;
ip->ttl = 64;
ip->protocol = IPPROTO_IPIP;
ip->daddr = REMOTE_TUNNEL_ENDPOINT;
ip->saddr = LOCAL_TUNNEL_ENDPOINT;
/* 计算 checksum */
ip->check = csum_fold(ip->check, ip->saddr, ip->daddr);
return XDP_TX; /* 从原接口回传 */
}
五、头部剥离(decapsulation)
从数据包前端剥离协议头(如 VXLAN 或 IPIP 封装),使用正偏移:
int xdp_decap(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if (eth->h_proto == htons(ETH_P_IP)) {
struct iphdr *ip = data + sizeof(*eth);
if (ip + 1 > (struct iphdr *)data_end)
return XDP_ABORTED;
if (ip->protocol == IPPROTO_IPIP) {
/* 内层 IP,包含它自己的以太网头吗?
* 假设是 IPIP 封装,直接剥离外层的 eth+ip */
int inner_off = sizeof(*eth) + ip->ihl * 4;
/* 将 data 前移到内层数据开始 */
if (bpf_xdp_adjust_head(ctx, inner_off) != 0)
return XDP_ABORTED;
/* 重新调整后 data 指向内层 IP 头,
* 可能需要根据需求再写新的 L2 头 */
}
}
return XDP_PASS;
}
六、与 bpf_xdp_adjust_tail 的区别
内核 4.18 引入 bpf_xdp_adjust_tail,调整 data_end 而非 data:
BPF_CALL_2(bpf_xdp_adjust_tail, struct xdp_buff *, xdp, int, offset)
{
void *new_data_end = xdp->data_end + offset;
/* 只能缩小尾部(offset < 0)或扩大尾部(offset > 0,需额外页面) */
if (unlikely(new_data_end < xdp->data))
return -EINVAL;
/* 扩大尾部需要分配新页面 */
if (offset > 0) {
struct page *page = xdp->rxq->mem.allocator->alloc(...);
if (!page)
return -ENOMEM;
/* 将新 page 链到 skb 后面 */
}
xdp->data_end = new_data_end;
return 0;
}
bpf_xdp_adjust_head 调整的是 data 指针,用于头部操作;bpf_xdp_adjust_tail 调整 data_end 指针,用于尾部操作(如添加隧道尾部标记)。
七、验证器对调整 head 的跟踪
BPF 验证器在分析 bpf_xdp_adjust_head 调用时,需要重新计算 data 和 data_end 的相对关系:
static int check_xdp_adjust_head(struct bpf_verifier_env *env)
{
struct bpf_reg_state *regs = cur_regs(env);
/* 调用 adjust_head 后,data/data_end 指针的偏移值变为未知 */
regs[BPF_REG_6].type = PTR_TO_PACKET; /* data */
regs[BPF_REG_6].off = 0;
regs[BPF_REG_6].range = 0; /* 现在 data_end - data 不确定了 */
/* 后续所有数据访问都必须重新做边界检查 */
return 0;
}
这意味着调用了 adjust_head 后,BPF 程序必须重新从 data/data_end 进行边界检查。
八、硬件 offload 支持
部分智能网卡(Mellanox CX5+)支持 XDP 头部调整的硬件 offload。硬件实现:
- 网卡在处理 XDP 程序后,硬件 DMA 引擎在 TX 描述符中增加 offset
- 无需软件 memmove,零拷贝实现头部调整
- 硬件约束:offset 必须在 [-128, 128] 范围内
软件 fallback 路径在驱动层处理越界的 offset:
if (offload && abs(offset) <= HW_HEADROOM_LIMIT)
hw_xdp_adjust_head(xdp, offset); /* 硬件直接调 */
else
bpf_xdp_adjust_head(xdp, offset); /* 软件 memmove */
Linux BPF XDP Adjust Head头部调整与数据移位
张小明
前端开发工程师
英雄联盟智能助手Akari:告别繁琐操作,专注游戏乐趣的终极解决方案
英雄联盟智能助手Akari:告别繁琐操作,专注游戏乐趣的终极解决方案 【免费下载链接】League-Toolkit An all-in-one toolkit for LeagueClient. Gathering power 🚀. 项目地址: https://gitcode.com/gh_mirrors/le/League-Toolkit 还在…
ZigBee协议栈深度解析:从IEEE 802.15.4数据包到智能灯控命令的完整旅程
ZigBee协议栈深度解析:从IEEE 802.15.4数据包到智能灯控命令的完整旅程当你在深夜走进客厅,轻触手机上的"开灯"按钮时,一组由0和1组成的数字指令正悄然穿越复杂的无线协议栈。这个看似简单的动作背后,隐藏着ZigBee协议栈…
【Springboot毕设全套源码+文档】基于Java+springboot的手机电脑数码售卖系统的设计与实现(丰富项目+远程调试+讲解+定制)
博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…
网盘直链下载助手技术解析:浏览器脚本实现跨平台文件下载的技术深度指南
网盘直链下载助手技术解析:浏览器脚本实现跨平台文件下载的技术深度指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中…
用Python和Matlab搞定东南大学齿轮箱数据集:从CSV读取到特征提取的保姆级教程
用Python和Matlab搞定东南大学齿轮箱数据集:从CSV读取到特征提取的保姆级教程当你第一次打开东南大学齿轮箱数据集时,可能会被复杂的CSV文件结构和多通道信号搞得一头雾水。作为工程领域常用的基准数据集,它包含了齿轮和轴承的多种故障类型&a…
5分钟实战Py-ART:气象雷达数据分析的完整解决方案
5分钟实战Py-ART:气象雷达数据分析的完整解决方案 【免费下载链接】pyart The Python-ARM Radar Toolkit. A data model driven interactive toolkit for working with weather radar data. 项目地址: https://gitcode.com/gh_mirrors/py/pyart Py-ART&…