Linux 网络诊断实战:从 ss、tcpdump 到 conntrack 表满导致的丢包
1. 从一次线上丢包说起
假设你负责的一台网关服务器,业务方反馈"偶发连接超时,但后端服务日志里看不到请求"。你登录机器,ping 网关是通的,curl 本地回环也正常,服务进程没有重启,CPU 和内存都不高。真正可疑的是:客户端连接成功率从 99.99% 掉到 97%,丢包是随机发生的,不是整条链路全断。
这类问题的麻烦在于,网络故障不一定表现为"完全不通"。它可能表现为 TCP 握手阶段 SYN 丢失、建连后偶尔超时、重传升高但业务还能跑、NAT 场景下连接被错误丢弃。要定位它,不能只靠一个命令,而要按 Linux 收发包的路径逐层排查。
本文围绕一个核心目标:让你先能复述 Linux 网络诊断的整体框架,再用 ss、tcpdump、conntrack 等工具把每一层的问题落到具体现象和命令输出上。
2. 先记住一个最小模型:包从哪里进来,状态存在哪里
可以先把它理解成三个部分:协议栈状态、抓包观察点、连接跟踪表。
- 协议栈状态:Linux 内核维护 TCP 连接状态,比如 ESTABLISHED、TIME_WAIT、SYN_RECV。
ss就是读取这些状态。 - 抓包观察点:
tcpdump在网卡或指定接口上把流过该点的报文复制出来,用来确认"包到底有没有到""回复有没有发出去"。 - 连接跟踪表:Netfilter 的 conntrack 模块记录每条连接的五元组和状态,NAT、防火墙、容器网络都依赖它。表满时,新连接可能被直接丢弃。
把一次"客户端连服务端"的请求完整走一遍:
text
客户端应用
|
v
[ 客户端协议栈 ] -- SYN --> [ 服务端网卡驱动 ]
| |
| v
| [ conntrack 查表/建表 ]
| |
| v
| [ 服务端协议栈 ]
| |
|<-- SYN/ACK ------------------|
|
v
[ 连接建立,进入 ESTABLISHED ]
这个图帮助理解"包经过哪些位置",但不能替代真实细节:比如内核里的 Netfilter 钩子点、路由决策、socket 接收队列长度、NAT 双向转换,都需要进一步看具体输出。
3. 诊断全局框架:先看状态,再看报文,再看连接跟踪
遇到网络异常时,建议固定按下面顺序推进:
| 步骤 | 观察对象 | 常用命令 | 主要回答的问题 |
|---|---|---|---|
| 1 | 本机连接状态统计 | ss -s、ss -lnt |
连接是堆积在哪类状态,端口是否耗尽 |
| 2 | 实时报文 | tcpdump -i any -nn |
SYN、ACK、RST 是否到达或发出 |
| 3 | 连接跟踪表 | conntrack -S、dmesg |
是否发生表满、插入失败、丢包 |
| 4 | TCP 重传与 RTT | ss -ti、nstat |
是否存在重传、RTT 是否异常升高 |
这个顺序不是死规矩,但符合"先确认本机状态,再确认网络报文,最后确认内核辅助表"的思路。
text
业务异常
|
v
ss 看状态分布 ---> TIME_WAIT 过多? SYN_RECV 堆积?
|
v
tcpdump 抓关键报文 ---> SYN 有没有到? SYN/ACK 有没有回?
|
v
conntrack 看表 ---> 表满? 插入失败? NAT 是否正常?
|
v
ss -ti 看重传/RTT ---> 是否链路质量或拥塞问题
4. 用 ss 建立连接状态全景
ss 的作用是读取 socket 统计信息。相比 netstat,它在连接数很大时更快。入门先看整体统计:
bash
ss -s
输出中会看到 TCP: 后面的统计,例如 estab、closed、orphaned、synrecv、timewait。synrecv 表示收到了 SYN、回了 SYN/ACK,但还没收到最终 ACK 的半连接数量;timewait 表示主动关闭方留下的状态。
看监听端口和当前连接:
bash
ss -lntp
ss -ntp state established
-l只看监听 socket。-n不做服务名解析,直接显示端口号。-t只看 TCP。-p显示进程信息,需要相应权限。
如果看到某台客户端 IP 大量处于 SYN-RECV,可能是 SYN 洪水或客户端异常重试。如果看到大量 TIME_WAIT 集中在某个本地端口,可能是短连接频繁访问同一个目标端口。
这里最容易误解的是:ss -s 里 timewait 数量多,不一定就是故障。TIME_WAIT 是 TCP 正常关闭的一部分。真正需要关注的是它是否耗尽本地端口、是否伴随连接建立失败。
5. 用 tcpdump 把"丢包"落到具体报文
ss 告诉你状态,tcpdump 告诉你报文有没有到。典型用法:
bash
tcpdump -i any -nn -c 20 'tcp port 8080 and (tcp[tcpflags] & (tcp-syn|tcp-ack) != 0)'
-i any:在所有接口抓包。-nn:不解析主机名和端口名。-c 20:抓 20 个包后退出。- 过滤表达式只抓 8080 端口上带 SYN 或 ACK 标志的 TCP 包。
如果你只关心某台客户端:
bash
tcpdump -i eth0 -nn 'host 10.0.0.12 and tcp port 8080'
抓包时要注意:在 any 接口抓到的不一定等于网卡实际发送的报文,因为抓包点在内核协议栈中的位置不同。通常可以按"入方向看请求是否到达,出方向看响应是否发出"判断。
一个常见序列是:只看到 SYN,没有看到 SYN/ACK。这说明服务端没有回应,问题可能在服务端协议栈、conntrack 丢包、防火墙 DROP 或监听进程未工作。如果看到 SYN 和 SYN/ACK,但没有最终 ACK,则可能是客户端侧或链路中间丢包。
6. conntrack 表溢出为什么会造成丢包
连接跟踪表可以理解成内核维护的一张"连接登记簿"。每条经过 Netfilter 的连接会记录源 IP、源端口、目的 IP、目的端口、协议和状态。NAT 场景下,它还负责把内网地址映射成外网地址。
查看当前表使用情况:
bash
conntrack -S
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
如果 nf_conntrack_count 接近 nf_conntrack_max,新连接可能无法插入表,内核会记录类似 nf_conntrack: table full, dropping packet 的日志。
text
新连接到达
|
v
查 conntrack 表
|
+-- 表未满 --> 创建新表项,继续协议栈处理
|
+-- 表已满 --> 尝试删除过期项
|
+-- 删除失败 --> 丢弃报文,记录 table full
这里的设计取舍是:conntrack 为 NAT 和状态防火墙提供了必要信息,但表项数量和超时时间需要根据业务规模调整。表太小会在高并发下丢包,表太大又会占用更多内存。
临时调大表容量:
bash
echo 262144 > /proc/sys/net/netfilter/nf_conntrack_max
永久配置建议写入 /etc/sysctl.d/ 下的文件,例如:
ini
net.netfilter.nf_conntrack_max = 262144
net.netfilter.nf_conntrack_tcp_timeout_established = 86400
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 60
修改后执行 sysctl -p 生效。注意,调大 nf_conntrack_max 会增加内存占用,不是越大越好。
7. TIME_WAIT 与端口耗尽:短连接高并发下的真实瓶颈
TIME_WAIT 是主动关闭连接的一方在发送最后一个 ACK 后进入的状态,持续 2 倍 MSL。它的作用是保证旧连接报文不会干扰新连接,并确保对端能收到最终 ACK。
问题出在短连接场景:如果一台机器每秒发起大量到同一个目标 IP 和端口的连接,本地临时端口范围有限,TIME_WAIT 会占用端口,最终出现"Cannot assign requested address"。
查看本地端口范围和 TIME_WAIT:
bash
cat /proc/sys/net/ipv4/ip_local_port_range
ss -tan state time-wait | wc -l
调优思路有几种:
| 方案 | 做法 | 适用场景 | 注意点 |
|---|---|---|---|
| 启用复用 | net.ipv4.tcp_tw_reuse = 1 |
客户端主动关闭多 | 只对出方向连接生效,依赖时间戳 |
| 减少主动关闭 | 使用长连接/连接池 | 后端访问下游 | 需要应用层改造 |
| 扩大端口范围 | 调整 ip_local_port_range |
短连接不可避免 | 端口仍有限 |
| 分散目标 | 多目标 IP 或多端口 | 下游支持 | 依赖架构 |
需要强调:tcp_tw_recycle 在现代内核中已移除或默认关闭,不建议依赖它。真正的工程解法通常是连接池加合理超时,而不是只调内核参数。
8. 重传与 RTT:从 ss -ti 和 nstat 看链路质量
当连接能建立但偶尔超时,需要看重传和往返时间。ss -ti 可以显示每个连接的详细 TCP 信息:
bash
ss -tin state established '( dport = :8080 or sport = :8080 )'
输出中会看到 rtt、rttvar、retrans、cwnd 等字段。retrans 表示重传次数,rtt 表示平滑后的往返时间。
也可以用 nstat 看全局统计:
bash
nstat -az | grep -E 'TcpRetransSegs|TcpExtTCPLostRetransmit|TcpExtTCPTimeouts'
如果重传率持续升高,可能是链路拥塞、中间设备丢包、MTU 问题或对端处理慢。RTT 突然升高通常说明路径上出现排队或绕行。
这里要区分:偶发重传 在公网环境很常见,不一定是故障;持续重传加业务超时 才需要处理。诊断时把 ss -ti 的单连接视角和 nstat 的全局视角结合。
9. 完整示例一:复现并观察 conntrack 表满导致的丢包
目标:在一台测试机上人为制造 conntrack 表压力,观察表满时的丢包日志。
前置环境:Linux 测试机,具备 root 权限,已安装 conntrack-tools 和 iperf3(可选)。
步骤:
bash
# 1. 记录当前最大值
cat /proc/sys/net/netfilter/nf_conntrack_max
# 2. 临时调小最大值,制造表满条件
echo 1024 > /proc/sys/net/netfilter/nf_conntrack_max
# 3. 持续观察日志
dmesg -w | grep -i conntrack
另开终端制造大量短连接:
bash
for i in $(seq 1 2000); do
curl -s -o /dev/null --connect-timeout 1 http://127.0.0.1:8080/ &
done
wait
预期结果:在 dmesg 中看到 nf_conntrack: table full, dropping packet,部分 curl 失败。
验证与清理:
bash
cat /proc/sys/net/netfilter/nf_conntrack_count
echo 262144 > /proc/sys/net/netfilter/nf_conntrack_max
容易改错的地方:直接把最大值调得很小可能影响本机其他服务,测试机要隔离;生产环境不要用这种方式复现。
10. 完整示例二:用 ss 与 tcpdump 定位 SYN 重传
目标:模拟服务端未监听,观察客户端 SYN 重传,并用 tcpdump 验证。
前置环境:Linux 机器,安装 tcpdump。
步骤:
bash
# 终端 A:抓包
tcpdump -i lo -nn 'tcp port 9999 and (tcp[tcpflags] & tcp-syn != 0)'
bash
# 终端 B:向未监听端口发起连接
curl -s --connect-timeout 3 http://127.0.0.1:9999/ || echo "connect failed"
预期结果:tcpdump 看到多个 SYN 包,间隔大约 1 秒、2 秒、4 秒,这是 TCP 的指数退避重传。
也可以同时观察:
bash
ss -tan state syn-sent
如果连接很快失败,可能看到 syn-sent 短暂出现后消失。
适用范围:验证"请求到底有没有发出去"以及"对端有没有回 SYN/ACK"。边界是回环接口不涉及物理链路,不能模拟中间网络设备丢包。
11. 完整示例三:生产排障脚本,采集网络诊断快照
目标:写一个可复用的 shell 脚本,在故障时一次性采集状态、报文统计和 conntrack 信息。
前置环境:Linux 服务器,具备 root 权限,安装 ss、tcpdump、conntrack-tools。
脚本 net-diag.sh:
bash
#!/bin/bash
set -euo pipefail
OUT_DIR="/tmp/net-diag-$(date +%Y%m%d%H%M%S)"
mkdir -p "$OUT_DIR"
echo "[1/6] ss summary"
ss -s > "$OUT_DIR/ss-summary.txt"
echo "[2/6] listening sockets"
ss -lntp > "$OUT_DIR/ss-listen.txt"
echo "[3/6] established sockets"
ss -ntp state established > "$OUT_DIR/ss-established.txt"
echo "[4/6] timewait count"
ss -tan state time-wait | wc -l > "$OUT_DIR/timewait-count.txt"
echo "[5/6] conntrack stats"
conntrack -S > "$OUT_DIR/conntrack-stats.txt" 2>/dev/null || echo "conntrack not available" > "$OUT_DIR/conntrack-stats.txt"
cat /proc/sys/net/netfilter/nf_conntrack_count > "$OUT_DIR/conntrack-count.txt" 2>/dev/null || true
cat /proc/sys/net/netfilter/nf_conntrack_max > "$OUT_DIR/conntrack-max.txt" 2>/dev/null || true
echo "[6/6] tcp retrans stats"
nstat -az | grep -E 'TcpRetransSegs|TcpExtTCPTimeouts' > "$OUT_DIR/tcp-retrans.txt" 2>/dev/null || true
echo "done: $OUT_DIR"
执行:
bash
chmod +x net-diag.sh
sudo ./net-diag.sh
预期结果:在 /tmp/net-diag-时间戳/ 下得到一组文本文件,可以打包给同事或留档对比。
关键点:脚本要加 set -euo pipefail,避免某条命令失败后继续;生产机器上抓包要控制时长和数量,避免磁盘被写满。
12. 常见误区:别把正常状态当成故障
| 现象 | 常见误解 | 更合理的判断 |
|---|---|---|
| 大量 TIME_WAIT | 认为内核有 bug | 先看是否耗尽端口、是否短连接过多 |
| SYN_RECV 多 | 直接认定被攻击 | 结合来源 IP、半连接队列、SYN cookie |
| 偶发重传 | 认为链路完全不可用 | 看重传率是否持续,业务是否超时 |
| conntrack 表满 | 只调大 max | 同时看超时时间、业务连接模型 |
| tcpdump 没抓到包 | 认为没有流量 | 抓包点、过滤表达式、权限都可能影响 |
最常见的错误是"看到指标升高就改参数"。更稳妥的方式是先确认指标是否对应真实业务损失,再决定是否调整。
13. 生产实践建议:按条件选择手段
当现象是连接建立失败、且本机是 NAT 网关时,优先查 conntrack 表。当现象是短连接高并发、本地端口耗尽时,优先查 TIME_WAIT 和端口范围。当现象是连接能建立但偶发超时,优先查重传和 RTT。当现象是"服务端说没收到请求"时,优先用 tcpdump 确认报文是否到达。
参数调整要遵循小步验证:先记录基线,再修改,再观察 ss、nstat、dmesg 的变化。不要一次改多个参数,否则无法归因。
对于后端服务,优先使用长连接和连接池,减少 TIME_WAIT 和 conntrack 表项压力。对于网关和入口服务,监控 nf_conntrack_count / nf_conntrack_max 比例,设置告警阈值。
14. 排障清单:从现象到命令
- 连接超时,先执行
ss -s看状态分布。 - 怀疑端口耗尽,执行
ss -tan state time-wait | wc -l。 - 怀疑报文没到,执行
tcpdump -i any -nn 'tcp port <端口>'。 - 怀疑 conntrack 表满,执行
conntrack -S和dmesg | grep -i conntrack。 - 怀疑链路质量,执行
ss -tin和nstat -az。 - 采集完成后,把命令输出和业务时间点对齐,避免只看单点数据。
15. 面试/复盘问题
- TIME_WAIT 为什么存在?它解决什么问题?
- conntrack 表满时,内核为什么会丢包,而不是排队?
tcpdump -i any和tcpdump -i eth0的抓包点有什么差异?- 重传率高一定是网络故障吗?如何结合业务判断?
- 如果让你设计一个高并发短连接客户端,你会如何避免端口耗尽?
16. 总结
把 Linux 网络诊断记成一条链路:ss 看本机连接状态,tcpdump 看报文有没有到,conntrack 看连接跟踪表是否成为瓶颈,ss -ti 和 nstat 看重传与 RTT。遇到丢包时,先确认是握手阶段、建连后、还是表项资源问题,再选对应命令。
真正有效的排障不是背命令,而是能说清楚"谁在什么条件下做了什么,结果怎样"。当你能把一次请求从应用、协议栈、网卡到 conntrack 的路径复述出来,剩下的就是按证据缩小范围。
17. 参考资料
- Linux man-pages:
ss(8),tcpdump(8),conntrack(8) - Linux 内核文档:
networking/netfilter/nf_conntrack-sysctl.txt - RFC 793: Transmission Control Protocol
- RFC 1122: Requirements for Internet Hosts
- 《TCP/IP 详解 卷1:协议》,W. Richard Stevens
- 《Linux 高性能服务器编程》,游双