Linux

网络丢包时netstat -s有drop计数,但tcpdump抓不到,内核在哪个环节把包丢了?

👤 为我痴狂 👁 13 阅读 ❤ 0 点赞 ➦ 0 分享 📅 2026-11-04
首页› 大数据› Linux› 正文
网络丢包时netstat -s有drop计数,但tcpdump抓不到,内核在哪个环节把包丢了?
摘要:“netstat -s 显示有 drop,tcpdump 却一个包都没抓到”是排障现场最常见的悖论之一。本文以 Linux 收包路径为分析主线,把丢包拆成“抓包点之前”与“抓包点之后”两个空间,逐层定位网卡环形缓冲、NAPI 轮询预算、软中断 backlog、协议栈入口与套接字接收队列五类丢包点,并给出基于 /proc/net、dropwatch、perf 与 eBPF 的可操作定位路径。核心结论是:tcpdump 只能看见“已经进入协议栈”的包,凡是在 netif_receive_skb 之前被丢弃的帧,抓包工具天然不可见。

目录

一、引言:一个被反复误读的“矛盾”

在线上排障中,几乎每个做网络的人都会遇到这样的场景:业务侧反馈丢包,运维登录机器执行 netstat -s,输出里 packet receive errors 或 receive buffer errors 在持续增长;于是立刻在对应网卡上开 tcpdump -i eth0,结果抓了几分钟,一个异常包都没看到,甚至抓到的包数量与业务侧统计的发送量完全对得上。于是问题就变成了:既然内核说丢了包,为什么抓包工具抓不到?包到底是在哪一层被丢掉的?

这个“矛盾”之所以反复出现,根源在于多数人对 netstat -s 的计数器语义和 tcpdump 的抓包位置存在双重误解。前者把来自不同子系统、不同统计口径的计数器汇总成一个“drop”印象;后者则只在协议栈的特定钩子点上复制数据,凡是没走到这个钩子的帧,它根本无从知晓。本文的主线就是:以“抓包点”为分界线,把丢包拆成“抓包点之前”和“抓包点之后”两个空间,逐层定位。

需要先说明本文的分析边界。本文讨论的是 Linux 内核(以 5.x/6.x 主线为参照)在接收方向上的丢包,聚焦以太网与 IP 层,不展开 TCP 重传、拥塞控制等传输层语义;涉及具体计数器名称时以 /proc/net/snmp、/proc/net/netstat、/proc/net/softnet_stat 与 ethtool -S 的实际输出为准。本文评述:把“drop”当作单一事实是一种危险的简化,工程上必须把它还原为“哪个计数器、在哪个代码路径、由什么条件触发”的三元组,否则任何调优都是盲目的。

二、背景与问题定义:drop 计数到底在数什么

2.1 netstat -s 的计数器来源与口径

netstat -s 本质上是读取 /proc/net/snmp 与 /proc/net/netstat 并按协议族格式化输出。它里面的“drop”并不是一个统一计数器,而是至少来自三个不同层次的统计:IP 层的 InDiscards、TCP/UDP 层的 InErrors 与 RcvbufErrors,以及套接字层的 drops。它们的触发条件、代码位置和是否经过抓包点,完全不同。

更隐蔽的是,netstat -s 并不会把网卡驱动层的丢包(例如 ring buffer 溢出、DMA 失败)计入其中,这些数字只出现在 ethtool -S 的驱动私有计数里,以及 /proc/net/dev 的 rx_dropped 中。因此,当有人说“netstat -s 有 drop”时,第一件事不是抓包,而是确认这个 drop 究竟来自哪个文件、哪一行。

快速判别命令:
# 网卡层丢包(驱动/ring buffer)
cat /proc/net/dev | awk '{print $1, $4, $5}'
ethtool -S eth0 | grep -iE 'drop|discard|miss|err'

# 软中断层丢包(backlog / 预算耗尽)
cat /proc/net/softnet_stat

# 协议栈层丢包(IP/TCP/UDP)
grep -E 'InDiscards|InErrors|RcvbufErrors' /proc/net/snmp /proc/net/netstat

2.2 tcpdump 的抓包位置决定了它的“盲区”

tcpdump 依赖 libpcap,在 Linux 上通过 AF_PACKET 套接字接收数据。其数据来源是内核在 netif_receive_skb 路径上分发给 ptype_all 链表的副本(传统路径),或通过 dev_queue_xmit_nit 在发送方向复制。这意味着:任何在进入协议栈主路径之前就被丢弃的帧,tcpdump 都看不到。这不是 tcpdump 的 bug,而是它的设计位置决定的。

这里有一个常被忽略的细节:在启用 RPS/RFS 或使用 XDP 的现代内核上,包的分发路径已经发生变化,AF_PACKET 的 tap 点可能位于 RPS 之后、也可能位于 XDP 之后,取决于内核版本与配置。本文评述:把 tcpdump 当作“地面真值”是一种方法论错误,它只是一个“协议栈视角的观测点”,而不是“物理链路的观测点”。真正的地面真值应当来自网卡硬件计数器与交换机端口统计。

三、原理:Linux 收包路径与五类丢包点

3.1 收包路径全景

一个以太网帧从到达网卡到被应用读取,大致经过以下阶段:网卡 DMA 写入 RX ring → 触发硬中断 → 驱动在中断处理中关闭中断并调度 NAPI → 软中断中 napi_poll 调用驱动 poll 函数取包 → napi_gro_receive → netif_receive_skb(此处分发到 ptype_all,tcpdump 可见)→ 协议栈处理 → 放入套接字接收队列 → 应用 recv() 读取。

在这条链路上,至少有五个位置会因资源不足而丢包,且它们是否被 tcpdump 看见,取决于是否位于 netif_receive_skb 之前。下表给出这五类丢包点的对照。

丢包点 触发条件 观测计数器 tcpdump 可见
RX ring 溢出DMA 写入时描述符耗尽ethtool -S rx_dropped / rx_missed否
NAPI 预算耗尽单次 poll 处理包数超 netdev_budgetsoftnet_stat 第 2 列部分可见
backlog 队列满enqueue_to_backlog 失败softnet_stat 第 1 列否
协议栈入口丢弃校验和错、无路由、防火墙snmp InDiscards/InErrors是
套接字队列满sk_rmem_alloc 超 rcvbufsnmp RcvbufErrors是

3.2 RX ring 与 DMA:最容易被 tcpdump 漏掉的一层

网卡收到帧后,通过 DMA 把数据写入驱动预先分配的 RX 描述符环形缓冲区。如果 CPU 来不及消费,环形缓冲区被填满,后续到达的帧就没有描述符可用,网卡只能直接丢弃。这一层丢包发生在驱动与硬件之间,数据从未进入内核网络栈,因此 tcpdump 完全不可见。它的典型特征是:ethtool -S 中 rx_dropped、rx_missed_errors、rx_no_buffer_count 等计数增长,而 /proc/net/dev 的 rx_dropped 也可能同步增长。

判断 ring 是否过小的一个经验方法是观察 ethtool -g eth0 的当前值与最大值,并结合 ethtool -S 的丢弃计数变化率。若在流量高峰时丢弃计数持续增长,而 CPU 软中断并未饱和,则优先怀疑 ring 过小或中断合并参数不当。本文评述:ring buffer 调优不是越大越好,过大的 ring 会增加单次丢包时的突发延迟,且占用更多 DMA 内存;合理的做法是结合 rx-usecs 中断合并参数一起调整,而不是单纯拉满。

3.3 NAPI 预算与 softnet_stat:最常被误读的计数器

/proc/net/softnet_stat 是定位软中断层丢包的核心文件。每一行对应一个 CPU,十六进制输出。其中第 1 列是 processed(已处理包数),第 2 列是 dropped(因 backlog 队列满而丢弃的包数),第 3 列是 time_squeeze(因预算耗尽而提前结束 poll 的次数)。很多资料把第 2 列直接称为“NAPI 丢包”,这是不准确的:它统计的是 enqueue_to_backlog 失败,即包在进入 per-CPU backlog 队列时被丢弃,而不是 NAPI poll 内部丢弃。

真正与 NAPI 预算相关的是 time_squeeze。当一次 net_rx_action 处理的总包数超过 netdev_budget(默认 300)或总时间超过 netdev_budget_usecs(默认 2000 微秒)时,软中断会主动让出 CPU,剩余包留待下次调度。这本身不是丢包,但如果 backlog 队列在此期间被填满,就会触发第 2 列的丢弃。本文评述:把 time_squeeze 增长直接等同于丢包是一种常见误判,它更多反映 CPU 争用与调度延迟,需要结合第 2 列与业务侧重传率一起看。

softnet_stat 解读示例(十六进制):
# 列: processed dropped time_squeeze ...
# 以下为模拟数据,仅用于说明列含义
0001a2b3 0000000c 0000002f ...
# 第2列 0x0c = 12 次 backlog 入队失败
# 第3列 0x2f = 47 次预算耗尽提前退出

3.4 协议栈入口与套接字队列:tcpdump 能看见但仍会丢

当包成功进入 netif_receive_skb 之后,tcpdump 就能看到它了。但这并不意味着它最终会被应用收到。IP 层会因校验和错误、TTL 耗尽、无路由、防火墙规则等原因丢弃,这些计入 InDiscards 或 InErrors;UDP 层在套接字接收队列满时会丢弃并计入 RcvbufErrors;TCP 层虽然会通过窗口控制避免接收队列溢出,但在零窗口探测失败或内存压力下仍可能丢包。

这一类丢包的特点是:tcpdump 能看到包进来,但应用层统计不到。因此,如果 tcpdump 抓到了包而应用侧没有收到,问题就在协议栈内部或套接字队列,而不是网卡层。本文评述:排障时应先建立“tcpdump 是否可见”这一二分,再在各自空间内细分,这比一上来就调 ring buffer 或改内核参数要高效得多。

四、关键数据与案例:从计数器反推丢包位置

4.1 一套可复用的定位流程

下面给出一条经过工程验证的定位路径,按“先分层、再定位、后验证”的顺序推进。每一步都给出具体命令与判据,读者可直接在目标机器上执行。

步骤 1:确认丢包发生在哪一层
# 网卡层
ethtool -S eth0 | grep -iE 'drop|miss|err|no_buffer'
# 软中断层
awk '{print $1, $2, $3}' /proc/net/softnet_stat
# 协议栈层
netstat -s | grep -iE 'drop|error|buffer'
# 套接字层
ss -lntu  # 观察 Recv-Q 是否持续非零
步骤 2:用 dropwatch 定位内核丢包函数
# 安装后运行,观察 kfree_skb 调用点
dropwatch -l kas
# 输出示例(模拟):
# 0xffffffff8a1b2c3d  netif_receive_skb_internal+0x1d
# 0xffffffff8a1b4e5f  enqueue_to_backlog+0x8f

dropwatch 的原理是跟踪 kfree_skb 及其变体,并打印调用栈。它能直接告诉你包是在哪个内核函数里被释放的,是定位“抓不到”问题的最有效工具之一。本文评述:dropwatch 的输出需要结合内核符号表解读,且在高流量下自身开销不可忽略,建议在复现窗口内短时开启,而不是长期挂载。

步骤 3:用 eBPF 做低开销的持续观测
# 使用 bpftrace 跟踪 kfree_skb 的调用者
bpftrace -e 'kprobe:kfree_skb { @[kstack] = count(); }'
# 跟踪 backlog 入队失败
bpftrace -e 'kretprobe:enqueue_to_backlog /retval != 0/ { @ = count(); }'

4.2 一个典型的“抓不到”案例复盘

某在线服务在流量高峰期出现 UDP 丢包告警,netstat -s 显示 RcvbufErrors 持续增长,但 tcpdump 抓包数量与发送端统计一致,未发现异常。按前述流程排查:ethtool -S 无丢弃,softnet_stat 第 2 列无增长,说明包已成功进入协议栈。进一步用 ss -lun 观察发现目标 UDP 套接字的 Recv-Q 长期处于高位,最终定位为应用读取速度不足、SO_RCVBUF 偏小。

这个案例的关键启示是:tcpdump 抓得到包,不代表应用收得到包。RcvbufErrors 属于“抓包点之后”的丢包,tcpdump 当然能看到包进来,但它在套接字队列处被丢弃了。本文评述:很多团队把“抓包正常”当作“链路正常”的证据,这在 UDP 场景下尤其危险,因为 UDP 没有重传与窗口控制,接收队列溢出是静默的。

4.3 数据来源与预处理说明

本文引用的内核默认参数(netdev_budget=300、netdev_budget_usecs=2000、netdev_max_backlog=1000)来自 Linux 内核源码 net/core/dev.c 与 include/linux/netdevice.h 的默认定义,不同发行版可能通过 sysctl 覆盖,实际值应以 sysctl net.core.netdev_budget 等命令为准。文中标注为“模拟数据”的表格与输出仅用于说明格式与列含义,不代表任何真实生产环境统计。

五、对比与局限:tcpdump、dropwatch、eBPF 的能力边界

5.1 三类工具的能力对照

工具 观测位置 能否看到 ring 丢包 开销
tcpdumpnetif_receive_skb 之后否中高
dropwatchkfree_skb 调用点部分(依赖驱动是否调用)中
eBPF/bpftrace任意内核函数可(跟踪驱动)低(需谨慎)
ethtool -S驱动统计是极低

从表中可以看出,没有任何单一工具能覆盖全部丢包点。ethtool 能看到硬件层但看不到协议栈细节;tcpdump 能看到协议栈入口但看不到硬件层;dropwatch 与 eBPF 介于两者之间,但需要内核符号与编程能力。本文评述:合理的做法是建立“分层观测矩阵”,先用 ethtool 与 softnet_stat 做粗筛,再用 dropwatch/eBPF 做精确定位,最后用 tcpdump 验证协议栈行为,而不是指望一个工具解决所有问题。

5.2 局限与常见误区

第一个误区是把 softnet_stat 第 2 列的增长直接归因为“网卡丢包”。实际上它统计的是 backlog 入队失败,通常与 RPS 配置、单 CPU 软中断过载有关,而非网卡硬件。第二个误区是认为“加大 ring buffer 就能解决所有丢包”,但在 CPU 软中断饱和的场景下,加大 ring 只会把丢包点从网卡层推迟到 backlog 层,问题并未消失。第三个误区是忽视 GRO/LRO 对 tcpdump 的影响:启用 GRO 后,tcpdump 看到的是聚合后的大包,与线上实际帧不一致,容易误判包大小与数量。

此外,虚拟化与容器环境引入了额外变量。veth 对、网桥、OVS 数据路径都可能在 netif_receive_skb 之前或之后引入新的丢包点,且这些点的计数器分散在 /proc/net/dev 的虚拟接口条目中。本文评述:在容器化环境中,排障必须把“宿主机物理网卡—网桥/veth—容器内网卡”三段分别观测,任何一段的 drop 都可能导致应用侧丢包,而 tcpdump 在容器内只能看到最后一段。

六、结论与展望:把“抓不到”变成“可解释”

回到最初的问题:netstat -s 有 drop 计数而 tcpdump 抓不到,内核在哪个环节把包丢了?答案不是单一的,而是取决于 drop 计数来自哪个文件。若来自 ethtool -S 或 /proc/net/dev 的 rx_dropped,丢包点在 RX ring 或 DMA,tcpdump 天然不可见;若来自 softnet_stat 第 2 列,丢包点在 backlog 入队,同样在抓包点之前;若来自 snmp 的 RcvbufErrors,丢包点在套接字队列,tcpdump 能看到包但应用收不到。

因此,正确的排障顺序应当是:先定位计数器来源,再判断是否位于抓包点之前,最后选择对应工具验证。这条主线可以把大量“玄学丢包”转化为可解释、可复现、可验证的工程问题。展望未来,随着 XDP、AF_XDP 与 eBPF 在生产环境的普及,收包路径的可观测性正在从“事后抓包”转向“路径内埋点”,内核社区也在持续改进 drop_monitor 与 tracepoint 的覆盖范围。本文评述:工具会演进,但“以抓包点为分界、以计数器为线索”的分析框架不会过时,掌握这条主线比记住某个命令更重要。

拓展阅读方面,建议结合内核文档与社区教程动手验证:Linux 内核网络文档、net/core/dev.c 源码、Brendan Gregg 的 eBPF 资料。这些资源能帮助读者把本文的路径图与真实代码对应起来。

主要参考文献

  1. Linux Kernel Documentation. Networking. kernel.org, 2024.
  2. Torvalds L. et al. net/core/dev.c. Linux kernel source, 2024.
  3. Gregg B. BPF Performance Tools. Addison-Wesley, 2019.
  4. Høiland-Jørgensen T. et al. The eXpress Data Path. ACM CoNEXT, 2018.
  5. Red Hat. Monitoring and Tuning the Linux Networking Stack. 2023.
  6. Cloudflare Blog. How to Drop 10 Million Packets per Second. 2017.
  7. Linux Foundation. softnet_stat 与 NAPI 调优实践. 2023.
  8. IETF RFC 9293. Transmission Control Protocol (TCP). 2022.
文章声明:本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。
内容仅供学习参考。如需引用,请以原始文献为准。  全文约 6800 字  |  参考文献 62 篇(主要 8 篇)。
🔒 复制本站文章内容需登录并达到 L3。当前:未登录

分享到

💬
微信
📷
朋友圈
🐧
QQ好友
🌐
QQ空间
👁
微博
📌
钉钉
🔗
复制链接
📑
复制图文

微信扫一扫分享

打开微信「扫一扫」,扫描二维码后在微信中分享给好友或朋友圈。

💬 评论 (0)

评论功能已关闭

⏸️ 本站暂未开放评论功能,不能进行评论,此为规划的后续开发预留
首页| 关于本网| 网站声明| 联系我们| 网站纠错| 服务| 网站地图
黔ICP备19010680号-1  |  邮箱:six528528@163.com
贵公网安备 52010302001819号
Copyright 2019-2026 http://www.databrush.com/ All rights reserved.
QQ
QQ扫一扫
Logo
DBN数据刷