Skip to content

ss 与 tcpdump:连接状态与抓包 ​

ss 看"连接现在处于什么状态",tcpdump 看"包到底发了什么"。两者配合,能定位 90% 的网络问题。

一、ss 总览 ​

bash
ss -s
text
Total: 512
TCP:   489 (estab 128, closed 340, orphaned 0, timewait 332)
UDP:   23
RAW:   0
FRAG:  0

连接数不对时,先看这四类状态:

状态含义正常吗
ESTAB已建立正常
TIME_WAIT主动关闭方等待 2MSL少量正常,几万要处理
CLOSE_WAIT被动关闭方还没 close异常,应用没关连接
SYN-RECV收到 SYN,未完成握手大量 = SYN Flood 或半开连接
bash
ss -tan state established | wc -l
ss -tan state time-wait | wc -l
ss -tan state close-wait | head
ss -tan state syn-recv | wc -l

二、TIME_WAIT 堆积 ​

bash
ss -tan state time-wait | awk '{print $4}' | sed 's/:[0-9]*$//' | sort | uniq -c | sort -rn | head
text
  28412 10.0.0.31
   1284 127.0.0.1

两万多条,全在本地 10.0.0.31 上,说明这台机器作为客户端主动关闭了大量短连接(调下游 API、连数据库没复用连接)。

先分清方向:

bash
ss -tan state time-wait | head -3
text
State      Recv-Q Send-Q  Local Address:Port    Peer Address:Port
TIME-WAIT  0      0       10.0.0.31:51244       10.0.0.32:3306
TIME-WAIT  0      0       10.0.0.31:51246       10.0.0.32:3306

Local 是随机高位端口、Peer 是服务端口 → 本机是客户端。反之如果 Local 是 80/8080,是对外服务被客户端先关了。

处理顺序:

  1. 先修根因:用连接池、HTTP keep-alive。
  2. 再考虑内核参数(治标)。
ini
# /etc/sysctl.d/99-net.conf
net.ipv4.tcp_tw_reuse = 1          # 允许复用 TIME_WAIT 连接(只对客户端有效)
net.ipv4.tcp_fin_timeout = 30      # FIN_WAIT2 超时
net.ipv4.ip_local_port_range = 10000 65000   # 扩大可用端口
net.ipv4.tcp_max_tw_buckets = 262144
bash
sysctl -p /etc/sysctl.d/99-net.conf

不要开 tcp_tw_recycle

net.ipv4.tcp_tw_recycle 在 NAT 环境下会导致大量连接失败(同一 NAT 后不同客户端时间戳不一致)。该参数在 Linux 4.12 已被移除。看到老文章推荐它,直接跳过。

三、CLOSE_WAIT 堆积 ​

bash
ss -tan state close-wait | head
text
State       Recv-Q Send-Q  Local Address:Port   Peer Address:Port
CLOSE-WAIT  0      0       10.0.0.31:8080       10.0.0.61:52344

CLOSE_WAIT 只会因为应用没调 close() 而产生。 对端已经发了 FIN,内核回了 ACK,等应用关闭 socket——应用不关,它就一直在。

排查:

bash
ss -tanp state close-wait | head
text
CLOSE-WAIT 0 0 10.0.0.31:8080 10.0.0.61:52344 users:(("java",pid=28441,fd=182))

进程是 java。结合 lsof -p 28441 | wc -l 看 fd 是否逼近上限:

bash
lsof -p 28441 | wc -l
cat /proc/28441/limits | grep 'open files'
text
5821
Max open files            65535                65535                files

5821 还没到 65535,但在涨。这类问题的正解是查代码:

  • HTTP 客户端没关 Response/Connection。
  • 数据库连接没 close()。
  • 异常路径跳过了 finally 里的关闭。

四、按端口和进程过滤 ​

bash
ss -lntp 'sport = :8080'
ss -tanp '( sport = :8080 or dport = :8080 )'
ss -tan dst 10.0.0.32
ss -tan 'dport = :3306' | wc -l
text
State  Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
LISTEN 0      4096   0.0.0.0:8080        0.0.0.0:*     users:(("java",pid=28441,fd=42))

ss 的过滤语法比 grep 准,且不用解析端口名。

五、tcpdump 基础 ​

bash
tcpdump -i eth0 -nn -c 20
text
22:50:01.114221 IP 10.0.0.31.51244 > 10.0.0.32.8080: Flags [S], seq 184320123, win 64240, options [mss 1460], length 0
22:50:01.114388 IP 10.0.0.32.8080 > 10.0.0.31.51244: Flags [S.], seq 983211, ack 184320124, win 65535, options [mss 1460], length 0
22:50:01.114402 IP 10.0.0.31.51244 > 10.0.0.32.8080: Flags [.], ack 1, length 0
22:50:01.114511 IP 10.0.0.31.51244 > 10.0.0.32.8080: Flags [P.], seq 1:118, ack 1, length 117
参数作用
-i any所有网卡
-nn不解析主机名和端口名
-c N抓 N 个包后退出
-s 0抓完整包(默认已不截断)
-w file.pcap写文件(用 Wireshark 分析)
-r file.pcap读文件
-A以 ASCII 显示 payload
-Xhex + ASCII
-tttt可读时间戳
-l行缓冲(配合管道)

六、过滤表达式 ​

bash
# 主机
tcpdump -i any -nn host 10.0.0.32

# 网段
tcpdump -i any -nn net 10.0.0.0/24

# 端口
tcpdump -i any -nn port 8080
tcpdump -i any -nn 'port 80 or port 443'

# 源/目的
tcpdump -i any -nn src 10.0.0.31 and dst port 3306

# 只有 SYN
tcpdump -i any -nn 'tcp[tcpflags] & tcp-syn != 0'

# 只有 RST
tcpdump -i any -nn 'tcp[tcpflags] & tcp-rst != 0'

# HTTP 请求行
tcpdump -i any -nn -A 'tcp port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'

最后一条是匹配 payload 以 GET 开头,用于只看请求。

抓包先想清楚在哪抓

  • 容器问题:docker run --rm --net=container:xxx nicolaka/netshoot tcpdump ...
  • 本机与外部之间:-i eth0
  • 本机进程之间:-i lo
  • 不确定走哪块网卡:-i any(注意 any 抓的是链路层,过滤表达式可能失效)

七、读握手与断开 ​

text
[S]      客户端发起连接
[S.]     服务端同意
[.]      ACK
[P.]     携带数据
[F.]     一方请求关闭
[R]      重置

连不上 ​

text
IP 10.0.0.31.51244 > 10.0.0.32.8080: Flags [S], seq 1, win 64240
IP 10.0.0.31.51244 > 10.0.0.32.8080: Flags [S], seq 1, win 64240
IP 10.0.0.31.51244 > 10.0.0.31.51244: Flags [R], ...

只有 [S] 重传没有 [S.] → 对端没收到或内核丢了,通常是防火墙 DROP 或路由不对。

text
10.0.0.31.51244 > 10.0.0.32.8080: Flags [S]
10.0.0.32.8080 > 10.0.0.31.51244: Flags [R.]

立即回 R → 对端主机在,但端口没监听(或 REJECT)。

连接被重置 ​

text
10.0.0.31.51244 > 10.0.0.32.8080: Flags [P.], length 117
10.0.0.32.8080 > 10.0.0.31.51244: Flags [R], seq 184320240

服务端发 R,通常三种原因:

  1. 服务端进程崩了/重启了。
  2. 连接的 socket 被服务端关闭后又有数据到达。
  3. 中间设备(负载均衡、防火墙)主动重置。

重传与丢包 ​

text
Flags [P.], seq 1:1449, ack 1        <- 第 1 个包
Flags [P.], seq 1449:2897, ack 1     <- 正常
Flags [P.], seq 1:1449, ack 1        <- 重传同一段

同一个 seq 重复出现 = 丢包。连续大量重传 → 网络质量差或对端接收窗口为 0。

八、抓包存盘再分析 ​

bash
tcpdump -i any -nn -s 0 -w /tmp/db.pcap 'host 10.0.0.32 and port 3306'
text
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
^C
142 packets captured
142 packets received by filter
0 packets dropped by kernel

拿到本地用 Wireshark 打开,可以看 TCP 流、重传时间、RTT。

bash
# 没有 Wireshark 时用 tshark
tshark -r /tmp/db.pcap -Y 'tcp.analysis.retransmission' | head
tshark -r /tmp/db.pcap -q -z io,stat,1

packets dropped by kernel 非 0 说明抓包太慢丢包了,加过滤条件或加大 buffer。

九、踩过的坑 ​

现象根因处置
ss 显示监听但外部连不上监听在 127.0.0.1改 0.0.0.0
TIME_WAIT 几万条短连接不复用连接池 + tcp_tw_reuse
CLOSE_WAIT 持续增长应用没 close查代码的 finally
tcpdump 抓不到包网卡选错-i any 试试
any 上过滤表达式无效链路层类型不同指定具体网卡
容器里看不到宿主机流量网络命名空间隔离在宿主机或对应容器的 netns 抓
抓包文件巨大没加过滤加 host/port,限 -c
You don't have permission非 rootsudo,或给 CAP_NET_RAW
packets dropped by kernel抓包速率过高加过滤、用 -B 调大缓冲