写在前面

sk_buff 是 Linux 网络栈最核心、也最容易被误解的数据结构之一。它并不是“装着一个完整网络包的结构体”,而是贯穿驱动、协议栈、队列、Socket、Netfilter、tc、BPF 与硬件 offload 的控制对象和所有权载体

从 Hook、verdict 和所有权出发,比较 Netfilter/Conntrack/NAT、tc action、eBPF __sk_buff 视图,以及 XDP 进入 skb 世界前后的边界。

本文以本地 Linux 5.10.209 源码为主线;涉及演进时对照 Linux 6.1.99,涉及网卡驱动时结合 Firefly RK3588 SDK Linux 5.10.198。本文是源码分析,不把尚未执行的 QEMU、tracepoint 或开发板实验写成已验证结论。

Linux sk_buff 深度解析(六):Netfilter、tc、eBPF 与 XDP

阅读前先建立四个问题

阅读任何 skb 代码路径,都建议反复追问:

  1. 当前 skb->data 指向哪一层协议头,linear 与 non-linear 数据分别在哪里?
  2. 哪些字段刚被修改,下一层为什么依赖这些字段?
  3. 当前谁持有 skb shell、head、frag page 以及 socket/dst/conntrack 等外部引用?
  4. 成功、失败、重试、redirect 和丢弃分支分别由谁消费或释放 skb?

本篇核心结论

  • Hook 框架的核心不是“调用规则”,而是 verdict 如何改变控制流和 skb 所有权。
  • Conntrack、dst、socket、BPF metadata 都有独立于 skb shell 的引用语义。
  • struct __sk_buff 是 verifier 管理的 ABI 视图,不等于程序可任意解引用内核 struct sk_buff
  • XDP 在 skb 分配前处理 RX buffer;只有 XDP_PASS 才通常进入完整 skb 生命周期。

1. Netfilter、Conntrack 与 NAT

结论摘要

Netfilter hook 把 skb 交给按优先级注册的 hook function。verdict 不只是允许/拒绝,还决定 skb 所有权:ACCEPT 继续,DROP 释放,STOLEN 表示 hook 已接管,QUEUE 把 skb 交给用户队列并等待 reinject,REPEAT 重跑当前 hook。conntrack 通过 skb _nfct 挂接共享连接引用;NAT 修改 header 前必须确保线性、可写并同步 checksum、route 状态。

一、hook 位置

IPv4 典型:

1
2
3
4
5
RX:
PRE_ROUTING → route → LOCAL_IN 或 FORWARD → POST_ROUTING

TX local:
LOCAL_OUT → route/output → POST_ROUTING

bridge、ARP、IPv6 有各自 hook family。skb 在不同 hook 时的 data/header/dev/dst 状态不同,hook 不能假定所有层都已解析。

二、nf_hook_slow

入口:kernel-5.10/net/netfilter/core.c:577

它从 hook entries 指定索引开始遍历:

  1. 调用 hook function;
  2. 读取 verdict;
  3. ACCEPT 继续下一个;
  4. DROP 释放并返回错误;
  5. QUEUE 调用 nf_queue()
  6. STOLEN 停止,skb 已被 hook 接管;
  7. 最终全部 ACCEPT 后调用 okfn 继续原协议路径。

三、verdict 与所有权

定义:kernel-5.10/include/uapi/linux/netfilter.h:11-15

Verdict skb 语义
NF_ACCEPT Netfilter 不保留,继续下一 hook/okfn
NF_DROP 当前路径丢弃,core 负责或已执行释放
NF_STOLEN hook 已接管 skb,调用者不能释放或继续
NF_QUEUE queue subsystem 持有 skb,等待 verdict/reinject
NF_REPEAT 从当前 hook 再执行,需要避免无限循环

NF_DROP_ERR() 还能把 errno 编码进 verdict。

四、NFQUEUE 与 reinject

nf_queue() 位于 kernel-5.10/net/netfilter/nf_queue.c:246。queue entry 保存:

  • skb;
  • hook state;
  • 当前 hook index;
  • 设备/socket/netns 等引用。

用户空间给出 verdict 后 nf_reinject() 从保存位置继续。排队期间 skb 生命周期跨越异步用户交互,所以必须增加所有关联引用;queue overflow、进程退出或 verdict drop 都要释放。

五、conntrack

conntrack 识别 tuple、状态和方向,把连接对象引用编码在 skb->_nfct。skb clone/copy 时 __nf_copy() 增加引用,释放时 nf_conntrack_put(),见 skb_release_head_state()

conntrack entry 是共享 flow 状态,不在 skb 数据区中。一个连接的多个 skb 指向同一对象;错误漏 ref 会 use-after-free,漏 put 会连接表泄漏。

六、NAT

nf_nat_packet() 位于 kernel-5.10/net/netfilter/nf_nat_core.c:695。NAT 根据 conntrack 方向修改:

  • IPv4/IPv6 地址;
  • TCP/UDP 端口;
  • ICMP 内嵌报文;
  • checksum;
  • 可能触发 route 重新计算。

修改前必须保证:

  1. 相关 header 在线性区;
  2. skb header 可写,必要时 skb_ensure_writable()/COW;
  3. 修改长度时 tailroom/headroom 合法;
  4. checksum metadata 与实际字节一致;
  5. cloned skb 不直接污染其它共享者。

七、skb 字段影响

  • _nfct:连接引用与 ctinfo;
  • mark:规则、策略路由和 tc 共享的通用标记;
  • nf_trace:trace 状态;
  • secmark:安全标记;
  • dev/sk/dst:hook state 与 route;
  • header offsets/checksum:NAT 修改;
  • extensions:可携带 secpath、bridge 等额外状态。

八、fragment 与 conntrack

L4 conntrack/NAT 需要端口,后续分片不一定含 L4 header。Netfilter defrag 在合适 hook 前重组,确保 conntrack 看到完整报文;之后转发/输出可能再次分片。

这意味着 skb 可能在 Netfilter 前后从多个 fragment skb 变成一个重组 skb,数据布局和 truesize 都会改变。

九、bridge Netfilter

桥接包可能同时经历 L2 bridge hook 与伪装成 L3 的 IPv4/IPv6 hook。nf_bridge/相关 extension 保存原设备、物理接口和 header 状态,避免二层/三层视图切换丢失信息。

不能只看 skb->dev 就判断原物理入口。

十、错误与释放

  • hook DROP:drop path 释放;
  • STOLEN:hook 自己最终释放或转交;
  • QUEUE:queue/reinject 负责;
  • NAT COW 失败:返回 drop/error 并释放;
  • conntrack invalid:按规则 accept/drop,引用仍需平衡;
  • clone 给日志/tap:每个引用独立释放。

十一、常见误区

  1. NF_STOLEN 不等于 drop,它表示所有权被 hook 接管。
  2. QUEUE 后原协议路径不能继续访问 skb。
  3. conntrack 数据不在 packet bytes 中,而是 skb 外部引用。
  4. NAT 不只是改地址,还要维护端口、checksum、route 和 clone 可写性。
  5. mark 不是 conntrack state,但规则可互相读写。
  6. hook 所在位置决定 header/data 是否可用,不能通用地直接 ip_hdr()

字段与所有权统一核对表

对象或字段 主要修改者/使用者 所有权与释放要点
_nfct conntrack lookup/confirm skb 持有 conntrack 引用
mark 规则、策略路由、tc 通用元数据跨 hook 传递
data/header/checksum NAT/mangle 修改前确保可写并修正 checksum

最小调用链

1
2
3
4
5
协议栈进入 Netfilter hook
→ conntrack lookup/attach
→ rule verdict/mangle
→ NAT/confirm
→ ACCEPT 继续或 DROP/QUEUE 等路径消费 skb

2. tc ingress/egress 的分类与动作

tc ingress/egress 的分类与动作

结论摘要

tc ingress 位于 RX core 中,egress 位于 __dev_queue_xmit() 早期。classifier 读取 skb 字段/包数据生成 class result,action 可以修改、drop、redirect、mirror、reclassify 或继续。TC_ACT_STOLEN/REDIRECT/SHOT 都涉及消费语义,但具体由 action/core 合同决定。mirred mirror 通常 clone,redirect 通常把原 skb 改 dev 后转交。

一、入口位置

  • ingress:sch_handle_ingress()kernel-5.10/net/core/dev.c:5000,RX core 调用点约 5241;
  • egress:sch_handle_egress()kernel-5.10/net/core/dev.c:3904__dev_queue_xmit() 调用点约 4134。

因此:

1
2
RX driver/GRO → ingress tc → rx handler/protocol stack
TX IP output → egress tc → TX queue/qdisc → driver

redirect 后可能重新进入另一个设备的 RX/TX 路径,必须有递归/loop 防护。

二、classifier

tcf_classify() 位于 kernel-5.10/net/sched/cls_api.c:1583,内部 __tcf_classify() 在 1529。

classifier 可依据:

  • protocol/VLAN;
  • IP/port;
  • mark/priority/tc_index
  • skb hash;
  • BPF 程序结果;
  • flower/u32 等 key。

结果可能选择 classid 或执行 action chain。classful qdisc 也可调用同一分类框架。

三、action 执行

tcf_action_exec() 位于 kernel-5.10/net/sched/act_api.c:675。action chain 按序执行,每个 action 返回控制码:

  • TC_ACT_OK:停止 action chain并正常继续;
  • TC_ACT_PIPE:继续下一个 action;
  • TC_ACT_RECLASSIFY:回到分类;
  • TC_ACT_SHOT:丢弃;
  • TC_ACT_STOLEN:action 接管 skb;
  • TC_ACT_REDIRECT:重定向并消费当前路径;
  • trap 等其它扩展语义。

常量见 kernel-5.10/include/uapi/linux/pkt_cls.h:60-67

四、mirred

tcf_mirred_act() 位于 kernel-5.10/net/sched/act_mirred.c:228

mirror

为了让原路径继续,同时给目标设备一个副本,通常 clone skb:

1
2
original skb → continue
clone skb → target dev

clone 共享数据,修改前仍需 COW。

redirect

原 skb 改变 dev 后交给目标路径,当前路径停止:

1
2
original skb → target dev
current path → consumed

需要处理 MAC header push/pull、ingress/egress 方向、redirected/from_ingress 标志和 nesting level。

五、skb 字段

  • mark:规则/action 通用标记;
  • priority:qdisc/class 优先级;
  • tc_index:tc index;
  • tc_skip_classify:避免重复分类;
  • tc_at_ingress:当前方向;
  • redirected/from_ingress:redirect 状态;
  • dev:目标设备;
  • queue_mapping:跨设备后可能重置;
  • cb:qdisc/tc 临时数据;
  • checksum/header offsets:改包 action 必须同步。

六、修改 packet data

pedit、BPF、VLAN push/pop、tunnel key 等 action 修改 skb 时必须考虑:

  1. header 是否在线性区;
  2. cloned head 是否可写;
  3. headroom/tailroom;
  4. checksum 更新;
  5. GSO/encapsulation metadata;
  6. 旧 data pointer 失效;
  7. redirect 目标设备 feature 差异。

修改 GSO skb 的 header 比普通包更敏感,因为一个逻辑修改会影响所有未来 segment。

七、ingress 与 egress 的 data 视图

ingress 时 MAC header 可能已 pull,但 skb_mac_header() 仍保存原 L2;某些 action 需要临时 push MAC header 再向 L2 设备 redirect。

egress 时 data 通常指向待发送 L2 header,但隧道/虚拟设备可能仍在构造过程中。action 应使用 header helper,而不是固定假设 data 指向 IP。

八、所有权表

Action/result 原路径 skb/clone owner
OK/PIPE 继续 tc 返回调用者
SHOT 停止 tc/core 释放
STOLEN 停止 action 接管
REDIRECT 停止 目标设备路径
MIRROR 原路径继续 clone 交目标,原 skb 返回
RECLASSIFY 重跑 tc 仍持有,需循环限制

九、与 qdisc 的关系

  • egress tc 通常在选择/进入 qdisc 前运行;
  • qdisc 内部也可绑定 classifier;
  • action 可改变 priority/classid/queue mapping;
  • redirect 可能完全绕开当前设备 qdisc,进入目标设备的新 TX/RX 流程;
  • qdisc cb 与其它 cb overlay 共享空间,必须遵守当前层所有权。

十、常见误区

  1. tc 不只是 qdisc;ingress/egress classifier/action 可在排队外执行。
  2. mirror 与 redirect 的关键区别是原路径是否继续及是否 clone。
  3. STOLEN 后调用者不能再次释放 skb。
  4. 修改包后只改 bytes 不更新 checksum/GSO metadata 会产生坏包。
  5. redirect 后原 queue mapping/设备 feature 不一定有效。
  6. cb 是临时空间,不能把 tc 私有数据假定为跨协议层永久存在。

字段与所有权统一核对表

对象或字段 主要修改者/使用者 所有权与释放要点
tc_index/priority/mark classifier/action 影响分类、策略与后续队列
redirected/dev mirred/redirect 动作改变设备和再次进入路径
data/header pedit、vlan、tunnel action 修改前 COW,失败时 action 消费或丢弃

最小调用链

1
2
3
4
ingress/egress hook
→ classifier 读取 skb 元数据
→ action 修改/clone/redirect/drop
→ 重新进入设备或继续 qdisc/协议栈

3. eBPF 的 struct __sk_buff 受控视图

结论摘要

BPF 程序看到的 struct __sk_buff 是稳定的 UAPI 上下文,不是内核 struct sk_buff 的真实内存布局。verifier/JIT 把字段访问转换为底层 skb helper 或偏移访问。direct packet access 使用 data/data_end 边界;任何可能 pull、COW、扩展或重分配 head 的 helper 都会使已验证的数据指针失效,程序必须重新加载和检查。

一、两种结构不能混淆

UAPI 定义:kernel-5.10/include/uapi/linux/bpf.h:4088

struct __sk_buff 暴露:

  • len、pkt_type、mark、queue_mapping、protocol、vlan;
  • priority、ingress_ifindex、ifindex;
  • tc_index、cb[5];
  • hash、tc_classid;
  • data/data_end;
  • family、remote/local IP/port;
  • data_meta、flow_keys、tstamp、wire_len、gso_segs 等。

它的字段和大小属于 BPF ABI。内核 struct sk_buff 受配置、版本和 KABI 影响,不能直接暴露给程序。

二、上下文字段转换

不同 BPF program type 有自己的 convert_ctx_access

  • tc classifier/action;
  • socket filter;
  • cgroup skb;
  • sk_skb/stream parser/verdict;
  • lwt 等。

verifier 根据字段、读写方向和 program type:

  1. 判断是否允许访问;
  2. 生成读取底层 skb 字段或调用 helper 的指令;
  3. 对写操作增加范围/语义检查;
  4. 某些字段返回派生值,而非一一映射。

因此相同 __sk_buff 字段在不同 program type 可能只读、可写或不可用。

三、direct packet access

程序常见模式:

1
2
3
4
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
if (data + sizeof(struct ethhdr) > data_end)
return ...;

verifier 追踪 data 指针和边界证明。data_end 通常只覆盖当前可直接线性访问区域,不保证 frags 全部连续。

协议 header 访问前必须逐层边界检查,不能因为 skb->len 足够就直接解引用 linear data 之外的字节。

四、bpf_skb_pull_data

实现:kernel-5.10/net/core/filter.c:1830

用途:

  • 确保前 len 字节在线性区;
  • 必要时调用 pull/linearize;
  • 对 cloned skb 做 unclone,使后续 direct write 安全。

调用后所有旧的 packet pointers 都被 verifier 视为失效,必须重新读取 data/data_end 并重新边界检查。

五、修改 helper

bpf_skb_store_bytes

实现:kernel-5.10/net/core/filter.c:1684。在指定逻辑 offset 写数据,可选择重新计算/使 checksum 失效。需要处理 non-linear、clone 和边界。

bpf_skb_change_head / change_tail

改变 headroom 或 packet length,可能重新分配 head、移动 data、trim/expand。调用后 packet pointers 全失效。

bpf_skb_adjust_room

UAPI 文档约 kernel-5.10/include/uapi/linux/bpf.h:1712。可在 MAC/NET 等位置增减空间,用于封装/解封装;会更新或要求调用者更新:

  • header offsets;
  • checksum;
  • GSO/encapsulation metadata;
  • protocol 相关内容。

六、redirect 与 clone_redirect

bpf_redirect

程序记录目标 ifindex/map,返回 redirect action;实际执行阶段把原 skb 交给目标路径。当前路径不再继续。

bpf_clone_redirect

实现:kernel-5.10/net/core/filter.c:2430。先 clone skb,把 clone 发送到目标,原 skb 可继续在当前 BPF 程序/路径处理。代价包括新 shell、引用计数、目标发送路径。

clone 共享数据,因此目标路径或原路径修改 header 前仍需 COW。

七、checksum helper

BPF 修改地址、端口或 payload 后可使用:

  • bpf_l3_csum_replace()
  • bpf_l4_csum_replace()
  • bpf_csum_diff()
  • store_bytes flags。

仅修改字节不维护 skb ip_summed、checksum 字段和 pseudo header,会使硬件 offload 或接收验证产生错误。

八、cb 的差异

struct __sk_buff.cb[5] 对 BPF 暴露 20 字节,不等于内核完整 skb->cb[48]。可用范围和跨 tail call/clone/层级的持久性受 program type 约束。

内核其它层仍会复用完整 cb,所以 BPF 元数据要跨 hook 传递时通常更适合 mark、priority、BPF metadata、maps 或明确的协议字段,而不是假定 cb 永久保存。

九、不同 program type 的 skb 语义

Program type 典型位置 主要动作
socket filter socket 入队前 accept/truncate/drop
tc cls/act ingress/egress classify/edit/redirect
cgroup_skb socket/cgroup 边界 policy/mark/drop
sk_skb sockmap/skmsg 路径 parser/verdict/redirect
lwt route/lightweight tunnel encap/redirect

相同 helper 不一定在所有类型可用,verifier 根据 bpf_func_proto 限制。

十、所有权规则

BPF 程序本身通常借用 skb 上下文,不直接持有 C 引用。返回 action 决定由框架:

  • 继续;
  • drop/free;
  • redirect/consume;
  • clone 后双路径;
  • socket queue。

程序不能保存 skb 指针到 map 跨调用使用。

十一、常见误区

  1. __sk_buff 不是可用 BTF 直接解释的内核 skb 布局副本。
  2. len 足够不代表 direct data 区连续足够。
  3. pull/adjust/change helper 后旧 data 指针必须重新验证。
  4. clone_redirect 与 redirect 的所有权和成本不同。
  5. 修改 header 后必须维护 checksum/GSO/header metadata。
  6. BPF cb 只暴露内核 cb 的一部分,且有生命周期限制。

字段与所有权统一核对表

对象或字段 主要修改者/使用者 所有权与释放要点
__sk_buff 字段 verifier 转译 程序看到受控上下文,不直接暴露全部内核结构
data/data_end direct packet access helper 或写操作后需重新验证指针
mark/priority/cb BPF helper 修改效果和可用 helper 由 attach type 决定

最小调用链

1
2
3
4
attach point 生成 __sk_buff 上下文
→ verifier 将字段访问转换到真实 skb/helper
→ 程序读写 packet/metadata
→ verdict 决定继续、redirect 或 drop

4. XDP 与 sk_buff 的边界

XDP 与 sk_buff 的边界

结论摘要

native XDP 在驱动 RX buffer 上运行,发生在 skb 分配之前;generic XDP 在 RX core 中运行,操作已经存在的 skb。xdp_buff 是驱动/NAPI 周期中的可变 buffer 视图,xdp_frame 是可跨 redirect/TX 异步持有的固定 frame。XDP_PASS 后驱动才构造 skb;redirect/TX/drop 必须正确转移或归还 page_pool buffer。

一、native XDP 位置

1
2
3
4
5
NIC DMA → RX page/xdp_buff → BPF_PROG_RUN_XDP
├─ PASS → build_skb/napi_alloc_skb → GRO/stack
├─ DROP/ABORTED → recycle page
├─ TX → same device TX path
└─ REDIRECT → xdp_frame/target → flush

它省去了被丢弃/重定向包的 skb shell 分配、初始化和协议栈成本。

二、generic XDP

do_xdp_generic() 位于 kernel-5.10/net/core/dev.c:4784,在 __netif_receive_skb_core() 中调用。此时:

  • skb 已分配;
  • packet 可能非线性;
  • helper 需要围绕 skb 构造 XDP 视图;
  • 性能优势小于 native;
  • 语义用于兼容没有驱动 native XDP 的设备。

不能用 generic XDP 的成功运行推断驱动支持 native XDP 或 XDP_TX 零拷贝。

三、xdp_buff

定义于 include/net/xdp.h,主要字段:

  • data
  • data_end
  • data_meta
  • data_hard_start
  • rxq
  • frame size/flags(版本相关)。

它描述当前 RX buffer 的窗口,不负责像 skb 一样携带 socket、dst、Netfilter、qdisc 等丰富元数据。

BPF 可用 bpf_xdp_adjust_head/tail/meta 改变视图,但必须保持在驱动提供的 hard start/frame 范围内。

四、xdp_frame

redirect 或 XDP_TX 需要在当前驱动 poll 之外持有数据,不能长期持有栈上的 xdp_buff 描述。转换为 xdp_frame 后保存:

  • data 相对 frame 起始;
  • len;
  • headroom;
  • memory model;
  • frame size/metadata。

目标完成后通过 xdp_return_frame() 等根据 memory model 归还 page/page_pool。

五、XDP_PASS 到 skb

驱动常见策略:

  1. XDP 程序可能调整 data/meta;
  2. 根据调整后的范围构造 skb;
  3. build_skb() 包装原 page,或分配 skb 后 copy/attach frag;
  4. 设置 headroom、len、protocol、checksum、hash、queue;
  5. 交给 GRO。

若 build_skb 直接复用 page,skb free 必须知道如何返回内存池;若 copy,原 page 可立即 recycle。

六、redirect

xdp_do_redirect() 查找 devmap/cpumap/xskmap 等目标,把 frame/buffer 入目标批次。poll 尾部 xdp_do_flush() 才批量提交。

所有权时间线:

1
2
3
4
RX driver owns buffer
→ redirect accepted: redirect subsystem owns
→ target device/CPU/AF_XDP owns
→ completion/drop: memory model return callback

redirect 失败时当前路径必须回收,不能既交目标又 recycle。

七、XDP_TX

XDP_TX 通常把收到的 frame 从同设备发出:

  • 交换/修改 MAC;
  • 映射/复用 RX page;
  • 放入专用 XDP TX ring;
  • completion 后归还 page_pool。

它不是普通 skb ndo_start_xmit,通常没有 qdisc/socket owner/truesize 语义。

八、multi-buffer XDP

Linux 5.10 的 XDP 主要假设单 buffer。Linux 6.1 提供:

  • xdp_buff_has_frags()kernel-6.1/include/net/xdp.h:88
  • xdp_update_skb_shared_info()kernel-6.1/include/net/xdp.h:223

多 buffer packet 在 XDP PASS 转 skb 时,需要把额外 page fragments 写入 skb_shared_info,同步:

  • nr_frags
  • len/data_len
  • truesize
  • frag page ownership。

BPF 程序对 multi-buffer 数据访问能力也受版本/program support 限制。

九、RK3588 stmmac 边界

本地 Firefly BSP stmmac RX 笔记显示传统 skb/page_pool 构造路径。是否支持 native XDP 必须检查:

  • ndo_bpf/XDP setup;
  • RX poll 中 bpf_prog 执行;
  • xdp_rxq_info 注册;
  • XDP_TX ring;
  • redirect flush;
  • page_pool memory model。

如果这些闭环缺失,只能使用 generic XDP,不能因为内核启用 CONFIG_BPF 就声称 native 支持。

十、XDP 与 skb 功能边界

能力 XDP skb stack
时机 驱动 RX 早期 构造 skb 后
元数据 精简 buffer/rxq/meta socket/dst/nfct/qdisc/offload 等
排队 devmap/cpumap/xsk/XDP TX backlog/socket/qdisc/TCP 等
典型目标 drop/redirect/fast TX 完整协议栈与 socket
内存 page_pool/driver model slab head + page refs

十一、常见误区

  1. XDP 不是“更快的 skb hook”,native XDP 根本还没有 skb。
  2. generic XDP 已支付 skb 分配成本。
  3. xdp_buff 不能异步长期持有,需转 frame。
  4. redirect 成功后驱动不能再次 recycle page。
  5. XDP_TX 不经过普通 qdisc/ndo_start_xmit 语义。
  6. 5.10 单 buffer 结论不能直接推广到 6.1 multi-buffer。

字段与所有权统一核对表

对象或字段 主要修改者/使用者 所有权与释放要点
xdp_buff data pointers 驱动 RX/XDP 动作完成前仍是 RX buffer 所有权
xdp_frame redirect/TX 转换后由目标设备或 map 完成路径持有
skb/shared_info PASS/build_skb 跨入 skb 世界后遵守 skb/page_pool 回收语义

最小调用链

1
2
3
4
driver RX buffer/xdp_buff
→ BPF XDP verdict
→ PASS 构造 skb,TX/REDIRECT 转换 xdp_frame,DROP 直接回收
→ 对应 completion 释放

源码阅读路线

建议不要从 struct sk_buff 声明开始逐字段背诵,而应使用下面的阅读顺序:

  1. 先在入口函数确认 skb 是新分配、clone、队列取出还是从驱动 buffer 构造;
  2. 沿成功主线记录 data/len/data_len、header offset、checksum、hash、queue mapping 等字段变化;
  3. 再回到每个失败分支,确认 skb、page、DMA、socket 和记账引用是否回滚;
  4. 最后对照 5.10、6.1 或 BSP 的同名 symbol,区分语义变化、性能优化和 vendor backport。

本篇总结

  1. Hook 框架的核心不是“调用规则”,而是 verdict 如何改变控制流和 skb 所有权。
  2. Conntrack、dst、socket、BPF metadata 都有独立于 skb shell 的引用语义。
  3. struct __sk_buff 是 verifier 管理的 ABI 视图,不等于程序可任意解引用内核 struct sk_buff
  4. XDP 在 skb 分配前处理 RX buffer;只有 XDP_PASS 才通常进入完整 skb 生命周期。

sk_buff 的难点从来不只是字段多,而是同一个对象不断改变数据视图、元数据状态和持有者。只要把“布局、字段、所有权、释放”四条线同时画出来,绝大多数网络栈源码都会从零散函数变成可推导的生命周期。

系列目录

  1. Linux sk_buff 深度解析(一):数据结构、四指针与核心字段
  2. Linux sk_buff 深度解析(二):非线性数据、引用计数与生命周期
  3. Linux sk_buff 深度解析(三):从网卡 DMA 到 Socket 的 RX 路径
  4. Linux sk_buff 深度解析(四):从 sendmsg 到 TX completion
  5. Linux sk_buff 深度解析(五):GRO、GSO、Checksum 与多核流调度
  6. Linux sk_buff 深度解析(六):Netfilter、tc、eBPF 与 XDP(本文)
  7. Linux sk_buff 深度解析(七):VLAN、隧道、分片与重组
  8. Linux sk_buff 深度解析(八):Socket 内存、零拷贝、丢包与版本演进