当前位置: 首页 > news >正文

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 场景下的原始长度获取。

示例:

void*data=(void*)(long)skb->data;void*data_end=(void*)(long)skb->data_end;if(data+sizeof(structiphdr)>data_end)returnTC_ACT_OK;structiphdr*ip=data;

2.2 元数据标记与优先级

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

示例:

// 设置 mark 和 classidskb->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_ifindexifindex,仅用于 ingress 场景。只读同上。
protocol链路层协议类型(如ETH_P_IPETH_P_ARP)。只读快速判断上层协议,通常需要bpf_htons()转换。
pkt_type包类型,如PACKET_HOST(发给本机)、PACKET_BROADCAST(广播)等。只读过滤非本机流量。

示例:

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

2.4 VLAN 相关

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

示例:

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

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

字段说明可读写使用场景
gso_size如果数据包是 GSO 分段,表示每个分段的大小(MSS)。只读用于优化处理,修改头部时需谨慎,因为会影响所有分段。
gso_segsGSO 分段数。只读可用于统计或控制。
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 入口):

// tc_ingress.c#include<linux/bpf.h>#include<linux/if_ether.h>#include<linux/pkt_cls.h>#include<bpf/bpf_helpers.h>SEC("classifier")inttc_ingress(struct__sk_buff*skb){// 只处理 IPv4 包if(skb->protocol==bpf_htons(ETH_P_IP)){// 若 mark 为 0x1234,则丢弃if(skb->mark==0x1234)returnTC_ACT_SHOT;// 设置 cb[0] 标记,供 egress 使用skb->cb[0]=0xabcd;}returnTC_ACT_OK;}char_license[]SEC("license")="GPL";

egress 部分(用于 tun0 出口):

// tc_egress.c#include<linux/bpf.h>#include<linux/pkt_cls.h>#include<bpf/bpf_helpers.h>SEC("classifier")inttc_egress(struct__sk_buff*skb){// 如果 ingress 设置了 cb[0] 标记,则提高优先级(降低数值)if(skb->cb[0]==0xabcd){// 原优先级 3,改为 1(更高优先级)skb->priority=1;// 清除标记(可选)skb->cb[0]=0;}returnTC_ACT_OK;}char_license[]SEC("license")="GPL";

3.2 编译与挂载

# 编译clang-targetbpf-O2-g-Wall-ctc_ingress.c-otc_ingress.o clang-targetbpf-O2-g-Wall-ctc_egress.c-otc_egress.o# 创建 clsact qdiscsudotc qdiscadddev tun0 clsact# 挂载 ingress 和 egress 程序sudotc filteradddev tun0 ingress bpf obj tc_ingress.o sec classifiersudotc filteradddev 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工具的使用手册
http://www.cnnetsun.cn/news/1545112.html

相关文章:

  • evo轨迹评估实战:解析时间戳未对齐的根源与修复方案
  • 告别三小时格式挣扎:Cloud Document Converter让飞书文档转Markdown效率提升10倍
  • 用Logisim玩转数字电路:从七段数码管到简易计算器(附完整电路文件)
  • 从LOAM到Fast-LIO2:观测模型演进与h_share_model函数设计思想剖析
  • Thanos对象存储终极指南:如何实现多云长期存储方案
  • 效率提升:用快马平台将origin绘图流程模板化,一键生成论文级图表
  • Multer视频处理终极指南:上传后自动转码为多种分辨率的完整实现
  • 网易云音乐评论爬虫实战:破解加密接口抓取数据
  • InstructPix2Pix算法解析:从Pix2Pix到指令驱动的进化
  • Font-Awesome-SVG-PNG 常见问题解决:rsvg-convert和librsvg安装配置
  • 精锐纵横营销顾问——以全链路实战能力迭代营销咨询行业
  • 别再只当CANopen网关用!EL6751的‘直通CAN’模式,让你像用CAN盒一样玩转倍福PLC
  • 终极SoundRedux API集成指南:如何与SoundCloud API进行无缝数据交互
  • 高效配置黑苹果:智能工具驱动的OpenCore部署新方案
  • Gear-Lib系统抽象层揭秘:POSIX适配与硬件抽象设计思想
  • Curated Programming Resources项目管理实践:GitHub开源项目的成功经验分享
  • COMSOL熔池枝晶模型
  • 保姆级教程:用微信小程序模拟蓝牙钥匙,5分钟搞定充电桩自动充电(附完整代码)
  • 【声纳与人工智能融合——从理论前沿到自主系统实战】第七章 无人系统(AUV/USV)智能感知与协同作战
  • 2026降AI率工具红黑榜:降AIGC平台怎么选?看完少走弯路
  • SOONet实战案例:客服视频质检——自动定位‘承诺退款’关键对话时段
  • LoadRunner11中文破解版安装全攻略:从下载到脚本录制一步到位
  • 多功能雷达工作模式识别实战指南:从搜索到跟踪的5种典型场景解析
  • 5分钟掌握FlexASIO:让普通声卡秒变专业音频接口的终极指南
  • Linux 系统下最强大的多媒体处理工具:FFmpeg,这10大技巧不要太好用
  • mxbai-embed-large-v1实战教程:手把手教你实现文本分类与聚类
  • Qwen3Guard-Gen-WEB实战体验:一键部署,轻松审核中英文内容安全
  • 新手福音:用快马平台生成带详解的计算器单元测试入门代码
  • 终极B站视频下载教程:3分钟学会使用BilibiliDown批量下载高清视频
  • 如何永久保存微信聊天记录:WeChatExporter完整解决方案