4、TCP握手分析:三次握手原理、SYN Flood攻击、半连接队列溢出、tcpdump抓包分析
说到连接超时,TCP三次握手是绕不开的第一道坎。我排查过的超时案例里,大概有三分之一都跟握手阶段有关。说白了,连接都还没建起来,业务怎么可能不超时?
这一节我们就把TCP握手这件事彻底讲透。从原理到攻击,从队列到抓包,一条线串下来。
4.1 三次握手,到底在握什么
先看最经典的流程。客户端和服务端要建立连接,得交换三个报文:
- 客户端 → 服务端:SYN
客户端说:“我想跟你连,我的初始序列号是X。” - 服务端 → 客户端:SYN + ACK
服务端说:“收到,我的初始序列号是Y,我确认收到了你的X。” - 客户端 → 服务端: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:客户端 → 服务端,Flags [S],Seq=0(相对序列号)
- 包2:服务端 → 客户端,Flags [S.], Seq=0, Ack=1
- 包3:客户端 → 服务端,Flags [.], Seq=1, Ack=1
注意那个Flags字段:
- [S] 表示SYN
- [S.] 表示SYN+ACK
- [.] 表示ACK(没有其他标志位)
- [R] 表示RST
- [F] 表示FIN
避坑指南:我曾经在抓包时忘了加 -i 指定网卡,结果抓了半天全是环回口的流量。还有一次过滤条件写错了,抓了几个G的包才发现不对。建议先不加 -w 跑几秒看看输出对不对,确认过滤条件正确了再写文件。
4.5 知识体系总览
下面这张图把TCP握手相关的核心知识点串起来了。从正常流程到异常场景,从攻击原理到排查手段,一条线讲清楚。
4.6 实战:三步定位握手超时
如果你遇到连接超时,怀疑是握手阶段的问题,按这个顺序排查:
- 第一步:看队列
用ss -tlnp看 Listen 状态的 Recv-Q 和 Send-Q。Recv-Q 是全连接队列的积压数,如果接近或等于 backlog,说明应用层处理不过来。 - 第二步:看统计
netstat -s | grep -E "SYN|overflow|drop",看有没有半连接队列溢出、SYN重传等计数在增长。 - 第三步:抓包确认
在客户端和服务端同时抓包,对比时间戳。如果客户端发了SYN但没收到SYN+ACK,可能是中间网络丢包或服务端半连接队列满了。如果服务端发了SYN+ACK但没收到ACK,那就是客户端没响应或者被中间设备拦截了。
一个小技巧:我习惯在抓包时加上 -tttt 参数显示微秒级时间戳,方便对比两端的时间差。有时候差个几毫秒就能看出问题在哪一端。
TCP握手看起来简单,但实际排查时坑不少。半连接队列溢出、全连接队列积压、中间设备丢包、内核参数不合理……每一个都可能让你抓耳挠腮。但只要你掌握了抓包和看队列这两个基本功,大部分问题都能快速定位。