前言

日志说不清、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()

七、抓包注意事项

  1. 先收窄过滤条件再抓——生产高流量机器上裸抓会刷爆磁盘和 CPU
  2. -w 存 pcap 时过滤条件同样生效(在目标机器上过滤,别拉回来再筛)
  3. 抓敏感协议注意合规(密码/token 明文会进 pcap)
  4. tcpdump -D 列出可用网卡;容器场景记得 tcpdump -i docker0 或 any
  5. 长期分析用 Wireshark 打开 pcap,用 tcp.stream eq N 追踪单条流

小结

症状 包形态 方向
连不上 SYN 无响应 防火墙 DROP
Connection refused SYN → RST 端口没监听
偶发卡顿 数据包重传 网络丢包,两端对抓
假死 握手后无 ACK conntrack/应用挂起
连接堆积 ss 看 t/tb、CLOSE_WAIT 应用不收/不关

本文是「Linux 运维」系列第 13 篇。