如何激活用于修改出口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命令挂载的方法(可选)
如果需要手动操作,步骤如下:
- 加载eBPF程序到内核,用
bpftool prog list查看程序ID - 为eth0添加clsact队列(用于挂载eBPF程序):
tc qdisc add dev eth0 root clsact - 将eBPF程序挂载到出口方向过滤器:
tc filter add dev eth0 egress bpf da <程序ID>
内容的提问来源于stack exchange,提问作者towi
相关产品推荐
相关产品推荐

