[译] tcpdump在内核中的工作原理
译者序
近期在调研通过 eBPF 做出口网络管控的方案,demo 方案使用了基于 TC 类型 eBPF 程序实现。测试过程中发现 TC egress 处丢掉数据包以后,使用 tcpdump 无法抓到对应的包,比较好奇 tcpdump 工具在内核中的什么位置捕获数据包,于是发现了这篇文章。
原文链接:https://medium.com/@chnhaoran/tcpdump-deep-dive-how-tcpdump-works-in-the-kernel-b1976ea39f7e
译文内容如下。
背景
在使用 Cilium 时,有些情况下使用 ping 命令发现网络已经通了,但 tcpdump 却无法捕获任何数据包。本文旨在从源代码的角度深入探讨 tcpdump 在内核中的工作机制。
内核的网络处理流程
在深入探讨之前,我们需要了解内核是如何处理网络流量的。

当物理网卡(NIC)从物理链路接收到数据帧时:
利用直接内存访问(DMA)将数据帧写入预先分配的环形缓冲区。
触发硬中断,通知 CPU 有网络数据需要接收。
CPU 收到后,触发软中断。
调用网卡驱动程序注册的轮询函数,从环形缓冲区中轮询数据帧。
数据帧进入网络子系统,经过 tc、netfilter 和 route 等子系统,处理 L2/L3/L4 协议。
数据帧被发送到相应的用户空间注册的套接字。
初始化
内核需要完成初始化以支持后续的数据包处理,包括:
驱动程序初始化
软中断线程初始化
网络协议栈处理函数的初始化
本文仅关注网络协议栈处理函数的初始化。
以 IP 协议为例,IPv4 和 IPv6 的初始化过程如下:
在函数 dev_add_pack 中,协议和相应的handler 函数被添加到 ptype_all(ETH_P_ALL)或 ptype_base(对应其他协议)。
接收端处理
在接收端,__netif_receive_skb_core 首先遍历 ptype_all sniffers 确认是否注册了 ETH_P_ALL 类型的协议,如果有则将 skb(套接字缓冲区)传递给对应的 handler。然后根据数据包类型(例如,ETH_P_IP),将 skb 传递给注册到 ptype_base 的 handler。
查看 libpcap 源代码,我们发现它使用 ETH_P_ALL 作为协议类型。
在 Linux 内核中,注册套接字时需要提供协议和对应的 handler,对应的 skb 会发送给该协议的所有套接字。这就是为什么 tcpdump 能够从网卡捕获数据包的原因。
需要注意的是,XDP hook 点位于 ETH_P_ALL 处理之前。这就是为什么在某些情况下,我们在 Cilium 中使用 tcpdump 无法捕获数据包,因为数据在最开始的入口点就被重定向了。
发送端处理
网络协议栈处理之后,最后一步是 __dev_queue_xmit。dev_queue_xmit_nit 遍历每个与 ETH_P_ALL 相关的套接字,并将数据包传递给相应的 handler 函数。
结论
我们深入研究了源代码,可以更好地理解 tcpdump 的工作原理。简要总结如下:
收包路径: XDP ->
Tcpdump-> TC -> network stack发包路径: network stack -> TC ->
Tcpdump
参考资料
https://chnhaoran.github.io/blog/how-is-tcpdump-working/
Last updated