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

如何激活用于修改出口UDP流量的BCC eBPF程序?

问题描述

我用BPF Compiler Collection(BCC)编写了一个用于修改出口UDP数据包的eBPF程序,选用了BPF_PROG_TYPE_SCHED_ACT程序类型。但程序加载后不知道如何“激活”,尝试将其附加到eth0网卡并未生效。我猜测需要使用tc命令,但不清楚该命令的作用及具体用法。

Python脚本

import sys
import time

from bcc import BPF

def main():
    src = open("my_example.c").read()

    b = BPF(text=src, cflags=["-Wno-macro-redefined"])  # 3

    # fn = b.load_func("my_example", BPF.SK_SKB)
    fn = b.load_func("my_example", BPF.SCHED_CLS)

    b.attach_raw_socket(fn, "eth0") # this doesn't work

if __name__ == "__main__":
    main()

eBPF程序代码

#include <bcc/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>

int my_example(struct __sk_buff *skb)
{
    if(skb->protocol != htons(ETH_P_IP)) return TC_ACT_OK;
    if(skb->pkt_type != PACKET_OUTGOING) return TC_ACT_OK;

    if(skb->wire_len<176) return TC_ACT_OK;
    if(skb->len<176) return TC_ACT_OK;

    bpf_skb_pull_data(skb, 176);

    u8* packet = (u8*)(long)skb->data;
    u8* packet_end = (u8*)(long)skb->data_end;
    if(packet_end<packet+176) return TC_ACT_OK;

    if(packet[94] != 0x54) return TC_ACT_OK;

    bpf_skb_change_tail(skb, 30, 0);

    packet=(u8*)(long)skb->data;
    packet_end=(u8*)(long)skb->data_end;
    if (packet_end<packet+206) return TC_ACT_OK;

    packet[205] = packet[205-30];
    packet[94] = 0x56;
    // ...

    // more code for length and checksums ...

    return TC_ACT_RECLASSIFY;
}
解决方案

你的问题核心是错误使用了attach_raw_socket方法,SCHED_CLS(对应BPF_PROG_TYPE_SCHED_CLS)类型的eBPF程序需要通过Linux流量控制(tc)框架挂载,而非raw socket。

1. 关于tc的基础认知

tc是Linux内核提供的流量控制工具,用于在网卡的数据包处理路径上挂载过滤、修改、整形规则。你的eBPF程序属于tc分类器,必须挂载到tc的钩子点才能捕获并处理数据包。

2. 修改Python脚本,用BCC内置tc挂载API

无需手动执行tc命令,BCC已经封装了tc挂载的便捷方法,替换原脚本中的attach_raw_socket即可:

完整修改后的Python脚本

import sys
import time

from bcc import BPF

def main():
    src = open("my_example.c").read()

    b = BPF(text=src, cflags=["-Wno-macro-redefined"])

    fn = b.load_func("my_example", BPF.SCHED_CLS)
    # 将程序挂载到eth0网卡的出口方向
    b.attach_tc(fn, "eth0", direction="out")

    # 保持程序运行,否则eBPF程序会被内核自动卸载
    print("eBPF程序已激活,按Ctrl+C退出")
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("正在退出程序...")
        # 可选:卸载tc规则(退出时内核也会自动清理,此步可省略)
        b.remove_tc("eth0", direction="out")

if __name__ == "__main__":
    main()

3. 原方法失效的原因

  • attach_raw_socket仅适用于SK_SKB类型的eBPF程序,这类程序绑定到单个socket,仅处理该socket的数据包,无法全局修改网卡出口的所有数据包。
  • SCHED_CLS类型程序必须通过tc挂载到网卡的数据包处理链路,才能捕获到指定方向(入口/出口)的所有流量。

4. 手动用tc命令挂载的方法(可选)

如果需要手动操作,步骤如下:

  1. 加载eBPF程序到内核,用bpftool prog list查看程序ID
  2. 为eth0添加clsact队列(用于挂载eBPF程序):tc qdisc add dev eth0 root clsact
  3. 将eBPF程序挂载到出口方向过滤器:tc filter add dev eth0 egress bpf da <程序ID>

内容的提问来源于stack exchange,提问作者towi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 04:08:18