QUIC协议详解2
本篇:QUIC 系列 ③/④ · QUIC协议详解2 · 系列总览见 QUIC协议系列导读
Connection ID 把 连接身份 与 IP:Port 解耦,手机换网可不断连;Stream 在连接内提供 多路独立字节流 ;TLS 1.3 经 CRYPTO 帧 嵌入 QUIC,无传统 TLS Record。本篇覆盖:连接迁移 、Stream 、TLS over QUIC 、关闭与版本协商。
速览
- CID 查表定位连接;迁移后更新对端地址,密钥与 PN 空间保留。
- Stream ID 低 2 bit:发起方 + 单向/双向;HTTP/3 每请求一条双向流。
- 无 TLS Record;握手走 CRYPTO + Initial/Handshake 包。
- CONNECTION_CLOSE 分传输/应用;Idle Timeout 常静默丢弃。
目录
一、连接与迁移
- [1. 为何 TCP 不能迁移](#1. 为何 TCP 不能迁移)
- [2. Connection ID 与迁移流程](#2. Connection ID 与迁移流程)
- [3. 迁移期的丢包与乱序](#3. 迁移期的丢包与乱序)
二、Stream 模型
- [4. Stream ID 与方向](#4. Stream ID 与方向)
- [5. 生命周期与 HTTP/3 映射](#5. 生命周期与 HTTP/3 映射)
三、TLS 与连接生命周期
- [6. TLS over QUIC 与 CRYPTO](#6. TLS over QUIC 与 CRYPTO)
- [7. 0-RTT 重放防护](#7. 0-RTT 重放防护)
- [8. 关闭、空闲与版本协商](#8. 关闭、空闲与版本协商)
1. 为何 TCP 不能迁移
TCP 用 四元组 标识连接:
text
(源 IP, 源 Port, 目的 IP, 目的 Port)
Wi-Fi → 蜂窝时 源 IP 变 ,内核视为新连接,必须 TCP + TLS 重握手。
QUIC 用 Connection ID(CID) :与 IP/Port 无关的 opaque 标识(RFC 9000 至少 64 bit ,常 8~20 字节)。收包 查 CID → 连接对象,IP/Port 只是投递地址。
2. Connection ID 与迁移流程
2.1 双端 CID
| 方向 | 含义 |
|---|---|
| 客户端 → 服务端 | Destination CID = 服务端分配的 Client-facing CID |
| 服务端 → 客户端 | Destination CID = 客户端分配的 Server-facing CID |
2.2 客户端主动迁移(典型:换网)
RFC 9000:仅客户端可主动迁移 (例外:Server Preferred Address------服务端在握手参数里通告优选地址,客户端可迁往该地址)。
text
① 初始:Client(IP1:Port1) ←QUIC→ Server;Destination CID = 已协商 CID
② 换网:出口变为 (IP2, Port2)
③ 路径验证(推荐):新路径发 PATH_CHALLENGE → 对端 PATH_RESPONSE
④ 正式迁移:新四元组发普通 QUIC 包,CID 不变(或换预分配新 CID)
服务端按 CID 匹配 → 更新对端地址 → 密钥/Stream/未 ACK 数据保留
⑤ 拥塞:RFC 9000 建议重置 cwnd;具体策略由实现决定(有的保守继承)
Server Client Server Client Wi-Fi IP1 → 4G IP2 按 CID 匹配,更新对端地址 PATH_CHALLENGE(新路径,可选) PATH_RESPONSE 普通包,CID 不变,源地址 IP2
| 步骤 | 行为 |
|---|---|
| 检测换网 | 本地出口变为新四元组 |
| 路径验证 | PATH_CHALLENGE / PATH_RESPONSE(防伪造迁移) |
| 正式迁移 | 新地址发 QUIC 包,CID 不变(或换预分配新 CID,见下) |
| 服务端 | 更新连接记录的 对端地址 |
| 拥塞控制 | RFC 建议 重置 cwnd;实现可不同;Stream/TLS/未 ACK 数据保留 |
NAT Rebinding:同 CID、新源端口到达 → 服务端视为 NAT 重绑定 → 验证 → 更新地址。
Server Preferred Address :握手 preferred_address 参数携带服务端另一 IP:Port;客户端验证后可迁往,仍靠 CID 标识同一连接。
2.3 换 CID 与 NEW_CONNECTION_ID
长期固定 CID 可被跨网络关联。握手时双方通过 NEW_CONNECTION_ID 帧 预分配 CID 列表 ;主动迁移时可 换新 CID (仍映射同一连接),外部观察者难关联。对端不再使用的 CID 用 RETIRE_CONNECTION_ID 退役。
| 帧 | 作用 |
|---|---|
| NEW_CONNECTION_ID | 下发 Sequence + CID + 可选 Stateless Reset Token |
| RETIRE_CONNECTION_ID | 通知对端某 Sequence 的 CID 已停用 |
负载均衡常从 SCID 嵌入 server_id,对 Destination CID 做 consistent hash(见 QUIC应用实践)。
| 项目 | TCP | QUIC |
|---|---|---|
| 连接标识 | 四元组 | CID |
| IP 变化 | 断连重连 | 保持(+ 路径验证) |
| 密钥 | 新连接新密钥 | 沿用 |
3. 迁移期的丢包与乱序
迁移 ≠ 新连接 :PN 空间、密钥、Stream、发送缓冲 不变 ;变的是 对端地址 与常见 拥塞窗口。
3.1 丢包
未 ACK 的包:与普通丢包相同 ------PTO 或判 lost 后,帧内容封装进新 PN ,从 当前生效路径 发出。QUIC 不区分「迁移导致」与「普通」丢包。
3.2 新旧路径交叉
| 情况 | 处理 |
|---|---|
| 旧路径包晚到(正确 CID) | 可接收、回 ACK;不再从旧地址发数据 |
| 新先到、旧后到 | PN 单调 + 已 ACK 的 PN 不重复执行;Stream Offset 保证应用有序 |
| 未验证新路径 | 可收包;不更新对端地址;防 off-path 伪造 |
PATH_CHALLENGE 丢失 → PTO 重发,与数据重传机制一致。
3.3 迁移期丢包 + 乱序时序(示例)
text
旧路径(IP1) 新路径(IP2)
| |
| PKN=100 (STREAM) ----->| (未 ACK,切网)
| |-- PKN=101 PATH_CHALLENGE -->
| |<-- PATH_RESPONSE
|<-- ACK PKN=100 --------| (旧路径迟到 ACK,可接受)
| |-- PKN=102 (重封装 PKN=100 数据 + 新数据)
- PKN=100 疑似丢失 → 时间阈值或 PTO → 新 PN 重封装,从 当前生效路径 发出。
- 旧路径迟到 ACK 仍记录,不再从旧地址发数据 ;Stream 靠 Offset 保证应用有序。
4. Stream ID 与方向
Stream = 连接内 单向或双向逻辑字节流 ;CID + Stream ID 唯一标识。
Stream ID 编码(低 2 bit):
| Bit | 含义 |
|---|---|
| Bit 0 | 发起者:0=Server,1=Client |
| Bit 1 | 方向:0=双向 ,1=单向 |
| Stream ID | 含义 |
|---|---|
| 0 | Client 发起 · 双向 |
| 1 | Server 发起 · 双向 |
| 2 | Client 发起 · 单向 |
| 3 | Server 发起 · 单向 |
| 4,8,12... | Client 双向递增(步长 4) |
双向流 :双方可读写;单向流:仅发起方可写(Control、QPACK 等)。
类比:一条 QUIC 连接 ≈ 多路 mini-TCP ,且 无连接级队头阻塞(丢包只挡含该数据的 Stream)。
5. 生命周期与 HTTP/3 映射
HTTP/3 不再依赖 TCP 上的多路复用 ,而是天然基于 QUIC 独立 Stream(与入门篇「无队头阻塞」呼应)。
5.1 正常关闭
text
发送方:STREAM(FIN=1, Offset=end) → 对端 ACK → 流结束
接收方:读 EOF → 消费完毕
5.2 异常
| 帧 | 作用 |
|---|---|
| RESET_STREAM | 发送方放弃该 Stream |
| STOP_SENDING | 接收方要求对端停止发送 |
5.2.1 Stream 状态机(发送侧,简化)
text
Open → Data Sent → Data Recvd → Closed
↓
Reset Sent → Reset Recvd → Closed
(接收侧镜像对称;RFC 9000 §3.4 有完整状态图。)
5.3 HTTP/3 如何用 Stream(预览)
| 用途 | Stream 类型 |
|---|---|
| 每个 HTTP 请求/响应 | Client 双向(0,4,8...) |
| Control(SETTINGS/GOAWAY) | 单向 type=0x00 |
| QPACK Encoder/Decoder | 单向 type=0x02 / 0x03 |
详细帧格式见 QUIC应用实践。
text
QUIC Connection
├── 双向 Stream 0 → GET /
├── 双向 Stream 4 → POST /api
├── 单向 Stream 2 → Client Control
└── 单向 Stream 3 → Server Control
6. TLS over QUIC 与 CRYPTO
QUIC 用 AEAD 加密包;TLS 只负责 密钥协商、证书、导出密钥 ,不用 TLS Record Layer。
| 项目 | HTTPS/TCP | HTTP/3/QUIC |
|---|---|---|
| 握手载体 | TCP 字节流 | CRYPTO 帧 in Initial/Handshake |
| 记录加密 | TLS Record | QUIC AEAD |
| 顺序 | TCP 有序 | CRYPTO Offset 连续;应用数据走 STREAM |
CRYPTO 帧 :Offset + Length + Crypto Data(TLS Handshake 消息片段),按 Offset 重组后送 TLS 状态机。
握手阶段映射:
| 阶段 | QUIC 包 | 内容 |
|---|---|---|
| Initial | Client→Server | ClientHello |
| Initial/Handshake | Server→Client | ServerHello、Cert、Finished... |
| Handshake | Client→Server | Client Finished |
| --- | Server→Client | HANDSHAKE_DONE |
HANDSHAKE_DONE :服务端通知 握手已确认 ;Client 收到后才认为 handshake confirmed,再全面使用 1-RTT 密钥。TLS Finished 发出 ≠ QUIC 层握手结束。
6.1 密钥更新(Key Update)
握手完成后,任一方可在 1-RTT 阶段发起 Key Update :Short Header 的 Key Phase(C bit) 在「当前密钥 / 下一密钥」间切换,双方确认后完成轮换。CRYPTO 帧仅出现在握手阶段,不随 Key Update 再出现。
7. 0-RTT 重放防护
0-RTT 数据用 early key 加密,可被截获重放。
| 缓解 | 说明 |
|---|---|
| 仅幂等请求 | GET/HEAD;禁止盲目重放 POST |
| NEW_TOKEN | 服务端可在 Initial 响应中发 NEW_TOKEN;Client 下次 Initial 携带,服务端校验区域/IP,减轻重放 |
| Anti-replay | Retry Token、限流、直接 拒绝 0-RTT |
| 应用语义 | 写操作等 handshake confirmed 后再发 |
与 QUIC协议入门 中 0-RTT 预告一致;工程上默认 谨慎启用 0-RTT 写。
8. 关闭、空闲与版本协商
8.1 CONNECTION_CLOSE
| Type | 含义 |
|---|---|
| 0x1c 传输层 | 协议错误,立即不可用 |
| 0x1d 应用层 | 业务主动关闭,可排空 |
流程:发 CLOSE → Draining (可能 重发 CLOSE 防 UDP 丢)→ 超时销毁。
HTTP/3 推荐:GOAWAY → 等 Stream 结束 → CONNECTION_CLOSE(app)。
8.2 Idle Timeout
协商 max_idle_timeout (取双方较小值)。超时 静默丢弃 ,常不发 CLOSE。保活可用 PING。
8.3 Version Negotiation
服务端不支持 Client 版本 → 回 VN 包 (版本列表、无 Long/Short Header、无加密、无 PN )。Client 换版本主动重试 ;VN 包 不能被 ACK。
QUIC 不会自动降级到 TCP ;TCP fallback 由 Happy Eyeballs 等应用层策略处理(见 QUIC应用实践)。
8.4 连接关闭状态机(简化)
text
Established
├─ 发出/收到 CONNECTION_CLOSE → Draining(可重发 CLOSE 防 UDP 丢)
└─ drain 超时 → Destroyed
(Closing:已发 CLOSE 等 drain;Draining:主要收包触发 CLOSE 重发,不处理新数据。)
收到/发出 CONNECTION_CLOSE
drain 超时
Established
Draining
Destroyed
一句话 :QUIC协议详解2 讲 连接是谁(CID) 、数据分哪条路(Stream) 、密钥怎么来(CRYPTO/TLS) 、怎么收尾(CLOSE/Idle/VN)------迁移与 Stream 是 HTTP/3 移动端体验的核心。