4、TCP握手分析:三次握手原理、SYN Flood攻击、半连接队列溢出、tcpdump抓包分析

说到连接超时,TCP三次握手是绕不开的第一道坎。我排查过的超时案例里,大概有三分之一都跟握手阶段有关。说白了,连接都还没建起来,业务怎么可能不超时?

这一节我们就把TCP握手这件事彻底讲透。从原理到攻击,从队列到抓包,一条线串下来。

4.1 三次握手,到底在握什么

先看最经典的流程。客户端和服务端要建立连接,得交换三个报文:

  1. 客户端 → 服务端:SYN
    客户端说:“我想跟你连,我的初始序列号是X。”
  2. 服务端 → 客户端:SYN + ACK
    服务端说:“收到,我的初始序列号是Y,我确认收到了你的X。”
  3. 客户端 → 服务端:ACK
    客户端说:“收到你的Y了,连接建立。”

为什么是三次,不是两次?我刚开始学网络时也想过这个问题。其实核心就一点:防止失效的连接请求突然传到服务端。两次握手的话,服务端发完SYN+ACK就认为连接建好了,但客户端可能根本没收到,或者那个SYN是网络里残留的旧包。三次握手让服务端等客户端的ACK确认,确保双方都准备好了。

核心要点:三次握手的本质是双方各自确认对方的收发能力正常。客户端收到SYN+ACK,证明服务端能收能发;服务端收到ACK,证明客户端能收能发。

4.2 SYN Flood攻击:握手环节的噩梦

SYN Flood是最经典的DDoS攻击方式之一。原理很简单——攻击者伪造大量源IP,疯狂发SYN包,但从不回复最后的ACK。

服务端收到SYN后,会分配资源,把连接放到半连接队列里,然后回复SYN+ACK。它等着客户端的ACK,但永远等不到。半连接队列很快就满了,正常的SYN包进不来,新用户连不上。

我在项目中遇到过这种情况。有一次线上告警,用户反馈App登录超时。我上机器一看,netstat -s 里显示 SYN_RECV 状态的连接有几千个,半连接队列溢出计数一直在涨。嗯,典型的SYN Flood。

快速判断命令:

# 查看半连接队列当前大小
netstat -ant | grep SYN_RECV | wc -l

# 查看半连接队列溢出统计
netstat -s | grep -i "SYN.*overflow"

# 查看当前半连接队列最大容量
cat /proc/sys/net/ipv4/tcp_max_syn_backlog

4.3 半连接队列 vs 全连接队列

这里有个容易混淆的点。TCP有两个队列:

队列名称 存放什么 什么时候进入 溢出后果
半连接队列(SYN Queue) 收到SYN但未完成握手的连接 服务端收到SYN后 新SYN被丢弃,客户端连接超时
全连接队列(Accept Queue) 已完成三次握手,等待应用层accept() 服务端收到ACK后 客户端连接建立成功,但服务端无法处理,丢包或RST

你想想看,如果全连接队列满了,客户端其实已经完成了三次握手,但服务端应用层来不及accept。这时候客户端发数据过来,服务端会回复RST,客户端就懵了——连接明明建好了,怎么被重置了?

我曾经排查过一个诡异问题:客户端偶尔报“Connection reset by peer”,但大部分时候正常。最后发现是应用层处理请求太慢,全连接队列满了,新连接进来就被RST了。调整了应用层的并发处理能力,问题就解决了。

4.4 tcpdump抓包分析:亲手看一次握手

理论说再多,不如亲手抓一次包。我个人习惯用tcpdump配合Wireshark分析,但在服务器上通常只用tcpdump。

抓一个完整的握手过程:

# 在服务端抓包,过滤客户端IP和端口
tcpdump -i eth0 host 192.168.1.100 and port 80 -w handshake.pcap

# 或者只抓SYN包
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn) != 0' -w syn_only.pcap

抓完用Wireshark打开,你会看到这样的序列:

  1. 包1:客户端 → 服务端,Flags [S],Seq=0(相对序列号)
  2. 包2:服务端 → 客户端,Flags [S.], Seq=0, Ack=1
  3. 包3:客户端 → 服务端,Flags [.], Seq=1, Ack=1

注意那个Flags字段:

  • [S] 表示SYN
  • [S.] 表示SYN+ACK
  • [.] 表示ACK(没有其他标志位)
  • [R] 表示RST
  • [F] 表示FIN

避坑指南:我曾经在抓包时忘了加 -i 指定网卡,结果抓了半天全是环回口的流量。还有一次过滤条件写错了,抓了几个G的包才发现不对。建议先不加 -w 跑几秒看看输出对不对,确认过滤条件正确了再写文件。

4.5 知识体系总览

下面这张图把TCP握手相关的核心知识点串起来了。从正常流程到异常场景,从攻击原理到排查手段,一条线讲清楚。

TCP握手知识体系图 TCP握手知识体系 三次握手 正常流程:SYN → SYN+ACK → ACK 异常场景:重传、超时、RST 攻击:SYN Flood 半连接队列(SYN Queue) 全连接队列(Accept Queue) tcpdump 抓包分析 netstat / ss 查看队列状态 内核参数调优(tcp_max_syn_backlog等) 排查思路 先看队列是否溢出 → 再抓包看握手是否完成 → 最后调参或加机器

4.6 实战:三步定位握手超时

如果你遇到连接超时,怀疑是握手阶段的问题,按这个顺序排查:

  1. 第一步:看队列
    用 ss -tlnp 看 Listen 状态的 Recv-Q 和 Send-Q。Recv-Q 是全连接队列的积压数,如果接近或等于 backlog,说明应用层处理不过来。
  2. 第二步:看统计
    netstat -s | grep -E "SYN|overflow|drop",看有没有半连接队列溢出、SYN重传等计数在增长。
  3. 第三步:抓包确认
    在客户端和服务端同时抓包,对比时间戳。如果客户端发了SYN但没收到SYN+ACK,可能是中间网络丢包或服务端半连接队列满了。如果服务端发了SYN+ACK但没收到ACK,那就是客户端没响应或者被中间设备拦截了。

一个小技巧:我习惯在抓包时加上 -tttt 参数显示微秒级时间戳,方便对比两端的时间差。有时候差个几毫秒就能看出问题在哪一端。

TCP握手看起来简单,但实际排查时坑不少。半连接队列溢出、全连接队列积压、中间设备丢包、内核参数不合理……每一个都可能让你抓耳挠腮。但只要你掌握了抓包和看队列这两个基本功,大部分问题都能快速定位。