QUIC 报文
长头是 初期报文
端头是 长连接报文
1. 长头部包通用结构(Initial/0-RTT/Handshake)
长头部包共有的基础结构如下(比特级):
| 字段 | 长度(比特) | 说明 |
|---|---|---|
| Header Form | 1 | 1(表示长头部) |
| Fixed Bit | 1 | 1(固定为 1,用于区分 QUIC 与其他协议) |
| Long Packet Type | 2 | 0=Initial,1=0-RTT,2=Handshake,3=Retry |
| Type-Specific Bits | 4 | 不同类型包的特有标志(如 Initial 含 Reserved Bits 和 Packet Number Length) |
| Version | 32 | QUIC 版本号(32 位无符号整数) |
| DST CID Length | 8 | 目标连接 ID 长度(0-20 字节,即 0-160 比特) |
| DST CID | 可变(8*N) | 目标连接 ID(长度由上一字段指定) |
| SRC CID Length | 8 | 源连接 ID 长度(0-20 字节) |
| SRC CID | 可变(8*N) | 源连接 ID(长度由上一字段指定) |
| (类型特有字段) | 可变 | 如 Initial 含 Token Length+Token;其他长头部包含 Length |
| Length | 可变(i) | 载荷长度(i 表示变长整数,用于标识后续 Packet Number+Payload 的总长度) |
| Packet Number | 8/16/32/64 | 包序号(长度由 Type-Specific Bits 中的 Packet Number Length 指定) |
| Packet Payload | 可变 | 载荷数据(加密的帧序列) |
2. Initial Packet 结构(长头部)
在长头部基础上增加 Token 相关字段:
| 字段 | 长度(比特) | 说明 |
|---|---|---|
| Header Form | 1 | 1 |
| Fixed Bit | 1 | 1 |
| Long Packet Type | 2 | 0(Initial) |
| Reserved Bits | 2 | 保留位(必须为 0) |
| Packet Number Length | 2 | 包序号长度(0=1 字节,1=2 字节,2=4 字节,3=8 字节) |
| Version | 32 | QUIC 版本号 |
| DST CID Length | 8 | 目标连接 ID 长度 |
| DST CID | 8*N | 目标连接 ID |
| SRC CID Length | 8 | 源连接 ID 长度 |
| SRC CID | 8*N | 源连接 ID |
| Token Length | 可变(i) | Token 长度(i 为变长整数) |
| Token | 8*M | Token 数据(服务器返回的验证令牌,长度由上一字段指定) |
| Length | 可变(i) | 后续 Packet Number+Payload 的总长度 |
| Packet Number | 8/16/32/64 | 包序号(长度由 Packet Number Length 指定) |
| Packet Payload | 可变 | 加密的 Initial 帧(如 crypto 帧) |
3. 0-RTT/Handshake Packet 结构(长头部)
与 Initial 类似,但无 Token 字段:
| 字段 | 长度(比特) | 说明 |
|---|---|---|
| Header Form | 1 | 1 |
| Fixed Bit | 1 | 1 |
| Long Packet Type | 2 | 1=0-RTT,2=Handshake |
| Reserved Bits | 2 | 保留位(0-RTT/Handshake 特有) |
| Packet Number Length | 2 | 包序号长度 |
| Version | 32 | QUIC 版本号 |
| DST CID Length | 8 | 目标连接 ID 长度 |
| DST CID | 8*N | 目标连接 ID |
| SRC CID Length | 8 | 源连接 ID 长度 |
| SRC CID | 8*N | 源连接 ID |
| Length | 可变(i) | 后续 Packet Number+Payload 的总长度 |
| Packet Number | 8/16/32/64 | 包序号 |
| Packet Payload | 可变 | 加密的载荷(0-RTT 为应用数据,Handshake 为握手数据) |
4. Retry Packet 结构(长头部)
服务器用于重定向连接的包,无包序号和 Length 字段:
| 字段 | 长度(比特) | 说明 |
|---|---|---|
| Header Form | 1 | 1 |
| Fixed Bit | 1 | 1 |
| Long Packet Type | 2 | 3(Retry) |
| Unused | 4 | 未使用(必须为 0) |
| Version | 32 | 必须与 Initial 包版本一致 |
| DST CID Length | 8 | 目标连接 ID 长度(通常与 Initial 包的源 CID 长度一致) |
| DST CID | 8*N | 目标连接 ID(通常为 Initial 包的源 CID) |
| SRC CID Length | 8 | 源连接 ID 长度(服务器生成的新 CID) |
| SRC CID | 8*N | 源连接 ID(服务器生成的新 CID) |
| Retry Token | 可变 | 重传令牌(包含原始连接信息,用于客户端验证) |
| Retry Integrity Tag | 128 | 完整性标签(16 字节,用于验证 Retry 包合法性) |
5. 1-RTT Packet 结构(短头部)
连接建立后的应用数据传输包,头部更紧凑:
| 字段 | 长度(比特) | 说明 |
|---|---|---|
| Header Form | 1 | 0(表示短头部) |
| Fixed Bit | 1 | 1 |
| Spin Bit | 1 | 用于检测往返时间(RTT),客户端 / 服务器交替翻转 |
| Reserved Bits | 2 | 保留位(必须为 0) |
| Key Phase | 1 | 密钥阶段(0 或 1,标识当前使用的密钥集) |
| Packet Number Length | 2 | 包序号长度(0=1 字节,1=2 字节,2=4 字节,3=8 字节) |
| DST CID | 可变(8*N) | 目标连接 ID(长度固定,由连接上下文协商) |
| Packet Number | 8/16/32/64 | 包序号(长度由 Packet Number Length 指定) |
| Packet Payload | 可变 | 加密的应用数据帧(如流帧、确认帧等) |
QUIC 帧
- 所有帧均以 “1 字节类型字节” 开头:通过类型字节直接区分帧类型,部分帧(如 Stream、Ack)的类型字节包含额外控制位(低 N 位)。
- 变长字段统一用 varint 编码:varint 编码按 “字节最高位标识是否续接”,可表示 1~ 10 字节的整数,减少小数值的字节消耗(如 1 字节可表示 0~ 127,2 字节可表示 128~16383)。
- 可选字段由类型字节控制:如 Stream Frame 的 offset/data_len 是否存在,由类型字节的 bit2/bit1 决定;Ack Frame 的 ECN 扩展是否存在,由类型字节是否为 0x03 决定。
一、数据传输类帧(承载核心数据)
1. Stream Frame(流数据帧)⭐
- 类型字节:0x08~0x0F(低 3 位为控制位,含义:
bit0=fin(流结束)、bit1=has_length(是否带数据长度)、bit2=has_offset(是否带数据偏移)) - 帧格式(字段按顺序排列):
- 类型字节(1 字节):含控制位,如 0x08(无 fin、无长度、无偏移)、0x0F(fin + 有长度 + 有偏移)
- stream_id(varint):流唯一标识(如客户端发起的双向流为奇数,服务器发起为偶数)
- offset(varint,可选):仅当
bit2=1时存在,标识当前数据在流中的偏移量 - data_len(varint,可选):仅当
bit1=1时存在,标识后续 data 字段的长度 - data(固定长度):仅当 data_len>0 时存在,长度 = data_len,为实际流数据
2. Crypto Frame(加密握手帧)⭐
- 类型字节:0x06(固定)
- 帧格式:
- 类型字节(1 字节):0x06
- offset(varint):当前加密数据在握手数据流中的偏移量
- data_len(varint):后续加密数据的长度
- data(固定长度):长度 = data_len,为 TLS 握手数据(如 ClientHello、ServerHello)
3. Datagram Frame(数据报帧,代码未实现)
- 类型字节:0x30(无 fin)、0x31(有 fin)
- 帧格式(协议规范定义):
- 类型字节(1 字节):0x30/0x31(bit0=fin,标识数据报是否结束)
- data_len(varint):后续数据报的长度
- data(固定长度):长度 = data_len,为不可靠传输的应用数据(无需确认)
二、流量控制类帧(控制数据发送限制)
1. MaxData Frame(连接级最大数据帧)⭐
所有流的总数据量
- 类型字节:0x10(固定)
- 帧格式:
- 类型字节(1 字节):0x10
- maximum_data(varint):整个连接允许发送的最大数据量(字节数)
2. MaxStreamData Frame(流级最大数据帧)⭐
单个特定流(由 stream_id 指定)的数据量
- 类型字节:0x11(固定)
- 帧格式:
- 类型字节(1 字节):0x11
- stream_id(varint):目标流的唯一标识
- maximum_stream_data(varint):该流允许发送的最大数据量(字节数)
3. MaxStreams Frame(最大流数量帧)⭐
限制连接上可以创建的流数量
- 类型字节:0x12(双向流)、0x13(单向流)
- 帧格式:
- 类型字节(1 字节):0x12(双向)/0x13(单向)
- maximum_streams(varint):允许创建的该类型流的最大数量
4. DataBlocked Frame(连接数据阻塞帧)⭐
- 类型字节:0x14(固定)
- 帧格式:
- 类型字节(1 字节):0x14
- offset(varint):当前连接已发送数据的偏移量(标识阻塞时的数据位置)
5. StreamDataBlocked Frame(流数据阻塞帧)⭐
- 类型字节:0x15(固定)
- 帧格式:
- 类型字节(1 字节):0x15
- stream_id(varint):目标流的唯一标识
- offset(varint):该流已发送数据的偏移量(标识阻塞时的数据位置)
6. StreamsBlocked Frame(流数量阻塞帧)⭐
- 类型字节:0x16(双向流)、0x17(单向流)
- 帧格式:
- 类型字节(1 字节):0x16(双向)/0x17(单向)
- stream_limit(varint):该类型流的最大数量限制(即已达到的上限值)
三、连接管理类帧(维护连接生命周期)
1. NewConnectionId Frame(新连接 ID 帧)⭐
- 类型字节:0x18(固定)
- 帧格式:
- 类型字节(1 字节):0x18
- sequence(varint):新连接 ID 的序号(用于标识 ID 版本)
- retire_prior_to(varint):序号小于该值的连接 ID 均需废弃
- cid_len(1 字节):后续连接 ID 的长度(1~20 字节)
- cid(固定长度):长度 = cid_len,新的连接 ID(二进制数据)
- stateless_reset_token(16 字节):无状态重置令牌(固定长度,用于连接异常重置)
2. RetireConnectionId Frame(废弃连接 ID 帧)⭐
- 类型字节:0x19(固定)
- 帧格式:
- 类型字节(1 字节):0x19
- seq_num(varint):需废弃的连接 ID 的序号
3. HandshakeDone Frame(握手完成帧)⭐
- 类型字节:0x1E(固定)
- 帧格式:仅 1 字节类型字节(0x1E),无后续字段(标识握手完成,进入应用数据阶段)
4. ConnectionClose Frame(连接关闭帧)⭐
- 类型字节:0x1C(传输层错误)、0x1D(应用层错误)
- 帧格式:
- 类型字节(1 字节):0x1C(传输层)/0x1D(应用层)
- error_code(varint):关闭错误码(传输层错误码如 0x01 = 连接超时,应用层错误码自定义)
- frame_type(varint,可选):仅当类型字节 = 0x1C 时存在,标识导致关闭的错误帧类型(如 0x08=Stream Frame)
- reason_phrase_len(varint):后续错误描述的长度
- reason_phrase(固定长度):长度 = reason_phrase_len,UTF-8 编码的错误描述(如 “connection timeout”)
5. RstStream Frame(流重置帧)⭐
终止流并提供错误信息
- 类型字节:0x04(固定)
- 帧格式:
- 类型字节(1 字节):0x04
- stream_id(varint):需重置的流的唯一标识
- final_offset(varint):流的最终数据偏移量(标识重置前已接收的数据位置,同步双方的流状态,避免数据重复处理
- error_code(varint):流重置的错误码(如 0x02 = 流被拒绝)
6. StopSending Frame(停止发送帧)⭐
- 类型字节:0x05(固定)
- 帧格式:
- 类型字节(1 字节):0x05
- stream_id(varint):需停止发送的流的唯一标识
- error_code(varint):停止发送的错误码(如 0x03 = 接收缓冲区已满)
四、路径验证类帧(确认路径有效性)
1. PathChallenge Frame(路径探测帧)⭐
- 类型字节:0x1A(固定)
- 帧格式:
- 类型字节(1 字节):0x1A
- data(8 字节,固定长度):路径探测随机数据(接收方需原封不动通过 PathResponse 返回)
2. PathResponse Frame(路径响应帧)⭐
- 类型字节:0x1B(固定)
- 帧格式:
- 类型字节(1 字节):0x1B
- data(8 字节,固定长度):原封不动返回的 PathChallenge 探测数据(用于确认路径可达)
3. NEW_TOKEN Frame(令牌帧)⭐
- 类型字节:0x07(固定)
- 帧格式:Token (variable): 服务器生成的令牌数据(不透明二进制)
五、特殊功能类帧(辅助性作用)
1. Padding Frame(填充帧)
- 类型字节:0x00(固定)
- 帧格式:仅 1 字节类型字节(0x00),无后续字段(可连续多个 Padding Frame 叠加,用于填充数据包到指定长度)
2. Ping Frame(保活帧)⭐
- 类型字节:0x01(固定)
- 帧格式:仅 1 字节类型字节(0x01),无后续字段(用于测量 RTT 和维持连接活性)
3. Ack Frame(确认帧)⭐
- 类型字节:0x02(无 ECN)、0x03(带 ECN)
- 帧格式(最复杂,分基础部分和 ECN 扩展部分):
- 类型字节(1 字节):0x02(无 ECN)/0x03(带 ECN)
- largest_ack(varint):已确认的最大数据包号
- ack_delay(varint):从接收最大数据包到发送 Ack 的延迟时间(单位:微秒)
- ack_block_count(varint):确认块的数量(= 实际确认块数 - 1,如 1 个确认块则该值为 0)
- first_ack_block(varint):第一个确认块的长度(=largest_ack - 最小确认包)。比如:若最大包号
largest=10,first_ack_block=5,则最小包号smallest=10-5=5,第一个 ACK 范围为[5,10](表示 5-10 号包均已收到)。 - gap(varint,可选):仅当 ack_block_count>0 时存在,当前确认块与前一个确认块的间隔(= 前一个确认块最小包号 - 当前确认块最大包号 - 2)
- ack_block(varint,可选):仅当 ack_block_count>0 时存在,当前确认块的长度(= 当前确认块最大包号 - 当前确认块最小包号)
- ECN 扩展(可选,仅类型字节 = 0x03 时存在):
- ect0_count(varint):标记为 ECN-ECT0 的数据包数量
- ect1_count(varint):标记为 ECN-ECT1 的数据包数量
- ecn_ce_count(varint):标记为 ECN-CE(拥塞)的数据包数量
带 ECN 的 ACK(ACK with ECN) 是接收方向发送方反馈网络拥塞状态的关键机制,它在常规 ACK 帧(用于确认数据包接收)的基础上,额外携带了接收方观察到的ECN 标记统计信息,帮助发送方感知网络拥塞,而无需依赖丢包作为唯一拥塞信号。
- 发送方标记 ECN 能力:发送方在 QUIC 数据包的首部(长首部或短首部)设置
ECT0或ECT1,表明该数据包支持 ECN。- 路由器标记拥塞:若中间路由器检测到拥塞,会将数据包的 ECN 标记改为
CE(而非丢弃数据包)。- 接收方统计并反馈:接收方收到数据包后,统计
ECT0/ECT1/CE的数量,并将这些计数器放入 ACK 帧中,回传给发送方。- 发送方调整策略:发送方通过 ACK 中的 CE 计数判断拥塞程度(如 CE 数量越多,拥塞越严重),触发拥塞控制逻辑(如减小拥塞窗口、降低发送速率)。