如何在运行时判断eBPF中sk_buff是L2帧还是L3数据包?
解决方案
针对不同网卡(物理网卡如eth0传递L2以太网帧,WireGuard虚拟网卡如wg0传递L3 IP包)的数据包起始层差异问题,有两种可行的判断方式:
方法一:用户空间记录网卡类型,传递至eBPF程序
利用BPF哈希表存储每个网卡索引对应的数据包起始层标记,在挂载网卡时提前判断网卡类型,再在eBPF程序中通过网卡索引查表区分:
用户空间Python代码:
遍历所有网卡,判断是否为WireGuard类型(可通过网卡名前缀wg或网卡属性wireguard标识),将网卡索引与标记(如0代表L3,1代表L2)存入BPF map:import bcc import pyroute2 src_path = "your_bpf_code.c" b = bcc.BPF(src_file=src_path, debug=0) # 创建哈希表:key为网卡索引(int),value为标记(int) iface_type_map = b.get_table("iface_type_map") # 遍历所有网卡 ipr = pyroute2.IPRoute() for link in ipr.get_links(): ifname = link.get_attr("IFLA_IFNAME") ifindex = link["index"] # 判断是否为WireGuard网卡 link_info = link.get_attr("IFLA_LINKINFO") if link_info and link_info.get_attr("IFLA_INFO_KIND") == "wireguard": iface_type_map[bcc.BPF.Key(ifindex)] = 0 # L3包 else: iface_type_map[bcc.BPF.Key(ifindex)] = 1 # L2帧 # 挂载socket filter fn = b.load_func('sock_peek_packet', bcc.BPF.SOCKET_FILTER) b.attach_raw_socket(fn, ifname)eBPF C代码:
通过skb->ifindex查询哈希表,获取当前数据包的起始层类型:#include <uapi/linux/bpf.h> #include <uapi/linux/if_ether.h> #include <uapi/linux/ip.h> BPF_HASH(iface_type_map, int, int); BPF_PERF_OUTPUT(output); struct event { // 自定义事件字段 }; int sock_peek_packet(struct __sk_buff *skb) { int *type = iface_type_map.lookup(&skb->ifindex); if (!type) return 0; if (*type == 1) { // 处理L2以太网帧 struct ethhdr eth; if (skb->len < sizeof(struct ethhdr)) return 0; bpf_skb_load_bytes(skb, 0, ð, sizeof(eth)); // ... 后续处理 } else { // 处理L3 IP包 struct iphdr iph; if (skb->len < sizeof(struct iphdr)) return 0; bpf_skb_load_bytes(skb, 0, &iph, sizeof(iph)); // ... 后续处理 } output.perf_submit(skb, &event, sizeof(event)); return 0; }
方法二:eBPF程序内直接检测数据包起始格式
利用以太网帧与IP包的头部特征差异,直接读取数据包首字节判断:
- IPv4包首字节为
0x40-0x4F(高4位是版本号4) - IPv6包首字节为
0x60-0x6F(高4位是版本号6) - 以太网帧首字节为MAC地址,不会符合上述范围(除非极端异常包)
eBPF代码示例:
#include <uapi/linux/bpf.h> #include <uapi/linux/if_ether.h> #include <uapi/linux/ip.h> BPF_PERF_OUTPUT(output); struct event { // 自定义事件字段 }; int sock_peek_packet(struct __sk_buff *skb) { u8 first_byte; if (skb->len < 1) return 0; // 读取数据包首字节 bpf_skb_load_bytes(skb, 0, &first_byte, sizeof(first_byte)); if ((first_byte & 0xF0) == 0x40 || (first_byte & 0xF0) == 0x60) { // 处理L3 IP包 struct iphdr iph; if (skb->len < sizeof(struct iphdr)) return 0; bpf_skb_load_bytes(skb, 0, &iph, sizeof(iph)); // ... 后续处理 } else { // 处理L2以太网帧 struct ethhdr eth; if (skb->len < sizeof(struct ethhdr)) return 0; bpf_skb_load_bytes(skb, 0, ð, sizeof(eth)); // ... 后续处理 } output.perf_submit(skb, &event, sizeof(event)); return 0; }
方法对比
- 方法一更可靠:可精准匹配特定类型网卡,避免异常数据包误判,但需要维护网卡类型映射表。
- 方法二更简洁:无需额外用户空间逻辑,依赖数据包本身特征,适用于大多数场景。
内容的提问来源于stack exchange,提问作者Knio
相关产品推荐
相关产品推荐

