tcpdump抓不到的丢包去哪了?

话题来源: Linux网络排查“兵器谱”:从tcpdump、eBPF到内核日志的深度探索

当网络连接出现丢包,而你在网卡上用tcpdump抓了个空时,那种感觉就像侦探明明知道凶手进了房间,监控录像却一片空白。这些“幽灵丢包”并没有消失,它们只是死在了你看不见的地方——通常是在Linux内核协议栈的深处。

tcpdump的视野盲区

理解这个问题的前提是搞清楚tcpdump的工作原理。它本质上是一个用户态工具,通过libpcap库调用PF_PACKET套接字。当网卡驱动收到一个数据包,它会将其放入内核的接收队列(ring buffer)。关键就在这里:tcpdump抓取的,是从这个接收队列中复制出来的数据包镜像。如果一个数据包在抵达这个队列之前,或者在协议栈处理过程中被丢弃,tcpdump自然就无能为力了。

这就像一个快递分拣中心,tcpdump只能看到成功进入分拣流水线的包裹。而那些在门口因为地址模糊、包装破损、甚至货车根本没停稳就被扔掉的包裹,它永远也记录不到。

丢包“第一现场”:硬件与驱动层

很多丢包发生在tcpdump的“上游”。使用ethtool -S eth0命令,你能看到网卡硬件统计计数器,那里藏着第一批线索。

  • rx_missed_errors: 网卡DMA引擎处理不过来,数据包直接被硬件丢弃。这通常意味着流量洪峰超过了网卡的处理能力。
  • rx_fifo_errors / rx_over_errors: 驱动层的接收缓冲区(FIFO)溢出了。数据包到了门口,但“房间”已经塞满,只好扔掉。
  • rx_length_errors: 数据包长度不符合规范,硬件或驱动直接拒收。

这些计数器一旦开始增长,就是明确的硬件或驱动层告警,tcpdump对此完全沉默。

内核协议栈的“沉默杀手”

假设数据包成功闯过了硬件和驱动关,进入了内核网络子系统,它依然危机四伏。这里有几个常见的“行刑地”:

  • 反向路径过滤(RPF): 内核检查数据包的源IP是否可以从接收网卡路由回去。如果检查失败(通常出于安全考虑),包会被静默丢弃。检查/proc/sys/net/ipv4/conf/*/rp_filter文件。
  • 连接跟踪(conntrack)表满: 在NAT或严格防火墙环境下,每个连接都需要在conntrack表中占一个条目。当表被填满(比如遭遇DDoS),新的连接请求包会被直接丢弃。看看/proc/sys/net/netfilter/nf_conntrack_count是不是接近了nf_conntrack_max
  • 协议栈内存不足: sk_buff是内核中存储数据包的结构。如果系统内存紧张,或者net.core.rmem_default等参数设置过小,协议栈可能无法为包分配内存,导致丢弃。

如何追踪这些“幽灵”?

既然tcpdump失效,我们就需要更底层的工具。内核的/proc/net/netstat/proc/net/snmp文件是宝藏。

# 查看IP层的丢包
cat /proc/net/netstat | grep -i ipext
# 关注 `InNoRoutes`(没有路由)、`InAddrErrors`(地址错误)等字段

# 查看更详细的UDP/TCP丢包统计
cat /proc/net/snmp
# 在 `Udp` 行里,`RcvbufErrors` 和 `SndbufErrors` 是缓冲区错误导致的丢包。
# 在 `Tcp` 行里,`AttemptFails`(尝试失败)、`EstabResets`(连接重置)也值得关注。

对于实时动态追踪,eBPF技术是终极武器。你可以编写或使用现成的eBPF程序,在内核函数kfree_skb()(释放socket buffer的地方)设置探针。每当内核丢弃一个数据包,这个函数都会被调用,通过解析其调用栈或参数,就能精确知道丢包的原因和位置。工具dropwatch就是基于这个原理。

所以,下次再遇到抓不到的丢包,别只盯着网卡流量。去内核的日志、统计文件和eBPF的视野里找找,那些“幽灵”一定在那里留下了死亡的痕迹。

评论