2026/4/5 11:50:19
网站建设
项目流程
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的不同字段例如TCTraffic Control程序可以访问绝大多数字段支持mark、priority、tc_classid等。cgroup skb 程序额外提供remote_ip4、local_port等直接的四层信息。XDP 程序在 skb 模式下支持data、data_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(datasizeof(structiphdr)data_end)returnTC_ACT_OK;structiphdr*ipdata;2.2 元数据标记与优先级字段说明可读写使用场景markskb 的防火墙标记32 位值。可读写可在入口设置 mark出口根据 mark 进行策略路由、过滤或连接跟踪。priority调度优先级skb-priority值越小优先级越高。可读写影响 Qdisc 调度顺序可用于实现 QoS。tc_classidTC 分类 ID格式为major:minor每个 16 位。可读写在分类器中设置使数据包进入指定的 classful qdisc 队列。cb[5]控制块control block5 个 32 位值的数组。可读写可在同一钩子的多个 eBPF 程序间传递数据例如 ingress 到 egress。示例// 设置 mark 和 classidskb-mark0x12345678;skb-tc_classidbpf_htons(116|10);// major1, minor10// 使用 cb 传递临时数据skb-cb[0]0xdeadbeef;2.3 设备与协议信息字段说明可读写使用场景ifindex设备索引。对于 ingress是接收接口对于 egress是发送接口。只读判断流量来自/去向哪个接口或用于重定向。ingress_ifindex同ifindex仅用于 ingress 场景。只读同上。protocol链路层协议类型如ETH_P_IP或ETH_P_ARP。只读快速判断上层协议通常需要bpf_htons()转换。pkt_type包类型如PACKET_HOST发给本机、PACKET_BROADCAST广播等。只读过滤非本机流量。示例if(skb-protocolbpf_htons(ETH_P_IP)){// 处理 IPv4 包}if(skb-pkt_typePACKET_HOST){// 只处理发往本机的包}2.4 VLAN 相关字段说明可读写使用场景vlan_present布尔值表示是否存在 VLAN 标签。只读条件判断。vlan_tciVLAN 标签控制信息Tag Control Information包含 VID12位、PCP3位、DEI1位。只读读取 VLAN ID 或优先级。vlan_protoVLAN 协议类型如ETH_P_8021Q0x8100或ETH_P_8021AD0x88A8。只读区分单层 VLAN 或 QinQ。示例if(skb-vlan_present){__u16 vidskb-vlan_tci0xFFF;// 根据 VID 处理}2.5 GSO/TSO 相关高版本内核字段说明可读写使用场景gso_size如果数据包是 GSO 分段表示每个分段的大小MSS。只读用于优化处理修改头部时需谨慎因为会影响所有分段。gso_segsGSO 分段数。只读可用于统计或控制。csum_level校验和层级用于隧道封装。只读处理隧道包的校验和时参考。2.6 时间戳与哈希字段说明可读写使用场景tstamp时间戳通常为 ns 单位需要内核配置CONFIG_NET_CLS_ACT和CONFIG_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 方向检查数据包根据protocol和mark决定是否丢弃并在 egress 方向根据cb传递的标记修改priority。3.1 程序代码ingress 部分用于 tun0 入口// tc_ingress.c#includelinux/bpf.h#includelinux/if_ether.h#includelinux/pkt_cls.h#includebpf/bpf_helpers.hSEC(classifier)inttc_ingress(struct__sk_buff*skb){// 只处理 IPv4 包if(skb-protocolbpf_htons(ETH_P_IP)){// 若 mark 为 0x1234则丢弃if(skb-mark0x1234)returnTC_ACT_SHOT;// 设置 cb[0] 标记供 egress 使用skb-cb[0]0xabcd;}returnTC_ACT_OK;}char_license[]SEC(license)GPL;egress 部分用于 tun0 出口// tc_egress.c#includelinux/bpf.h#includelinux/pkt_cls.h#includebpf/bpf_helpers.hSEC(classifier)inttc_egress(struct__sk_buff*skb){// 如果 ingress 设置了 cb[0] 标记则提高优先级降低数值if(skb-cb[0]0xabcd){// 原优先级 3改为 1更高优先级skb-priority1;// 清除标记可选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 classifier3.3 验证通过tc filter show dev tun0 ingress和egress查看挂载状态。使用bpftool prog show查看加载的程序。发送测试流量观察skb-mark和skb-priority的变化可通过其他工具如nstat或自定义程序验证。四、字段访问的注意事项边界检查不可省略当通过data/data_end访问数据包内容时必须进行严格的边界检查否则 verifier 会拒绝加载。字段的可写性某些字段如len、protocol是只读的尝试写入会导致验证失败。请查阅内核文档或linux/bpf.h中的注释。大小端转换protocol、vlan_proto等字段通常使用网络字节序与bpf_htons()配合。内核版本差异部分字段如wire_len、gso_size需要较新的内核5.x 以上在低版本上可能不存在或行为不同。cb数组的范围只有 5 个元素不要越界访问。五、总结__sk_buff是 eBPF 网络程序访问数据包元数据的标准接口。通过它我们可以读取或修改数据包的各种属性包括数据包内容通过data/data_end配合辅助函数标记和优先级mark、priority流量分类tc_classid临时状态传递cb设备信息ifindex协议类型protocolVLAN 信息vlan_tci等GSO 元数据gso_size等理解这些字段的作用和限制是编写高效、安全网络程序的基础。希望本文能帮助你在 eBPF 网络开发中更加得心应手。参考资料Linux 内核源码include/uapi/linux/bpf.hman 2 bpf内核文档Documentation/bpf/bpftool工具的使用手册