三次握手在做什么
TCP 建连要走三步:客户端发 SYN,带上自己的初始序列号与 MSS、窗口缩放、SACK 许可等选项;服务端回 SYN-ACK,带上自己的初始序列号并确认对方;客户端再回一个 ACK 确认对方。三步走完连接进入已建立状态,之后才能传数据。抓包判读的价值在于把「连不上」拆成三种完全不同的结论:请求没发出、发出了没人应答、对方回了本地没收到。这三者对应的责任方分别是本机配置、路径与中间设备、以及回程链路。
基本信息
| 判读依据 | 序列号与确认号的对应关系,以及标志位组合 |
|---|---|
| tcpdump 过滤 | tcp[tcpflags] = tcp-syn(纯 SYN)、tcp[tcpflags] = tcp-syn|tcp-ack(SYN-ACK) |
| Wireshark 过滤 | tcp.flags.syn == 1 && tcp.flags.ack == 0 |
| 时延基线 | SYN 到 SYN-ACK 的间隔约为半个往返时延 |
| 协议依据 | RFC 9293(TCP) |
典型使用场景
- 判断服务是否可达:SYN 发出后有没有收到 SYN-ACK
- 判断慢在哪一段:看 SYN 到 SYN-ACK 的间隔,以及是否出现重复 SYN
- 判断责任方:静默无响应多为路径丢弃,立刻回 RST 说明端口没有进程监听
- 判断队列压力:服务端收到 SYN 却不回,可能是半连接队列溢出
- 验证策略改动:对比变更前后的握手结果,确认放行是否真的生效
抓取与判读
抓取与判读
tcpdump -i eth0 -nn 'tcp[tcpflags] = tcp-syn' -c 20 # 只看纯 SYN
tcpdump -i eth0 -nn 'tcp[tcpflags] = tcp-syn|tcp-ack' # 只看 SYN-ACK
tcpdump -i eth0 -nn -ttt 'tcp port 443' -c 30 # 带相对时间看间隔
ss -lnt | grep :443 # 服务端核对是否在监听
sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog # 半连接队列相关参数三步逐包读法
| 报文 | 序列号关系 | 如何判读 |
|---|---|---|
| SYN | seq=x,携带 MSS 与窗口选项 | 建连请求,方向由客户端指向服务端口 |
| SYN-ACK | seq=y,ack=x+1 | 服务端同意建连;确认号不等于 SYN 序列号加一即为异常 |
| ACK | seq=x+1,ack=y+1 | 客户端确认,连接进入已建立状态 |
| 重复 SYN | 序列号与首个 SYN 相同,间隔递增 | 未收到 SYN-ACK 的重传,问题在上行路径或服务端 |
| SYN 后紧跟 RST | 无对应确认关系 | 端口未监听或被策略明确拒绝 |
| 只有 SYN 无任何回包 | 无 | 路径静默丢弃,查中间设备与会话表 |
SYN 与 SYN-ACK 的间隔约等于半个往返时延,是判断「建连慢」的基线。这个间隔很小而整体依然慢,说明耗时在后续的数据传输或应用处理阶段,不要继续在建连上找原因。
用抓包下结论前先说明抓包点:在客户端侧抓只能证明「这里没收到 SYN-ACK」,不能证明服务端没有发出,关键问题必须两端同时抓取对照。
命令行核对
命令行核对
ss -s # 看 socket 总量与各状态计数
netstat -s | grep -i -A2 listen # 观察因队列满而丢弃的 SYN 计数
tcpdump -i eth0 -nn -c 10 'tcp[tcpflags] & tcp-syn != 0' | grep -c 'S\.' # 统计回包形式常见结论与依据
- 只有 SYN、没有 SYN-ACK、也没有 RST:请求被路径上某台设备丢弃,或服务端队列已满而静默丢弃
- SYN 与 SYN-ACK 都有,但客户端重传 SYN:说明回包在回程路径上丢失,重点查反向链路
- 立刻收到 RST:报文到达了主机,但没有进程监听该端口,或被策略配置为拒绝
- 握手正常但仍然无法通信:问题在 TLS 或应用层,继续看后续数据报文
- 握手时延接近秒级:通常是初始重传超时造成,说明第一个 SYN 或 SYN-ACK 被丢弃
- 客户端与抓包点在同一台机器时,源端口相同而序列号递增的重传说明本地已经重试过多次
不要把「没有看到 SYN-ACK」直接写成结论。单向抓包点的缺失只能说明本地没收到,回包可能在回程路径上被丢弃,也可能根本没发出,两种情况的责任方完全不同。