eBPF:TC egress侧如何识别并忽略TC ingress侧直接回复的报文
解决方案:TC egress侧过滤ingress直接回包的实现方法
核心识别原理
TC ingress hook 调用bpf_redirect/bpf_send类 helper 直接回送的报文,没有经过内核协议栈的上行处理,也没有和用户空间进程交互,和正常用户态程序生成的出站报文有两个可识别的核心特征:
- 未关联
sock结构体,或者sock的类型为特殊的内核临时sock - 可以通过自定义的eBPF标记逻辑完成精准识别
具体实现方案
方案1:基于skb的sock字段判定(无需修改ingress侧代码)
正常用户态程序生成的出站报文,skb->sk指针会指向对应进程的socket结构体,而TC ingress直接redirect回送的报文,skb->sk为空,或者指向内核内部的伪socket。
在eBPF代码中直接读取struct __sk_buff的sk字段即可判定:
// TC egress hook 代码 SEC("tc/egress") int egress_filter(struct __sk_buff *skb) { // sk为0说明没有关联用户态socket,属于内核直接回送的报文 if (skb->sk == 0) { return TC_ACT_OK; // 直接跳过你的业务逻辑即可 } // 后续处理用户态生成的正常报文 // ... 你的业务逻辑代码 return TC_ACT_OK; }
注意:部分内核版本中,内核协议栈自动生成的ICMP等控制报文也会存在
skb->sk为空的情况,如果需要进一步区分,可以额外匹配报文的五元组特征做二次过滤。
方案2:自定义标记法(最精准,推荐)
在TC ingress侧的回包逻辑中,给直接回送的报文打上自定义标记,egress侧直接读取标记过滤即可,不会误判其他内核生成报文。
可以用两种方式打标记:
- 利用skb的
mark字段(内核原生支持,无需额外map)
// TC ingress侧回包前打标记 SEC("tc/ingress") int ingress_handle(struct __sk_buff *skb) { // 满足直接回包条件的业务逻辑 if (need_direct_reply(skb)) { // 打自定义标记,比如用0x1234作为ingress直接回包的专属标识 skb->mark = 0x1234; return bpf_redirect(skb->ifindex, 0); } // 正常上行报文处理逻辑 return TC_ACT_OK; } // TC egress侧过滤逻辑 SEC("tc/egress") int egress_filter(struct __sk_buff *skb) { if (skb->mark == 0x1234) { // 清除标记避免影响其他内核模块的正常逻辑 skb->mark = 0; return TC_ACT_OK; // 跳过你的业务逻辑 } // 处理正常出站报文 return TC_ACT_OK; }
- 用eBPF per-CPU hash map存储报文的哈希值作为标记,适合不能占用
skb->mark字段的场景。
常见问题排查
- 如果用
skb->sk判定不准,可以先通过bpf_printk打印不同场景下的skb->sk、skb->mark值确认特征 - 注意
bpf_redirect的第二个参数如果传BPF_F_INGRESS的话,报文不会走到egress hook,不需要额外过滤
内容的提问来源于stack exchange,提问作者hi_glenn
相关产品推荐
相关产品推荐

