使用AF_PACKET捕获并桥接重注入RoCEv2 Infiniband数据包时标记规则不匹配及多tap方案实现问题
我来帮你拆解下遇到的两个核心问题,一步步解决:
一、标记规则不匹配的原因与解决
你现在遇到的标记不生效问题,核心是用错了套接字标记选项:
你代码里用的是SOL_SOCKET层的SO_MARK,但这个选项是给传输层连接(比如TCP/UDP)打标记的,对AF_PACKET这类原始套接字来说,内核不会自动把socket的SO_MARK同步到数据包的skb->nfmark字段——而ebtables的--mark匹配的正是这个nfmark值,所以规则自然匹配不上。
修复方法
把代码里的setsockopt调用改成AF_PACKET专属的PACKET_MARK选项(属于SOL_PACKET层):
// 替换原来的SO_MARK设置代码 int mark = PACKET_MARK; if (setsockopt(send_sock, SOL_PACKET, PACKET_MARK, &mark, sizeof(mark)) < 0) { perror("setsockopt(PACKET_MARK)"); exit(EXIT_FAILURE); }
另外还要注意ebtables的链选择:
你当前的规则只配置了OUTPUT链,但从rxe0→br0→gretap1的转发流量属于FORWARD链(OUTPUT链仅处理本地进程生成的数据包)。所以需要同时配置两个链的规则,确保原始流量被拦截,只有标记后的重注入流量放行:
# 处理桥转发的流量 sudo ebtables -A FORWARD -o gretap1 --mark 0x42 -j ACCEPT sudo ebtables -A FORWARD -o gretap1 -j DROP # 处理你程序重注入的本地生成流量 sudo ebtables -A OUTPUT -o gretap1 --mark 0x42 -j ACCEPT sudo ebtables -A OUTPUT -o gretap1 -j DROP
二、重复数据包检测方案
因为你的程序会捕获gretap1上的所有数据包(包括自己重注入的),很容易陷入循环捕获的死循环。推荐两种简单可行的方案:
1. BPF过滤排除标记数据包
给接收套接字绑定BPF过滤器,直接跳过带有0x42标记的数据包,这样重注入的数据包不会被再次捕获。代码示例:
#include <linux/filter.h> // 构建BPF过滤规则:只捕获没有0x42标记的数据包 struct sock_filter filter[] = { BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct sk_buff, mark)), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, PACKET_MARK, 0, 1), BPF_STMT(BPF_RET | BPF_K, 0), // 丢弃带标记的数据包 BPF_STMT(BPF_RET | BPF_K, 0xFFFF), // 放行其他数据包 }; struct sock_fprog fprog = { .len = sizeof(filter)/sizeof(filter[0]), .filter = filter, }; // 给接收套接字绑定过滤器 if (setsockopt(recv_sock, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0) { perror("SO_ATTACH_FILTER"); exit(EXIT_FAILURE); }
2. 修改数据包标识字段(复杂度较高)
比如修改IP头部的ID字段(需要重新计算IP校验和),或者给RoCEv2的UDP封装添加自定义标识,但这种方法需要处理协议正确性,不如BPF过滤直接高效。
三、多tap接口方案实现
如果想改用“捕获gretap1,重注入到gretap2”的方案,按以下步骤操作即可解决地址问题:
1. 在两端创建第二个GRE隧道
在Machine A上:
sudo ip link add gretap2 type gretap local <A的公网IP> remote <B的公网IP> ttl 64 sudo ip link set gretap2 up sudo brctl addif br0 gretap2
在Machine B上对应创建:
sudo ip link add gretap2 type gretap local <B的公网IP> remote <A的公网IP> ttl 64 sudo ip link set gretap2 up sudo brctl addif br0 gretap2
2. 调整ebtables规则
禁止原始流量从gretap1流出,放行gretap2方向的流量:
# 拦截gretap1的所有出站流量 sudo ebtables -A FORWARD -o gretap1 -j DROP sudo ebtables -A OUTPUT -o gretap1 -j DROP # 放行gretap2的所有出站流量 sudo ebtables -A FORWARD -o gretap2 -j ACCEPT sudo ebtables -A OUTPUT -o gretap2 -j ACCEPT
3. 修改程序的接口配置
把代码里的捕获接口改为gretap1,发送接口改为gretap2即可——这样捕获的原始流量会通过第二个GRE隧道传输,完全避免重复捕获的问题。
内容来源于stack exchange

