Linux sk_buff 深度解析(六):Netfilter、tc、eBPF 与 XDP
写在前面
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 或开发板实验写成已验证结论。
阅读前先建立四个问题
阅读任何 skb 代码路径,都建议反复追问:
- 当前
skb->data指向哪一层协议头,linear 与 non-linear 数据分别在哪里? - 哪些字段刚被修改,下一层为什么依赖这些字段?
- 当前谁持有 skb shell、head、frag page 以及 socket/dst/conntrack 等外部引用?
- 成功、失败、重试、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 | RX: |
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 指定索引开始遍历:
- 调用 hook function;
- 读取 verdict;
- ACCEPT 继续下一个;
- DROP 释放并返回错误;
- QUEUE 调用
nf_queue(); - STOLEN 停止,skb 已被 hook 接管;
- 最终全部 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 重新计算。
修改前必须保证:
- 相关 header 在线性区;
- skb header 可写,必要时
skb_ensure_writable()/COW; - 修改长度时 tailroom/headroom 合法;
- checksum metadata 与实际字节一致;
- 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:每个引用独立释放。
十一、常见误区
NF_STOLEN不等于 drop,它表示所有权被 hook 接管。- QUEUE 后原协议路径不能继续访问 skb。
- conntrack 数据不在 packet bytes 中,而是 skb 外部引用。
- NAT 不只是改地址,还要维护端口、checksum、route 和 clone 可写性。
- mark 不是 conntrack state,但规则可互相读写。
- hook 所在位置决定 header/data 是否可用,不能通用地直接
ip_hdr()。
字段与所有权统一核对表
| 对象或字段 | 主要修改者/使用者 | 所有权与释放要点 |
|---|---|---|
_nfct |
conntrack lookup/confirm | skb 持有 conntrack 引用 |
mark |
规则、策略路由、tc | 通用元数据跨 hook 传递 |
| data/header/checksum | NAT/mangle | 修改前确保可写并修正 checksum |
最小调用链
1 | 协议栈进入 Netfilter hook |
2. 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 | RX driver/GRO → ingress tc → rx handler/protocol stack |
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 | original skb → continue |
clone 共享数据,修改前仍需 COW。
redirect
原 skb 改变 dev 后交给目标路径,当前路径停止:
1 | original skb → target dev |
需要处理 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 时必须考虑:
- header 是否在线性区;
- cloned head 是否可写;
- headroom/tailroom;
- checksum 更新;
- GSO/encapsulation metadata;
- 旧 data pointer 失效;
- 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 共享空间,必须遵守当前层所有权。
十、常见误区
- tc 不只是 qdisc;ingress/egress classifier/action 可在排队外执行。
- mirror 与 redirect 的关键区别是原路径是否继续及是否 clone。
- STOLEN 后调用者不能再次释放 skb。
- 修改包后只改 bytes 不更新 checksum/GSO metadata 会产生坏包。
- redirect 后原 queue mapping/设备 feature 不一定有效。
- cb 是临时空间,不能把 tc 私有数据假定为跨协议层永久存在。
字段与所有权统一核对表
| 对象或字段 | 主要修改者/使用者 | 所有权与释放要点 |
|---|---|---|
tc_index/priority/mark |
classifier/action | 影响分类、策略与后续队列 |
redirected/dev |
mirred/redirect | 动作改变设备和再次进入路径 |
| data/header | pedit、vlan、tunnel action | 修改前 COW,失败时 action 消费或丢弃 |
最小调用链
1 | ingress/egress hook |
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:
- 判断是否允许访问;
- 生成读取底层 skb 字段或调用 helper 的指令;
- 对写操作增加范围/语义检查;
- 某些字段返回派生值,而非一一映射。
因此相同 __sk_buff 字段在不同 program type 可能只读、可写或不可用。
三、direct packet access
程序常见模式:
1 | void *data = (void *)(long)skb->data; |
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 跨调用使用。
十一、常见误区
__sk_buff不是可用 BTF 直接解释的内核 skb 布局副本。len足够不代表 direct data 区连续足够。- pull/adjust/change helper 后旧 data 指针必须重新验证。
- clone_redirect 与 redirect 的所有权和成本不同。
- 修改 header 后必须维护 checksum/GSO/header metadata。
- BPF cb 只暴露内核 cb 的一部分,且有生命周期限制。
字段与所有权统一核对表
| 对象或字段 | 主要修改者/使用者 | 所有权与释放要点 |
|---|---|---|
__sk_buff 字段 |
verifier 转译 | 程序看到受控上下文,不直接暴露全部内核结构 |
| data/data_end | direct packet access | helper 或写操作后需重新验证指针 |
| mark/priority/cb | BPF helper | 修改效果和可用 helper 由 attach type 决定 |
最小调用链
1 | attach point 生成 __sk_buff 上下文 |
4. 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 | NIC DMA → RX page/xdp_buff → BPF_PROG_RUN_XDP |
它省去了被丢弃/重定向包的 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
驱动常见策略:
- XDP 程序可能调整 data/meta;
- 根据调整后的范围构造 skb;
build_skb()包装原 page,或分配 skb 后 copy/attach frag;- 设置 headroom、len、protocol、checksum、hash、queue;
- 交给 GRO。
若 build_skb 直接复用 page,skb free 必须知道如何返回内存池;若 copy,原 page 可立即 recycle。
六、redirect
xdp_do_redirect() 查找 devmap/cpumap/xskmap 等目标,把 frame/buffer 入目标批次。poll 尾部 xdp_do_flush() 才批量提交。
所有权时间线:
1 | RX driver owns buffer |
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 |
十一、常见误区
- XDP 不是“更快的 skb hook”,native XDP 根本还没有 skb。
- generic XDP 已支付 skb 分配成本。
- xdp_buff 不能异步长期持有,需转 frame。
- redirect 成功后驱动不能再次 recycle page。
- XDP_TX 不经过普通 qdisc/ndo_start_xmit 语义。
- 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 | driver RX buffer/xdp_buff |
源码阅读路线
建议不要从 struct sk_buff 声明开始逐字段背诵,而应使用下面的阅读顺序:
- 先在入口函数确认 skb 是新分配、clone、队列取出还是从驱动 buffer 构造;
- 沿成功主线记录
data/len/data_len、header offset、checksum、hash、queue mapping 等字段变化; - 再回到每个失败分支,确认 skb、page、DMA、socket 和记账引用是否回滚;
- 最后对照 5.10、6.1 或 BSP 的同名 symbol,区分语义变化、性能优化和 vendor backport。
本篇总结
- 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 生命周期。
sk_buff 的难点从来不只是字段多,而是同一个对象不断改变数据视图、元数据状态和持有者。只要把“布局、字段、所有权、释放”四条线同时画出来,绝大多数网络栈源码都会从零散函数变成可推导的生命周期。
系列目录
- Linux sk_buff 深度解析(一):数据结构、四指针与核心字段
- Linux sk_buff 深度解析(二):非线性数据、引用计数与生命周期
- Linux sk_buff 深度解析(三):从网卡 DMA 到 Socket 的 RX 路径
- Linux sk_buff 深度解析(四):从 sendmsg 到 TX completion
- Linux sk_buff 深度解析(五):GRO、GSO、Checksum 与多核流调度
- Linux sk_buff 深度解析(六):Netfilter、tc、eBPF 与 XDP(本文)
- Linux sk_buff 深度解析(七):VLAN、隧道、分片与重组
- Linux sk_buff 深度解析(八):Socket 内存、零拷贝、丢包与版本演进
