问题
发数据之前,两端要先确认两件事
第一,对方在线,而且能收也能发。第二,对方从哪个编号开始给字节编号。TCP 给每个字节编一个号,叫序列号(Sequence Number, seq);起始编号叫初始序列号(Initial Sequence Number, ISN)。
先别往下看:只看图 1,你觉得第 ③ 个包是必需的吗?如果去掉它,谁会“不知道”什么?
去掉 ③,服务器就不知道客户端是否真的收到了 y,也分不清这个 SYN 是新的还是网络里滞留很久的旧包。第 4 节会用对比图细讲。
机制
ack 的意思是“下一个请从这里开始”
每个包有两个关键数字。seq 是这个包第一个字节的编号。确认号(Acknowledgment Number, ack)是“我已收到之前所有字节,下一个期待的编号”。SYN 包本身不带数据,但规定它占用一个编号,所以确认 SYN 时 ack = 对方 seq + 1。
下图用 1000 和 5000 当初始序列号,方便看清楚。真实连接里的 ISN 是难以预测的 32 位数。点“下一步”逐包看。
- 客户端发出 SYN(同步)包,带上自己的初始序列号 1000,进入
SYN_SENT。猜猜:服务器回的包里,ack 是多少? - 服务器回 SYN+ACK。
ack=1001的意思是“1000 收到了,下一个从 1001 开始”。它同时带上自己的初始序列号 5000,进入SYN_RCVD。客户端接下来回什么?ack 是多少? - 客户端收到 SYN+ACK 就进入
ESTABLISHED,并回 ACK:ack=5001,确认收到服务器的 5000。 - 服务器收到这个 ACK 才进入
ESTABLISHED。现在双方都确认了对方能收能发,也都知道了对方的 ISN。客户端发的第一个数据字节,seq 是多少? - 第一个数据字节的编号是 1001,不是 1000:编号 1000 已经被 SYN 用掉了。
用一两句话解释:确认 SYN 时,为什么 ack 是对方 seq + 1,而不是 seq 本身?
参考答案:ack 表示“我期待收到的下一个编号”。SYN 虽然不带数据,但规定占用一个编号,所以收到 seq=x 的 SYN 后,下一个期待的编号就是 x+1。
- 说出 ack 表示“下一个期待的编号”,而不是“我收到的编号”
- 说出 SYN 占用一个序号
动手
在自己的电脑上抓到这三个包
开三个终端窗口,都在同一台 Linux 机器上(虚拟机或 WSL 也行)。抓包需要 root 权限。
怎么读:Flags [S] 是 SYN,[S.] 是 SYN+ACK(点号表示 ACK 标志),[.] 是纯 ACK。后面还有 HTTP 数据和关闭连接的包,先只看前三行。
深入一点:不加 -S 会怎样?
tcpdump 默认在握手之后改用“相对序列号”,把 ISN 当作 0 来显示,所以第三行会显示 ack 1。加 -S 才能看到上面这样的原始数字,方便验证 ack = seq + 1。
换一组数字。客户端 ISN = 3000,服务器 ISN = 7000。把四个空填完整。
② SYN+ACK:seq ,ack
③ ACK:ack
客户端第一个数据字节:seq
服务器的 seq 是它自己的 ISN 7000;它确认客户端的 SYN,所以 ack = 3000 + 1。客户端确认服务器的 SYN,ack = 7000 + 1。SYN 用掉了 3000,所以数据从 3001 开始。
你抓到一个包:Flags [S.], seq 2408150375, ack 913477602。客户端的初始序列号是多少?
这是 SYN+ACK 自己的 seq,也就是服务器的 ISN。
这是 ack,表示“下一个期待的编号”,比客户端的 ISN 大 1。
这是之后客户端回 ACK 时会用的 ack 值,确认的是服务器的 ISN。
[S.] 是服务器发的 SYN+ACK。它的 ack 确认的是客户端的 SYN,等于客户端 ISN + 1,所以客户端 ISN = 913477602 − 1 = 913477601。
为什么是三次
第三个包让服务器能识别“过期的连接请求”
网络里的包可能滞留很久才到达。设想客户端很早以前发过一个 SYN,后来超时放弃了,这个旧 SYN 现在才到服务器。RFC 9293 指出,三次握手的主要原因就是防止这种旧的重复连接请求造成混乱。
如果规则是“服务器回完 SYN+ACK 就算连接建立”(两次握手),那个迟到的旧 SYN 会导致什么?
服务器会为一个早已没人要的连接分配资源,然后一直等客户端发数据。客户端根本不认识这个连接,不会再发任何东西。三次握手里,服务器在收到第三个包前只处于 SYN_RCVD,客户端看到对不上的 SYN+ACK 会回 RST(重置),服务器随即丢弃这个半连接。
资源被白占假如只有两次
及时清理实际的三次
下面每一项,属于客户端还是服务器?
- 经历 SYN_SENT
- 经历 LISTEN
- 经历 SYN_RCVD
- 先进入 ESTABLISHED
- 发出第一个 SYN
- 发出 SYN+ACK
主动发起连接的一方叫“主动打开”,经历 SYN_SENT;先调用 listen() 等待的一方叫“被动打开”,经历 LISTEN 和 SYN_RCVD。客户端比服务器早一个包进入 ESTABLISHED。
边界情况
只发 SYN 不回 ACK,连接会卡在半路
服务器每收到一个 SYN,都要记下这个半连接,等第三个包。攻击者可以伪造大量源地址只发 SYN,服务器发出的 SYN+ACK 没人回应,半连接越积越多,正常用户就连不上了。这叫 SYN 洪泛(SYN flood)。
服务器遭受 SYN 洪泛时,大量堆积的是哪种状态的连接?
要进入 ESTABLISHED,服务器得收到第三个包。攻击者恰恰不发它。
LISTEN 是监听套接字本身的状态,每个端口只有一个,不会因为连接请求增多而堆积。
SYN_SENT 是发起方(客户端)的状态,出现在攻击者那边,不在服务器上。
服务器回完 SYN+ACK 进入 SYN_RCVD,等第三个包。伪造的源地址永远不会回 ACK,这些半连接就一直停在 SYN_RCVD,直到超时。Linux 的 SYN cookies 让服务器在队列满时先不保存状态,而是把信息编码进自己的 ISN,等合法的 ACK 回来再还原。
回顾
要记住的
- 三个包:SYN 报出自己的 ISN → SYN+ACK 确认对方并报出自己的 ISN → ACK 确认对方。
- ack 是“下一个期待的编号”。SYN 占一个编号,所以确认 SYN 时 ack = 对方 seq + 1。
- 客户端收到第 ② 个包就进入 ESTABLISHED;服务器要等第 ③ 个包。
- 第三个包让服务器确认客户端确实收到了它的 ISN,也让旧的重复 SYN 能被 RST 清理。
- 大量 SYN_RCVD(
ss显示为 SYN-RECV)堆积,可能是 SYN 洪泛。
来源
- RFC 9293, Transmission Control Protocol (TCP), §3.4 序列号、§3.5 建立连接 — https://www.rfc-editor.org/rfc/rfc9293
- RFC 6528, Defending against Sequence Number Attacks(ISN 生成) — https://www.rfc-editor.org/rfc/rfc6528
- tcp(7) Linux 手册页:tcp_syncookies — https://man7.org/linux/man-pages/man7/tcp.7.html
- tcpdump(1) 手册页:-S、-n、-t 选项 — https://www.tcpdump.org/manpages/tcpdump.1.html