QUIC:基于UDP的多路复用安全传输(部分翻译)

文档信息

Workgroup: QUIC
Internet-Draft: draft-ietf-quic-transport-32
Published: 20 October 2020
Intended Status: Standards Track
Expires: 23 April 2021
Authors: J.Iyengar,Ed. Fastly M.Thomson,Ed. Mozilla

原文:QUIC: A UDP-Based Multiplexed and Secure Transport

简介

本文档定义了QUIC传输协议的核心。随附的文档描述了QUIC的丢失检测和拥塞控制,以及使用TLS进行密钥协商。

3.流状态

本节根据流的发送或接收组件来描述它们。描述了两种状态机:一种用于端点在其上发送数据的流(第3.1节),另一种用于端点在其上接收数据的流(3.2节)。

单向流直接使用适用的状态机。双向流使用两个状态机。在大多数情况下,无论状态是单向还是双向,这些状态机的用法都是相同的。对于双向流,打开流的条件稍微复杂些,因为发送侧或接收侧的打开都会导致流在两个方向上打开。

本节中显示的状态机在很大程度上提供了信息。本文档使用流状态来描述何时以及如何发送不同类型的帧的规则,以及接收到不同类型的帧时预期的反应。尽管这些状态机旨在用于实现QUIC,但这些状态并不旨在限制实现。一个实现可以定义一个不同的状态机,只要它的行为与实现这些状态的实现一致即可。

注意:
在某些情况下,单个事件或动作可能导致通过多个状态的转换。例如,发送带有FIN位置1的STREAM会导致发送流发生两种状态转换:从就绪状态到发送状态,以及从发送状态到数据已发送状态。

3.1. 发送流状态

图2显示了将数据发送到对等方的流部分的状态。

  o
    | Create Stream (Sending)
    | Peer Creates Bidirectional Stream
    v
+-------+
| Ready | Send RESET_STREAM
|       |-----------------------.
+-------+                       |
    |                           |
    | Send STREAM /             |
    |      STREAM_DATA_BLOCKED  |
    |                           |
    | Peer Creates              |
    |      Bidirectional Stream |
    v                           |
+-------+                       |
| Send  | Send RESET_STREAM     |
|       |---------------------->|
+-------+                       |
    |                           |
    | Send STREAM + FIN         |
    v                           v
+-------+                   +-------+
| Data  | Send RESET_STREAM | Reset |
| Sent  |------------------>| Sent  |
+-------+                   +-------+
    |                           |
    | Recv All ACKs             | Recv ACK
    v                           v
+-------+                   +-------+
| Data  |                   | Reset |
| Recvd |                   | Recvd |
+-------+                   +-------+

应用程序打开了端点启动的流的发送部分(对于客户端,类型为0和2,对于服务器,类型为1和3)。“就绪”状态表示一个新创建的流,该流能够接受来自应用程序的数据。流数据可能在此状态下被缓冲以准备发送。

发送第一个STREAM或STREAM_DATA_BLOCKED帧会使流的发送部分进入“发送”状态。一个实现可能选择将流ID分配给流,直到它发送第一个STREAM帧并进入此状态为止,这样可以更好地确定流优先级。

当创建接收方时,由对等方(服务器的类型为0,客户机的类型为1)启动的双向流的发送部分以“就绪”状态开始。

在“发送”状态下,端点在STREAM帧中发送-并根据需要重新发送流数据。端点遵守其对等方设置的流控制限制,并继续接受和处理MAX_STREAM_DATA帧。如果流或连接流控制限制第4.1节阻止了处于“发送”状态的端点生成STREAM_DATA_BLOCKED帧 。

在应用程序指示已发送所有流数据并发送包含FIN位的STREAM帧之后,流的发送部分进入“数据已发送”状态。从此状态开始,端点仅在必要时重新传输流数据。端点无需检查流控制限制或在此状态下为流发送STREAM_DATA_BLOCKED帧。在对等方收到最终流偏移之前,可能会收到MAX_STREAM_DATA帧。对于处于这种状态的流,端点可以安全地忽略从对等方收到的任何MAX_STREAM_DATA帧。

一旦成功确认了所有流数据,流的发送部分就进入“数据接收”状态,这是终端状态。

从“就绪”,“发送”或“已发送数据”状态中的任何状态,应用程序都可以发出信号,表示希望放弃流数据的传输。或者,端点可能从其对等方接收到STOP_SENDING帧。在这两种情况下,端点都会发送RESET_STREAM帧,这会使流进入“重置已发送”状态。

端点可以发送RESET_STREAM作为提到流的第一帧;这将导致该流的发送部分打开,然后立即转换为“重置已发送”状态。

一旦确认了包含RESET_STREAM的数据包,流的发送部分便进入“复位接收”状态,这是终端状态。

3.2. 接收流状态

图3显示了从对等方接收数据的流部分的状态。流的接收部分的状态仅反映对等方流的发送部分的一些状态。流的接收部分不会跟踪发送部分上无法观察到的状态,例如“就绪”状态。取而代之的是,流的接收部分跟踪向应用程序的数据传递,其中某些发送者无法观察到。

o
     | Recv STREAM / STREAM_DATA_BLOCKED / RESET_STREAM
     | Create Bidirectional Stream (Sending)
     | Recv MAX_STREAM_DATA / STOP_SENDING (Bidirectional)
     | Create Higher-Numbered Stream
     v
 +-------+
 | Recv  | Recv RESET_STREAM
 |       |-----------------------.
 +-------+                       |
     |                           |
     | Recv STREAM + FIN         |
     v                           |
 +-------+                       |
 | Size  | Recv RESET_STREAM     |
 | Known |---------------------->|
 +-------+                       |
     |                           |
     | Recv All Data             |
     v                           v
 +-------+ Recv RESET_STREAM +-------+
 | Data  |--- (optional) --->| Reset |
 | Recvd |  Recv All Data    | Recvd |
 +-------+<-- (optional) ----+-------+
     |                           |
     | App Read All Data         | App Read RST
     v                           v
 +-------+                   +-------+
 | Data  |                   | Reset |
 | Read  |                   | Read  |
 +-------+                   +-------+

当为该流接收到第一个STREAM,STREAM_DATA_BLOCKED或RESET_STREAM帧时,将创建由对等方(客户机的类型1和3,服务器的类型0和2)启动的流的接收部分。对于由对等方发起的双向流,对于流的发送部分的MAX_STREAM_DATA或STOP_SENDING帧的接收也会创建接收部分。流的接收部分的初始状态为“ Recv”。

当由端点(客户端为0,服务器为1)发起的双向流的发送部分进入“就绪”状态时,流的接收部分进入“正在接收”状态。

当从对等流的对等方接收到MAX_STREAM_DATA或STOP_SENDING帧时,端点将打开双向流。接收到未打开的流的MAX_STREAM_DATA帧表示远程对等方已打开该流并正在提供流控制信用。接收到未打开流的STOP_SENDING帧表示远程对等方不再希望在该流上接收数据。如果数据包丢失或重新排序,则两个帧都可能在STREAM或STREAM_DATA_BLOCKED帧之前到达。

在创建流之前,必须创建所有具有较低流ID的相同类型的流。这样可以确保流的创建顺序在两个端点上都一致。

在“接收”状态下,端点接收STREAM和STREAM_DATA_BLOCKED帧。传入的数据经过缓冲,可以重新组合成正确的顺序以传递给应用程序。当数据被应用程序占用并且缓冲区空间可用时,端点将发送MAX_STREAM_DATA帧以允许对等方发送更多数据。

当接收到带有FIN位的STREAM帧时,流的最终大小是已知的。参见第4.5节。流的接收部分然后进入“已知大小”状态。在这种状态下,端点不再需要发送MAX_STREAM_DATA帧,它仅接收流数据的任何重传。

一旦接收到流的所有数据,接收部分就进入“数据接收”状态。这可能是由于接收到相同的STREAM帧导致过渡到“已知大小”而发生的。接收完所有数据后,可以丢弃该流的任何STREAM或STREAM_DATA_BLOCKED帧。

“数据接收”状态持续存在,直到流数据已传递到应用程序为止。一旦传递了流数据,流就进入“数据读取”状态,这是终端状态。

在“接收”或“已知大小”状态下接收RESET_STREAM帧会使流进入“重置接收”状态。这可能会导致流数据到应用程序的传送被中断。

接收到RESET_STREAM时(即处于“数据接收”状态),可能已经接收到所有流数据。类似地,剩余的流数据有可能在接收到RESET_STREAM帧(“ Reset Recvd”状态)之后到达。一个实现可以自由选择管理这种情况。

发送RESET_STREAM表示端点无法保证流数据的传递;但是,如果接收到RESET_STREAM,则不要求不传送流数据。一个实现可以中断流数据的传递,丢弃任何未被消耗的数据,并发出接收到RESET_STREAM的信号。如果流数据被完全接收并被缓冲以供应用程序读取,则RESET_STREAM信号可能会被抑制或保留。如果RESET_STREAM被抑制,则流的接收部分保留在“ Data Recvd”中。

一旦应用程序接收到指示流已重置的信号,流的接收部分将转换为“重置读取”状态,这是终端状态。

3.3. 允许的帧类型

流的发送方仅发送三种会影响发送方或接收方流状态的帧类型:STREAM(第19.8节),STREAM_DATA_BLOCKED(第19.13节)和RESET_STREAM(第19.4节)。

发送者不得从终端状态(“数据接收”或“重置接收”)发送任何这些帧。发送方不得在“重置已发送”状态或任何终端状态下(即在发送RESET_STREAM帧之后)为流发送STREAM或STREAM_DATA_BLOCKED帧。由于携带它们的分组的延迟传送的可能性,接收机可以在任何状态下接收这三个帧中的任何一个。

流的接收方发送MAX_STREAM_DATA(第19.10节)和STOP_SENDING帧(第19.5节)。

接收器仅在“ Recv”状态下发送MAX_STREAM_DATA。接收者可以在没有收到RESET_STREAM帧的任何状态下发送STOP_SENDING;除了“重置已接收”或“重置已读”以外的状态。但是,由于已接收到所有流数据,因此在“ Data Recvd”状态下发送STOP_SENDING帧的价值很小。由于数据包的延迟传送,发送方可以在任何状态下接收这两个帧中的任何一个。

3.4. 双向流状态

双向流由发送和接收部分组成。实现可以将双向流的状态表示为发送和接收流状态的组合。最简单的模型在发送或接收部分都处于非终端状态时将流显示为“打开”,而在发送和接收流都处于终端状态时将其显示为“关闭”。

表2显示了双向流状态的更复杂的映射,该映射大致对应于HTTP / 2 [ HTTP2 ]中的流状态 。这表明流的发送或接收部分的多个状态被映射到相同的复合状态。请注意,这只是这种映射的一种可能性。此映射要求在转换到“关闭”或“半关闭”状态之前确认数据。

Sending Part Receiving Part Composite State
No Stream/Ready No Stream/Recv *1 idle
Ready/Send/Data Sent Recv/Size Known open
Ready/Send/Data Sent Data Recvd/Data Read half-closed (remote)
Ready/Send/Data Sent Reset Recvd/Reset Read half-closed (remote)
Data Recvd Recv/Size Known half-closed (local)
Reset Sent/Reset Recvd Recv/Size Known half-closed (local)
Reset Sent/Reset Recvd Data Recvd/Data Read closed
Reset Sent/Reset Recvd Reset Recvd/Reset Read closed
Data Recvd Data Recvd/Data Read closed
Data Recvd Reset Recvd/Reset Read closed
注意(* 1):如果尚未创建流,或者流的接收部分处于“ Recv”状态,但尚未接收到任何帧,则将其视为“空闲”。 ### 3.5. 主动状态转换 如果应用程序不再对流中接收到的数据感兴趣,则可以中止读取流并指定应用程序错误代码。

如果流处于“ Recv”或“ Size Size Known”状态,则传输应通过发送STOP_SENDING帧来提示该流以相反方向关闭,从而发出信号。这通常表示接收应用程序不再读取它从流中接收到的数据,但这不能保证传入的数据将被忽略。

发送STOP_SENDING帧后接收到的STREAM帧仍计入连接和流控制,即使这些帧在接收时可以丢弃。

STOP_SENDING帧请求接收端点发送RESET_STREAM帧。如果流处于“就绪”或“发送”状态,则接收到STOP_SENDING帧的端点必须发送RESET_STREAM帧。如果流处于“已发送数据”状态,则端点可以推迟发送RESET_STREAM帧,直到包含未完成数据的数据包被确认或声明为丢失。如果任何未完成的数据被声明丢失,则端点应该发送一个RESET_STREAM帧而不是重新发送数据。

端点应该将错误代码从STOP_SENDING帧复制到它发送的RESET_STREAM帧,但是可以使用任何应用程序错误代码。发送STOP_SENDING帧的端点可以忽略随后为该流接收的任何RESET_STREAM帧中的错误代码。

STOP_SENDING应该仅针对尚未被对等方重置的流发送。STOP_SENDING对于处于“接收”或“已知大小”状态的流最有用。

如果包含先前STOP_SENDING的数据包丢失,则端点将发送另一个STOP_SENDING帧。但是,一旦已接收到所有流数据或该流的RESET_STREAM帧(即,该流处于“ Recv”或“已知大小”以外的任何状态),则无需发送STOP_SENDING帧。

希望终止双向流的两个方向的端点可以通过发送RESET_STREAM帧来终止一个方向,并且可以通过发送STOP_SENDING帧来鼓励在相反方向上迅速终止。

12. Packets and Frames

QUIC端点通过交换数据包进行通信。数据包具有机密性和完整性保护;见12.1节。数据包在UDP数据报中承载;见12.2节。

这个版本的QUIC在建立连接时使用长数据包头。参见第17.2节。带有长报头的数据包是初始(第17.2.2节),0-RTT(第17.2.3节),握手(第17.2.4节)和重试(第17.2.5节)。版本协商使用带有长标头的与版本无关的数据包;参见第17.2.1节。
具有短报头的数据包旨在将开销降至最低,并在建立连接并提供1-RTT密钥后使用;见 17.3节。

12.1. 受保护的数据包

QUIC数据包根据数据包的类型具有不同级别的密码保护。有关数据包保护的详细信息,请参见[ QUIC-TLS ]。本节概述了所提供的保护。

版本协商数据包没有加密保护。参见 [ QUIC-INVARIANTS ]。

重试数据包使用带有关联数据功能(AEAD; [ AEAD ])的经过身份验证的加密,以防止意外修改。

初始数据包使用AEAD,其密钥是使用在线上可见的值导出的。因此,初始数据包没有有效的机密性保护。存在初始保护以确保数据包的发送方在网络路径上。从客户端收到初始数据包的任何实体都可以恢复密钥,从而使它们既可以读取数据包的内容,又可以生成将在任一端点成功通过身份验证的初始数据包。

所有其他数据包都使用从加密握手中获得的密钥进行保护。加密握手确保只有通信端点才能接收到握手,0-RTT和1-RTT数据包的相应密钥。用0-RTT和1-RTT密钥保护的数据包具有强大的机密性和完整性保护。

出现在某些数据包类型中的“数据包编号”字段具有替代机密性保护,它用作标头保护的一部分。有关详细信息,请参见[ QUIC-TLS ]的5.4节。随着在给定的数据包编号空间中发送的每个数据包,基础数据包编号增加;有关详细信息,请参见第12.3节。

12.2. 合并数据包

初始(第17.2.2节),0-RTT(第17.2.3节)和握手(第17.2.4节)数据包包含一个长度字段,该字段确定数据包的结尾。长度包括数据包编号和有效负载字段,这两个字段均受机密性保护,并且最初长度未知。删除标头保护后,即可了解有效负载字段的长度。

使用“长度”字段,发送方可以将多个QUIC数据包合并为一个UDP数据报。这样可以减少完成加密握手并开始发送数据所需的UDP数据报的数量。这也可以用来构建PMTU探针。参见14.4.1节。接收者必须能够处理合并的数据包。

按照加密级别递增的顺序合并数据包(初始,0-RTT,握手,1-RTT;请参阅[ QUIC-TLS ]的第4.1.4节)使接收器更有可能在一个接收器中处理所有数据包通过。标头较短的数据包不包含长度,因此只能是UDP数据报中包含的最后一个数据包。如果要以相同的加密级别发送,则端点应该在单个数据包中包含多个帧,而不是在相同的加密级别上合并多个数据包。

接收者可以根据UDP数据报中包含的第一个数据包中的信息进行路由。发送者不得将具有不同连接ID的QUIC数据包合并为一个UDP数据报。接收者应该忽略具有与数据报中第一个数据包不同的目的地连接ID的任何后续数据包。

合并为单个UDP数据报的每个QUIC数据包都是独立且完整的。合并的QUIC数据包的接收者必须单独处理每个QUIC数据包,并分别对其进行确认,就好像它们是作为不同UDP数据报的有效载荷接收的一样。例如,如果解密失败(因为密钥不可用或任何其他原因),则接收者可以丢弃或缓冲该分组以供以后处理,并且必须尝试处理其余的分组。

重试数据包(第17.2.5节),版本协商数据包(第17.2.1节)和带有短标头的数据包(第17.3节)不包含“长度”字段,因此同一UDP数据报中的其他数据包将无法跟随。还请注意,在任何情况下都不会将“重试”或“版本协商”数据包与另一个数据包合并。

12.3. 封包编号

报文号是0到2 ^ 62-1之间的整数。此数字用于确定用于数据包保护的加密随机数。每个端点维护一个单独的数据包编号,用于发送和接收。

数据包号限制在此范围内,因为它们需要在ACK帧的“最大已确认”字段中完整表示(第19.3节)。但是,当出现在长标题或短标题中时,数据包号会减少并以1到4个字节进行编码;参见第17.1节。

版本协商(第17.2.1节)和重试(第17.2.5节)数据包不包括数据包编号。

数据包编号在QUIC中分为3个空格:

  • 初始空间:所有初始数据包(第17.2.2节)都在此空间中。
  • 握手空间:所有握手数据包(第17.2.4节)都在此空间中。
  • 应用程序数据空间:所有0-RTT(第17.2.3节)和1-RTT(第17.3节)加密的数据包都在此空间中。

如[ QUIC-TLS ]中所述,每种数据包类型都使用不同的保护密钥。

从概念上讲,数据包编号空间是可以在其中处理和确认数据包的上下文。初始数据包只能使用初始数据包保护密钥发送,并在也是初始数据包的数据包中进行确认。同样,握手数据包以握手加密级别发送,并且只能在握手数据包中进行确认。

这将在不同数据包编号空间中发送的数据之间强制进行密码分离。每个空间中的数据包编号均始于数据包编号0。在同一数据包编号空间中发送的后续数据包务必将数据包编号至少增加一个。

0-RTT和1-RTT数据存在于相同的数据包编号空间中,以使两种数据包类型之间的丢失恢复算法更易于实现。

QUIC端点不得在一个连接中重用同一数据包编号空间内的数据包编号。如果要发送的数据包号达到2 ^ 62-1,则发送方必须关闭连接,而不必发送CONNECTION_CLOSE帧或任何其他数据包;端点可以发送无状态复位(第10.3节)以响应其收到的更多数据包。

除非确定接收者没有处理过来自相同数据包号空间中具有相同数据包号的另一个数据包,否则接收者必须丢弃一个新的未受保护的数据包。出于[ QUIC-TLS ]第9.3节中所述的原因,必须在删除数据包保护后进行重复抑制。

跟踪所有单个数据包以检测重复项的端点有累积过多状态的风险。检测重复项所需的数据可以通过保持最小数据包数来限制,在该数据包数以下,所有数据包都将立即丢弃。任何最低限度都需要考虑往返时间的巨大变化,这包括对等点可能以更大的往返时间探测网络路径的可能性;参见第9节。

在第17.1节中描述了发送方的数据包编号编码和接收方的数据包解码 。

12.4. 框架和框架类型

QUIC数据包的有效负载在删除数据包保护后由一系列完整的帧组成,如图11所示。版本协商,无状态重置和重试数据包不包含帧。

Packet Payload {
  Frame (8..) ...,
}

包含帧的包的有效载荷必须至少包含一个帧,并且可以包含多个帧和多种帧类型。帧始终适合单个QUIC数据包,不能跨越多个数据包。

每个框架都以一个“框架类型”开始,指示其类型,然后是其他与类型相关的字段:

Frame {
  Frame Type (i),
  Type-Dependent Fields (..),
}

表3中列出了本规范中定义的帧类型。ACK,STREAM,MAX_STREAMS,STREAMS_BLOCKED和CONNECTION_CLOSE帧中的帧类型用于承载其他特定于帧的标志。对于所有其他框架,“框架类型”字段仅标识该框架。这些框架在第19节中有更详细的说明。
| Type Value | Frame Type Name |
|:–|:–|
0x00 | PADDING
0x01 | PING
0x02 - 0x03 | ACK
0x04 | RESET_STREAM
0x05 | STOP_SENDING
0x06 | CRYPTO
0x07 | NEW_TOKEN
0x08 - 0x0f | STREAM
0x10 | MAX_DATA
0x11 | MAX_STREAM_DATA
0x12 - 0x13 | MAX_STREAMS
0x14 | DATA_BLOCKED
0x15 | STREAM_DATA_BLOCKED
0x16 - 0x17 | STREAMS_BLOCKED
0x18 | NEW_CONNECTION_ID
0x19 | RETIRE_CONNECTION_ID
0x1a | PATH_CHALLENGE
0x1b | PATH_RESPONSE
0x1c - 0x1d | CONNECTION_CLOSE
0x1e | HANDSHAKE_DONE

12.5. 框架和数字空间

某些帧在不同的数据包编号空间中被禁止。这里的规则概括了TLS的规则,因为与建立连接相关联的帧通常可以出现在任何数据包编号空间的数据包中,而与传输数据相关联的帧只能出现在应用程序数据包编号空间中:

  • PADDING,PING和CRYPTO帧可能出现在任何数据包编号空间中。
  • 在QUIC层(类型0x1c)表示错误的CONNECTION_CLOSE帧可能出现在任何数据包编号空间中。表示应用程序错误(类型0x1d)的CONNECTION_CLOSE帧必须仅出现在应用程序数据包编号空间中。
  • ACK帧可以出现在任何数据包编号空间中,但是只能确认出现在该数据包编号空间中的数据包。但是,如下所述,0-RTT数据包不能包含ACK帧。
  • 所有其他帧类型只能在应用数据包编号空间中发送。

请注意,由于各种原因,无法在0-RTT数据包中发送以下帧:ACK,CRYPTO,HANDSHAKE_DONE,NEW_TOKEN,PATH_RESPONSE和RETIRE_CONNECTION_ID。服务器可以将0-RTT数据包中这些帧的接收视为PROTOCOL_VIOLATION类型的连接错误。

13. 打包和可靠性

17.报文格式

所有数值均以网络字节顺序(即big-endian)编码,并且所有字段大小均以位为单位。十六进制表示法用于描述字段的值。

17.1. 数据包编号编码和解码

数据包号是0到2 ^ 62-1(第12.3节)范围内的整数。当出现在长或短数据包头中时,它们以1到4个字节进行编码。通过仅包括分组号的最低有效位,减少了表示分组号所需的位数。

编码的数据包编号受[ QUIC-TLS ]的5.4节所述 。

在接收到一个包号空间的确认之前,必须包括完整的包号。请勿如下所述将其截断。

在收到一个包号空间的确认之后,发送者必须使用一个包号大小,该大小代表比最大已确认包和要发送的包号之差大两倍的范围。然后,接收到该数据包的对等方将正确解码该数据包编号,除非该数据包在传输中被延迟,以致在收到许多更高编号的数据包之后到达。端点应该使用足够大的包号编码,以使包号得以恢复,即使包在之后发送的包之后到达也是如此。

结果,分组编号编码的大小比包括新分组在内的连续未确认分组编号的数目的以2为底的对数至少大一位。

例如,如果端点已收到对数据包0xabe8bc的确认,则发送数量为0xac5c02的数据包需要使用16位或更多位的数据包编号进行编码;而发送24x数据包编号需要发送0xace8fe编号的数据包。

在接收器处,在恢复完整的数据包编号之前,将删除对数据包编号的保护。然后,根据存在的有效位数,这些位的值以及在成功通过身份验证的数据包上收到的最大数据包数量,重建完整的数据包编号。要成功删除数据包保护,必须恢复完整的数据包编号。

一旦删除了报头保护,便通过找到最接近下一个预期数据包的数据包编号值来对数据包编号进行解码。下一个预期的数据包是最高的接收数据包号加1。例如,如果成功认证的最高数据包的数据包编号为0xa82f30ea,则包含16位值0x9b32的数据包将被解码为0xa82f9b32。可在附录A中找到用于数据包编号解码的伪代码示例 。

17.2. Long Header Packets

Long Header Packet {
  Header Form (1) = 1,
  Fixed Bit (1) = 1,
  Long Packet Type (2),
  Type-Specific Bits (4),
  Version (32),
  Destination Connection ID Length (8),
  Destination Connection ID (0..160),
  Source Connection ID Length (8),
  Source Connection ID (0..160),
}

长标头用于在建立1-RTT密钥之前发送的数据包。一旦1-RTT密钥可用,发送方就切换到使用短报头发送数据包(第17.3节)。长格式允许特殊的数据包(例如版本协商数据包)以这种统一的固定长度数据包格式表示。使用长标头的数据包包含以下字段:

  • 标题形式:对于长标头,字节0(第一个字节)的最高有效位(0x80)设置为1。
  • 固定位:字节0的下一个位(0x40)设置为1。对此位包含零值的数据包在该版本中不是有效数据包,必须将其丢弃。
  • 长数据包类型:字节0的后两位(掩码为0x30的那些位)包含一个数据包类型。包类型在表5中列出。
  • 类型专用位:字节0的低四位(掩码为0x0f的那四位)是特定于类型的。
  • 版:QUIC版本是第一个字节之后的32位字段。该字段指示正在使用的QUIC的版本,并确定如何解释其余协议字段。
  • 目标连接ID长度:版本之后的字节包含其后的“目标连接ID”字段的长度(以字节为单位)。该长度被编码为8位无符号整数。在QUIC版本1中,该值不得超过20。接收到版本1长的标头且其值大于20的端点必须丢弃该数据包。为了正确地形成一个版本协商包,服务器应该能够从其他QUIC版本中读取更长的连接ID。
  • 目标连接ID:目标连接ID字段位于目标连接ID长度字段之后,该字段指示此字段的长度。 第7.2节详细介绍了此字段的使用。
  • 源连接ID长度:目标连接ID后面的字节包含其后的源连接ID字段的字节长度。该长度编码为8位无符号整数。在QUIC版本1中,该值不得超过20个字节。接收到大于1的版本1长报头的端点必须丢弃该数据包。为了正确地形成一个版本协商包,服务器应该能够从其他QUIC版本中读取更长的连接ID。
  • 源连接ID:源连接ID字段位于源连接ID长度字段之后,该字段指示此字段的长度。第7.2节 详细介绍了此字段的使用。

在此版本的QUIC中,定义了带有长标头的以下数据包类型:

类型 名称
0x0 初始
0x1 0-RTT
0x2 握手
0x3 重试

标头形式位,目标和源连接ID长度,目标和源连接ID字段以及长标头数据包的版本字段与版本无关。第一个字节中的其他字段是特定于版本的。有关如何解释来自不同版本QUIC的数据包的详细信息,请参见[ QUIC-INVARIANTS ]。

字段和有效负载的解释特定于版本和数据包类型。虽然以下各节描述了此版本的特定于类型的语义,但此版本的QUIC中的几个长标头数据包包含以下附加字段:

  • 保留位:字节0的两个位(那些掩码为0x0c的位)在多种数据包类型之间被保留。这些位使用头保护来保护;请参阅[ QUIC-TLS ]的5.4节。保护之前包含的值必须设置为0。端点务必在删除数据包和报头保护后,将接收到的这些位具有非零值的数据包视为PROTOCOL_VIOLATION类型的连接错误。仅删除标题保护后丢弃此类数据包会使端点遭受攻击;请参阅[ QUIC-TLS ]的9.3节 。
  • 包号长度:在包含“数据包编号”字段的数据包类型中,字节0的最低有效两位(掩码为0x03的那些字节)包含数据包编号的长度,编码为无符号的两位整数,比长度小1数据包编号字段的字节数。即,分组号字段的长度是该字段的值加1。这些位使用头保护来保护;请参阅[ QUIC-TLS ]的5.4节。
  • 长度:数据包其余部分(即“数据包编号”和“有效负载”字段)的长度,以字节为单位,编码为可变长度整数(第16节)。
  • 包号:数据包编号字段的长度为1到4个字节。数据包号使用标题保护来保护;请参阅[ QUIC-TLS ]的5.4节。数据包编号字段的长度被编码在字节0的数据包编号长度位中。看上面。

17.2.1. 版本协商包

版本协商数据包本质上不是特定于版本的。在接收由客户端,它会被识别为基于具有0值的版本字段一个版本协商分组

版本协商数据包是对客户端数据包的响应,该客户端数据包包含服务器不支持的版本,仅由服务器发送。

版本协商数据包的布局为:

Version Negotiation Packet {
  Header Form (1) = 1,
  Unused (7),
  Version (32) = 0,
  Destination Connection ID Length (8),
  Destination Connection ID (0..2040),
  Source Connection ID Length (8),
  Source Connection ID (0..2040),
  Supported Version (32) ...,
}

服务器会随机选择“未使用”字段中的值。客户必须忽略该字段的值。服务器应将此字段的最高有效位(0x40)设置为1,以使版本协商数据包看起来具有“固定位”字段。

版本协商包的版本字段必须设置为0x00000000。

服务器必须在目标连接ID字段中包含其收到的数据包的源连接ID字段中的值。源连接ID的值必须从接收到的数据包的目标连接ID中复制,它最初是由客户端随机选择的。回显这两个连接ID,可以使客户端确信服务器已收到该数据包,并且版本协商数据包不是由路径外攻击者生成的。

QUIC的未来版本可能对连接ID的长度有不同的要求。特别是,连接ID可能具有更小的最小长度或更大的最大长度。因此,连接ID的特定于版本的规则绝不能影响服务器关于是否发送版本协商数据包的决定。

版本协商数据包的其余部分是服务器支持的32位版本的列表。

版本协商包未得到确认。它仅在响应指示不支持版本的数据包时才发送;见5.2.2节。

版本协商数据包不包括使用长标头形式的其他数据包中的数据包编号和长度字段。因此,版本协商数据包会消耗整个UDP数据报。

响应单个UDP数据报,服务器不得发送多个版本协商包。

有关版本协商过程的说明,请参见第6节。

17.2.2. 初始数据包

初始数据包使用类型值为0x0的长标头。它承载由客户端和服务器发送的第一个CRYPTO Frame以执行密钥交换,并在任一方向上承载ACK。

Initial Packet {
  Header Form (1) = 1,
  Fixed Bit (1) = 1,
  Long Packet Type (2) = 0,
  Reserved Bits (2),
  Packet Number Length (2),
  Version (32),
  Destination Connection ID Length (8),
  Destination Connection ID (0..160),
  Source Connection ID Length (8),
  Source Connection ID (0..160),
  Token Length (i),
  Token (..),
  Length (i),
  Packet Number (8..32),
  Packet Payload (..),
}

初始数据包包含一个长标头以及“长度”和“数据包编号”字段;参见第17.2节。第一个字节包含保留和数据包编号长度位;另请参见第17.2节。在“源连接ID”和“长度”字段之间,还有两个附加字段专用于“初始”数据包。

  • 令牌长度:一个可变长度的整数,以字节为单位指定令牌字段的长度。如果没有令牌,则该值为零。服务器发送的初始数据包必须将令牌长度字段设置为零;收到带有非零令牌长度字段的初始数据包的客户端必须丢弃该数据包或产生类型为PROTOCOL_VIOLATION的连接错误。
  • Token :先前在“重试”数据包或NEW_TOKEN帧中提供的令牌的值;见8.1节。
  • 数据包有效载荷:数据包的有效载荷。
    为了防止不了解版本的中间盒对数据包的篡改,如[ QUIC-TLS ]中所述,使用特定于连接和版本的密钥(初始密钥)来保护初始数据包。此保护不提供针对路径上攻击者的机密性或完整性,但是提供某种程度的针对路径外攻击者的保护。

客户端和服务器对包含初始加密握手消息的任何数据包使用“初始”数据包类型。这包括需要创建包含初始加密消息的新数据包的所有情况,例如在收到重试数据包后发送的数据包(第17.2.5节)。

服务器发送第一个Initial数据包以响应客户端Initial。服务器可以发送多个初始数据包。密码密钥交换可能需要多次往返或重新传输此数据。

初始数据包的有效负载包括一个或多个CRYPTO Frame,其中包含一个加密握手消息,ACK帧或两者。还允许使用0x1c类型的PING,PADDING和CONNECTION_CLOSE帧。接收到包含其他帧的初始数据包的端点可以将其视为虚假丢弃,也可以将其视为连接错误。

客户端发送的第一个数据包始终包含一个CRYPTO Frame,该帧包含第一个或所有第一个加密握手消息。发送的第一个CRYPTO Frame始终从偏​​移量0开始;参见第7节。

请注意,如果服务器发送HelloRetryRequest,则客户端将发送另一系列的Initial数据包。这些初始数据包将继续进行加密握手,并将包含CRYPTO Frame,其起始偏移量与在初始数据包的第一次飞行中发送的CRYPTO Frame的大小匹配。

17.2.2.1. 放弃初始数据包

客户端在发送其第一个握手数据包时,将停止发送和处理初始数据包。服务器在收到其第一个握手数据包时,将停止发送和处理初始数据包。尽管数据包可能仍在飞行中或正在等待确认,但在此之后,无需再交换其他初始数据包。初始的数据包保护密钥以及任何丢失恢复和拥塞控制状态都将被丢弃(请参阅[ QUIC-TLS ]的4.9.1节);参见[ QUIC-RECOVERY ]的6.4节。

当初始密钥被丢弃时,CRYPTO Frame中的任何数据都将被丢弃-不再重传。

17.2.3. 0-RTT

0-RTT数据包使用类型值为0x1的长标头,后跟“长度”和“数据包编号”字段。参见第17.2节。第一个字节包含保留和数据包编号长度位;参见第17.2节。0-RTT数据包用于在握手完成之前将“早期”数据从客户端传送到服务器,作为第一次飞行的一部分。作为TLS握手的一部分,服务器可以接受或拒绝此早期数据。

有关0-RTT数据及其限制的讨论,请参见[ TLS13 ]的2.3节。

0-RTT Packet {
  Header Form (1) = 1,
  Fixed Bit (1) = 1,
  Long Packet Type (2) = 1,
  Reserved Bits (2),
  Packet Number Length (2),
  Version (32),
  Destination Connection ID Length (8),
  Destination Connection ID (0..160),
  Source Connection ID Length (8),
  Source Connection ID (0..160),
  Length (i),
  Packet Number (8..32),
  Packet Payload (..),
}

0-RTT保护的数据包的数据包编号与1-RTT保护的数据包使用相同的空间。

客户端收到重试数据包后,服务器可能会丢失或丢弃0-RTT数据包。客户端应该在发送新的初始包后尝试重新发送0-RTT包中的数据。新的数据包号必须用于发送的任何新的数据包。如第17.2.5.3节所述,重用数据包编号可能会损害数据包保护。

握手完成后,客户端仅会收到其0-RTT数据包的确认,如[ QUIC-TLS ]的第4.1.1节所定义。

客户端一旦开始处理来自服务器的1-RTT数据包,就不得发送0-RTT数据包。这意味着0-RTT数据包不能包含对1-RTT数据包的帧的任何响应。例如,客户端无法在0-RTT数据包中发送ACK帧,因为它只能确认1-RTT数据包。对1-RTT分组的确认必须在1-RTT分组中进行。

服务器应将违反记住的限制(第7.4.1节)的情况视为适当类型的连接错误(例如,超出流数据限制的FLOW_CONTROL_ERROR)。

17.2.4. 握手包

握手数据包使用类型值为0x2的长标头,后跟“长度”和“数据包编号”字段;参见第17.2节。第一个字节包含保留和数据包编号长度位;参见第17.2节。它用于承载来自服务器和客户端的加密握手消息和确认。

Handshake Packet {
  Header Form (1) = 1,
  Fixed Bit (1) = 1,
  Long Packet Type (2) = 2,
  Reserved Bits (2),
  Packet Number Length (2),
  Version (32),
  Destination Connection ID Length (8),
  Destination Connection ID (0..160),
  Source Connection ID Length (8),
  Source Connection ID (0..160),
  Length (i),
  Packet Number (8..32),
  Packet Payload (..),
}

客户端从服务器接收到握手数据包后,便使用握手数据包向服务器发送后续的加密握手消息和确认。

握手数据包中的“目标连接ID”字段包含由数据包的接收者选择的连接ID。源连接ID包括分组的发送者希望使用的连接ID;参见 第7.2节。

握手分组是自己的分组号的空间,并且因此能够由服务器的第一握手分组中发送包含为0的数据包序号

该数据包的有效载荷包含CRYPTO Frame,并且可以包含PING,PADDING或ACK帧。握手包可能包含类型为0x1c的CONNECTION_CLOSE帧。端点必须将收到带有其他帧的握手数据包视为连接错误。

像初始数据包(请参阅第17.2.2.1节)一样,当握手保护密钥被丢弃时,握手数据包的CRYPTO Frame中的数据将被丢弃-不再重传。

17.2.5. 重试数据包

重试数据包使用类型为0x3的长数据包头。它带有服务器创建的地址验证令牌。希望重试的服务器使用它。见8.1节。

Retry Packet {
  Header Form (1) = 1,
  Fixed Bit (1) = 1,
  Long Packet Type (2) = 3,
  Unused (4),
  Version (32),
  Destination Connection ID Length (8),
  Destination Connection ID (0..160),
  Source Connection ID Length (8),
  Source Connection ID (0..160),
  Retry Token (..),
  Retry Integrity Tag (128),
}

重试数据包(如图18所示)不包含任何受保护的字段。服务器将“未使用”字段中的值设置为任意值;客户端必须忽略这些位。除了长标头中的字段之外,它还包含以下其他字段:

  • 重试令牌:服务器可以用来验证客户端地址的不透明令牌。
  • 重试完整性标签:请参阅[ QUIC-TLS ]的“重试数据包完整性”部分。
17.2.5.1. 发送重试数据包

服务器使用客户端包含在初始数据包的源连接ID中的连接ID填充目标连接ID。

服务器在“源连接ID”字段中包括其选择的连接ID。此值不得等于客户端发送的数据包的“目标连接ID”字段。客户端必须丢弃重试包,该重试包包含的源连接ID字段与其初始包的目标连接ID字段相同。客户端必须在其发送的后续数据包的“目标连接ID”字段中使用“重试”数据包的“源连接ID”字段中的值。

服务器可以发送Retry包以响应初始包和0-RTT包。服务器可以丢弃或缓冲其接收到的0-RTT数据包。服务器可以在收到初始或0-RTT数据包时发送多个Retry数据包。响应单个UDP数据报,服务器不得发送多个Retry数据包。

17.2.5.2. 处理重试数据包

对于每次连接尝试,客户端必须最多接受并处理一个重试数据包。客户端从服务器接收并处理了初始或重试数据包后,必须丢弃其收到的任何后续重试数据包。

客户端必须丢弃具有无法验证的重试完整性标签的重试数据包;请参阅[ QUIC-TLS ]的“重试数据包完整性”部分。这削弱了路径外攻击者注入重试数据包的能力,并防止了重试数据包的意外损坏。客户端必须丢弃带有零长度重试令牌字段的重试包。

客户端使用包含提供的重试令牌的初始数据包响应重试数据包,以继续建立连接。

客户端将此初始数据包的目标连接ID字段设置为重试数据包中源连接ID的值。更改目标连接ID也会导致用于保护初始数据包的密钥发生更改。还将“令牌”字段设置为“重试”中提供的令牌。客户端不得更改源连接ID,因为服务器可以将连接ID​​包含在其令牌验证逻辑中。见 8.1.4节。

重试数据包不包括数据包编号,并且客户端无法明确确认。

17.2.5.3. 重试后继续握手

来自客户端的后续初始数据包包括来自重试数据包的连接ID和令牌值。客户端将“源连接ID”字段从“重试”数据包复制到“目标连接ID”字段,并使用该值,直到接收到具有更新值的“初始”数据包为止。参见 第7.2节。令牌字段的值将复制到所有后续的初始数据包;见8.1.2节。

除了更新目标连接ID和令牌字段外,客户端发送的初始数据包与第一个初始数据包受到相同的限制。客户端必须使用与该数据包中相同的加密握手消息。服务器可以将包含不同加密握手消息的包视为连接错误,或者将其丢弃。

客户端可以通过向服务器提供的连接ID发送0-RTT数据包,在接收到重试数据包后尝试0-RTT。客户端不得更改其发送的加密握手消息以响应接收到重试。

客户端在处理一个重试包后,不得在任何包号空间重设包号。特别是,0-RTT数据包包含机密信息,最有可能在接收到重试数据包后重新发送。作为对重试数据包的响应,用于保护这些新的0-RTT数据包的密钥不会更改。但是,这些数据包中发送的数据可能与之前发送的数据不同。发送具有相同数据包编号的这些新数据包可能会损害那些数据包的数据包保护,因为可以使用相同的密钥和随机数来保护不同的内容。如果服务器检测到客户端重置了数据包号,则服务器可以中止连接。

在客户端和服务器之间交换的Initial和Retry数据包上使用的连接ID将复制到传输参数,并按照7.3节中的说明进行验证。

1 7.3. 短报头包

这个版本的QUIC定义了一个使用短数据包头的数据包类型。

Short Header Packet {
  Header Form (1) = 0,
  Fixed Bit (1) = 1,
  Spin Bit (1),
  Reserved Bits (2),
  Key Phase (1),
  Packet Number Length (2),
  Destination Connection ID (0..160),
  Packet Number (8..32),
  Packet Payload (..),
}

可以在协商版本和1-RTT密钥后使用short标头。使用短头的数据包包含以下字段:

  • 标题形式:短报头的字节0的最高有效位(0x80)设置为0。
  • 固定位:字节0的下一个位(0x40)设置为1。对此位包含零值的数据包在该版本中不是有效数据包,必须将其丢弃。
  • 旋转位:字节0的第三个最高有效位(0x20)是等待时间旋转位,如第17.3.1节中所述设置。
  • 保留位:保留字节0的后两位(掩码为0x18的两位)。这些位使用头保护来保护;请参阅[ QUIC-TLS ]的5.4节 。保护之前包含的值必须设置为0。端点必须在删除数据包和报头保护后,将接收到的这些位具有非零值的数据包视为PROTOCOL_VIOLATION类型的连接错误。仅删除标题保护后丢弃此类数据包会使端点遭受攻击;请参阅[ QUIC-TLS ]的9.3节 。
  • 关键阶段:字节0的下一个位(0x04)指示密钥阶段,该阶段使数据包的接收者可以识别用于保护数据包的数据包保护密钥。有关详细信息,请参见[ QUIC-TLS ]。该位通过头保护来保护。请参阅[ QUIC-TLS ]的5.4节。
  • 包号长度:字节0的最低有效两位(掩码为0x03的那些)包含数据包编号的长度,编码为无符号的两位整数,比数据包编号字段的长度(以字节为单位)小一。即,分组号字段的长度是该字段的值加1。这些位使用头保护来保护;请参阅[ QUIC-TLS ]的5.4节。
  • 目标连接ID:目标连接ID是由数据包的预期接收者选择的连接ID。有关更多详细信息,请参见第5.1节。
  • 包号:数据包编号字段的长度为1到4个字节。数据包编号具有与数据包保护不同的机密性保护,如[ QUIC-TLS ]的5.4节所述。分组号字段的长度被编码在分组号长度字段中。有关详细信息,请参见第17.1节。
  • 数据包有效载荷:具有短标头的数据包始终包含受1-RTT保护的有效负载。
    短报头包的报头形式位和连接ID字段与版本无关。其余字段特定于所选的QUIC版本。有关如何解释来自不同版本QUIC的数据包的详细信息,请参见[ QUIC-INVARIANTS ]。

17.3.1. 延迟旋转位

延迟自旋位允许在整个连接期间从网络路径上的观察点进行被动延迟监视。自旋位仅存在于短数据包报头中,因为可以通过观察握手来测量连接的初始RTT。因此,在版本协商和连接建立完成之后,旋转位可用。[ QUIC-MANAGEABILITY ]中进一步讨论了延迟自旋位的按路径测量和使用。

自旋位是QUIC的可选功能。选择支持自旋位的QUIC堆栈必须按照本节中的说明实现。

每个端点单方面决定是为连接启用还是禁用旋转位。实施必须允许客户端和服务器的管理员全局或在每个连接的基础上禁用旋转位。即使管理员未禁用旋转位,端点也必须禁用对旋转位的使用,以随机选择每16条网络路径中的至少一个或每16个连接ID中的一个。由于每个端点都独立禁用旋转位,因此可以确保在八分之一的网络路径中禁用旋转位信号。

当旋转位被禁用时,端点可以将旋转位设置为任何值,并且必须忽略任何传入的值。建议端点将自旋位设置为随机值,该随机值是针对每个数据包独立选择的,还是针对每个连接ID独立选择的。

如果为连接启用了旋转位,则端点将为每个网络路径维护一个旋转值,并在该路径上发送带有短标头的数据包时,将短标头中的旋转位设置为当前存储的值。在每个网络路径的端点中,旋转值都初始化为0。每个端点还记住在每个路径上从其对等方看到的最高数据包编号。

当服务器在给定的网络路径上从客户端收到的短报头分组增加了服务器看到的最大分组数目时,它将该路径的旋转值设置为等于接收到的数据包中的旋转位。

当客户端从给定网络路径上的服务器接收到的短报头分组增加了客户端看到的最高分组数时,它将将该路径的旋转值设置为接收到的数据包中旋转位的倒数。

更改该网络路径上使用的连接ID时,端点会将网络路径的旋转值重置为零。

通过这种机制,服务器反映收到的旋转值,而客户端在一个RTT之后对其进行“旋转”。路径上的观察者可以测量两个自旋位切换事件之间的时间,以估计连接的端到端RTT。

18.传输参数编码

在[ QUIC-TLS ]中定义的quic_transport_parameters扩展名的extension_data字段 包含QUIC传输参数。它们被编码为一系列传输参数,如下所示:

Transport Parameters {
  Transport Parameter (..) ...,
}

每个传输参数都编码为(标识符,长度,值)元组,如下所示:

Transport Parameter {
  Transport Parameter ID (i),
  Transport Parameter Length (i),
  Transport Parameter Value (..),
}

运输参数长度字段包含运输参数值字段的长度。

QUIC将传输参数编码为字节序列,然后将其包含在加密握手中。

18.1. 保留的运输参数

31 * N + 27保留标识符形式为N的整数的传输参数,以执行忽略未知传输参数的要求。这些传输参数没有语义,可以带有任意值。

18.2. 传输参数定义

本节详细介绍本文档中定义的传输参数。

此处列出的许多传输参数都具有整数值。那些标识为整数的传输参数使用可变长度整数编码;参见第16节。除非另有说明,否则如果没有传输参数,则传输参数的默认值为0。

定义了以下传输参数:

  • original_destination_connection_id(0x00):客户端发送的第一个初始数据包中的“目标连接ID”字段的值;参见第7.3节。此传输参数仅由服务器发送。
  • max_idle_timeout(0x01):最大空闲超时是一个以毫秒为单位的值,它被编码为整数。请参阅(第10.1节)。空闲超时时禁用两个端点省略此传输参数,或指定为0的值
  • stateless_reset_token(0x02):无状态重置令牌用于验证无状态重置;见 10.3节。此参数是一个16字节的序列。该传输参数不得由客户端发送,而可以由服务器发送。不发送此传输参数的服务器不能对握手期间协商的连接ID使用无状态复位(第10.3节)。
  • max_udp_payload_size(0x03):最大UDP负载大小参数是一个整数值,该值限制了端点愿意接收的UDP负载的大小。有效负载大于此限制的UDP数据报不太可能被接收方处理。
    此参数的默认值为允许的最大UDP有效负载65527。低于1200的值无效。
    与路径MTU一样,此限制确实是对数据报大小的附加约束,但这是端点的属性,而不是路径的属性。参见第14节。可以预期,这是端点专用于保存传入数据包的空间。
  • initial_max_data(0x04):初始最大数据参数是一个整数值,其中包含可以在连接上发送的最大数据量的初始值。这等效于完成握手后立即为连接发送MAX_DATA(第19.9节)。
  • initial_max_stream_data_bidi_local(0x05):此参数是一个整数值,它指定本地启动的双向流的初始流量控制限制。此限制适用于由发送传输参数的端点打开的新创建的双向流。在客户端传输参数中,这适用于具有标识符的流,该标识符的最低有效两位设置为0x0;在服务器传输参数中,这适用于将最低有效两位设置为0x1的流。
  • initial_max_stream_data_bidi_remote(0x06):此参数是一个整数值,它指定对等方发起的双向流的初始流控制限制。此限制适用于由接收传输参数的端点打开的新创建的双向流。在客户端传输参数中,这适用于具有标识符的流,该标识符的最低有效两位设置为0x1;在服务器传输参数中,这适用于将最低有效两位设置为0x0的流。
  • initial_max_stream_data_uni(0x07):此参数是整数值,用于指定单向流的初始流量控制限制。此限制适用于由接收传输参数的端点打开的新创建的单向流。在客户端传输参数中,这适用于具有标识符的流,该标识符的最低有效两位设置为0x3;在服务器传输参数中,这适用于将最低有效两位设置为0x2的流。
  • initial_max_streams_bidi(0x08):初始最大双向流参数是一个整数值,其中包含对等方可能发起的双向流的初始最大数目。如果此参数不存在或为零,则对等方无法打开双向流,直到发送MAX_STREAMS帧为止。设置此参数等效于发送具有相同值的相应类型的MAX_STREAMS(第19.11节)。
  • initial_max_streams_uni(0x09):初始最大单向流参数是一个整数值,其中包含对等方可能发起的初始最大单向流数。如果此参数不存在或为零,则对等方无法打开单向流,直到发送MAX_STREAMS帧为止。设置此参数等效于发送具有相同值的相应类型的MAX_STREAMS(第19.11节)。
  • ack_delay_exponent(0x0a):确认延迟指数是一个整数值,指示用于解码ACK帧中ACK延迟字段的指数(见19.3节)。如果不存在此值,则假定默认值为3(表示乘数8)。大于20的值无效。
  • max_ack_delay(0x0b):最大确认延迟是一个整数值,指示端点将延迟发送确认的最大时间(以毫秒为单位)。该值应包括接收器在警报触发中的预期延迟。例如,如果接收器将计时器设置为5毫秒,而警报通常会延迟到1毫秒,则它应发送6毫秒的max_ack_delay。如果不存在此值,则默认为25毫秒。2 ^ 14或更大的值无效。
  • disable_active_migration(0x0c):如果端点在握手期间使用的地址上不支持活动连接迁移(第9节),则包括禁用活动迁移传输参数。当对等方设置此传输参数时,端点在发送到对等方在握手期间使用的地址时,不得使用新的本地地址。客户端对preferred_address传输参数执行操作后,此传输参数不会禁止连接迁移。此参数是零长度值。
  • preferred_address(0x0d):服务器的首选地址用于在握手结束时更改服务器地址,如9.6节所述。此传输参数仅由服务器发送。服务器可以选择只发送一个地址族的首选地址,方法是发送另一个地址族的全零地址和端口(0.0.0.0:0或::。0)。IP地址以网络字节顺序编码。

preferred_address传输参数包含IP版本4和6的地址和端口。四字节的IPv4地址字段后跟关联的两字节的IPv4端口字段。随后是16字节的IPv6地址字段和2字节的IPv6端口字段。在地址和端口对之后,“连接ID长度”字段描述了以下“连接ID”字段的长度。最后,一个16字节的无状态重置令牌字段包括与连接ID关联的无状态重置令牌。该传输参数的格式如下所示。

Preferred Address {
  IPv4 Address (32),
  IPv4 Port (16),
  IPv6 Address (128),
  IPv6 Port (16),
  Connection ID Length (8),
  Connection ID (..),
  Stateless Reset Token (128),
}

连接ID字段和无状态重置令牌字段包含序列号为1的备用连接ID。见5.1.1节。将这些值与首选地址一起发送可确保当客户端启动迁移到首选地址时,至少有一个未使用的活动连接ID。

首选地址的连接ID和无状态重置令牌字段在语法和语义上与NEW_CONNECTION_ID Frame的相应字段相同(第19.15节)。选择零长度连接ID的服务器不得提供首选地址。同样,服务器不得在此传输参数中包含零长度的连接ID。客户端必须将违反这些要求的行为视为TRANSPORT_PARAMETER_ERROR类型的连接错误。

19.帧类型和格式

如第12.4节所述,数据包包含一个或多个帧。本节描述了核心QUIC帧类型的格式和语义。

19.1. PADDING Frames

PADDING Frame(类型= 0x00)没有语义值。填充帧可用于增加数据包的大小。填充可用于将初始客户端数据包增加到所需的最小大小,或提供保护以防止对受保护数据包进行流量分析。

PADDING Frame的格式如图23所示,这表明PADDING Frame不包含任何内容。即,PADDING Frame由将帧识别为PADDING Frame的单个字节组成。

PADDING Frame {
  Type (i) = 0x00,
}

19.2. PING Frames

端点可以使用PING Frame(类型= 0x01)来验证其对等方仍然存在或检查对等方的可达性。 PING Frame的格式如图24所示,这表明PING Frame不包含任何内容。

PING Frame {
  Type (i) = 0x01,
}

PING Frame的接收者只需要确认包含该帧的数据包即可。 当应用程序或应用程序协议希望防止连接超时时,可以使用PING Frame使连接保持活动状态。参见第10.1.2节。

19.3. ACK Frames

接收方发送ACK帧(类型0x02和0x03)以将其已接收和处理的数据包通知发送方。ACK帧包含一个或多个ACK范围。ACK范围标识已确认的数据包。如果帧类型为0x03,则ACK帧还包含截至此点为止在连接上接收到的带有相关ECN标记的QUIC数据包的总和。QUIC实现必须正确处理这两种类型,并且如果它们为发送的数据包启用了ECN,则应使用ECN部分中的信息来管理其拥塞状态。

QUIC的确认是不可撤销的。确认后,即使未出现在将来的ACK帧中,数据包也保持确认状态。这与不需要TCP SACK([ RFC2018 ])不同。

可以使用相同的数值标识来自不同数据包编号空间的数据包。数据包的确认需要同时指示数据包编号和数据包编号空间。通过使每个ACK帧仅在与包含ACK帧的数据包相同的空间中确认数据包编号来实现此目的。

版本协商和重试数据包不包含数据包编号,因此无法确认。这些数据包不是依靠ACK帧,而是由客户端发送的下一个初始数据包隐式确认。

ACK帧的格式如图25所示。

ACK Frame {
  Type (i) = 0x02..0x03,
  Largest Acknowledged (i),
  ACK Delay (i),
  ACK Range Count (i),
  First ACK Range (i),
  ACK Range (..) ...,
  [ECN Counts (..)],
}

ACK帧包含以下字段:

  • 最大的确认:长度可变的整数,表示对等方确认的最大数据包号;这通常是对等方在生成ACK帧之前已收到的最大分组数。与QUIC长或短报头中的数据包编号不同,ACK帧中的值不会被截断。
  • 确认延迟:长度可变的整数,以毫秒为单位对确认延迟进行编码;见13.2.5节。通过将字段中的值乘以ACK帧的发送方发送的ack_delay_exponent传输参数的幂来对其进行解码;见 18.2节。与简单地将延迟表示为整数相比,此编码允许在相同数量的字节内包含更大范围的值,但以较低的分辨率为代价。
  • ACK范围计数:一个可变长度的整数,指定帧中的“间隙”和“ ACK范围”字段的数量。
  • 第一ACK范围:长度可变的整数,表示正在被确认的“最大已确认”之前的连续数据包的数量。第一ACK范围被编码为ACK范围;从最大确认开始,请参见第19.3.1节。也就是说,范围中确认的最小数据包是通过从最大确认值中减去“第一ACK范围”值来确定的。
  • ACK范围:包含交替不被确认(间隙)和被确认(ACK范围)的其他范围的数据包;参见第19.3.1节。
  • ECN计数:三个ECN计数;参见第19.3.2节。

19.3.1. ACK Ranges

每个ACK范围均由降序的数据包编号顺序的交替Gap和ACK Range值组成。ACK范围可以重复。间隙和ACK范围值的数量由ACK范围计数字段确定;在“ ACK范围计数”字段中,每个值都会显示一个值。

ACK范围的结构如图26所示。

ACK Range {
  Gap (i),
  ACK Range Length (i),
}

构成每个ACK范围的字段是:

  • 间隙:长度可变的整数,指示在数据包编号之前的连续未确认数据包的数量比在前一个ACK范围中的最小应答数量低一个。
  • ACK范围长度:可变长度整数,用于指示最大包数之前的连续已确认包数,该数量由前面的间隙确定。
    间隙和ACK范围值使用相对整数编码以提高效率。尽管每个编码值都是正数,但会减去这些值,以便每个ACK范围描述编号逐渐减少的数据包。

每个ACK范围通过指示在该范围内最大的数据包编号之前的已确认数据包数量来确认连续的数据包范围。零值表示仅确认最大的数据包号。ACK范围值越大,表示范围越大,范围内最小的数据包编号对应的值越小。因此,给定范围内最大的数据包编号,最小值由以下公式确定:

smallest = largest - ack_range

ACK范围确认最小数据包号和最大数据包号之间的所有数据包。

ACK范围的最大值由累积减去所有先前ACK范围和间隙的大小确定。

每个间隙表示未确认的数据包范围。间隙中的数据包数量比间隙字段的编码值高一。

Gap字段的值使用以下公式为后续的ACK范围建立最大的数据包编号值:

largest = previous_smallest - gap - 2

如果任何计算出的数据包号为负,则端点必须产生类型为FRAME_ENCODING_ERROR的连接错误。

19.3.2. ECN计数

ACK帧使用最低有效位(即类型0x03)指示ECN反馈,并报告QUIC数据包的接收,该数据包的IP标头中具有ECT(0),ECT(1)或CE的相关ECN码点。仅当ACK帧类型为0x03时,才会显示ECN计数。

存在时,有3个ECN计数,如图27所示。

ECN Counts {
  ECT0 Count (i),
  ECT1 Count (i),
  ECN-CE Count (i),
}

三个ECN计数为:

  • ECT0计数:长度可变的整数,表示在ACK帧的数据包编号空间中使用ECT(0)码点接收的数据包总数。
  • ECT1计数:一个可变长度整数,表示在ACK帧的数据包编号空间中使用ECT(1)码点接收的数据包总数。
  • CE计数:可变长度整数,表示在ACK帧的数据包编号空间中使用CE码点接收的数据包总数。
    每个数据包编号空间的ECN计数均单独维护。

19.4. RESET_STREAM Frame

端点使用RESET_STREAM Frame(类型= 0x04)来突然终止流的发送部分。

发送RESET_STREAM之后,端点停止在标识的流上传输和重新传输STREAM帧。RESET_STREAM的接收器可以丢弃已在该流上接收到的任何数据。

接收到仅发送流的RESET_STREAM Frame的端点务必终止,并出现错误STREAM_STATE_ERROR。

RESET_STREAM Frame的格式如图28所示。

RESET_STREAM Frame {
  Type (i) = 0x04,
  Stream ID (i),
  Application Protocol Error Code (i),
  Final Size (i),
}

RESET_STREAM Frame包含以下字段:

  • 流ID:流终止的流ID的可变长度整数编码。
  • 应用协议错误代码:包含应用协议错误代码(请参见第20.2节)的可变长整数,该错误代码指示为什么关闭流。
  • 最终大小:一个可变长度的整数,指示RESET_STREAM发送方的流的最终大小,以字节为单位;参见第4.5节。

    19.5. STOP_SENDING Frame

    端点使用STOP_SENDING Frame(类型= 0x05)来传达在收到应用程序请求后接收到的数据将被丢弃的信息。STOP_SENDING请求对等方停止流上的传输。

可以为Recv或Size Known状态的流发送STOP_SENDING Frame;请参阅第3.1节。接收到尚未创建的本地启动流的STOP_SENDING Frame,必须将其视为STREAM_STATE_ERROR类型的连接错误。接收到仅接收流的STOP_SENDING Frame的端点务必以错误STREAM_STATE_ERROR终止连接。

STOP_SENDING Frame的格式如图29所示。

STOP_SENDING Frame {
  Type (i) = 0x05,
  Stream ID (i),
  Application Protocol Error Code (i),
}

STOP_SENDING Frame包含以下字段:

  • 流ID:携带流的流ID的长度可变的整数,将被忽略。
  • 应用协议错误代码:一个可变长度的整数,包含发送方忽略流的应用程序指定的原因;参见第20.2节。

19.6. CRYPTO Frame

CRYPTO Frame(类型= 0x06)用于传输加密握手消息。可以以除0-RTT外的所有数据包类型发送。CRYPTO Frame向加密协议提供字节顺序的流。CRYPTO Frame在功能上与STREAM帧相同,只是它们不带有流标识符。它们不受流量控制;并且它们不带有用于可选偏移量,可选长度和流结束的标记。

CRYPTO Frame的格式如图30所示。

CRYPTO Frame {
  Type (i) = 0x06,
  Offset (i),
  Length (i),
  Crypto Data (..),
}

CRYPTO Frame包含以下字段:

  • 抵消:一个可变长整数,指定此CRYPTO Frame中数据的流中的字节偏移量。
  • 长度:一个可变长度的整数,指定此CRYPTO Frame中Crypto Data字段的长度。
  • 加密数据:加密消息数据。
    每个加密级别都有一个单独的加密握手数据流,每个加密握手数据都从偏移量0开始。这意味着每个加密级别都被视为单独的CRYPTO数据流。

流上传递的最大偏移量-偏移量和数据长度的总和-不能超过2 ^ 62-1。超过此限制的帧的接收必须视为FRAME_ENCODING_ERROR或CRYPTO_BUFFER_EXCEEDED类型的连接错误。

与STREAM帧不同,STREAM帧包括指示数据属于哪个流的流ID,CRYPTO Frame按每个加密级别承载单个流的数据。流没有明确的结尾,因此CRYPTO Frame没有FIN位。

19.7. NEW_TOKEN Frames

服务器发送NEW_TOKEN帧(类型= 0x07),以向客户端提供令牌以发送初始数据包的报头以供将来连接。

NEW_TOKEN帧的格式如图31所示。

NEW_TOKEN Frame {
  Type (i) = 0x07,
  Token Length (i),
  Token (..),
}

NEW_TOKEN框架包含以下字段:

  • 令牌长度:一个可变长度的整数,以字节为单位指定令牌的长度。
  • Token :客户端可以在将来的初始数据包中使用的不透明斑点。令牌不得为空。端点必须将收到带有空令牌字段的NEW_TOKEN帧作为FRAME_ENCODING_ERROR类型的连接错误。
    如果包含帧的数据包被错误地确定为丢失,则端点可能会收到多个包含相同令牌值的NEW_TOKEN帧。端点负责丢弃重复的值,这些值可能用于链接连接尝试;见8.1.3节。

客户不得发送NEW_TOKEN帧。服务器必须将收到NEW_TOKEN帧视为PROTOCOL_VIOLATION类型的连接错误。

19.8. STREAM Frames

STREAM帧隐式创建一个流并携带流数据。STREAM帧类型字段的格式为0b00001XXX(或从0x08到0x0f的一组值)。帧类型的三个低位决定了帧中存在的字段:

  • 帧类型中的OFF位(0x04)被设置为指示存在Offset字段。设置为1时,将显示“偏移”字段。设置为0时,“偏移”字段不存在,并且“流数据”以0的偏移量开始(也就是说,该帧包含流的第一个字节,或者不包含数据的流的末尾)。
  • 帧类型中的LEN位(0x02)被设置为指示存在“长度”字段。如果此位设置为0,则长度字段不存在,流数据字段扩展到数据包的末尾。如果此位设置为1,则显示“长度”字段。
  • FIN位(0x01)表示帧标记了流的结尾。流的最终大小是偏移量和该帧的长度之和。
    如果端点收到尚未创建的本地启动流或仅发送流的STREAM帧,则端点必须以错误STREAM_STATE_ERROR终止连接。

STREAM帧的格式如图32所示。

STREAM Frame {
  Type (i) = 0x08..0x0f,
  Stream ID (i),
  [Offset (i)],
  [Length (i)],
  Stream Data (..),
}

STREAM帧包含以下字段:

  • 流ID:长度可变的整数,指示流的流ID;参见 第2.1节。
  • 抵消:一个可变长度的整数,指定此STREAM帧中数据的流中的字节偏移量。这个字段存在时断开位被设置为1。当偏移字段不存在,则偏移量为0。
  • 长度:一个可变长度的整数,指定此STREAM帧中Stream Data字段的长度。当LEN位设置为1时,此字段存在。当LEN位设置为0时,流数据字段将占用数据包中的所有剩余字节。
  • 流数据:指定流中要传送的字节。
    当流数据字段的长度为0时,STREAM帧中的偏移量是将要发送的下一个字节的偏移量。

流中的第一个字节的偏移量为0。流中传递的最大偏移量-偏移量和数据长度的总和-不能超过2 ^ 62-1,因为无法为该数据提供流控制功劳。超过此限制的帧的接收必须视为FRAME_ENCODING_ERROR或FLOW_CONTROL_ERROR类型的连接错误。

19.9. MAX_DATA Frame

在流控制中使用MAX_DATA Frame(类型= 0x10)来通知对等端整个连接上可以发送的最大数据量。

MAX_DATA Frame的格式如图33所示。

MAX_DATA Frame {
  Type (i) = 0x10,
  Maximum Data (i),
}

MAX_DATA Frame包含以下字段:

  • 最大数据:一个长度可变的整数,以字节为单位指示可以在整个连接上发送的最大数据量。
    在STREAM帧中发送的所有数据均计入此限制。所有流(包括处于终端状态的流)的最终大小之和不得超过接收者通告的值。如果端点接收的数据多于其已发送的最大数据值,则它必须以FLOW_CONTROL_ERROR错误终止连接。这包括违反早期数据中记住的限制;见7.4.1节。

19.10. MAX_STREAM_DATA Frame

在流控制中使用MAX_STREAM_DATA Frame(类型= 0x11)来通知对等端可以在流上发送的最大数据量。

可以在Recv状态下为流发送MAX_STREAM_DATA Frame。请参阅 第3.1节。接收尚未创建的本地启动流的MAX_STREAM_DATA Frame,必须将其视为STREAM_STATE_ERROR类型的连接错误。接收到仅接收流的MAX_STREAM_DATA Frame的端点务必以错误STREAM_STATE_ERROR终止连接。

MAX_STREAM_DATA Frame的格式如图34所示。

MAX_STREAM_DATA Frame {
  Type (i) = 0x11,
  Stream ID (i),
  Maximum Stream Data (i),
}

MAX_STREAM_DATA Frame包含以下字段:

  • 流ID:受影响的流的流ID编码为可变长度整数。
  • 最大流数据:长度可变的整数,以字节为单位指示可以在已标识的流上发送的最大数据量。
    在将数据计数到此限制时,端点会考虑在流上发送或接收的数据的最大接收偏移量。丢失或重新排序可能意味着流上接收到的最大偏移量可能大于该流上接收到的数据的总大小。接收STREAM帧可能不会增加最大接收偏移量。

在流上发送的数据不得超过接收方公布的最大最大流数据值。如果端点接收的数据多于为受影响的流发送的最大最大流数据,则端点必须终止并产生FLOW_CONTROL_ERROR错误。这包括违反早期数据中记住的限制;见7.4.1节。

19.11. MAX_STREAMS Frames

MAX_STREAMS帧(类型= 0x12或0x13)将允许打开的给定类型的流的累积数量通知对等方。类型为0x12的MAX_STREAMS帧适用于双向流,类型为0x13的MAX_STREAMS帧适用于单向流。

MAX_STREAMS帧的格式如图35所示;

MAX_STREAMS Frame {
  Type (i) = 0x12..0x13,
  Maximum Streams (i),
}

MAX_STREAMS帧包含以下字段:

  • 最大流:在连接的生存期内可以打开的相应类型的流的累积数量的计数。该值不能超过2 ^ 60,因为不可能对大于2 ^ 62-1的流ID进行编码。允许打开大于此限制的流的帧的接收必须视为FRAME_ENCODING_ERROR。
    丢失或重新排序可能会导致接收到MAX_STREAMS帧,该帧声明的流限制比端点先前接收到的流限制要低。不增加流限制的MAX_STREAMS帧必须被忽略。

端点打开的流不得超过其对等方设置的当前流限制所允许的流。例如,允许单向流限制为3的服务器打开流3、7和11,但不允许打开流15。如果对等方打开的流比允许的多,则端点​​必须终止并出现STREAM_LIMIT_ERROR错误。这包括违反早期数据中记住的限制;见7.4.1节。

请注意,这些帧(和相应的传输参数)没有描述可以同时打开的流的数量。该限制包括已关闭和已打开的流。

19.12. DATA_BLOCKED Frame

发送方在希望发送数据时应发送DATA_BLOCKED Frame(类型= 0x14),但由于连接级流控制而无法发送;参见 第4节。DATA_BLOCKED Frame可用作调整流控制算法的输入;参见第4.2节。

DATA_BLOCKED Frame的格式如图36所示。

DATA_BLOCKED Frame {
  Type (i) = 0x14,
  Maximum Data (i),
}

DATA_BLOCKED Frame包含以下字段:

  • 最大数据:一个可变长度的整数,指示发生阻塞的连接级别限制。

19.13. STREAM_DATA_BLOCKED Frame

发送方在希望发送数据时应发送STREAM_DATA_BLOCKED Frame(类型= 0x15),但由于流级别的流控制而无法发送。该帧类似于DATA_BLOCKED(第19.12节)。

接收到仅发送流的STREAM_DATA_BLOCKED Frame的端点务必终止,并出现错误STREAM_STATE_ERROR。

STREAM_DATA_BLOCKED Frame的格式如图37所示 。

STREAM_DATA_BLOCKED Frame {
  Type (i) = 0x15,
  Stream ID (i),
  Maximum Stream Data (i),
}

STREAM_DATA_BLOCKED Frame包含以下字段:

  • 流ID:长度可变的整数,指示由于流控制而被阻塞的流。
  • 最大流数据:一个可变长度的整数,指示发生阻塞的流的偏移量。

19.14. STREAMS_BLOCKED Frame

当发送者希望打开一个流时,应该发送一个STREAMS_BLOCKED Frame(类型= 0x16或0x17),但由于对端设置的最大流限制而不能发送;参见第19.11节。类型0x16的STREAMS_BLOCKED Frame用于指示已达到双向流限制,类型0x17的STREAMS_BLOCKED Frame用于指示已达到单向流限制。

STREAMS_BLOCKED Frame不会打开流,但是会通知对等端需要新的流,并且流限制阻止了该流的创建。

STREAMS_BLOCKED Frame的格式如图38所示。

STREAMS_BLOCKED Frame {
  Type (i) = 0x16..0x17,
  Maximum Streams (i),
}

STREAMS_BLOCKED Frame包含以下字段:

  • 最大流:一个可变长度的整数,指示在发送帧时允许的最大流数。该值不能超过2 ^ 60,因为不可能对大于2 ^ 62-1的流ID进行编码。编码较大流ID的帧的接收务必视为STREAM_LIMIT_ERROR或FRAME_ENCODING_ERROR。

19.15. NEW_CONNECTION_ID Frames

端点发送NEW_CONNECTION_ID Frame(类型= 0x18),以向其对等方提供备用连接ID,这些ID可用于在迁移连接时中断可链接性;参见9.5节。

NEW_CONNECTION_ID Frame的格式如图39所示。

NEW_CONNECTION_ID Frame {
  Type (i) = 0x18,
  Sequence Number (i),
  Retire Prior To (i),
  Length (8),
  Connection ID (8..160),
  Stateless Reset Token (128),
}

NEW_CONNECTION_ID框架包含以下字段:

  • 序列号:发送方分配给连接ID的序列号,编码为可变长度整数;见5.1.1节。
  • 退休之前:一个可变长度的整数,指示应淘汰哪些连接ID;见5.1.2节。
  • 长度:一个8位无符号整数,包含连接ID的长度。小于1且大于20的值无效,必须将其视为FRAME_ENCODING_ERROR类型的连接错误。
  • 连接ID:指定长度的连接ID。
  • 无状态重置令牌:使用相关联的连接ID时将用于无状态重置的128位值;见10.3节。
    如果端点当前要求其对等方发送具有零长度目标连接ID的数据包,则不得发送该帧。将连接ID​​的长度更改为零长度或从零长度更改,很难确定连接ID的值何时更改。发送长度为零长度的目标连接ID的包的端点必须将收到NEW_CONNECTION_ID Frame视为PROTOCOL_VIOLATION类型的连接错误。

传输错误,超时和重新传输可能导致相同的NEW_CONNECTION_ID Frame被多次接收。多次接收相同的帧不得将其视为连接错误。接收者可以使用NEW_CONNECTION_ID Frame中提供的序列号来处理多次接收相同的NEW_CONNECTION_ID Frame。

如果端点接收到一个NEW_CONNECTION_ID Frame,该帧以不同的无状态重置令牌或不同的序列号重复先前发布的连接ID,或者如果序列号用于不同的连接ID,则端点可以将该收据视为类型的连接错误PROTOCOL_VIOLATION。

“优先使用之前退休”字段适用于在连接建立期间建立的连接ID和preferred_address传输参数;默认值为0。见 5.1.2节。“在此之前退休”字段必须小于或等于“序列号”字段。接收到大于序列号的值,必须将其视为FRAME_ENCODING_ERROR类型的连接错误。

一旦发送方指示“先于退休”值,则在后续的NEW_CONNECTION_ID Frame中发送的较小值将无效。接收方必须忽略任何不会增加最大的“先验退休”值的“先验退休”字段。

接收到序列号小于先前接收的NEW_CONNECTION_ID Frame的Retire Before To字段的NEW_CONNECTION_ID Frame的端点必须发送对应的RETIRE_CONNECTION_ID Frame,以退休新接收的连接ID,除非它已经对该序列号这样做了。

19.16. RETIRE_CONNECTION_ID Frames

端点发送RETIRE_CONNECTION_ID Frame(类型= 0x19)以指示其将不再使用其对等方发出的连接ID。这可能包括握手期间提供的连接ID。发送RETIRE_CONNECTION_ID Frame还可以作为对等体的请求,以发送其他连接ID供将来使用。参见第5.1节。可以使用NEW_CONNECTION_ID框架将新的连接ID传递给对等方(第19.15节)。

退出连接ID将使与该连接ID相关联的无状态重置令牌无效。

RETIRE_CONNECTION_ID Frame的格式如图40所示 。

RETIRE_CONNECTION_ID Frame {
  Type (i) = 0x19,
  Sequence Number (i),
}

RETIRE_CONNECTION_ID框架包含以下字段:

  • 序列号:即将淘汰的连接ID的序号;见5.1.2节。
    收到序列号大于先前发送给对等方的序列号的RETIRE_CONNECTION_ID Frame,必须将其视为PROTOCOL_VIOLATION类型的连接错误。

在RETIRE_CONNECTION_ID Frame中指定的序列号不得引用包含该帧的数据包的目标连接ID字段。对等体可以将其视为PROTOCOL_VIOLATION类型的连接错误。

如果端点由其对等方提供了零长度的连接ID,则端点无法发送该帧。提供零长度连接ID的端点必须将收到RETIRE_CONNECTION_ID Frame视为PROTOCOL_VIOLATION类型的连接错误。

19.17. PATH_CHALLENGE Frames

端点可以使用PATH_CHALLENGE帧(类型= 0x1a)来检查对等节点的可达性并在连接迁移期间进行路径验证。

PATH_CHALLENGE帧的格式如图41所示。

PATH_CHALLENGE Frame {
  Type (i) = 0x1a,
  Data (64),
}

PATH_CHALLENGE框架包含以下字段:

  • 数据:这个8字节字段包含任意数据。
    在PATH_CHALLENGE帧中包含64位熵可以确保比正确猜测值更容易接收数据包。

该帧的接收者必须生成一个包含相同数据的PATH_RESPONSE帧(第19.18节)。

19.18. PATH_RESPONSE Frames

响应于PATH_CHALLENGE帧,发送PATH_RESPONSE帧(类型= 0x1b)。

PATH_RESPONSE帧的格式如图42所示,与PATH_CHALLENGE帧相同(第19.17节)。

PATH_RESPONSE Frame {
  Type (i) = 0x1b,
  Data (64),
}

如果PATH_RESPONSE帧的内容与端点先前发送的PATH_CHALLENGE帧的内容不匹配,则端点可以产生PROTOCOL_VIOLATION类型的连接错误。

19.19. CONNECTION_CLOSE Frames

端点发送CONNECTION_CLOSE帧(类型= 0x1c或0x1d)以通知其对等方该连接正在关闭。帧类型为0x1c的CONNECTION_CLOSE仅用于在QUIC层上发出错误或没有错误(带有NO_ERROR代码)的信号。类型为0x1d的CONNECTION_CLOSE帧用于向使用QUIC的应用程序发出错误信号。

如果存在未显式关闭的打开流,则在关闭连接时将隐式关闭它们。

CONNECTION_CLOSE帧的格式如图43所示。

CONNECTION_CLOSE Frame {
  Type (i) = 0x1c..0x1d,
  Error Code (i),
  [Frame Type (i)],
  Reason Phrase Length (i),
  Reason Phrase (..),
}

CONNECTION_CLOSE框架包含以下字段:

  • 错误代码:可变长度整数错误代码,指示关闭此连接的原因。类型0x1c的CONNECTION_CLOSE帧使用第20.1节中定义的空间中的代码。类型为0x1d的CONNECTION_CLOSE帧使用来自应用协议错误代码空间的代码;参见 第20.2节。
  • 镜框类型:一个可变长度的整数,用于编码触发错误的帧的类型。当帧类型未知时,将使用0值(相当于对PADDING Frame的提及)。CONNECTION_CLOSE的特定于应用程序的变体(类型0x1d)不包括此字段。
  • 原因短语长度:一个可变长度的整数,以字节为单位指定原因短语的长度。因为CONNECTION_CLOSE帧不能在数据包之间分割,所以任何对数据包大小的限制都会限制原因短语的可用空间。
  • 原因短语:为何关闭连接的易于理解的解释。如果发件人选择不提供错误代码以外的详细信息,则长度可以为零。这应该是UTF-8编码的字符串[ RFC3629 ]。
    只能使用0-RTT或1-RTT数据包发送CONNECTION_CLOSE(类型0x1d)的特定于应用的变体。参见第12.5节。当应用程序希望在握手期间放弃连接时,端点可以在初始或握手数据包中发送带有错误代码APPLICATION_ERROR的CONNECTION_CLOSE帧(类型0x1c)。

19.20. HANDSHAKE_DONE Frames

服务器使用HANDSHAKE_DONE帧(类型= 0x1e)向客户端发送握手确认信号。

HANDSHAKE_DONE帧的格式如图44所示,这表明HANDSHAKE_DONE帧不包含任何内容。

HANDSHAKE_DONE Frame {
  Type (i) = 0x1e,
}

HANDSHAKE_DONE帧只能由服务器发送。服务器在完成握手之前不得发送HANDSHAKE_DONE帧。服务器必须将收到HANDSHAKE_DONE帧视为PROTOCOL_VIOLATION类型的连接错误。

19.21. 扩展框架

QUIC帧不使用自描述编码。因此,端点必须先了解所有帧的语法,然后才能成功处理数据包。这样可以对帧进行有效的编码,但是这意味着端点无法发送其对等方未知的帧。

希望使用新型帧的QUIC扩展必须首先确保对等方能够理解该帧。端点可以使用传输参数来表示其接收扩展帧类型的意愿。一个传输参数可以指示对一种或多种扩展帧类型的支持。

除非明确定义了组合的行为,否则修改或替换核心协议功能(包括帧类型)的扩展将很难与修改或替换相同功能的其他扩展组合。这些扩展应该定义它们与修改相同协议组件的先前定义的扩展的交互。

扩展帧必须被拥塞控制,并且必须导致发送一个ACK帧。例外情况是扩展帧替换或补充了ACK帧。除非扩展中指定,否则扩展框架不包括在流控制中。

IANA注册中心用于管理帧类型的分配;参见 第22.3节。

20. Error Codes

QUIC传输错误代码和应用程序错误代码是62位无符号整数。

20.1. 传输错误代码

代码 说明
NO_ERROR(0x0) 端点将其与CONNECTION_CLOSE一起使用,以发出在没有任何错误的情况下突然关闭连接的信号。
INTERNAL_ERROR(0x1) 端点遇到内部错误,无法继续连接。
CONNECTION_REFUSED(0x2) 服务器拒绝接受新连接。
FLOW_CONTROL_ERROR(0x3) 端点收到的数据超过其公布的数据限制所允许的数量;参见第4节。
STREAM_LIMIT_ERROR(0x4) 端点接收到流标识符的帧,该帧超出了其针对相应流类型的广告流限制。
STREAM_STATE_ERROR(0x5) 端点收到的流帧不在允许状态下;参见第3节。
FINAL_SIZE_ERROR(0x6) 端点接收到一个STREAM帧,其中包含的数据超过了先前确定的最终大小。或端点接收到的STREAM帧或RESET_STREAM Frame的最终大小小于已接收的流数据的大小。或端点收到的STREAM帧或RESET_STREAM Frame包含与已建立的帧不同的最终大小。
FRAME_ENCODING_ERROR(0x7) 端点接收到格式错误的帧。例如,未知类型的帧或确认范围比数据包的其余部分可能携带的ACK帧多的ACK帧。
TRANSPORT_PARAMETER_ERROR(0x8) 端点接收到格式错误的传输参数,包括无效值,即使是强制性的,也不存在,即使被禁止也存在,或者存在错误,传输参数仍然不存在。
CONNECTION_ID_LIMIT_ERROR(0x9) 对等方提供的连接ID的数量超过了公布的active_connection_id_limit。
PROTOCOL_VIOLATION(0xa) 端点检测到符合协议的错误,但更具体的错误代码未涵盖该错误。
INVALID_TOKEN(0xb) 服务器收到了包含无效令牌字段的客户端Initial。
APPLICATION_ERROR(0xc) 应用程序或应用程序协议导致连接被关闭。
CRYPTO_BUFFER_EXCEEDED(0xd) 端点在CRYPTO Frame中收到的数据量超出其缓冲能力。
KEY_UPDATE_ERROR(0xe) 端点在执行密钥更新时检测到错误;请参阅[ QUIC-TLS ]的第6节 。
AEAD_LIMIT_REACHED(0xf) 端点已达到给定连接使用的AEAD算法的机密性或完整性限制。
CRYPTO_ERROR(0x1XX) 加密握手失败。保留256个值的范围,以携带特定于所使用的加密握手的错误代码。在[ QUIC-TLS ]的4.8节中介绍了使用TLS进行加密握手时发生的错误的代码。

有关注册新错误代码的详细信息,请参见第22.4节。

在定义这些错误代码时,应用了几种原理。可能要求接收者采取特定措施的错误条件将获得唯一代码。代表一般情况的错误将使用特定的代码。不存在这些条件时,将使用错误代码来标识堆栈的一般功能,例如流量控制或传输参数处理。最后,针对无法实现或不愿使用更具体代码的情况提供了通用错误。

20.2. 应用协议错误代码

应用程序错误代码的管理留给应用程序协议。应用协议错误代码用于RESET_STREAM Frame(第19.4节),STOP_SENDING Frame(第19.5节)和类型为0x1d的CONNECTION_CLOSE帧(第19.19节)。