QUIC 报文/帧

181 阅读12分钟

QUIC 报文

长头是 初期报文
端头是 长连接报文

1. 长头部包通用结构(Initial/0-RTT/Handshake)

长头部包共有的基础结构如下(比特级):

字段长度(比特)说明
Header Form11(表示长头部)
Fixed Bit11(固定为 1,用于区分 QUIC 与其他协议)
Long Packet Type20=Initial,1=0-RTT,2=Handshake,3=Retry
Type-Specific Bits4不同类型包的特有标志(如 Initial 含 Reserved Bits 和 Packet Number Length)
Version32QUIC 版本号(32 位无符号整数)
DST CID Length8目标连接 ID 长度(0-20 字节,即 0-160 比特)
DST CID可变(8*N)目标连接 ID(长度由上一字段指定)
SRC CID Length8源连接 ID 长度(0-20 字节)
SRC CID可变(8*N)源连接 ID(长度由上一字段指定)
(类型特有字段)可变如 Initial 含 Token Length+Token;其他长头部包含 Length
Length可变(i)载荷长度(i 表示变长整数,用于标识后续 Packet Number+Payload 的总长度)
Packet Number8/16/32/64包序号(长度由 Type-Specific Bits 中的 Packet Number Length 指定)
Packet Payload可变载荷数据(加密的帧序列)
2. Initial Packet 结构(长头部)

在长头部基础上增加 Token 相关字段:

字段长度(比特)说明
Header Form11
Fixed Bit11
Long Packet Type20(Initial)
Reserved Bits2保留位(必须为 0)
Packet Number Length2包序号长度(0=1 字节,1=2 字节,2=4 字节,3=8 字节)
Version32QUIC 版本号
DST CID Length8目标连接 ID 长度
DST CID8*N目标连接 ID
SRC CID Length8源连接 ID 长度
SRC CID8*N源连接 ID
Token Length可变(i)Token 长度(i 为变长整数)
Token8*MToken 数据(服务器返回的验证令牌,长度由上一字段指定)
Length可变(i)后续 Packet Number+Payload 的总长度
Packet Number8/16/32/64包序号(长度由 Packet Number Length 指定)
Packet Payload可变加密的 Initial 帧(如 crypto 帧)
3. 0-RTT/Handshake Packet 结构(长头部)

与 Initial 类似,但无 Token 字段:

字段长度(比特)说明
Header Form11
Fixed Bit11
Long Packet Type21=0-RTT,2=Handshake
Reserved Bits2保留位(0-RTT/Handshake 特有)
Packet Number Length2包序号长度
Version32QUIC 版本号
DST CID Length8目标连接 ID 长度
DST CID8*N目标连接 ID
SRC CID Length8源连接 ID 长度
SRC CID8*N源连接 ID
Length可变(i)后续 Packet Number+Payload 的总长度
Packet Number8/16/32/64包序号
Packet Payload可变加密的载荷(0-RTT 为应用数据,Handshake 为握手数据)
4. Retry Packet 结构(长头部)

服务器用于重定向连接的包,无包序号和 Length 字段:

字段长度(比特)说明
Header Form11
Fixed Bit11
Long Packet Type23(Retry)
Unused4未使用(必须为 0)
Version32必须与 Initial 包版本一致
DST CID Length8目标连接 ID 长度(通常与 Initial 包的源 CID 长度一致)
DST CID8*N目标连接 ID(通常为 Initial 包的源 CID)
SRC CID Length8源连接 ID 长度(服务器生成的新 CID)
SRC CID8*N源连接 ID(服务器生成的新 CID)
Retry Token可变重传令牌(包含原始连接信息,用于客户端验证)
Retry Integrity Tag128完整性标签(16 字节,用于验证 Retry 包合法性)
5. 1-RTT Packet 结构(短头部)

连接建立后的应用数据传输包,头部更紧凑:

字段长度(比特)说明
Header Form10(表示短头部)
Fixed Bit11
Spin Bit1用于检测往返时间(RTT),客户端 / 服务器交替翻转
Reserved Bits2保留位(必须为 0)
Key Phase1密钥阶段(0 或 1,标识当前使用的密钥集)
Packet Number Length2包序号长度(0=1 字节,1=2 字节,2=4 字节,3=8 字节)
DST CID可变(8*N)目标连接 ID(长度固定,由连接上下文协商)
Packet Number8/16/32/64包序号(长度由 Packet Number Length 指定)
Packet Payload可变加密的应用数据帧(如流帧、确认帧等)

QUIC 帧

  1. 所有帧均以 “1 字节类型字节” 开头:通过类型字节直接区分帧类型,部分帧(如 Stream、Ack)的类型字节包含额外控制位(低 N 位)。
  2. 变长字段统一用 varint 编码:varint 编码按 “字节最高位标识是否续接”,可表示 1~ 10 字节的整数,减少小数值的字节消耗(如 1 字节可表示 0~ 127,2 字节可表示 128~16383)。
  3. 可选字段由类型字节控制:如 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. 类型字节(1 字节):含控制位,如 0x08(无 fin、无长度、无偏移)、0x0F(fin + 有长度 + 有偏移)
    2. stream_id(varint):流唯一标识(如客户端发起的双向流为奇数,服务器发起为偶数)
    3. offset(varint,可选):仅当bit2=1时存在,标识当前数据在流中的偏移量
    4. data_len(varint,可选):仅当bit1=1时存在,标识后续 data 字段的长度
    5. data(固定长度):仅当 data_len>0 时存在,长度 = data_len,为实际流数据

2. Crypto Frame(加密握手帧)⭐

  • 类型字节:0x06(固定)
  • 帧格式
    1. 类型字节(1 字节):0x06
    2. offset(varint):当前加密数据在握手数据流中的偏移量
    3. data_len(varint):后续加密数据的长度
    4. data(固定长度):长度 = data_len,为 TLS 握手数据(如 ClientHello、ServerHello)

3. Datagram Frame(数据报帧,代码未实现)

  • 类型字节:0x30(无 fin)、0x31(有 fin)
  • 帧格式(协议规范定义):
    1. 类型字节(1 字节):0x30/0x31(bit0=fin,标识数据报是否结束)
    2. data_len(varint):后续数据报的长度
    3. data(固定长度):长度 = data_len,为不可靠传输的应用数据(无需确认)

二、流量控制类帧(控制数据发送限制)

1. MaxData Frame(连接级最大数据帧)⭐

所有流的总数据量

  • 类型字节:0x10(固定)
  • 帧格式
    1. 类型字节(1 字节):0x10
    2. maximum_data(varint):整个连接允许发送的最大数据量(字节数)

2. MaxStreamData Frame(流级最大数据帧)⭐

单个特定流(由 stream_id 指定)的数据量

  • 类型字节:0x11(固定)
  • 帧格式
    1. 类型字节(1 字节):0x11
    2. stream_id(varint):目标流的唯一标识
    3. maximum_stream_data(varint):该流允许发送的最大数据量(字节数)

3. MaxStreams Frame(最大流数量帧)⭐

限制连接上可以创建的流数量

  • 类型字节:0x12(双向流)、0x13(单向流)
  • 帧格式
    1. 类型字节(1 字节):0x12(双向)/0x13(单向)
    2. maximum_streams(varint):允许创建的该类型流的最大数量

4. DataBlocked Frame(连接数据阻塞帧)⭐

  • 类型字节:0x14(固定)
  • 帧格式
    1. 类型字节(1 字节):0x14
    2. offset(varint):当前连接已发送数据的偏移量(标识阻塞时的数据位置)

5. StreamDataBlocked Frame(流数据阻塞帧)⭐

  • 类型字节:0x15(固定)
  • 帧格式
    1. 类型字节(1 字节):0x15
    2. stream_id(varint):目标流的唯一标识
    3. offset(varint):该流已发送数据的偏移量(标识阻塞时的数据位置)

6. StreamsBlocked Frame(流数量阻塞帧)⭐

  • 类型字节:0x16(双向流)、0x17(单向流)
  • 帧格式
    1. 类型字节(1 字节):0x16(双向)/0x17(单向)
    2. stream_limit(varint):该类型流的最大数量限制(即已达到的上限值)

三、连接管理类帧(维护连接生命周期)

1. NewConnectionId Frame(新连接 ID 帧)⭐

  • 类型字节:0x18(固定)
  • 帧格式
    1. 类型字节(1 字节):0x18
    2. sequence(varint):新连接 ID 的序号(用于标识 ID 版本)
    3. retire_prior_to(varint):序号小于该值的连接 ID 均需废弃
    4. cid_len(1 字节):后续连接 ID 的长度(1~20 字节)
    5. cid(固定长度):长度 = cid_len,新的连接 ID(二进制数据)
    6. stateless_reset_token(16 字节):无状态重置令牌(固定长度,用于连接异常重置)

2. RetireConnectionId Frame(废弃连接 ID 帧)⭐

  • 类型字节:0x19(固定)
  • 帧格式
    1. 类型字节(1 字节):0x19
    2. seq_num(varint):需废弃的连接 ID 的序号

3. HandshakeDone Frame(握手完成帧)⭐

  • 类型字节:0x1E(固定)
  • 帧格式:仅 1 字节类型字节(0x1E),无后续字段(标识握手完成,进入应用数据阶段)

4. ConnectionClose Frame(连接关闭帧)⭐

  • 类型字节:0x1C(传输层错误)、0x1D(应用层错误)
  • 帧格式
    1. 类型字节(1 字节):0x1C(传输层)/0x1D(应用层)
    2. error_code(varint):关闭错误码(传输层错误码如 0x01 = 连接超时,应用层错误码自定义)
    3. frame_type(varint,可选):仅当类型字节 = 0x1C 时存在,标识导致关闭的错误帧类型(如 0x08=Stream Frame)
    4. reason_phrase_len(varint):后续错误描述的长度
    5. reason_phrase(固定长度):长度 = reason_phrase_len,UTF-8 编码的错误描述(如 “connection timeout”)

5. RstStream Frame(流重置帧)⭐

终止流并提供错误信息

  • 类型字节:0x04(固定)
  • 帧格式
    1. 类型字节(1 字节):0x04
    2. stream_id(varint):需重置的流的唯一标识
    3. final_offset(varint):流的最终数据偏移量(标识重置前已接收的数据位置,同步双方的流状态,避免数据重复处理转存失败,建议直接上传图片文件
    4. error_code(varint):流重置的错误码(如 0x02 = 流被拒绝)

6. StopSending Frame(停止发送帧)⭐

  • 类型字节:0x05(固定)
  • 帧格式
    1. 类型字节(1 字节):0x05
    2. stream_id(varint):需停止发送的流的唯一标识
    3. error_code(varint):停止发送的错误码(如 0x03 = 接收缓冲区已满)

四、路径验证类帧(确认路径有效性)

1. PathChallenge Frame(路径探测帧)⭐

  • 类型字节:0x1A(固定)
  • 帧格式
    1. 类型字节(1 字节):0x1A
    2. data(8 字节,固定长度):路径探测随机数据(接收方需原封不动通过 PathResponse 返回)

2. PathResponse Frame(路径响应帧)⭐

  • 类型字节:0x1B(固定)
  • 帧格式
    1. 类型字节(1 字节):0x1B
    2. 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. 类型字节(1 字节):0x02(无 ECN)/0x03(带 ECN)
    2. largest_ack(varint):已确认的最大数据包号
    3. ack_delay(varint):从接收最大数据包到发送 Ack 的延迟时间(单位:微秒)
    4. ack_block_count(varint):确认块的数量(= 实际确认块数 - 1,如 1 个确认块则该值为 0)
    5. first_ack_block(varint):第一个确认块的长度(=largest_ack - 最小确认包)。比如:若最大包号largest=10first_ack_block=5,则最小包号smallest=10-5=5,第一个 ACK 范围为[5,10](表示 5-10 号包均已收到)。
    6. gap(varint,可选):仅当 ack_block_count>0 时存在,当前确认块与前一个确认块的间隔(= 前一个确认块最小包号 - 当前确认块最大包号 - 2)
    7. ack_block(varint,可选):仅当 ack_block_count>0 时存在,当前确认块的长度(= 当前确认块最大包号 - 当前确认块最小包号)
    8. 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 标记统计信息,帮助发送方感知网络拥塞,而无需依赖丢包作为唯一拥塞信号。

  1. 发送方标记 ECN 能力:发送方在 QUIC 数据包的首部(长首部或短首部)设置ECT0ECT1,表明该数据包支持 ECN。
  2. 路由器标记拥塞:若中间路由器检测到拥塞,会将数据包的 ECN 标记改为CE(而非丢弃数据包)。
  3. 接收方统计并反馈接收方收到数据包后,统计ECT0/ECT1/CE的数量,并将这些计数器放入 ACK 帧中,回传给发送方
  4. 发送方调整策略:发送方通过 ACK 中的 CE 计数判断拥塞程度(如 CE 数量越多,拥塞越严重),触发拥塞控制逻辑(如减小拥塞窗口、降低发送速率)。