eBPF 中的 `__sk_buff`

eBPF 中的 __sk_buff

在 Linux 内核网络处理中,struct sk_buff(通常称为 skb)是数据包的核心数据结构,包含了从链路层到应用层的所有信息以及内核处理过程中的元数据。然而,当我们在 eBPF 中编写网络程序(如 TC、XDP、cgroup skb 等)时,无法直接访问内核的 sk_buff,因为直接暴露如此庞大的结构会带来安全风险,且 eBPF 验证器(verifier)难以跟踪。为此,内核提供了一个精简、安全的视图------__sk_buff,定义在 <linux/bpf.h> 中。

本文旨在全面解析 __sk_buff 的各个字段,说明它们的含义、可读写性以及典型使用场景,并辅以简单的 TC 程序示例来展示如何访问和修改这些字段。无论你是编写 TC 分类器、防火墙、负载均衡器还是其他网络程序,理解 __sk_buff 都是关键的一步。


一、__sk_buff 的由来与定位

__sk_buff 是 BPF 程序与内核 sk_buff 之间的桥梁。eBPF 程序通过 __sk_buff 读取或写入元数据,并通过辅助函数(如 bpf_skb_load_bytes()bpf_skb_store_bytes() 等)操作数据包内容。这种设计既保证了安全性(verifier 可以严格检查访问范围),又提供了足够的灵活性。

不同的 BPF 程序类型支持 __sk_buff 的不同字段,例如:

  • TC(Traffic Control)程序 :可以访问绝大多数字段,支持 markprioritytc_classid 等。
  • cgroup skb 程序 :额外提供 remote_ip4local_port 等直接的四层信息。
  • XDP 程序 (在 skb 模式下):支持 datadata_end 等,但通常 XDP 使用原生模式(直接访问 xdp_md)。

本文以 TC 程序为上下文,但大部分字段的解释也适用于其他类型。


二、__sk_buff 字段详解

下面按功能类别对字段逐一说明。字段类型均为 __u32,除非特别注明。

2.1 数据包内容访问

字段 说明 可读写 使用要点
data 数据负载起始位置的偏移量(相对于 skb 起始)。 只读 在程序中通常转换为指针:void *data = (void *)(long)skb->data;,然后与 data_end 配合进行边界检查。
data_end 数据负载结束位置的偏移量。 只读 用于检查是否越界:if (data + size > data_end) return;
len 数据包的总长度(包括所有头部)。 只读 可用于快速判断包大小,但注意可能包含分片。
wire_len 原始包长度(GRO 合并前的长度)。 只读 需要较高版本内核(5.1+),用于 GSO 场景下的原始长度获取。

示例:

c 复制代码
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;

if (data + sizeof(struct iphdr) > data_end)
    return TC_ACT_OK;
struct iphdr *ip = data;

2.2 元数据标记与优先级

字段 说明 可读写 使用场景
mark skb 的防火墙标记,32 位值。 可读写 可在入口设置 mark,出口根据 mark 进行策略路由、过滤或连接跟踪。
priority 调度优先级(skb->priority),值越小优先级越高。 可读写 影响 Qdisc 调度顺序,可用于实现 QoS。
tc_classid TC 分类 ID,格式为 major:minor,每个 16 位。 可读写 在分类器中设置,使数据包进入指定的 classful qdisc 队列。
cb[5] 控制块(control block),5 个 32 位值的数组。 可读写 可在同一钩子的多个 eBPF 程序间传递数据(例如 ingress 到 egress)。

示例:

c 复制代码
// 设置 mark 和 classid
skb->mark = 0x12345678;
skb->tc_classid = bpf_htons(1 << 16 | 10); // major=1, minor=10

// 使用 cb 传递临时数据
skb->cb[0] = 0xdeadbeef;

2.3 设备与协议信息

字段 说明 可读写 使用场景
ifindex 设备索引。对于 ingress,是接收接口;对于 egress,是发送接口。 只读 判断流量来自/去向哪个接口,或用于重定向。
ingress_ifindex ifindex,仅用于 ingress 场景。 只读 同上。
protocol 链路层协议类型(如 ETH_P_IPETH_P_ARP)。 只读 快速判断上层协议,通常需要 bpf_htons() 转换。
pkt_type 包类型,如 PACKET_HOST(发给本机)、PACKET_BROADCAST(广播)等。 只读 过滤非本机流量。

示例:

c 复制代码
if (skb->protocol == bpf_htons(ETH_P_IP)) {
    // 处理 IPv4 包
}
if (skb->pkt_type == PACKET_HOST) {
    // 只处理发往本机的包
}

2.4 VLAN 相关

字段 说明 可读写 使用场景
vlan_present 布尔值,表示是否存在 VLAN 标签。 只读 条件判断。
vlan_tci VLAN 标签控制信息(Tag Control Information),包含 VID(12位)、PCP(3位)、DEI(1位)。 只读 读取 VLAN ID 或优先级。
vlan_proto VLAN 协议类型,如 ETH_P_8021Q(0x8100)或 ETH_P_8021AD(0x88A8)。 只读 区分单层 VLAN 或 QinQ。

示例:

c 复制代码
if (skb->vlan_present) {
    __u16 vid = skb->vlan_tci & 0xFFF;
    // 根据 VID 处理
}

2.5 GSO/TSO 相关(高版本内核)

字段 说明 可读写 使用场景
gso_size 如果数据包是 GSO 分段,表示每个分段的大小(MSS)。 只读 用于优化处理,修改头部时需谨慎,因为会影响所有分段。
gso_segs GSO 分段数。 只读 可用于统计或控制。
csum_level 校验和层级(用于隧道封装)。 只读 处理隧道包的校验和时参考。

2.6 时间戳与哈希

字段 说明 可读写 使用场景
tstamp 时间戳(通常为 ns 单位),需要内核配置 CONFIG_NET_CLS_ACTCONFIG_BPF 等。 可读写(需内核支持) 用于延迟测量、重放攻击检测等。
hash 包的哈希值,由硬件或软件计算。 可读写 可重写哈希以影响 RSS 或负载均衡决策。

2.7 cgroup skb 专用字段

以下字段仅在 cgroup skb 程序中可用(即挂载在 cgroup 上的 BPF 程序,用于对进程发出的包进行过滤或修改)。它们提供直接的四层信息,无需解析数据包,性能更高。

字段 说明 可读写
remote_ip4 远程 IPv4 地址。 只读
local_ip4 本地 IPv4 地址。 只读
remote_ip6[4] 远程 IPv6 地址(16 字节,分成 4 个 32 位)。 只读
local_ip6[4] 本地 IPv6 地址。 只读
remote_port 远程端口。 只读
local_port 本地端口。 只读
family 地址族(AF_INET 或 AF_INET6)。 只读

这些字段在实现 cgroup 级别的网络策略时非常方便。


三、通过 TC 程序使用 __sk_buff 字段

下面通过一个简单的 TC 程序示例,展示如何读取和修改部分字段。该程序在 ingress 方向检查数据包,根据 protocolmark 决定是否丢弃,并在 egress 方向根据 cb 传递的标记修改 priority

3.1 程序代码

ingress 部分(用于 tun0 入口):

c 复制代码
// tc_ingress.c
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/pkt_cls.h>
#include <bpf/bpf_helpers.h>

SEC("classifier")
int tc_ingress(struct __sk_buff *skb) {
    // 只处理 IPv4 包
    if (skb->protocol == bpf_htons(ETH_P_IP)) {
        // 若 mark 为 0x1234,则丢弃
        if (skb->mark == 0x1234)
            return TC_ACT_SHOT;

        // 设置 cb[0] 标记,供 egress 使用
        skb->cb[0] = 0xabcd;
    }
    return TC_ACT_OK;
}

char _license[] SEC("license") = "GPL";

egress 部分(用于 tun0 出口):

c 复制代码
// tc_egress.c
#include <linux/bpf.h>
#include <linux/pkt_cls.h>
#include <bpf/bpf_helpers.h>

SEC("classifier")
int tc_egress(struct __sk_buff *skb) {
    // 如果 ingress 设置了 cb[0] 标记,则提高优先级(降低数值)
    if (skb->cb[0] == 0xabcd) {
        // 原优先级 3,改为 1(更高优先级)
        skb->priority = 1;
        // 清除标记(可选)
        skb->cb[0] = 0;
    }
    return TC_ACT_OK;
}

char _license[] SEC("license") = "GPL";

3.2 编译与挂载

bash 复制代码
# 编译
clang -target bpf -O2 -g -Wall -c tc_ingress.c -o tc_ingress.o
clang -target bpf -O2 -g -Wall -c tc_egress.c -o tc_egress.o

# 创建 clsact qdisc
sudo tc qdisc add dev tun0 clsact

# 挂载 ingress 和 egress 程序
sudo tc filter add dev tun0 ingress bpf obj tc_ingress.o sec classifier
sudo tc filter add dev tun0 egress bpf obj tc_egress.o sec classifier

3.3 验证

  • 通过 tc filter show dev tun0 ingressegress 查看挂载状态。
  • 使用 bpftool prog show 查看加载的程序。
  • 发送测试流量,观察 skb->markskb->priority 的变化(可通过其他工具如 nstat 或自定义程序验证)。

四、字段访问的注意事项

  1. 边界检查不可省略 :当通过 data/data_end 访问数据包内容时,必须进行严格的边界检查,否则 verifier 会拒绝加载。
  2. 字段的可写性 :某些字段(如 lenprotocol)是只读的,尝试写入会导致验证失败。请查阅内核文档或 linux/bpf.h 中的注释。
  3. 大小端转换protocolvlan_proto 等字段通常使用网络字节序,与 bpf_htons() 配合。
  4. 内核版本差异 :部分字段(如 wire_lengso_size)需要较新的内核(5.x 以上),在低版本上可能不存在或行为不同。
  5. cb 数组的范围:只有 5 个元素,不要越界访问。

五、总结

__sk_buff 是 eBPF 网络程序访问数据包元数据的标准接口。通过它,我们可以读取或修改数据包的各种属性,包括:

  • 数据包内容(通过 data/data_end 配合辅助函数)
  • 标记和优先级(markpriority
  • 流量分类(tc_classid
  • 临时状态传递(cb
  • 设备信息(ifindex
  • 协议类型(protocol
  • VLAN 信息(vlan_tci 等)
  • GSO 元数据(gso_size 等)

理解这些字段的作用和限制,是编写高效、安全网络程序的基础。希望本文能帮助你在 eBPF 网络开发中更加得心应手。


参考资料:

  • Linux 内核源码 include/uapi/linux/bpf.h
  • man 2 bpf
  • 内核文档 Documentation/bpf/
  • bpftool 工具的使用手册
相关推荐
2301_794799512 小时前
35_简单快捷不可靠的_UDP ## 网络协议那些事儿
网络·网络协议·udp
左手厨刀右手茼蒿2 小时前
Flutter for OpenHarmony:Flutter 三方库 udp — 实现极速底层异步通信(适配鸿蒙 HarmonyOS Next ohos)
网络·网络协议·flutter·华为·udp·harmonyos
秋刀鱼不做梦2 小时前
网络编程和Socket套接字(UDP+TCP)(如果想知道Java中有关网络编程和Socket套接字的知识,那么只看这一篇就足够了!)
网络·网络协议·学习·tcp/ip·udp
liulilittle2 小时前
TC Hairpin NAT 驱动使用手册(个人版)
服务器·开发语言·网络·c++·网络协议·tcp/ip·tc
Alonse_沃虎电子2 小时前
沃虎工业级RJ45抗震动方案:破解严苛环境下的网络连接难题
网络·产品·电子元器件·电子元器件供应商·网络变压器
Bin努力加餐饭2 小时前
C++(3)TCP
网络·网络协议·tcp/ip
不一样的故事1263 小时前
测试的核心本质是风险管控
大数据·网络·人工智能·安全
威联通网络存储13 小时前
告别掉帧与素材损毁:威联通 QuTS hero 如何重塑影视后期协同工作流
前端·网络·人工智能·python
啃玉米的艺术家13 小时前
UART实验
网络