前言
日志说不清、ping 测不明的问题,最后都要靠看包。tcpdump 是网络排查的显微镜——这篇从过滤语法到典型故障的"包形态",练出直接读包排障的能力。
一、tcpdump 基本功
# 最小可用(-i 网卡, -n 不解析域名, 抓 20 个包)
tcpdump -i eth0 -n -c 20
# 常用选项
-nn # 端口也不解析(22 而不是 ssh)
-v / -vv # 详细程度
-w dump.pcap # 存文件(拿去 Wireshark 分析)
-r dump.pcap # 读文件
-s0 # 抓完整包(默认截断)
--immediate-mode # 实时输出(不缓冲, 配合管道 grep)
二、过滤表达式(BPF)速成
# ---- 按主机/端口 ----
tcpdump -i eth0 -nn host 10.0.0.5
tcpdump -i eth0 -nn src 10.0.0.5 # 只看来源
tcpdump -i any -nn port 3306 # 任意网卡的 3306
tcpdump -i eth0 -nn dst port 80
# ---- 组合逻辑 ----
tcpdump -nn host 10.0.0.5 and port 443
tcpdump -nn port 80 or port 8080
tcpdump -nn host 10.0.0.5 and not port 22 # 排除自己的 SSH(防刷屏!)
# ---- 按协议与标志位 ----
tcpdump -i eth0 -nn tcp
tcpdump -i eth0 -nn icmp
tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-syn != 0' # 所有 SYN 包
tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0' # 所有 RST
tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn|tcp-ack' # SYN+ACK
# ---- 实用组合 ----
tcpdump -i any -nn -c 100 'port 443 and host 10.0.0.5' -w /tmp/443.pcap
三、读懂包:一次健康的握手
tcpdump -i eth0 -nn host 10.0.0.5 and port 8080
10.0.0.5.45124 > 10.0.0.1.8080: Flags [S], seq 1234567890 # ① SYN 客户端发起
10.0.0.1.8080 > 10.0.0.5.45124: Flags [S.], seq 987654321, ack 1234567891 # ② SYN+ACK 服务端应答
10.0.0.5.45124 > 10.0.0.1.8080: Flags [.], ack 987654322 # ③ ACK 连接建立
10.0.0.5.45124 > 10.0.0.1.8080: Flags [P.], seq 1:200 # 数据包(PSH+ACK)
10.0.0.1.8080 > 10.0.0.5.45124: Flags [.], ack 200 # 服务端确认收到
10.0.0.5.45124 > 10.0.0.1.8080: Flags [F.], seq 200 # FIN 优雅挥手
10.0.0.1.8080 > 10.0.0.5.45124: Flags [F.], ack 201 # 对端也关
10.0.0.5.45124 > 10.0.0.1.8080: Flags [.], ack 202 # 最后的 ACK
标志速记:[S] SYN 建连 / [.] ACK 确认 / [P] PSH 带数据 / [F] FIN 关闭 / [R] RST 强断。
四、故障的"包形态"对照表
形态一:SYN 发出去,没有任何回应
10.0.0.5 > 10.0.0.1.8080: [S] # 客户端 SYN
(重复多次, 指数退避重试)
→ 包被丢弃(DROP 型防火墙/安全组),或对端没监听且被丢。查路径上的墙。
形态二:SYN 回来的是 RST
10.0.0.5 > 10.0.0.1.8080: [S]
10.0.0.1.8080 > 10.0.0.5: [R.] # 服务端直接拒绝
→ 对端明确拒绝:端口没监听(ss -tlnp | grep 8080),或 backlog 满。REJECT 型防火墙也是 RST。
形态三:握手成功,数据发出去没 ACK
... [S] [S.] [.] 正常握手
10.0.0.5 > ...: [P.] seq 1:500
10.0.0.5 > ...: [P.] seq 1:500 # 一直在重传!
→ 单向丢包(回程被丢)、对端假死。查 conntrack 表满(nf_conntrack: table full)。
形态四:大量短连接 TIME_WAIT
... [F] [F.] [.] ← 反复高频挥手
→ 应用短连接风暴,参考 sysctl 篇的治理方案。
形态五:三次握手中途大量 SYN+ACK 重传
... .8080 > client: [S.] # 服务端发 SYN+ACK
... .8080 > client: [S.] # 客户端不回第三次 ACK
→ SYN Flood 攻击 或 客户端伪造 IP。tcp_syncookies=1 缓解。
五、实战案例:接口偶发超时
# 1. 现象: 调用 10.0.0.9:6379 偶发 3s 超时
# 2. 抓 SYN 重传证据(只抓关键流量, 避免刷屏)
tcpdump -i eth0 -nn host 10.0.0.9 and port 6379 -w /tmp/redis.pcap &
# 3. 复现超时后停抓, 分析
tcpdump -r /tmp/redis.pcap -nn | grep -E "retrans|timeout"
# 发现: 每次超时前, 有连续 retransmission 记录
# 4. 结合内核计数器确认丢包位置
netstat -s | grep -iE "retrans|drop"
ip -s link show eth0 | grep -A2 errors # 网卡层有无错误
# 5. 结论方向: 中间网络丢包 → 抓两端对比, 定位丢在哪一段
# 服务器端抓不到包 = 包没到达; 抓到了但没回 = 应用层问题
两端对抓是定位丢包段的标准手法:A 有出、B 没进 → 中间丢;A 有出、B 有进没回 → B 的问题。
六、ss 深度用法(tcpdump 的搭档)
# 全量概览
ss -s
# 监听 + 进程(排障第一命令)
ss -tlnp
# 指定过滤
ss -tnp dst 10.0.0.9:6379
ss -tan state established '( sport = :8080 )' # 8080 的活跃连接
# 各状态统计(TIME_WAIT 是否堆积一目了然)
ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn
# 看发送/接收缓冲区堆积(应用不读数据的证据!)
ss -tnm state established
# ... skmem:(r0,rb131072,t87380,tb4737280...)
# t=待发送堆积的字节 tb=发送缓冲总大小
# t 逼近 tb → 对端不收 → 对端慢或网络堵
# 定位"哪个进程占着这条连接"
ss -tnp state close-wait | head
# 大量 CLOSE_WAIT + 具体进程名 → 该进程代码没调 close()
七、抓包注意事项
- 先收窄过滤条件再抓——生产高流量机器上裸抓会刷爆磁盘和 CPU
-w存 pcap 时过滤条件同样生效(在目标机器上过滤,别拉回来再筛)- 抓敏感协议注意合规(密码/token 明文会进 pcap)
tcpdump -D列出可用网卡;容器场景记得tcpdump -i docker0或any- 长期分析用 Wireshark 打开 pcap,用
tcp.stream eq N追踪单条流
小结
| 症状 | 包形态 | 方向 |
|---|---|---|
| 连不上 | SYN 无响应 | 防火墙 DROP |
| Connection refused | SYN → RST | 端口没监听 |
| 偶发卡顿 | 数据包重传 | 网络丢包,两端对抓 |
| 假死 | 握手后无 ACK | conntrack/应用挂起 |
| 连接堆积 | ss 看 t/tb、CLOSE_WAIT | 应用不收/不关 |
本文是「Linux 运维」系列第 13 篇。