基于 eBPF 的网络可观测协议栈路径与 sk_buff网络可观测要回答的是包从网卡到套接字或反向经过哪些阶段、在哪变慢、在哪被丢。eBPF 可以挂在XDP、TC、tracepoint、kprobe等位置对同一条流做分段计时与丢包归因。本文从内核收发包路径与sk_buff数据布局入手说明观测该怎么选挂点——不依赖其他篇章即可用于设计网络探针。目录网络可观测要看见什么发包路径TCP 为例收包路径对照sk_buff线性区与非线性区eBPF 常见挂点与能回答的问题可观测指标怎么设计丢包与延迟的归因思路实践注意系列结语从使用工具到设计方案网络可观测要看见什么相对「机器 CPU/内存」网络向可观测更关心信号例子延迟分段应用到协议栈、协议栈到驱动、驱动到线缆丢包点队列满、conntrack、netfilter、套接字接收队列流量结构五元组、重传、窗口、异常复位策略命中iptables/nft、cgroup、带宽限制eBPF 的优势是在不改业务、少采样偏差的前提下把这些点连成时间线。代价是内核版本、程序类型与开销控制。发包路径TCP 为例用户send/write之后数据大致经历简化忽略零拷贝与特殊加速用户缓冲区 → 套接字发送队列 / TCP 分段与拥塞控制 → IP 层封装 → netfilter / 路由决策 → 邻居与设备队列qdisc → 网卡驱动 TX → 线缆用户态 sendSocket / TCPIPNetfilter 等qdiscDriver TXNIC原书「TCP 发包流程图」强调的是这条自上而下的路径。可观测上可对关键函数或 tracepoint 打点例如进入协议栈的时刻进入 qdisc 的时刻驱动硬排队 / NAPI 相关事件重传定时器触发不同发行版符号名可能变化优先稳定 tracepoint动态 kprobe 作补充。收包路径对照收包大致反向但多了硬中断与软中断调度NIC RX → 驱动 / NAPI →可选XDP → 协议栈IP/TCP → netfilter → 套接字接收队列 → 用户态 recvXDP出现在协议栈之前可做丢弃、重定向、统计。若你的探针只挂在 TCP 层会看不见在 XDP 已被丢掉的包——挂点选择直接决定「视野」。PASSDROP/REDIRECTNIC RXDriver / NAPIXDP?协议栈提前结束Socket用户态 recv容器场景veth 会让路径多走一遍边界容器网络里报文常不止经过一块物理网卡。以宿主机路由到容器的典型路径为例具体顺序会受 CNI、桥接、隧道和策略配置影响物理网卡 RX宿主机协议栈 / 路由veth host 端veth 容器端 eth0容器 netns 协议栈容器 Socket / 应用veth像一根虚拟网线一端发出的包会成为另一端的接收包。因此宿主机物理网卡、veth host 端和容器eth0上的TC ingress/egress 观察结果并不等价方向还可能与直觉相反。设计探针时至少携带ifindex netns先画清 CNI 实际路径再决定在哪一端计数避免把同一包重复统计。sk_buff线性区与非线性区内核网络包的核心描述符是sk_buffskb。对 eBPF/内核代码读写载荷时必须区分区域含义访问方式概念线性区skb 头部连续缓冲区可通过skb-data起的一段连续内存直接读非线性区额外页片段frags不能假设都在data里需按frags/frag_list描述遍历物理页直观理解小包或已线性化的包头部与部分载荷在线性区大包、 gro/gso、零拷贝等场景下载荷常散在多个 page fragment 上。sk_buff ├── 线性区skb-data … skb-data len_linear └── 非线性区frags[i] → 某 page offset size对可观测与包解析的含义只probe_read线性区可能截断 HTTP 头/载荷需要完整载荷时要处理非线性区或接受「只解析头、统计长度」的折中部分 helper如包数据加载类会帮你按偏移取字节但仍受程序类型与校验器约束内核某些路径会调用skb_linearize()把非线性分片合并为连续线性区例如后续模块确实需要连续数据时。但 eBPF 观测不能假设「前面总有人替我拉平」是否线性化取决于具体路径、特性和内核版本而且线性化本身有复制成本。探针必须按最通用的非线性场景设计或明确只读取已证明连续的头部。XDP 的xdp_md为什么不是sk_buff写 XDP 时上下文是xdp_md模型与 skb 不同写 TC/socket 类程序才大量面对 skb维度XDPxdp_mdTC / socketskb 上下文所处阶段驱动早期Native 模式通常尚未分配 skb已进入更完整的网络栈数据视图data/data_end指向原始帧另有入接口、RX queue 等有限元数据可访问 skb 长度、协议标记等更丰富元数据套接字信息通常没有struct sock无法直接按 socket 归因某些挂点可关联 socket代价少了 skb 分配与管理路径极短信息更丰富但位置更晚、开销更高这意味着 XDP 里不能照搬skb-len、skb-dev、skb-sk等思路需要自己按data/data_end解析以太网、IP、TCP/UDP 头。XDP 的高性能恰恰来自它绕开了大量sk_buff分配与协议栈工作。eBPF 常见挂点与能回答的问题挂点层级典型能力擅长回答XDP极早统计/丢弃/重定向攻击流量、超高 PPS 过滤是否生效TC ingress/egress分类、标记、观测策略前后流量差、容器 veth 边界tracepoint:netif_/napi_驱动与软中断是否卡在 NAPI、是否丢在设备TCP tracepoints建连、重传、探测重传率、握手失败、RTOkprobe 路由/netfilter细函数级特定模块是否成为瓶颈脆弱、难移植sockops / sk_msg 等套接字级服务网格、加速路径视内核没有「一个挂点看清一切」。完整故事通常是多点串联 同一流标识关联五元组直观但经过 NAT、代理或不同 netns 后会变化socket cookie是内核为 socket 提供的稳定标识在该 socket 生命周期内可用于关联多处事件容器环境尤其有价值不是所有包级挂点都能取得 socket cookie跨 NAT、veth 与无 socket 的早期路径仍需结合五元组、netns、cgroup 等字段拼接。可观测指标怎么设计建议分层避免只做一个「总带宽」层示例指标设备RX/TX pps、drop、环缓冲不足qdisc排队时延、backlog、丢包协议重传、乱序、零窗口、监听队列溢出套接字发送缓冲占用、读缓冲积压应用可见RTT 分段应用时间戳 vs 栈内时间戳聚合上优先直方图 top-K 流而不是对每个包打全量日志。eBPF Map 用 per-CPU 计数降低竞争用户态再汇总。丢包与延迟的归因思路是否是否用户感知慢或超时重传升高?查 TCP 重传点 / RTT / 对端接收队列满?应用读太慢或 CPU 饥饿更早层 drop?XDP/TC/网卡环/qdisc实操顺序可按环境裁剪确认是客户端慢、服务端慢还是路径丢包看 TCP 重传与 RTT区分拥塞与应用迟钝看 socket 队列与应用 CPU再向下看 qdisc、驱动、XDP 计数器eBPF 负责把各层计数变成同一时间窗可比的数据最终结论仍要结合拓扑与业务。实践注意项说明开销每包逻辑要极短采样或按流采样可移植少绑私人 kprobe 符号多用 tracepoint / CO-RE容器ifindex、netns、veth 成对出现标识要带 netns校验和与 GRO/GSORX 侧 GRO 会合并多个包TX 侧 GSO 会在稍后才分段探针看到的可能是大 skbPPS 口径会随挂点变化安全解析载荷涉及隐私时需最小化与授权收束网络 eBPF 可观测的核心是在正确的路径高度挂点并用skb或 XDP 上下文的真实布局读数据线性区不够时要承认非线性区的存在。先画清收发包路径再写程序比先选框架更重要。尤其要先定义统计口径你要数的是线上实际包、GRO 聚合后的 RX skb还是 GSO 分段前的 TX skb同一流量在不同挂点得到的 PPS 不同并不一定是谁统计错了而可能是观察阶段不同。系列结语从使用工具到设计方案至此这套内容完成了一条从原理到工程的路径用挂载点回答代码在哪里运行用 Verifier、JIT 与 Map 回答代码如何安全存活、数据如何出来用框架选型回答怎样开发、部署与维护用 SSL/TLS 观测回答如何处理敏感的用户态边界用网络协议栈与sk_buff回答怎样在基础设施路径上定位延迟和丢包。eBPF 的强大不只在字节码本身而在于它提供了一组受控观察点让工程师可以重新审视操作系统内部路径。真正的能力分水岭也不是「会不会运行某个现成工具」而是能否从问题出发选对hook、数据结构、关联标识、聚合口径与权限边界设计出可验证、可回滚、开销可控的观测方案。整理自《深入理解 eBPF 与可观测性》网络可观测相关章节并扩展路径、挂点与归因方法。
基于 eBPF 的网络可观测:协议栈路径与 sk_buff
基于 eBPF 的网络可观测协议栈路径与 sk_buff网络可观测要回答的是包从网卡到套接字或反向经过哪些阶段、在哪变慢、在哪被丢。eBPF 可以挂在XDP、TC、tracepoint、kprobe等位置对同一条流做分段计时与丢包归因。本文从内核收发包路径与sk_buff数据布局入手说明观测该怎么选挂点——不依赖其他篇章即可用于设计网络探针。目录网络可观测要看见什么发包路径TCP 为例收包路径对照sk_buff线性区与非线性区eBPF 常见挂点与能回答的问题可观测指标怎么设计丢包与延迟的归因思路实践注意系列结语从使用工具到设计方案网络可观测要看见什么相对「机器 CPU/内存」网络向可观测更关心信号例子延迟分段应用到协议栈、协议栈到驱动、驱动到线缆丢包点队列满、conntrack、netfilter、套接字接收队列流量结构五元组、重传、窗口、异常复位策略命中iptables/nft、cgroup、带宽限制eBPF 的优势是在不改业务、少采样偏差的前提下把这些点连成时间线。代价是内核版本、程序类型与开销控制。发包路径TCP 为例用户send/write之后数据大致经历简化忽略零拷贝与特殊加速用户缓冲区 → 套接字发送队列 / TCP 分段与拥塞控制 → IP 层封装 → netfilter / 路由决策 → 邻居与设备队列qdisc → 网卡驱动 TX → 线缆用户态 sendSocket / TCPIPNetfilter 等qdiscDriver TXNIC原书「TCP 发包流程图」强调的是这条自上而下的路径。可观测上可对关键函数或 tracepoint 打点例如进入协议栈的时刻进入 qdisc 的时刻驱动硬排队 / NAPI 相关事件重传定时器触发不同发行版符号名可能变化优先稳定 tracepoint动态 kprobe 作补充。收包路径对照收包大致反向但多了硬中断与软中断调度NIC RX → 驱动 / NAPI →可选XDP → 协议栈IP/TCP → netfilter → 套接字接收队列 → 用户态 recvXDP出现在协议栈之前可做丢弃、重定向、统计。若你的探针只挂在 TCP 层会看不见在 XDP 已被丢掉的包——挂点选择直接决定「视野」。PASSDROP/REDIRECTNIC RXDriver / NAPIXDP?协议栈提前结束Socket用户态 recv容器场景veth 会让路径多走一遍边界容器网络里报文常不止经过一块物理网卡。以宿主机路由到容器的典型路径为例具体顺序会受 CNI、桥接、隧道和策略配置影响物理网卡 RX宿主机协议栈 / 路由veth host 端veth 容器端 eth0容器 netns 协议栈容器 Socket / 应用veth像一根虚拟网线一端发出的包会成为另一端的接收包。因此宿主机物理网卡、veth host 端和容器eth0上的TC ingress/egress 观察结果并不等价方向还可能与直觉相反。设计探针时至少携带ifindex netns先画清 CNI 实际路径再决定在哪一端计数避免把同一包重复统计。sk_buff线性区与非线性区内核网络包的核心描述符是sk_buffskb。对 eBPF/内核代码读写载荷时必须区分区域含义访问方式概念线性区skb 头部连续缓冲区可通过skb-data起的一段连续内存直接读非线性区额外页片段frags不能假设都在data里需按frags/frag_list描述遍历物理页直观理解小包或已线性化的包头部与部分载荷在线性区大包、 gro/gso、零拷贝等场景下载荷常散在多个 page fragment 上。sk_buff ├── 线性区skb-data … skb-data len_linear └── 非线性区frags[i] → 某 page offset size对可观测与包解析的含义只probe_read线性区可能截断 HTTP 头/载荷需要完整载荷时要处理非线性区或接受「只解析头、统计长度」的折中部分 helper如包数据加载类会帮你按偏移取字节但仍受程序类型与校验器约束内核某些路径会调用skb_linearize()把非线性分片合并为连续线性区例如后续模块确实需要连续数据时。但 eBPF 观测不能假设「前面总有人替我拉平」是否线性化取决于具体路径、特性和内核版本而且线性化本身有复制成本。探针必须按最通用的非线性场景设计或明确只读取已证明连续的头部。XDP 的xdp_md为什么不是sk_buff写 XDP 时上下文是xdp_md模型与 skb 不同写 TC/socket 类程序才大量面对 skb维度XDPxdp_mdTC / socketskb 上下文所处阶段驱动早期Native 模式通常尚未分配 skb已进入更完整的网络栈数据视图data/data_end指向原始帧另有入接口、RX queue 等有限元数据可访问 skb 长度、协议标记等更丰富元数据套接字信息通常没有struct sock无法直接按 socket 归因某些挂点可关联 socket代价少了 skb 分配与管理路径极短信息更丰富但位置更晚、开销更高这意味着 XDP 里不能照搬skb-len、skb-dev、skb-sk等思路需要自己按data/data_end解析以太网、IP、TCP/UDP 头。XDP 的高性能恰恰来自它绕开了大量sk_buff分配与协议栈工作。eBPF 常见挂点与能回答的问题挂点层级典型能力擅长回答XDP极早统计/丢弃/重定向攻击流量、超高 PPS 过滤是否生效TC ingress/egress分类、标记、观测策略前后流量差、容器 veth 边界tracepoint:netif_/napi_驱动与软中断是否卡在 NAPI、是否丢在设备TCP tracepoints建连、重传、探测重传率、握手失败、RTOkprobe 路由/netfilter细函数级特定模块是否成为瓶颈脆弱、难移植sockops / sk_msg 等套接字级服务网格、加速路径视内核没有「一个挂点看清一切」。完整故事通常是多点串联 同一流标识关联五元组直观但经过 NAT、代理或不同 netns 后会变化socket cookie是内核为 socket 提供的稳定标识在该 socket 生命周期内可用于关联多处事件容器环境尤其有价值不是所有包级挂点都能取得 socket cookie跨 NAT、veth 与无 socket 的早期路径仍需结合五元组、netns、cgroup 等字段拼接。可观测指标怎么设计建议分层避免只做一个「总带宽」层示例指标设备RX/TX pps、drop、环缓冲不足qdisc排队时延、backlog、丢包协议重传、乱序、零窗口、监听队列溢出套接字发送缓冲占用、读缓冲积压应用可见RTT 分段应用时间戳 vs 栈内时间戳聚合上优先直方图 top-K 流而不是对每个包打全量日志。eBPF Map 用 per-CPU 计数降低竞争用户态再汇总。丢包与延迟的归因思路是否是否用户感知慢或超时重传升高?查 TCP 重传点 / RTT / 对端接收队列满?应用读太慢或 CPU 饥饿更早层 drop?XDP/TC/网卡环/qdisc实操顺序可按环境裁剪确认是客户端慢、服务端慢还是路径丢包看 TCP 重传与 RTT区分拥塞与应用迟钝看 socket 队列与应用 CPU再向下看 qdisc、驱动、XDP 计数器eBPF 负责把各层计数变成同一时间窗可比的数据最终结论仍要结合拓扑与业务。实践注意项说明开销每包逻辑要极短采样或按流采样可移植少绑私人 kprobe 符号多用 tracepoint / CO-RE容器ifindex、netns、veth 成对出现标识要带 netns校验和与 GRO/GSORX 侧 GRO 会合并多个包TX 侧 GSO 会在稍后才分段探针看到的可能是大 skbPPS 口径会随挂点变化安全解析载荷涉及隐私时需最小化与授权收束网络 eBPF 可观测的核心是在正确的路径高度挂点并用skb或 XDP 上下文的真实布局读数据线性区不够时要承认非线性区的存在。先画清收发包路径再写程序比先选框架更重要。尤其要先定义统计口径你要数的是线上实际包、GRO 聚合后的 RX skb还是 GSO 分段前的 TX skb同一流量在不同挂点得到的 PPS 不同并不一定是谁统计错了而可能是观察阶段不同。系列结语从使用工具到设计方案至此这套内容完成了一条从原理到工程的路径用挂载点回答代码在哪里运行用 Verifier、JIT 与 Map 回答代码如何安全存活、数据如何出来用框架选型回答怎样开发、部署与维护用 SSL/TLS 观测回答如何处理敏感的用户态边界用网络协议栈与sk_buff回答怎样在基础设施路径上定位延迟和丢包。eBPF 的强大不只在字节码本身而在于它提供了一组受控观察点让工程师可以重新审视操作系统内部路径。真正的能力分水岭也不是「会不会运行某个现成工具」而是能否从问题出发选对hook、数据结构、关联标识、聚合口径与权限边界设计出可验证、可回滚、开销可控的观测方案。整理自《深入理解 eBPF 与可观测性》网络可观测相关章节并扩展路径、挂点与归因方法。