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

能否通过eBPF在动态大小数据包中添加/移除IP选项?

eBPF动态修改IP数据包添加/移除IP选项的解决方案

核心结论

完全可以通过eBPF在动态大小的数据包中添加或移除IP选项字段,核心是正确处理数据包内存的扩容/收缩,以及用内置工具高效移动负载,不需要依赖固定循环。

动态负载移动的实现方法

eBPF提供的bpf_memmove helper函数可以直接处理动态长度的内存拷贝,不用硬编码循环。步骤如下:

  1. 计算原始IP头长度:通过IP头的ihl字段(单位为4字节),得出ip_hdr_len = iph->ihl * 4。
  2. 定位负载位置:原始负载起始于skb->data + ip_hdr_len。
  3. 确定移动参数:如果插入N字节选项,目标位置为skb->data + ip_hdr_len + N;需要移动的字节数是skb->len - ip_hdr_len(数据包总长度减去IP头长度)。
  4. 执行移动:调用bpf_memmove(dst, src, len)即可,该函数会自动处理从后往前拷贝的逻辑,避免覆盖未拷贝的数据。

数据包内存调整的关键步骤

修改数据包长度必须先调用eBPF helper调整skb内存:

  • 添加选项:先调用bpf_skb_adjust_room,传入需要增加的字节数(注意IP选项长度必须是4字节的倍数,符合IP协议要求),调整成功后再移动负载、更新IP头的ihl和tot_len字段。
  • 移除选项:计算要减少的字节数,调用bpf_skb_adjust_room传入负数,随后将负载向前覆盖原选项位置,再修改IP头的ihl和tot_len。

加载失败的常见排查方向

你遇到的程序加载失败,大概率是以下原因:

  • Helper函数使用错误:比如bpf_memmove参数非法(长度为负、指针越界),或者bpf_skb_adjust_room传入的选项长度不是4字节倍数。
  • 权限不足:加载eBPF程序需要CAP_BPF和CAP_NET_ADMIN权限,Python程序需以root身份运行。
  • 内核版本不兼容:bpf_skb_adjust_room等helper函数需要4.18+内核版本,检查你的内核版本是否达标。
  • 内存边界检查缺失:eBPF验证器会严格检查内存访问安全性,若代码中存在未检查skb长度就直接访问字段的情况,会被拒绝加载。

示例代码片段(C eBPF部分)

#include <linux/bpf.h>
#include <linux/ip.h>
#include <bpf/bpf_helpers.h>

SEC("tc")
int ip_add_option(struct __sk_buff *skb) {
    struct iphdr *iph;
    void *data_end = (void *)(long)skb->data_end;
    void *data = (void *)(long)skb->data;
    const int option_len = 8; // 自定义选项长度,必须是4的倍数
    int ip_hdr_len;

    // 检查IP头完整性
    iph = data;
    if ((void *)iph + sizeof(*iph) > data_end)
        return TC_ACT_OK;

    ip_hdr_len = iph->ihl * 4;
    // 检查IP头+负载是否在skb内存范围内
    if ((void *)iph + ip_hdr_len > data_end)
        return TC_ACT_OK;

    // 调整skb内存,增加选项长度的空间
    if (bpf_skb_adjust_room(skb, option_len, BPF_ADJ_ROOM_NET, 0) != 0)
        return TC_ACT_OK;

    // 调整后重新获取指针
    data_end = (void *)(long)skb->data_end;
    data = (void *)(long)skb->data;
    iph = data;

    // 移动负载到新位置
    void *src = data + ip_hdr_len;
    void *dst = src + option_len;
    int payload_len = skb->len - ip_hdr_len;
    if (dst + payload_len > data_end)
        return TC_ACT_OK;

    bpf_memmove(dst, src, payload_len);

    // 填充自定义IP选项(示例内容,按需修改)
    __u8 *option_ptr = data + ip_hdr_len;
    option_ptr[0] = 0x00; // 选项类型
    option_ptr[1] = option_len; // 选项长度
    // 填充其他选项内容...

    // 更新IP头字段
    iph->ihl += option_len / 4;
    iph->tot_len += option_len;
    // 重新计算IP头校验和
    iph->check = 0;
    iph->check = bpf_csum_diff(0, 0, (unsigned short *)iph, iph->ihl * 4, 0);

    return TC_ACT_OK;
}
char _license[] SEC("license") = "GPL";

注意事项

  • IP选项长度必须是4字节的倍数,这是IP协议硬性要求,否则数据包会被内核丢弃。
  • 修改数据包后,需更新IP头校验和(必要时还要更新传输层校验和),部分场景内核会自动处理,但手动更新更可靠。
  • eBPF代码中必须严格检查所有指针与data_end的边界,否则会被验证器拒绝加载。

内容的提问来源于stack exchange,提问作者Đức Anh Nguyễn Đỗ

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 20:04:52