外观
ss 与 tcpdump:连接状态与抓包
ss 看"连接现在处于什么状态",tcpdump 看"包到底发了什么"。两者配合,能定位 90% 的网络问题。
一、ss 总览
bash
ss -stext
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 | headtext
28412 10.0.0.31
1284 127.0.0.1两万多条,全在本地 10.0.0.31 上,说明这台机器作为客户端主动关闭了大量短连接(调下游 API、连数据库没复用连接)。
先分清方向:
bash
ss -tan state time-wait | head -3text
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:3306Local 是随机高位端口、Peer 是服务端口 → 本机是客户端。反之如果 Local 是 80/8080,是对外服务被客户端先关了。
处理顺序:
- 先修根因:用连接池、HTTP
keep-alive。 - 再考虑内核参数(治标)。
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 = 262144bash
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 | headtext
State Recv-Q Send-Q Local Address:Port Peer Address:Port
CLOSE-WAIT 0 0 10.0.0.31:8080 10.0.0.61:52344CLOSE_WAIT 只会因为应用没调 close() 而产生。 对端已经发了 FIN,内核回了 ACK,等应用关闭 socket——应用不关,它就一直在。
排查:
bash
ss -tanp state close-wait | headtext
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 files5821 还没到 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 -ltext
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 20text
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 |
-X | hex + 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,通常三种原因:
- 服务端进程崩了/重启了。
- 连接的 socket 被服务端关闭后又有数据到达。
- 中间设备(负载均衡、防火墙)主动重置。
重传与丢包
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,1packets 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 | 非 root | sudo,或给 CAP_NET_RAW |
packets dropped by kernel | 抓包速率过高 | 加过滤、用 -B 调大缓冲 |