
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) | (ethernetIP) | |------------------------------------^ ^data data_end默认 headroom 为 XDP_PACKET_HEADROOM192 字节data 指向以太网头起始。二、bpf_xdp_adjust_head helper 实现核心代码在 net/core/filter.cBPF_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;/* 限制3metadata 区域不能非法 */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 封装直接剥离外层的 ethip */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 而非 dataBPF_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 路径在驱动层处理越界的 offsetif (offload abs(offset) HW_HEADROOM_LIMIT)hw_xdp_adjust_head(xdp, offset); /* 硬件直接调 */elsebpf_xdp_adjust_head(xdp, offset); /* 软件 memmove */