TCP 快速打开允许把数据放进 SYN,并在三次握手完成之前交给应用。饼干由服务器生成,对客户机不透明,用来认证客户机当时的源地址。截至 2026 年 10 月,RFC Editor 仍把 RFC 7413 标为 Experimental,没有后续文件取代它。它要求实现不得默认使用。
标准握手先不交出 SYN 里的数据
一个可独立引用的定义句:标准 TCP 已经允许 SYN 携带数据,但在三次握手完成之前不得把这些数据交给应用,因为握手要抓住旧的或重复的 SYN。TCP 快速打开取消这道限制(RFC 7413 第 2 节,其中对标准 TCP 的引用是 RFC 793)。
本文没有对照 RFC 9293 是否改写了「握手完成前不交付」这一句。下面的必须与应当,都来自 RFC 7413。
因此实现不得默认使用快速打开,只能在应用按服务端口明确请求时使用。监听套接字上的 FastOpenEnabled 默认关闭,必须由应用显式打开。关闭时,服务器不做任何快速打开操作,并且必须忽略全部饼干选项。
选项种类是 34。长度字段的取值范围在第 4.1.1 节写成 6 到 18,且必须为偶数。饼干为 0 字节,或 4 到 16 字节。第 4.2.1 节又把「请求饼干」写成长度字段为 0、饼干字段为空。两处用词并不一致。本文不把其中一处改写成另一处。没有 SYN 标志,或长度无效的选项,必须忽略。
饼干换来的是下一次连接
一个可独立引用的定义句:客户机先在一次普通连接的 SYN 里放上饼干为空的快速打开选项,向服务器要饼干。服务器在 SYN-ACK 的同一选项里送回饼干。之后的连接才在 SYN 里同时放上饼干和数据(RFC 7413 第 3 节)。
客户机必须缓存饼干。多宿主时,饼干依赖于客户机和服务器的 IP 地址,因此客户机对每一对地址至多缓存最近收到的一枚。服务器可以随时让饼干过期,办法包括轮换生成饼干的密钥,或在饼干里放入时间。第 4.1.2 节给了一个用 AES-128 加密地址再截成 64 位的例子,并把它写成一种简单实现,不是必须。饼干应当是消息鉴别码,应当只能由服务器生成。
服务器收到带饼干和数据的 SYN 之后:
- FastOpenEnabled 为假,或未完成的快速打开请求超过系统限额,就只确认 SYN 的序号;
- 饼干无效,同样只确认 SYN 的序号,不把数据交给应用。第 3 节把这一支写成丢掉数据;
- 饼干有效,则确认 SYN 和数据,把数据交给应用,并可以在握手完成前发送响应。响应的数量受拥塞控制约束。
客户机的数据若没有被确认,就在随后的 ACK 里重传。SYN 定时器到期时,客户机应当重传一份不带数据和快速打开选项的 SYN。服务器在 SYN-ACK 定时器到期时,应当重传不带数据的 SYN-ACK。
SYN 里的数据量不超过已经缓存的服务器最大段长度;没有缓存时,IPv4 默认不超过 536 字节,IPv6 默认不超过 1220 字节。这两个默认值是 RFC 7413 的写法,它分别引用 RFC 1122 和 RFC 2460。选项空间不够时,客户机必须放弃快速打开,退回普通三次握手。
最大段长度钳制 改的是通告出去的段长。快速打开在看到对方的 SYN-ACK 之前就要决定 SYN 里能放多少数据,所以它依赖上一次缓存的段长,而不是这一次握手里才出现的通告。
重放的是应用数据
RFC 7413 第 2.1 节写明两处完整性问题。一是接收方在已经忘记原先那份 SYN 之后,又收到带数据的重复 SYN,例如重启之后。二是原先的连接已经关闭,而且关闭是由发送方发起的,接收方因此不在 TIME-WAIT 的两倍最大段生存时间里。这份规范选择把快速打开限制在能够容忍重复 SYN 数据的应用,而不是把所有可疑 SYN 都拦下来。
这和 重放保护 不是同一套窗口。重放保护看隧道报文的编号。这里看的是同一对套接字上、握手完成之前就交给应用的那些字节。通过其中一层,不表示另一层的旧数据也会被拒绝。
伪造源地址的 SYN 本来就能填满监听队列。快速打开再进一步,允许服务器在握手完成前把数据交给应用,于是启用它的端口可能在处理请求和生成响应上消耗处理器和内存。响应若比请求大,还可能被用来对第三方做放大。RFC 7413 写明,针对普通 SYN 洪泛的那些缓解办法不适用于这里,所以才用服务器发放的饼干。本文不提供调整队列或限额的步骤。
TLS 会话票据与 0-RTT 少掉的是记录层上的一轮。快速打开少掉的是「握手完成才交付 TCP 数据」这一约束。两层可以先后发生在同一次连接上。某一浏览器在 2026 年 10 月是否同时使用两者,本文没有测量。
源地址一变,旧饼干就对不上
一个可独立引用的结论句:饼干应当认证 SYN 上客户机的源地址。这个地址可以是 IPv4,也可以是 IPv6。出口把源地址改写成另一个之后,按旧地址生成的饼干不再通过校验。服务器于是丢掉 SYN 里的数据,连接退回只确认 SYN 的那一支。
出口 NAPT 改的是拆包之后的内层源地址。网站看到的源,是转换之后的地址。快速打开的饼干认的是这一份源地址,不是隧道接口上封装之前的那个地址。源地址选择 决定的是本机写出包头时用哪一个地址。两步都变了,客户机缓存里的那一对地址也会对不上。
外层是 UDP 时,这一层没有 SYN,也就没有快速打开。外层是 TCP 时,隧道自己的那一条连接可以另有自己的选项。封装不把内层 SYN 上的选项抄到外层,也不把外层的选项抄进内层。某一客户端的外层 TCP 有没有请求快速打开,本文没有测量。外层用哪一种传输,见 VPN 用 TCP 还是 UDP。
带数据的 SYN 比只有标志的 SYN 更长。路径若装不下,丢的是这一份外层包,还是内层段,要分开看。本文不把「SYN 里有数据」写成路径 MTU 的测量结果。
本文不提供打开快速打开、清空饼干缓存或修改套接字选项的步骤。
现象对照
| 现象 | 更可能先看什么 |
|---|---|
| 第一次连接的 SYN 带有种类 34、饼干为空 | 正在请求饼干。数据还不应在握手完成前交给应用 |
| 下一次 SYN 同时有饼干和数据,SYN-ACK 确认到数据末尾 | 饼干被接受。确认号要加上数据长度 |
| SYN-ACK 只确认 SYN 的序号 | 饼干无效、功能关闭,或未完成请求超过限额。数据应在随后的 ACK 里重传 |
| 换了出口之后,第一份请求比上次慢一轮 | 旧饼干认证的是上一个源地址。这和 TLS 早期数据不是同一枚票据 |
| 外层是 UDP,内层网站仍会提前收到请求字节 | 快速打开在内层那条 TCP 上。外层没有 SYN |
核对清单
- 默认是关闭的:实现不得默认使用。监听端必须由应用显式打开;
- 先要饼干,再带数据:空饼干的那一次不把 SYN 数据提前交出;
- 无效则丢掉数据,不是拆掉连接:SYN-ACK 只确认 SYN 序号,数据在 ACK 里重传;
- 饼干绑定源地址:出口地址或本机选用的源地址变了,旧饼干不再匹配;
- 它和 0-RTT 分属两层:一个在 TCP,一个在 TLS。UDP 外层自己没有快速打开。