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

如何在运行时判断eBPF中sk_buff是L2帧还是L3数据包?

解决方案

针对不同网卡(物理网卡如eth0传递L2以太网帧,WireGuard虚拟网卡如wg0传递L3 IP包)的数据包起始层差异问题,有两种可行的判断方式:

方法一:用户空间记录网卡类型,传递至eBPF程序

利用BPF哈希表存储每个网卡索引对应的数据包起始层标记,在挂载网卡时提前判断网卡类型,再在eBPF程序中通过网卡索引查表区分:

  1. 用户空间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)
    
  2. 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, &eth, 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, &eth, sizeof(eth));
        // ... 后续处理
    }

    output.perf_submit(skb, &event, sizeof(event));
    return 0;
}

方法对比

  • 方法一更可靠:可精准匹配特定类型网卡,避免异常数据包误判,但需要维护网卡类型映射表。
  • 方法二更简洁:无需额外用户空间逻辑,依赖数据包本身特征,适用于大多数场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 17:43:22