RFC 9000 QUIC 传输协议 2021 年 5 月
Iyengar 与 Thomson 标准轨道 [页]
流:
互联网工程任务组(IETF)
RFC:
9000
类别:
标准轨道
发布:
ISSN:
2070-1721
作者:
J. Iyengar,编。
Fastly
M. Thomson,编。
Mozilla

RFC 9000

QUIC:一种基于 UDP 的多路复用安全传输

摘要

本文档定义了 QUIC 传输协议的核心。QUIC 为应用提供 受流量控制的流,用于结构化通信、低延迟连接建立 以及网络路径迁移。QUIC 包含安全措施,可在一系列 部署情形中确保机密性、完整性和可用性。配套文档描述了 用于密钥协商的 TLS 集成、丢包检测以及一个示例性的 拥塞控制算法。

本备忘录的状态

这是一份互联网标准轨道文档。

本文档是互联网工程任务组(IETF)的成果。它代表了 IETF 社区的共识。它已经接受公开评审,并已由 互联网工程指导组(IESG)批准发布。有关 互联网标准的更多信息可参见 RFC 7841 第 2 节。

关于本文档当前状态、任何勘误以及如何提供 反馈的信息,可在 https://www.rfc-editor.org/info/rfc9000 获取。

目录

1. 概述

QUIC 是一种安全的通用传输协议。本文档定义了 QUIC 的版本 1,它符合 [QUIC-INVARIANTS] 中定义的 QUIC 版本无关 属性。

QUIC 是一种面向连接的协议,它会在客户端与服务器之间创建有状态的 交互。

QUIC 握手结合了加密参数和传输参数的协商。QUIC 集成了 TLS 握手 [TLS13], 但使用了 定制的帧格式来保护分组。TLS 与 QUIC 的集成在 [QUIC-TLS] 中有更详细的描述。握手的 结构允许尽早 交换应用数据。这包括一个让客户端立即发送数据的选项 (0-RTT),这需要某种形式的先前 通信或配置才能启用。

端点通过交换 QUIC 分组在 QUIC 中进行通信。多数分组包含 帧,帧在端点之间携带控制信息和应用数据。 QUIC 会认证每个分组的整体,并尽可能多地加密每个 分组。QUIC 分组承载在 UDP 数据报中 [UDP],以便更好地部署到现有系统 和 网络中。

应用协议通过流在 QUIC 连接上交换信息, 流是有序的字节序列。可以创建两类流: 双向流,允许两个端点都发送数据;以及 单向流,只允许单个端点发送数据。使用一种 基于信用的方案来限制流的创建,并界定可发送的 数据量。

QUIC 提供实现可靠交付和拥塞控制所需的反馈。 用于检测并从数据丢失中恢复的算法在 第 6 节,见 [QUIC-RECOVERY] 中描述。QUIC 依赖 拥塞 控制来避免网络拥塞。一个示例性的拥塞控制算法 在 第 7 节,见 [QUIC-RECOVERY] 中描述。

QUIC 连接并不严格绑定到单一网络路径。连接 迁移使用连接标识符,使连接能够转移到新的 网络路径。在此版本的 QUIC 中,只有客户端能够迁移。这种 设计还允许连接在网络拓扑或 地址映射发生变化后继续存在,例如 NAT 重新绑定可能造成的变化。

连接一旦建立,就会提供多种用于连接终止的选项。 应用可以管理优雅关闭,端点可以协商超时 周期,错误可以导致连接立即拆除,而无状态 机制可在某个端点丢失状态后终止连接。

1.1. 文档结构

本文档描述了 QUIC 核心协议,其结构如下:

  • 流是 QUIC 提供的基本服务抽象。

  • 连接是 QUIC 端点进行通信的上下文。

  • 分组和帧是 QUIC 用于通信的基本单位。

    • 第 12 节描述与 分组和帧相关的概念,
    • 第 13 节定义数据 传输、重传和 确认的模型,并且
    • 第 14 节规定管理 承载 QUIC 分组的数据报 大小的规则。
  • 最后,QUIC 协议元素的编码细节在以下章节中描述:

配套文档描述了 QUIC 的丢包检测和拥塞控制 [QUIC-RECOVERY],以及 TLS 和 其他加密机制的使用 [QUIC-TLS]

本文档定义 QUIC 版本 1,它符合 [QUIC-INVARIANTS] 中的协议 不变式。

要引用 QUIC 版本 1,请引用本文档。对 QUIC 有限的 版本无关属性集合的引用可以引用 [QUIC-INVARIANTS]

1.2. 术语与定义

本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、 “SHALL NOT”、“SHOULD”、 “SHOULD NOT”、“RECOMMENDED”、“NOT RECOMMENDED”、“MAY”和“OPTIONAL” 应按 BCP 14 [RFC2119] [RFC8174] 中的描述解释,但仅在它们以全大写形式出现时才如此,如此处所示。

本文档中常用的术语如下所述。

QUIC:

本文档描述的传输协议。QUIC 是名称, 不是 首字母缩写。

端点:

能够通过生成、 接收 和处理 QUIC 分组来参与 QUIC 连接的实体。QUIC 中只有两类端点: 客户端和服务器。

客户端:

发起 QUIC 连接的端点。

服务器:

接受 QUIC 连接的端点。

QUIC 分组:

QUIC 的一个完整可处理单元,可以封装在 UDP 数据报中。一个或多个 QUIC 分组可以封装在单个 UDP 数据报中。

触发 ACK 的分组:

包含 ACK、PADDING 和 CONNECTION_CLOSE 以外帧的 QUIC 分组。这些分组会使接收方发送确认;见 第 13.2.1 节

帧:

结构化协议信息的一个单元。存在多种 帧类型, 每种帧类型携带不同的信息。帧包含在 QUIC 分组中。

地址:

在不加限定地使用时,表示网络路径一端的 IP 版本、IP 地址和 UDP 端口号的元组。

连接 ID:

用于在端点处标识 QUIC 连接的 标识符。 每个端点都会选择一个或多个连接 ID,供其对等方包含在 发往该端点的分组中。此值对对等方是不透明的。

流:

QUIC 连接内的有序字节的单向或双向通道。一个 QUIC 连接可以承载多个并发流。

应用:

使用 QUIC 发送和接收数据的实体。

本文档使用术语“QUIC 分组”、“UDP 数据报”和“IP 分组” 来指代各自协议的单位。也就是说,一个或多个 QUIC 分组可以封装在 UDP 数据报中,而 UDP 数据报又封装在 IP 分组中。

1.3. 记号约定

本文档中的分组和帧图使用自定义格式。该格式的目的 是概括而非定义协议元素。正文定义结构的 完整语义和细节。

复杂字段先给出名称,然后跟随一组由一对 匹配大括号包围的字段。该列表中的每个字段以逗号分隔。

单个字段包含长度信息,以及关于固定 值、可选性或重复的指示。单个字段使用以下 记号约定,所有长度均以比特为单位:

x (A):

表示 x 的长度为 A 比特

x (i):

表示 x 使用 第 16 节中描述的可变长度 编码保存整数值

x (A..B):

表示 x 可以是从 A 到 B 的任意长度;A 可以省略 以表示 最小值为零比特,B 可以省略以表示没有设定上限; 这种格式中的值总是以字节边界结束

x (L) = C:

表示 x 具有固定值 C;x 的长度由 L 描述,L 可以使用上面的任意长度形式

x (L) = C..D:

表示 x 的值位于从 C 到 D(含端点)的范围内, 长度由 L 描述,如上所述

[x (L)]:

表示 x 是可选的,且长度为 L

x (L) ...:

表示 x 重复零次或多次,且每个 实例的 长度为 L

本文档使用网络字节序(即大端)值。字段 从每个字节的高位比特开始放置。

按照约定,单个字段通过使用 复杂字段的名称来引用该复杂字段。

图 1提供了一个示例:

示例结构 {
  一比特字段 (1),
  具有固定值的 7 比特字段 (7) = 61,
  具有可变长度整数的字段 (i),
  任意长度字段 (..),
  可变长度字段 (8..24),
  具有最小长度的字段 (16..),
  具有最大长度的字段 (..128),
  [可选字段 (64)],
  重复字段 (8) ...,
}
图 1示例格式

当正文中引用单比特字段时,可以通过使用承载该字段的字节值 并将该字段的值置位,来明确该字段的位置。例如,值 0x80 可以用于指代 字节最高有效位中的单比特字段,例如 图 1 中的 一比特字段。

2.

QUIC 中的流向应用提供轻量的有序字节流抽象。 流可以是单向的,也可以是双向的。

流可以通过发送数据来创建。与流 管理相关的其他过程——结束、取消和管理流量控制——都被设计为 施加最小开销。例如,单个 STREAM 帧 (第 19.8 节)可以打开一个流、为其承载数据并关闭该流。 流也可以 长时间存在,并可持续整个连接期间。

流可以由任一端点创建,可以与其他流交错地并发发送 数据,并且可以被取消。QUIC 不提供任何 确保不同流上字节之间有序性的手段。

QUIC 允许任意数量的流并发运行,并允许在任何流上发送 任意数量的数据,但受流量控制 约束和流限制的约束;见 第 4 节

2.1. 流类型与 标识符

流可以是单向的,也可以是双向的。单向流在一个方向上携带 数据:从流的发起者到其对等方。 双向流允许在两个方向上发送数据。

流在一个连接内由一个数值标识,称为 流 ID。流 ID 是一个 62 比特整数(0 到 262-1),它在 一个连接上的所有流中唯一。流 ID 编码为 可变长度整数;见 第 16 节。QUIC 端点 MUST NOT 在一个连接内重用流 ID。

流 ID 的最低有效位(0x01)标识 该流的发起者。客户端发起的流具有偶数编号的流 ID(该 比特设为 0),服务器发起的流具有奇数编号的流 ID(该 比特设为 1)。

流 ID 的次低有效位(0x02)区分 双向流(该比特设为 0)和单向流(该 比特设为 1)。

因此,流 ID 中的两个最低有效位将流标识为 四种类型之一,如 表 1 所汇总。

表 1流 ID 类型
比特 流类型
0x00 客户端发起,双向
0x01 服务器发起,双向
0x02 客户端发起,单向
0x03 服务器发起,单向

每种类型的流空间从最小值(分别为 0x00 到 0x03) 开始;每种类型的后续流以数值 递增的流 ID 创建。乱序使用的流 ID 会导致 该类型中所有编号更低的流也被打开。

2.2. 发送与接收 数据

STREAM 帧(第 19.8 节) 封装应用发送的数据。端点使用 STREAM 帧中的 Stream ID 和 Offset 字段将数据按顺序放置。

端点 MUST 能够将流数据作为有序 字节流交付给应用。交付有序字节流要求端点缓冲 任何乱序接收的数据,直至所通告的流量控制限制。

QUIC 未对乱序交付流数据作出具体 规定。不过,实现 MAY 选择向接收应用提供 乱序交付数据的能力。

端点可能会多次在相同流偏移处接收到某个流的数据。 已经接收过的数据可以丢弃。在给定偏移处的数据 如果被多次发送,则 MUST NOT 改变;端点 MAY 将在一个流内相同偏移处接收到不同数据 视为类型为 PROTOCOL_VIOLATION 的连接 错误。

流是一种有序字节流抽象,QUIC 看不到其他结构。 当数据被传输、因分组丢失而重传,或在接收方交付给 应用时,不期望保留 STREAM 帧边界。

端点在任何流上发送数据前,MUST NOT 不确保其处于 对等方设置的流量控制限制之内。流量控制在 第 4 节中有详细描述。

2.3. 流优先级

如果正确确定分配给流的资源优先级,流复用会对应用性能产生 显著影响。

QUIC 不提供交换优先级信息的机制。 相反,它依赖从应用接收优先级信息。

QUIC 实现 SHOULD 提供某些方式, 使应用能够指示 流的相对优先级。实现使用应用提供的信息 来确定如何向活跃流分配资源。

2.4. 流上的操作

本文档不定义 QUIC 的 API;而是定义一组 应用协议可以依赖的流上的功能。应用 协议可以假定 QUIC 实现提供的接口 包含本节描述的操作。为特定应用协议 使用而设计的实现,可能只提供该协议使用的那些操作。

在流的发送部分,应用协议可以:

  • 写入数据,并了解何时已成功保留流量控制 信用 (第 4.1 节),以发送所写入的 数据;
  • 结束流(干净终止),从而产生一个 设置了 FIN 比特的 STREAM 帧 (第 19.8 节);以及
  • 重置流(突然终止),如果该流尚未处于 终止状态,则产生一个 RESET_STREAM 帧 (第 19.4 节)。

在流的接收部分,应用协议可以:

  • 读取数据;以及
  • 中止读取流并请求关闭, 可能产生 STOP_SENDING 帧(第 19.5 节)。

应用协议还可以请求获知流上的状态变化, 包括对等方何时打开或重置了流、对等方 何时中止读取流、何时有新数据可用,以及何时由于流量控制而可以或 不可以向流写入数据。

3. 流状态

本节按流的发送或接收组件来描述流。 这里描述了两个状态机:一个用于端点 传输数据的流(第 3.1 节),另一个用于端点 接收数据的流(第 3.2 节)。

单向流根据流类型和端点角色,使用发送或接收状态机。 双向流在两个端点都使用这两个 状态机。在大多数情况下,无论流是单向还是双向,这些状态 机的使用方式都是相同的。对于双向 流,打开流的条件稍微更复杂,因为发送侧或接收侧任一侧的打开 都会使该流在两个方向上打开。

本节中显示的状态机大体上是资料性的。本文档使用流状态 来描述不同类型的帧何时以及如何 可以被发送,以及在接收到不同类型 的帧时所期望的反应。虽然这些状态机旨在有助于 实现 QUIC,但这些状态并不意图约束 实现。只要实现的行为与实现这些 状态的实现一致,该实现就可以定义不同的状态机。

3.1. 发送流状态

图 2显示流中向对等方 发送数据的部分的状态。

       o
       | 创建流(发送)
       | 对等方创建双向流
       v
   +-------+
   | Ready | 发送 RESET_STREAM
   |       |-----------------------.
   +-------+                       |
       |                           |
       | 发送 STREAM /             |
       |      STREAM_DATA_BLOCKED  |
       v                           |
   +-------+                       |
   | Send  | 发送 RESET_STREAM     |
   |       |---------------------->|
   +-------+                       |
       |                           |
       | 发送 STREAM + FIN         |
       v                           v
   +-------+                   +-------+
   | Data  | 发送 RESET_STREAM | Reset |
   | Sent  |------------------>| Sent  |
   +-------+                   +-------+
       |                           |
       | 接收全部 ACK              | 接收 ACK
       v                           v
   +-------+                   +-------+
   | Data  |                   | Reset |
   | Recvd |                   | Recvd |
   +-------+                   +-------+
图 2流的发送部分的状态

由端点发起的流的发送部分(客户端的类型 0 和 2,服务器的类型 1 和 3)由应用打开。“Ready”状态表示 一个新创建的流,它能够接受来自 应用的数据。流数据可以在此状态下被缓冲,以准备 发送。

发送第一个 STREAM 或 STREAM_DATA_BLOCKED 帧会使流的发送部分 进入“Send”状态。实现可以选择推迟 为流分配流 ID,直到它发送第一个 STREAM 帧并 进入此状态,这样可以实现更好的流优先级处理。

由对等方发起的双向流的发送部分(服务器的类型 0, 客户端的类型 1)在接收部分 被创建时从“Ready”状态开始。

在“Send”状态中,端点在 STREAM 帧中传输——并在必要时重传—— 流数据。端点遵守其对等方设置的流量控制限制, 并继续接受和处理 MAX_STREAM_DATA 帧。处于 “Send”状态的端点如果因流量控制限制而被阻塞 无法发送(第 4.1 节),就会生成 STREAM_DATA_BLOCKED 帧。

在应用指示所有流数据均已发送,并且发送了 包含 FIN 比特的 STREAM 帧之后,该流的发送部分进入 “Data Sent”状态。从此状态起,端点仅在必要时重传流数据。 端点不需要为处于此状态的流检查流量控制限制或发送 STREAM_DATA_BLOCKED 帧。在对等方收到最终流偏移之前, 仍可能收到 MAX_STREAM_DATA 帧。对于此状态下的流, 端点可以安全地忽略从其对等方收到的任何 MAX_STREAM_DATA 帧。

一旦所有流数据都已被成功确认,该流的发送部分 进入“Data Recvd”状态,这是一个终止状态。

从“Ready”、“Send”或“Data Sent”中的任意状态,应用 都可以发出信号,表示它希望放弃流数据的传输。或者, 端点可能会从其对等方接收到 STOP_SENDING 帧。在这两种情况下, 端点都会发送 RESET_STREAM 帧,这会使流进入 “Reset Sent”状态。

端点 MAY 将 RESET_STREAM 作为第一个 提及某个流的帧发送; 这会使该流的发送部分打开,并立即 转换到“Reset Sent”状态。

一旦包含 RESET_STREAM 的分组被确认,该流的发送 部分进入“Reset Recvd”状态,这是一个终止状态。

3.2. 接收流 状态

图 3显示流中 从对等方接收数据的部分的状态。流的接收部分的状态仅 镜像对等方处流的发送部分的一部分状态。 流的接收部分不跟踪发送部分中无法被观察到的状态, 例如“Ready”状态。相反,流的接收部分 跟踪向应用交付数据的情况,其中有些无法由 发送方观察到。

       o
       | 接收 STREAM / STREAM_DATA_BLOCKED / RESET_STREAM
       | 创建双向流(发送)
       | 接收 MAX_STREAM_DATA / STOP_SENDING(双向)
       | 创建编号更高的流
       v
   +-------+
   | Recv  | 接收 RESET_STREAM
   |       |-----------------------.
   +-------+                       |
       |                           |
       | 接收 STREAM + FIN         |
       v                           |
   +-------+                       |
   | Size  | 接收 RESET_STREAM     |
   | Known |---------------------->|
   +-------+                       |
       |                           |
       | 接收全部数据              |
       v                           v
   +-------+ 接收 RESET_STREAM +-------+
   | Data  |--- (可选) ------>| Reset |
   | Recvd |  接收全部数据      | Recvd |
   +-------+<-- (可选) -------+-------+
       |                           |
       | 应用读取全部数据          | 应用读取重置
       v                           v
   +-------+                   +-------+
   | Data  |                   | Reset |
   | Read  |                   | Read  |
   +-------+                   +-------+
图 3流的接收部分的状态

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

对于双向流,当由端点发起的 发送部分(客户端的类型 0,服务器的类型 1)进入“Ready”状态时,接收部分进入“Recv”状态。

当从对等方收到针对某个双向流的 MAX_STREAM_DATA 或 STOP_SENDING 帧时,端点会打开该双向流。对于未打开的流收到 MAX_STREAM_DATA 帧表明远端对等方已经打开该 流,并正在提供流量控制信用。对于未打开的流收到 STOP_SENDING 帧表明远端对等方不再希望在此 流上接收数据。如果分组丢失或重排序,任一帧都可能在 STREAM 或 STREAM_DATA_BLOCKED 帧之前到达。

在创建一个流之前,所有具有更低编号 流 ID 的同类型流 MUST 都必须被创建。这确保 两个端点上的流创建顺序保持一致。

在“Recv”状态中,端点接收 STREAM 和 STREAM_DATA_BLOCKED 帧。传入数据会被缓冲,并可按正确顺序重新组装 以交付给应用。随着应用消耗数据且 缓冲空间变得可用,端点会发送 MAX_STREAM_DATA 帧, 以允许对等方发送更多数据。

当收到带有 FIN 比特的 STREAM 帧时,流的最终大小 即为已知;见 第 4.5 节。该流的接收部分 随后进入 “Size Known”状态。在此状态下,端点不再需要发送 MAX_STREAM_DATA 帧;它只接收流数据的任何重传。

一旦已收到该流的全部数据,接收部分便进入 “Data Recvd”状态。这可能因收到同一个使其转换到 “Size Known”的 STREAM 帧而发生。在收到全部数据之后, 可以丢弃该流的任何 STREAM 或 STREAM_DATA_BLOCKED 帧。

“Data Recvd”状态会持续到流数据已交付给 应用为止。一旦流数据已被交付,流进入“Data Read”状态,这是一个终止状态。

在“Recv”或“Size Known”状态收到 RESET_STREAM 帧会使 流进入“Reset Recvd”状态。这可能导致 向应用交付流数据被中断。

收到 RESET_STREAM 时,所有流数据可能已经被接收 (即处于“Data Recvd”状态)。同样,在收到 RESET_STREAM 帧(“Reset Recvd”状态)之后,剩余的流数据也可能到达。 实现可以自由选择如何处理这种 情况。

发送 RESET_STREAM 意味着端点无法保证 流数据的交付;但是,并没有要求在收到 RESET_STREAM 时 不得交付流数据。实现 MAY 中断 流数据的交付,丢弃任何未被消耗的数据,并发出已收到 RESET_STREAM 的信号。如果流数据已被完整接收并缓冲起来以供 应用读取,则 RESET_STREAM 信号可能被抑制或暂不发出。 如果 RESET_STREAM 被抑制,则该流的接收部分 保持在“Data Recvd”。

一旦应用收到表明该流 已被重置的信号,该流的接收部分就转换到“Reset Read” 状态,这是一个终止状态。

3.3. 允许的帧类型

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

发送方 MUST NOT 从终止状态 (“Data Recvd”或“Reset Recvd”)发送任何这些帧。发送方 MUST NOT 为处于“Reset Sent”状态或任何终止状态的流发送 STREAM 或 STREAM_DATA_BLOCKED 帧——也就是说,在发送 RESET_STREAM 帧之后不得发送。由于承载这些帧的分组 可能延迟交付,接收方可能在任何状态下收到这三种 帧中的任意一种。

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

接收方只在“Recv”状态中发送 MAX_STREAM_DATA 帧。接收方 MAY 在尚未收到 RESET_STREAM 帧的任何状态中发送 STOP_SENDING 帧——也就是说,除“Reset Recvd”或 “Reset Read”以外的状态。不过,在“Data Recvd”状态中发送 STOP_SENDING 帧几乎没有价值,因为所有流数据 都已被接收。由于分组延迟交付,发送方可能在任何状态中收到 这两种帧中的任意一种。

3.4. 双向流 状态

双向流由发送部分和接收部分组成。 实现可以将双向流的状态表示为 发送流状态和接收流状态的组合。最简单的模型将流 表示为:当发送部分或接收部分任一处于非终止状态时为“open”, 当发送流和接收流均处于终止状态时为“closed”。

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

表 2流状态到 HTTP/2 的可能映射
发送部分 接收部分 组合状态
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

3.5. 受请求的状态 转换

如果应用不再关注它在某个 流上正在接收的数据,它可以中止读取该流并指定应用错误码。

如果流处于“Recv”或“Size Known”状态,则传输 SHOULD 通过发送 STOP_SENDING 帧来发出此信号,以促使相反方向的流关闭。 这通常表明接收应用 不再读取它从该流接收的数据,但这并不保证 传入数据会被忽略。

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

STOP_SENDING 帧请求接收端点发送 RESET_STREAM 帧。收到 STOP_SENDING 帧的端点在流处于“Ready”或“Send”状态时 MUST 发送 RESET_STREAM 帧。如果流处于“Data Sent”状态,则端点 MAY 推迟发送 RESET_STREAM 帧,直到包含未完成数据的 分组被确认或被声明为丢失。如果任何未完成数据被声明为丢失,则端点 SHOULD 发送 RESET_STREAM 帧,而不是重传该数据。

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

STOP_SENDING SHOULD 只为尚未被对等方重置的 流发送。STOP_SENDING 对处于“Recv”或“Size Known” 状态的流最有用。

如果包含先前 STOP_SENDING 的分组丢失,则预期端点会 发送另一个 STOP_SENDING 帧。不过,一旦该流的所有流 数据或 RESET_STREAM 帧已经被接收——也就是说, 该流处于“Recv”或“Size Known”以外的任何状态——发送 STOP_SENDING 帧就是不必要的。

希望终止双向流两个方向的端点 可以通过发送 RESET_STREAM 帧来终止一个方向,并可以 通过发送 STOP_SENDING 帧来促使相反方向尽快终止。

4. 流量控制

接收方需要限制它们需要缓冲的数据量, 以防止快速发送方压垮它们,或恶意发送方 消耗大量内存。为了使接收方能够限制 一个连接的内存承诺,流既会被单独进行流量控制, 也会在整个连接范围内进行流量控制。QUIC 接收方控制 发送方在单个流上以及在任何时刻跨所有流所能发送的最大数据量, 如第 4.14.2 节所述。

类似地,为限制连接内的并发性,QUIC 端点控制 其对等方可以发起的传入流的最大累计数量,如 第 4.6 节所述。

在 CRYPTO 帧中发送的数据不会像流 数据那样进行流量控制。QUIC 依赖加密协议实现来避免 过度缓冲数据;见 [QUIC-TLS]。为避免在 多层中过度缓冲,QUIC 实现 SHOULD 为 加密协议实现提供一个接口,以传达其缓冲限制。

4.1. 数据流量控制

QUIC 采用基于限制的流量控制方案,其中接收方通告 它准备在给定流上或整个连接上接收的 总字节数限制。这导致 QUIC 中存在两个层级的数据流量控制:

  • 流流量控制,它通过限制可在每个流上 发送的数据量,防止单个流消耗连接的整个 接收缓冲区。
  • 连接流量控制,它通过限制所有流中 在 STREAM 帧中发送的流数据总字节数,防止发送方 超过接收方的连接缓冲容量。

发送方 MUST NOT 发送超过任一 限制的数据。

接收方在握手期间通过传输参数 为所有流设置初始限制(第 7.4 节)。 随后,接收方发送 MAX_STREAM_DATA 帧(第 19.10 节)或 MAX_DATA 帧 (第 19.9 节)给发送方,以通告更大的 限制。

接收方可以通过发送带有相应流 ID 的 MAX_STREAM_DATA 帧来为某个流通告更大的限制。MAX_STREAM_DATA 帧 表示流的最大绝对字节偏移。接收方可以 根据该流上当前已消耗数据的偏移 来确定要通告的流量控制偏移。

接收方可以通过发送 MAX_DATA 帧来为连接通告更大的限制,该帧表示 所有流的绝对字节偏移之和的最大值。接收方维护 所有流上已接收字节的累计和,用于检查是否违反 已通告的连接或流数据限制。接收方可以根据 所有流上已消耗字节之和来确定要通告的最大数据限制。

一旦接收方为连接或流通告了限制,通告 更小的限制并不是错误,但更小的限制没有效果。

如果发送方违反已通告的连接或流数据限制,接收方 MUST 以类型为 FLOW_CONTROL_ERROR 的错误 关闭连接;关于错误处理的详细信息见 第 11 节

发送方 MUST 忽略任何不会增加 流量控制限制的 MAX_STREAM_DATA 或 MAX_DATA 帧。

如果发送方已经发送到限制为止,它将无法发送新数据 并被视为阻塞。发送方 SHOULD 发送 STREAM_DATA_BLOCKED 或 DATA_BLOCKED 帧,以向接收方表明它有数据要写入但受到 流量控制限制阻塞。如果发送方被阻塞的时间超过 空闲超时(第 10.1 节),即使发送方有可用于传输的数据, 接收方也可能关闭连接。为防止 连接关闭,受流量控制限制的发送方在没有 正在传输的触发 ACK 的分组时,SHOULD 定期发送 STREAM_DATA_BLOCKED 或 DATA_BLOCKED 帧。

4.2. 增加流量 控制限制

实现决定何时以及通告多少信用 于 MAX_STREAM_DATA 和 MAX_DATA 帧中,但本节提供若干考虑事项。

为避免阻塞发送方,接收方 MAY 在一个 往返时间内多次发送 MAX_STREAM_DATA 或 MAX_DATA 帧,或足够早地发送它,以留出时间应对 该帧丢失和随后的恢复。

控制帧会造成连接开销。因此,频繁 发送只带有小幅变化的 MAX_STREAM_DATA 和 MAX_DATA 帧并不可取。另一方面,如果更新不够频繁, 就需要更大幅度地增加限制,以避免阻塞发送方, 这要求接收方承担更大的资源承诺。 在确定通告多大的限制时,资源承诺和开销之间存在权衡。

接收方可以使用自调优机制,根据往返时间估计和 接收应用消耗数据的速率,调节所通告附加信用的频率和数量, 类似于常见 TCP 实现。作为一种优化,端点可以只在有其他帧要发送时 发送与流量控制相关的帧,从而确保流量 控制不会导致发送额外的分组。

被阻塞的发送方并不需要发送 STREAM_DATA_BLOCKED 或 DATA_BLOCKED 帧。因此,接收方 MUST NOT 在发送 MAX_STREAM_DATA 或 MAX_DATA 帧之前等待 STREAM_DATA_BLOCKED 或 DATA_BLOCKED 帧;这样做可能导致发送方在连接余下期间都被阻塞。 即使发送方发送这些帧,等待它们也会导致发送方 至少被阻塞整整一个往返时间。

当发送方在被阻塞后收到信用时,它可能能够 立即发送大量数据,从而造成短期拥塞;关于发送方如何避免这种 拥塞的讨论,见 第 7.7 节,见 [QUIC-RECOVERY]

4.3. 流量控制 性能

如果端点无法确保其对等方在此连接上始终拥有 大于该对等方带宽时延积的可用流量控制 信用,则其接收吞吐量将受限于流量控制。

分组丢失会在接收缓冲区中造成空洞,从而阻止应用 消耗数据并释放接收缓冲空间。

及时发送流量控制限制更新可以提升性能。 仅为提供流量控制更新而发送分组会增加网络 负载并对性能产生不利影响。将流量控制更新与 其他帧(例如 ACK 帧)一起发送,可以降低这些更新的成本。

4.4. 处理流 取消

端点最终需要就每个流上已消耗的流量控制信用量 达成一致,以便能够统计连接级流量控制的 所有字节。

收到 RESET_STREAM 帧后,端点将拆除匹配流的状态, 并忽略随后到达该流的更多数据。

RESET_STREAM 会突然终止流的一个方向。对于 双向 流,RESET_STREAM 不影响相反方向的数据流。两个 端点 MUST 为该流中未终止 方向维护流量控制状态,直到该方向进入终止状态。

4.5. 流最终大小

最终大小是流消耗的流量控制信用量。 假设流上的每个连续字节都只发送一次,则 最终大小就是已发送的字节数。更一般地说, 这是流上发送的最大偏移字节的偏移加一;如果 没有发送任何字节,则为零。

发送方始终可靠地向接收方传达流的最终大小, 无论该流以何种方式终止。最终大小是带有 FIN 标志的 STREAM 帧的 Offset 和 Length 字段之和,需要注意 这些字段可能是隐式的。或者,RESET_STREAM 帧的 Final Size 字段承载此值。这保证两个端点就 发送方在该流上消耗了多少流量控制信用达成一致。

当流的接收部分进入“Size Known”或“Reset Recvd”状态 (第 3 节)时,端点将知道该流的最终大小。接收方 MUST 使用流的最终大小,在其连接级流量控制器中 统计该流上发送的所有字节。

端点 MUST NOT 在流的最终大小处或 之后发送数据。

一旦知道流的最终大小,它就不能改变。如果收到的 RESET_STREAM 或 STREAM 帧表明该流的最终大小发生变化,则端点 SHOULD 以类型为 FINAL_SIZE_ERROR 的错误作出响应; 关于错误处理的详细信息见 第 11 节。接收方 SHOULD 将在最终大小处或之后接收到数据 视为类型为 FINAL_SIZE_ERROR 的错误,即使流已经关闭也是如此。生成这些错误并非 强制要求,因为要求端点生成这些错误也意味着 端点需要为已关闭的流维护最终大小状态, 这可能意味着显著的状态承诺。

4.6. 控制 并发

端点限制对等方可以打开的传入流的累计数量。 只有流 ID 小于 (max_streams * 4 + first_stream_id_of_type) 的流才能被打开;见 表 1。初始 限制在传输参数中设置;见 第 18.2 节。后续限制使用 MAX_STREAMS 帧通告;见 第 19.11 节。单向流和双向流 适用单独的限制。

如果接收到的 max_streams 传输参数或 MAX_STREAMS 帧的值 大于 260,这将允许一个 无法表示为可变长度整数的最大流 ID;见 第 16 节。如果 收到其中任一项,则连接 MUST 立即关闭,并使用 连接 错误:如果违规值是在传输参数中收到的,则类型为 TRANSPORT_PARAMETER_ERROR;如果是在 帧中收到的,则类型为 FRAME_ENCODING_ERROR;见 第 10.2 节

端点 MUST NOT 超过其 对等方设置的限制。收到带有超过其已发送限制的流 ID 的帧的端点 MUST 将 此视为类型为 STREAM_LIMIT_ERROR 的连接错误;关于错误处理的详细信息,见 第 11 节

一旦接收方使用 MAX_STREAMS 帧通告了流限制, 通告更小的限制没有效果。不会 增加流限制的 MAX_STREAMS 帧 MUST 被忽略。

与流和连接流量控制一样,本文档将 何时以及应通过 MAX_STREAMS 向对等方通告多少流的决定 留给实现。实现可以选择在 流关闭时增加限制,以使对等方可用的流数量大致 保持一致。

因对等方限制而无法打开新流的端点 SHOULD 发送 STREAMS_BLOCKED 帧(第 19.14 节)。 此信号被认为有助于调试。端点 MUST NOT 等待接收 此 信号之后才通告附加信用,因为这样做将意味着 对等方至少被阻塞整整一个往返时间,并且如果对等方选择不发送 STREAMS_BLOCKED 帧,则可能无限期阻塞。

5. 连接

QUIC 连接是客户端和服务器之间的共享状态。

每个连接都从握手阶段开始,在此期间两个端点 使用加密握手协议 [QUIC-TLS] 建立共享秘密,并协商应用协议。 握手 (第 7 节)确认两个端点都愿意进行通信 (第 8.1 节),并为 连接建立参数 (第 7.4 节)。

应用协议可以在握手阶段使用连接,但存在 一些限制。0-RTT 允许客户端在 收到服务器响应之前发送应用数据。不过,0-RTT 不提供 针对重放攻击的保护;见 第 9.2 节,见 [QUIC-TLS]。服务器也可以在收到使其能够确认客户端身份和存活性的 最终加密握手消息之前,向客户端发送 应用数据。这些能力允许应用协议提供一种选项, 即用部分安全保证换取更低的延迟。

使用连接 ID(第 5.1 节)允许 连接迁移到新的 网络路径,无论这是端点的直接选择,还是由 中间盒变化所迫使。第 9 节描述了与迁移相关的 安全和隐私问题的缓解措施。

对于不再需要或不再期望的连接,客户端和服务器有多种方式可以 终止连接,如 第 10 节所述。

5.1. 连接 ID

每个连接拥有一组连接标识符,或称连接 ID, 每个连接 ID 都可以标识该连接。连接 ID 由端点独立 选择;每个端点选择其对等方使用的连接 ID。

连接 ID 的主要功能是确保 较低协议层(UDP、IP)中的地址变化不会导致 QUIC 连接的分组被递送到错误的端点。每个端点 使用一种特定于实现(也许还特定于部署)的方法选择 连接 ID,该方法允许带有该连接 ID 的分组 被路由回该端点,并在收到时由该端点识别。

使用多个连接 ID 是为了使端点能够发送 不会被观察者在没有端点协作的情况下识别为属于同一连接的 分组;见 第 9.5 节

连接 ID MUST NOT 包含任何 可被外部 观察者(即不与签发方协作的观察者)用于将它们 与同一连接的其他连接 ID 相关联的信息。举一个简单例子, 这意味着同一个连接 ID 在同一 连接上 MUST NOT 被签发超过一次。

带长报头的分组包含 Source Connection ID 和 Destination Connection ID 字段。这些字段用于为新连接设置连接 ID; 详情见 第 7.2 节

带短报头的分组(第 17.3 节)只包含 Destination Connection ID 并省略显式长度。端点预期知道 Destination Connection ID 字段的长度。使用基于连接 ID 路由的 负载均衡器的端点可以与负载均衡器约定连接 ID 的固定长度, 或约定一种编码方案。 固定部分可以编码显式长度,这使整个 连接 ID 能够在长度上变化,并且仍可被负载均衡器使用。

Version Negotiation(第 17.2.1 节)分组会回显客户端 选择的连接 ID,既用于确保正确路由到客户端,也用于 表明该分组是对客户端所发送分组的响应。

当不需要连接 ID 来路由到正确端点时,可以使用零长度连接 ID。 不过,在使用零长度连接 ID 的同时, 在同一本地 IP 地址和端口上复用连接,会在存在对等方连接迁移、 NAT 重新绑定以及客户端端口复用时导致失败。端点 MUST NOT 为多个 使用零长度连接 ID 的并发连接使用相同的 IP 地址和端口,除非它确信 那些协议特性未被使用。

当端点使用非零长度连接 ID 时,它需要确保 对等方拥有可供选择的连接 ID 供应,用于发往 该端点的分组。这些连接 ID 由端点使用 NEW_CONNECTION_ID 帧提供(第 19.15 节)。

5.1.1. 签发连接 ID

每个连接 ID 都有关联的序列号,以帮助 检测 NEW_CONNECTION_ID 或 RETIRE_CONNECTION_ID 帧何时引用同一值。 由端点签发的初始连接 ID 会在握手期间 通过长分组报头(第 17.2 节)的 Source Connection ID 字段发送。 初始连接 ID 的序列号为 0。如果发送了 preferred_address 传输参数,则所提供连接 ID 的序列号 为 1。

额外的连接 ID 使用 NEW_CONNECTION_ID 帧(第 19.15 节)传达给对等方。每个新签发的 连接 ID 上的序列 号 MUST 递增 1。客户端为其发送的第一个 Destination Connection ID 字段选择的连接 ID,以及 Retry 分组 提供的任何连接 ID,都不会分配序列号。

当端点签发连接 ID 时,它 MUST 在连接持续期间接受携带该 连接 ID 的分组,或直到其对等方通过 RETIRE_CONNECTION_ID 帧 (第 19.16 节)使该连接 ID 失效。 已签发且未退役的连接 ID 被视为活跃;任何活跃连接 ID 在当前连接中可随时用于任何分组类型。这包括 服务器通过 preferred_address 传输参数签发的连接 ID。

端点 SHOULD 确保其对等方 拥有足够数量的可用且 未使用的连接 ID。端点使用 active_connection_id_limit 传输 参数通告它们愿意维护的活跃连接 ID 数量。端点 MUST NOT 提供超过对等方 限制的连接 ID。若 NEW_CONNECTION_ID 帧也要求通过在 Retire Prior To 字段中 包含足够大的值来退役任何超出的连接 ID,则端点 MAY 发送暂时 超过对等方限制的连接 ID。

NEW_CONNECTION_ID 帧可能导致端点根据 Retire Prior To 字段的值 添加一些活跃连接 ID 并退役其他连接 ID。在处理 NEW_CONNECTION_ID 帧并添加和退役活跃连接 ID 之后,如果活跃连接 ID 的数量超过其 active_connection_id_limit 传输参数中通告的值,则端点 MUST 以类型为 CONNECTION_ID_LIMIT_ERROR 的错误关闭 连接。

当对等方退役某个连接 ID 时,端点 SHOULD 提供新的 连接 ID。如果端点提供的连接 ID 少于对等方的 active_connection_id_limit,则当收到带有先前未使用连接 ID 的 分组时,它 MAY 提供新的连接 ID。 端点 MAY 限制为每个连接签发的连接 ID 总数,以避免连接 ID 耗尽的风险;见 第 10.3.2 节。端点 MAY 还可以限制连接 ID 的签发,以减少 其维护的每路径状态数量,例如路径验证状态,因为其对等方 可能通过与已签发连接 ID 数量相同的多条路径与其交互。

发起迁移且需要非零长度连接 ID 的端点 SHOULD 确保其对等方可用的连接 ID 池 允许该对等方在迁移时使用新的连接 ID,因为如果该池耗尽, 对等方将无法响应。

在握手期间选择零长度连接 ID 的端点 不能签发新的连接 ID。在任何网络路径上发往此类端点的所有分组中 都使用零长度 Destination Connection ID 字段。

5.1.2. 使用与 退役连接 ID

端点可以在连接期间的任何时候,将它用于对等方的连接 ID 更改为另一个可用的连接 ID。端点会为了响应迁移的对等方而消耗连接 ID; 详见 第 9.5 节

端点维护从其 对等方收到的一组连接 ID,在发送分组时可以使用其中任何一个。 当端点希望从使用中移除某个 连接 ID 时,它会向其对等方发送 RETIRE_CONNECTION_ID 帧。 发送 RETIRE_CONNECTION_ID 帧表示该连接 ID 不会 再次使用,并请求对等方使用 NEW_CONNECTION_ID 帧 将其替换为新的连接 ID。

第 9.5 节中所讨论,端点将连接 ID 的使用限制为 从单个本地地址发送到单个 目的地址的分组。端点 SHOULD 在不再 主动使用连接 ID 所对应的本地地址或目的地址时, 退役该连接 ID。

端点在某些情况下可能需要停止接受先前签发的 连接 ID。 这样的端点可以通过发送带有增大的 Retire Prior To 字段的 NEW_CONNECTION_ID 帧,使其对等方退役连接 ID。该端点 SHOULD 继续接受先前签发的连接 ID,直到它们被对等方退役。如果端点无法再处理 所指示的连接 ID,它 MAY 关闭连接。

收到增大的 Retire Prior To 字段后,对等方 MUST 停止使用 相应的连接 ID,并在将新提供的连接 ID 加入活跃 连接 ID 集合之前,使用 RETIRE_CONNECTION_ID 帧退役它们。此顺序允许端点替换所有活跃 连接 ID,而不会出现对等方没有可用连接 ID 的可能性,也不会超过该对等方在 active_connection_id_limit 传输参数中设置的限制;见 第 18.2 节。在被请求时未停止使用连接 ID 可能导致连接失败,因为签发端点可能 无法继续将这些连接 ID 用于活跃连接。

端点 SHOULD 限制其在本地已退役但 对应 RETIRE_CONNECTION_ID 帧尚未被确认的 连接 ID 数量。端点 SHOULD 允许发送并跟踪数量至少为 active_connection_id_limit 传输参数值两倍的 RETIRE_CONNECTION_ID 帧。 端点 MUST NOT 在未退役连接 ID 的情况下忘记 它,不过它 MAY 选择将需要退役的连接 ID 数量超过此 限制的情况视为类型为 CONNECTION_ID_LIMIT_ERROR 的连接错误。

端点 SHOULD NOT 在收到 退役前一个 Retire Prior To 值所指示的所有连接 ID 的 RETIRE_CONNECTION_ID 帧之前,签发 Retire Prior To 字段的更新。

5.2. 将分组匹配到 连接

传入分组在接收时被分类。分组可以 与现有连接相关联,或者——对于服务器——可能创建一个新 连接。

端点会尝试将分组与现有连接相关联。如果分组 具有对应现有连接的非零长度 Destination Connection ID, QUIC 就会相应地处理该分组。注意,一个连接 可以关联多个连接 ID;见 第 5.1 节

如果 Destination Connection ID 为零长度,且分组中的寻址信息 与端点用于标识零长度连接 ID 连接的寻址信息匹配, QUIC 就会将该分组作为该 连接的一部分处理。端点可以仅使用目的 IP 和端口,也可以同时使用 源地址和目的地址进行标识,尽管这会使 连接变得脆弱,如 第 5.1 节所述。

端点可以针对任何无法归属于现有连接的分组发送 Stateless Reset (第 10.3 节)。 Stateless Reset 允许对等方更快识别连接何时变得不可用。

如果与现有连接匹配的分组 与该连接的状态不一致,则这些分组会被丢弃。例如,如果分组 指示的协议版本不同于该连接的 版本,或者在预期密钥可用后移除分组保护失败,则丢弃分组。

缺乏强完整性保护的无效分组,例如 Initial、Retry 或 Version Negotiation,MAY 被丢弃。端点在处理这些分组的内容后才 发现错误时,MUST 生成 连接错误,或者完全回滚该处理期间所做的任何更改。

5.2.1. 客户端分组 处理

发送给客户端的有效分组始终包含与客户端所选择值 匹配的 Destination Connection ID。选择接收零长度 连接 ID 的客户端可以使用本地地址和端口来标识连接。 不匹配现有连接的分组——根据 Destination Connection ID,或在该值为零长度时根据本地 IP 地址和端口—— 会被丢弃。

由于分组重排序或丢失,客户端可能收到某个 连接的分组,而这些分组使用它尚未计算出的密钥加密。客户端 MAY 丢弃这些分组,或者它 MAY 缓冲这些分组,以期待稍后 允许其计算密钥的分组到达。

如果客户端收到使用不同于其最初 选择版本的分组,它 MUST 丢弃该分组。

5.2.2. 服务器分组 处理

如果服务器收到指示不支持版本的分组,并且该 分组足够大,可以为任何受支持版本发起新连接, 则服务器 SHOULD第 6.1 节所述发送 Version Negotiation 分组。 服务器 MAY 限制其以 Version Negotiation 分组响应的分组数量。服务器 MUST 丢弃指定不支持 版本的较小分组。

不支持版本的第一个分组可以为任何版本特定字段使用不同的 语义和 编码。特别是,不同版本可能使用不同的分组 保护密钥。不支持某个特定版本的服务器不太可能 能够解密分组载荷或正确解释结果。只要数据报足够长, 服务器 SHOULD 以 Version Negotiation 分组响应。

具有受支持版本或没有 Version 字段的分组,会使用 连接 ID 或——对于具有零长度连接 ID 的分组——使用本地地址和端口匹配到 连接。这些分组会使用所 选择的连接进行处理;否则,服务器继续按下文所述处理。

如果分组是完全符合规范的 Initial 分组, 服务器就继续进行握手(第 7 节)。 这使服务器承诺使用 客户端所选择的版本。

如果服务器拒绝接受新连接,它 SHOULD 发送包含 错误码为 CONNECTION_REFUSED 的 CONNECTION_CLOSE 帧的 Initial 分组。

如果分组是 0-RTT 分组,服务器 MAY 缓冲有限数量的这些 分组,以等待迟到的 Initial 分组。客户端无法 在收到服务器响应之前发送 Handshake 分组,因此服务器 SHOULD 忽略任何这样的分组。

服务器 MUST 在所有 其他情况下丢弃传入分组。

5.2.3. 简单负载均衡器的考虑事项

服务器部署可以仅使用 源和目的 IP 地址以及端口在服务器之间进行负载均衡。 客户端 IP 地址或端口的变化可能导致分组被转发到错误的服务器。 这样的服务器部署可以使用以下方法之一,在客户端地址变化时 保持连接连续性。

  • 服务器可以使用带外机制, 基于连接 ID 将分组转发到正确的 服务器。
  • 如果服务器可以使用专用服务器 IP 地址或端口,而不是客户端最初连接到的那个 地址或端口,则它们可以使用 preferred_address 传输参数请求客户端将连接迁移到该专用 地址。注意,客户端可以选择不使用首选地址。

在未实现用于在客户端地址变化时维持 连接连续性的解决方案的部署中,服务器 SHOULD 通过使用 disable_active_migration 传输参数来表明 不支持迁移。disable_active_migration 传输参数并不禁止 客户端根据 preferred_address 传输参数采取行动之后的 连接迁移。

使用这种简单负载均衡形式的服务器部署 MUST 避免创建无状态重置预言机;见 第 21.11 节

5.3. 连接上的 操作

本文档不定义 QUIC 的 API;而是定义一组 应用协议可以依赖的 QUIC 连接功能。 应用协议可以假定 QUIC 实现提供的 接口包含本节描述的操作。为特定应用协议 使用而设计的实现可能只提供该协议使用的那些操作。

在实现客户端角色时,应用协议可以:

  • 打开连接,这会开始 第 7 节中描述的交换;
  • 在 Early Data 可用时启用它;以及
  • 获知 Early Data 是否已被服务器接受或 拒绝。

在实现服务器角色时,应用协议可以:

  • 监听传入连接,这会准备进行 第 7 节中描述的交换;
  • 如果支持 Early Data,则在发送给客户端的 TLS 恢复票据中嵌入由应用控制的数据;以及
  • 如果支持 Early Data,则从客户端的 恢复票据中取回由应用控制的数据,并基于该 信息接受或拒绝 Early Data。

在任一角色中,应用协议可以:

  • 配置每种类型允许的初始流数量的 最小值,如传输参数(第 7.4 节)中传达的那样;
  • 通过为流和连接 设置流量控制限制来控制接收缓冲区的资源分配;
  • 识别握手是否已经成功完成 或仍在进行;
  • 通过生成 PING 帧 (第 19.2 节)或请求传输在空闲超时到期之前 发送额外帧 (第 10.1 节),防止连接静默关闭; 以及
  • 立即关闭(第 10.2 节)连接。

6. 版本协商

版本协商允许服务器表明它不支持 客户端所使用的版本。服务器会响应每个可能发起 新连接的分组发送 Version Negotiation 分组;详情见 第 5.2 节

客户端发送的第一个分组的大小将决定服务器是否 发送 Version Negotiation 分组。支持多个 QUIC 版本的客户端 SHOULD 确保其发送的第一个 UDP 数据报的大小为 其支持的所有版本中最小数据报大小的最大值,并按需使用 PADDING 帧 (第 19.1 节)。这可确保在存在 双方共同支持的版本时服务器会作出响应。如果服务器收到的数据报 小于另一个版本中指定的最小大小,则服务器可能不会发送 Version Negotiation 分组;见 第 14.1 节

6.1. 发送版本 协商分组

如果客户端选择的版本不可被服务器接受,则 服务器以 Version Negotiation 分组响应;见 第 17.2.1 节。这 包含服务器将接受的版本列表。端点 MUST NOT 响应收到的 Version Negotiation 分组而发送 Version Negotiation 分组。

此系统允许服务器在不保留状态的情况下处理 使用不支持版本的分组。虽然 Initial 分组或作为响应发送的 Version Negotiation 分组都可能丢失,但客户端会持续发送新分组, 直到成功收到响应或放弃连接尝试。

服务器 MAY 限制其发送的 Version Negotiation 分组数量。例如,能够将分组识别为 0-RTT 的服务器,可以选择不响应 0-RTT 分组发送 Version Negotiation 分组,并预期它最终会收到 Initial 分组。

6.2. 处理版本 协商分组

Version Negotiation 分组被设计为允许未来定义 使 QUIC 能够协商连接所用 QUIC 版本的功能。 未来的标准轨道规范可能会改变支持多个 QUIC 版本的实现 对响应使用此版本建立连接的尝试而收到的 Version Negotiation 分组的反应方式。

只支持此版本 QUIC 的客户端在收到 Version Negotiation 分组时,MUST 放弃当前 连接尝试,但有以下两个例外。如果客户端已经收到并成功处理了 任何其他分组,包括较早的 Version Negotiation 分组,则客户端 MUST 丢弃任何 Version Negotiation 分组。 如果 Version Negotiation 分组列出了客户端所选择的 QUIC 版本, 客户端 MUST 丢弃该 Version Negotiation 分组。

如何执行版本协商留待未来由标准轨道规范定义。 特别是,未来的工作将确保其对版本降级攻击具有鲁棒性;见 第 21.12 节

6.3. 使用保留 版本

为了使服务器将来能够使用新版本,客户端需要正确 处理不支持的版本。某些版本号(0x?a?a?a?a,如 第 15 节所定义)被保留,用于包含版本 号的字段。

端点 MAY 在未知或不支持的 版本会被忽略的任何字段中添加保留版本,以测试对等方是否正确 忽略该值。例如,端点可以在 Version Negotiation 分组中包含保留版本;见 第 17.2.1 节。端点 MAY 发送带有保留 版本的分组,以测试对等方是否正确丢弃该分组。

7. 加密与传输握手

QUIC 依赖组合的加密与传输握手,以最小化 连接建立延迟。QUIC 使用 CRYPTO 帧(第 19.6 节) 传输加密握手。本文档定义的 QUIC 版本 标识为 0x00000001,并使用 [QUIC-TLS] 中描述的 TLS; 不同的 QUIC 版本可以指示正在使用不同的加密 握手协议。

QUIC 为加密握手数据提供可靠的、有序的交付。 QUIC 分组保护用于尽可能多地加密握手 协议。加密握手 MUST 提供以下 属性:

CRYPTO 帧可以在不同的分组编号空间中发送 (第 12.3 节)。CRYPTO 帧用于确保 加密握手数据有序交付的偏移量,在每个分组编号 空间中都从零开始。

图 4显示了简化的握手以及 用于推进握手的分组和帧的交换。 在可能的地方,握手期间允许交换应用数据,并用星号(“*”)表示。 一旦握手完成,端点就能够自由交换应用数据。

客户端                                               服务器

Initial (CRYPTO)
0-RTT (*)              ---------->
                                           Initial (CRYPTO)
                                         Handshake (CRYPTO)
                       <----------                1-RTT (*)
Handshake (CRYPTO)
1-RTT (*)              ---------->
                       <----------   1-RTT (HANDSHAKE_DONE)

1-RTT                  <=========>                    1-RTT
图 4简化的 QUIC 握手

端点可以使用握手期间发送的分组来测试显式 拥塞通知(ECN)支持;见 第 13.4 节。端点 通过观察确认其发送的第一批分组的 ACK 帧是否携带 ECN 计数,来验证对 ECN 的支持,如 第 13.4.2 节所述。

端点 MUST 显式协商应用协议。 这避免了对正在使用的协议存在分歧的情况。

7.1. 示例握手 流程

TLS 如何与 QUIC 集成的细节见 [QUIC-TLS], 但这里也提供一些示例。支持客户端地址验证的该交换扩展 显示在 第 8.1.2 节

一旦任何地址验证交换完成,就使用 加密握手来就加密密钥达成一致。加密握手承载于 Initial(第 17.2.2 节)和 Handshake (第 17.2.4 节)分组中。

图 5概述了 1-RTT 握手。每一行 显示一个 QUIC 分组,先显示分组类型和分组编号,随后显示 这些分组通常包含的帧。例如, 第一个分组的类型为 Initial,分组编号为 0,并包含携带 ClientHello 的 CRYPTO 帧。

多个 QUIC 分组——即使是不同分组类型——可以被合并 到单个 UDP 数据报中;见 第 12.2 节。 因此,此握手可以只由四个 UDP 数据报组成,也可以由更多数量的数据报组成 (受协议固有限制约束,例如拥塞控制和 反放大)。例如,服务器的第一轮发送包含 Initial 分组、Handshake 分组以及 1-RTT 分组中的“0.5-RTT 数据”。

客户端                                                  服务器

Initial[0]: CRYPTO[CH] ->

                                 Initial[0]: CRYPTO[SH] ACK[0]
                       Handshake[0]: CRYPTO[EE, CERT, CV, FIN]
                                 <- 1-RTT[0]: STREAM[1, "..."]

Initial[1]: ACK[0]
Handshake[0]: CRYPTO[FIN], ACK[0]
1-RTT[0]: STREAM[0, "..."], ACK[0] ->

                                          Handshake[1]: ACK[0]
         <- 1-RTT[1]: HANDSHAKE_DONE, STREAM[3, "..."], ACK[0]
图 51-RTT 握手示例

图 6显示了一个 带有 0-RTT 握手和单个 0-RTT 数据分组的连接示例。 注意,如 第 12.3 节所述, 服务器在 1-RTT 分组中确认 0-RTT 数据,并且 客户端在同一分组编号空间中发送 1-RTT 分组。

客户端                                                  服务器

Initial[0]: CRYPTO[CH]
0-RTT[0]: STREAM[0, "..."] ->

                                 Initial[0]: CRYPTO[SH] ACK[0]
                                  Handshake[0] CRYPTO[EE, FIN]
                          <- 1-RTT[0]: STREAM[1, "..."] ACK[0]

Initial[1]: ACK[0]
Handshake[0]: CRYPTO[FIN], ACK[0]
1-RTT[1]: STREAM[0, "..."] ACK[0] ->

                                          Handshake[1]: ACK[0]
         <- 1-RTT[1]: HANDSHAKE_DONE, STREAM[3, "..."], ACK[1]
图 60-RTT 握手示例

7.2. 协商连接 ID

连接 ID 用于确保分组的一致路由,如 第 5.1 节所述。长报头包含两个连接 ID:Destination Connection ID 由分组的接收方选择,并用于提供 一致路由;Source Connection ID 用于设置对等方所使用的 Destination Connection ID。

在握手期间,带长报头(第 17.2 节)的分组用于 建立两个端点使用的连接 ID。每个端点使用 Source Connection ID 字段指定在发往它们的分组的 Destination Connection ID 字段中使用的连接 ID。在处理 第一个 Initial 分组后,每个端点会将其随后发送的分组中的 Destination Connection ID 字段设置为它收到的 Source Connection ID 字段的值。

当客户端发送 Initial 分组且此前尚未从服务器收到 Initial 或 Retry 分组时,客户端会用不可预测的值填充 Destination Connection ID 字段。此 Destination Connection ID MUST 至少为 8 字节长。在从服务器收到分组之前, 客户端 MUST 在此连接的所有分组中使用相同的 Destination Connection ID 值。

客户端发送的第一个 Initial 分组中的 Destination Connection ID 字段 用于确定 Initial 分组的分组保护密钥。收到 Retry 分组后, 这些密钥会改变;见 第 5.2 节 ,见 [QUIC-TLS]

客户端用自己选择的值填充 Source Connection ID 字段, 并设置 Source Connection ID Length 字段以指示长度。

第一轮发送中的 0-RTT 分组使用与客户端第一个 Initial 分组相同的 Destination Connection ID 和 Source Connection ID 值。

在首次从服务器收到 Initial 或 Retry 分组时,客户端会使用 服务器提供的 Source Connection ID 作为后续分组的 Destination Connection ID, 包括任何 0-RTT 分组。这意味着客户端在连接建立期间 可能必须两次更改其在 Destination Connection ID 字段中设置的连接 ID:一次是响应 Retry 分组, 另一次是响应来自服务器的 Initial 分组。一旦客户端 收到来自服务器的有效 Initial 分组,它 MUST 丢弃 随后在该连接上收到的任何带有不同 Source Connection ID 的 分组。

客户端 MUST 只响应首次收到的 Initial 或 Retry 分组,更改它用于发送分组的 Destination Connection ID。 服务器 MUST 根据首次收到的 Initial 分组设置它用于发送分组的 Destination Connection ID。只有当值来自 NEW_CONNECTION_ID 帧时,才允许 对 Destination Connection ID 进行任何后续更改;如果 后续 Initial 分组包含不同的 Source Connection ID,则它们 MUST 被丢弃。这避免了对多个带有不同 Source Connection ID 的 Initial 分组进行无状态处理时可能产生的 不可预测结果。

端点发送的 Destination Connection ID 可以在连接的生命周期中改变, 尤其是在响应连接迁移 (第 9 节)时;详情见 第 5.1.1 节

7.3. 认证 连接 ID

每个端点在握手期间对连接 ID 所作的选择 通过在传输参数中包含所有值来认证;见 第 7.4 节。这确保用于 握手的所有连接 ID 也由加密握手进行认证。

每个端点都会在 initial_source_connection_id 传输 参数中包含它发送的第一个 Initial 分组的 Source Connection ID 字段值; 见 第 18.2 节。服务器会在 original_destination_connection_id 传输参数中包含它从 客户端收到的第一个 Initial 分组的 Destination Connection ID 字段; 如果服务器发送了 Retry 分组,则这里指的是在发送 Retry 分组之前收到的 第一个 Initial 分组。如果服务器发送 Retry 分组,还会在 retry_source_connection_id 传输参数中包含 Retry 分组的 Source Connection ID 字段。

对等方为这些传输参数提供的值 MUST 与 端点在其发送(以及服务器接收)的 Initial 分组的 Destination 和 Source Connection ID 字段中使用的值相匹配。 端点 MUST 验证收到的传输参数与收到的连接 ID 值匹配。 在传输参数中包含连接 ID 值并对其进行验证, 可确保攻击者无法通过在握手期间注入携带攻击者所选连接 ID 的分组来影响成功连接的连接 ID 选择。

端点 MUST 将任一端点缺少 initial_source_connection_id 传输 参数,或服务器缺少 original_destination_connection_id 传输参数的情况,视为 类型为 TRANSPORT_PARAMETER_ERROR 的连接错误。

端点 MUST 将以下情况视为 类型为 TRANSPORT_PARAMETER_ERROR 或 PROTOCOL_VIOLATION 的连接错误:

  • 在收到 Retry 分组后,服务器缺少 retry_source_connection_id 传输 参数,
  • 在未收到 Retry 分组时存在 retry_source_connection_id 传输 参数,或
  • 从对等方在这些传输参数中收到的值 与 Initial 分组中相应 Destination 或 Source Connection ID 字段中发送的值不匹配。

如果选择了零长度连接 ID,则对应的传输 参数以零长度值包含。

图 7显示完整握手中使用的连接 ID (其中 DCID=Destination Connection ID, SCID=Source Connection ID)。图中显示了 Initial 分组的交换, 以及之后包含握手期间建立的连接 ID 的 1-RTT 分组交换。

客户端                                                  服务器

Initial: DCID=S1, SCID=C1 ->
                                  <- Initial: DCID=C1, SCID=S3
                             ...
1-RTT: DCID=S3 ->
                                             <- 1-RTT: DCID=C1
图 7握手中连接 ID 的使用

图 8显示了一个 包含 Retry 分组的类似握手。

客户端                                                  服务器

Initial: DCID=S1, SCID=C1 ->
                                    <- Retry: DCID=C1, SCID=S2
Initial: DCID=S2, SCID=C1 ->
                                  <- Initial: DCID=C1, SCID=S3
                             ...
1-RTT: DCID=S3 ->
                                             <- 1-RTT: DCID=C1
图 8带 Retry 的握手中连接 ID 的使用

在两种情况下(图 78),客户端都会将 initial_source_connection_id 传输参数的值设置为 C1

当握手不包含 Retry(图 7)时,服务器将 original_destination_connection_id 设置为 S1(注意该值由 客户端选择),并将 initial_source_connection_id 设置为 S3。在这种情况下,服务器 不包含 retry_source_connection_id 传输参数。

当握手包含 Retry(图 8)时,服务器将 original_destination_connection_id 设置为 S1,将 retry_source_connection_id 设置为 S2, 并将 initial_source_connection_id 设置为 S3

7.4. 传输参数

在连接建立期间,两个端点都会对其传输参数作出 经过认证的声明。端点需要遵守 每个参数所定义的限制;每个参数的描述 都包含其处理规则。

传输参数是由每个端点单方面作出的声明。 每个端点可以独立于其对等方所选择的值来选择 传输参数的值。

传输参数的编码详见 第 18 节

QUIC 在加密握手中包含编码后的传输参数。 一旦握手完成,对等方声明的传输参数就可用。 每个端点都会验证其对等方提供的值。

每个已定义传输参数的定义包含在 第 18.2 节中。

端点 MUST 将收到值无效的传输 参数视为类型为 TRANSPORT_PARAMETER_ERROR 的连接错误。

端点 MUST NOT 在给定传输 参数扩展中多次发送同一参数。端点 SHOULD 将收到重复 传输参数视为类型为 TRANSPORT_PARAMETER_ERROR 的连接错误。

端点使用传输参数来认证握手期间的 连接 ID 协商;见 第 7.3 节

ALPN(见 [ALPN])允许客户端 在连接建立期间提供多个应用 协议。客户端在握手期间包含的传输参数适用于 该客户端提供的所有应用协议。应用协议可以为传输 参数推荐值,例如初始流量控制限制。不过, 对传输参数值设置约束的应用协议,如果这些 约束发生冲突,可能会使客户端无法提供多个应用协议。

7.4.1. 0-RTT 的 传输参数值

使用 0-RTT 取决于客户端和服务器都使用 从先前连接协商得到的协议参数。为启用 0-RTT,端点会把 在连接上收到的任何会话票据连同服务器传输参数的值一起存储。 端点还会存储应用协议或加密握手所需的任何信息;见 第 4.6 节,见 [QUIC-TLS]。 在使用会话票据尝试 0-RTT 时,会使用 所存储的传输参数值。

记住的传输参数会应用于新连接,直到握手 完成且客户端开始发送 1-RTT 分组。一旦握手 完成,客户端就使用握手中建立的传输参数。 并非所有传输参数都会被记住,因为其中一些不适用于 未来连接,或对 0-RTT 的使用没有影响。

新传输参数的定义(第 7.4.2 节MUST 指定是否必须、可选或禁止为 0-RTT 存储该传输参数。 客户端无需存储它无法处理的传输参数。

客户端 MUST NOT 对以下参数使用 记住的值: ack_delay_exponent、max_ack_delay、initial_source_connection_id、 original_destination_connection_id、preferred_address、 retry_source_connection_id 和 stateless_reset_token。客户端 MUST 改用握手中服务器的新值;如果服务器未提供新 值,则使用默认值。

尝试发送 0-RTT 数据的客户端 MUST 记住服务器所使用且它能够处理的所有其他传输 参数。服务器可以记住这些传输参数,或可以在票据中存储 这些值的受完整性保护副本,并在接受 0-RTT 数据时恢复该信息。 服务器会使用传输参数来决定是否接受 0-RTT 数据。

如果服务器接受 0-RTT 数据,则服务器 MUST NOT 降低任何可能被客户端的 0-RTT 数据违反的限制,或更改任何这样的值。特别是,接受 0-RTT 数据的服务器 MUST NOT 为以下参数(第 18.2 节) 设置小于这些参数记住值的值。

  • active_connection_id_limit
  • initial_max_data
  • initial_max_stream_data_bidi_local
  • initial_max_stream_data_bidi_remote
  • initial_max_stream_data_uni
  • initial_max_streams_bidi
  • initial_max_streams_uni

省略某些传输参数或将其设置为零值,可能导致 0-RTT 数据被启用但不可用。允许发送应用数据的 适用传输参数子集 SHOULD 为 0-RTT 设置为非零 值。这包括 initial_max_data,以及(1) initial_max_streams_bidi 和 initial_max_stream_data_bidi_remote,或(2) initial_max_streams_uni 和 initial_max_stream_data_uni。

服务器可能为流提供比客户端在发送 0-RTT 时应用的 记住值更大的初始流流量控制限制。 一旦握手完成,客户端会使用 initial_max_stream_data_bidi_remote 和 initial_max_stream_data_uni 的更新值 更新所有发送流上的流量控制 限制。

服务器 MAY 存储并恢复 先前发送的 max_idle_timeout、max_udp_payload_size 和 disable_active_migration 参数值,并在选择较小值时拒绝 0-RTT。 在接受 0-RTT 数据的同时降低这些 参数值可能会降低连接性能。具体而言,降低 max_udp_payload_size 可能导致分组被丢弃,从而与直接拒绝 0-RTT 数据相比导致更差性能。

如果恢复的传输参数值无法被支持, 服务器 MUST 拒绝 0-RTT 数据。

在 0-RTT 分组中发送帧时,客户端 MUST 只使用记住的 传输参数;重要的是,它 MUST NOT 使用从服务器更新后的 传输参数或从 1-RTT 分组中收到的帧中获知的 更新值。来自握手的传输参数更新值只适用于 1-RTT 分组。例如,即使握手或 1-RTT 分组中发送的帧增加了 流量控制限制,记住的传输参数中的流量控制限制仍适用于 所有 0-RTT 分组。服务器 MAY 将在 0-RTT 中使用更新后的传输参数视为类型为 PROTOCOL_VIOLATION 的连接错误。

7.4.2. 新的传输 参数

新的传输参数可用于协商新的协议行为。 端点 MUST 忽略其不支持的传输参数。 因此,缺少某个传输参数会禁用任何使用该参数 协商的可选协议特性。如 第 18.1 节所述, 一些标识符被保留,以便 演练这一要求。

不理解某个传输参数的客户端可以丢弃它,并在 后续连接上尝试 0-RTT。不过,如果客户端后来增加了对 被丢弃传输参数的支持,它在尝试 0-RTT 时就有违反该 传输参数所建立约束的风险。新的传输参数可以通过 将默认值设置为最保守的值来避免此问题。 客户端可以通过记住所有参数来避免此问题,即使是当前不 支持的参数。

新的传输参数可以按照 第 22.3 节中的规则注册。

7.5. 加密消息 缓冲

实现需要维护一个缓冲区,用于接收乱序到达的 CRYPTO 数据。由于 CRYPTO 帧没有流量控制,端点 可能迫使其对等方缓冲无界数量的数据。

实现 MUST 支持至少缓冲 4096 字节的乱序 CRYPTO 帧中接收的数据。端点 MAY 选择允许在握手期间缓冲更多数据。 握手期间较大的限制可以允许交换更大的密钥或凭据。 端点的缓冲区大小无需在连接生命周期内保持不变。

握手期间无法缓冲 CRYPTO 帧可能导致 连接失败。如果端点的缓冲区在握手期间被超出, 它可以临时扩展缓冲区以完成握手。如果端点 不扩展其缓冲区,它 MUST 以 CRYPTO_BUFFER_EXCEEDED 错误码关闭连接。

一旦握手完成,如果端点无法缓冲 CRYPTO 帧中的所有数据, 它 MAY 丢弃该 CRYPTO 帧以及未来收到的所有 CRYPTO 帧, 或者它 MAY 以 CRYPTO_BUFFER_EXCEEDED 错误 码关闭连接。包含被丢弃 CRYPTO 帧的分组 MUST 被确认,因为 即使 CRYPTO 帧被丢弃,该分组也已被传输层接收和处理。

8. 地址验证

地址验证确保端点不能被用于流量 放大攻击。在这种攻击中,攻击者向服务器发送一个带有 欺骗性源地址信息的分组,该源地址标识某个受害者。如果服务器 响应该分组生成更多或更大的分组,攻击者就可以 利用服务器向受害者发送比其自身能够发送的更多数据。

防御放大攻击的主要方法是验证对等方 能够在其声称的传输地址接收分组。因此, 在从尚未验证的地址收到分组后,端点 MUST 将其发送到未验证地址的数据量限制为 从该地址收到的数据量的三倍。 这种对响应大小的限制称为反放大限制。

地址验证既在连接建立期间执行(见 第 8.1 节),也在连接迁移期间执行(见 第 8.2 节)。

8.1. 连接建立期间的 地址验证

连接建立隐式地为两个端点提供地址验证。 特别是,收到一个受 Handshake 密钥保护的分组, 就确认对等方已经成功处理了 Initial 分组。一旦 端点成功处理了来自对等方的 Handshake 分组,它就可以 将对等方地址视为已经验证。

此外,如果对等方使用由端点选择的连接 ID, 且该连接 ID 至少包含 64 比特熵,则端点 MAY 将对等方地址视为已验证。

对于客户端而言,其第一个 Initial 分组中 Destination Connection ID 字段的值,允许其在成功处理任何分组时将服务器地址作为其中一部分进行验证。 来自服务器的 Initial 分组使用从该值派生的密钥保护(见 第 5.2 节 ,见 [QUIC-TLS])。或者,该值会由服务器在 Version Negotiation 分组(第 6 节)中回显,或包含在 Retry 分组的 Integrity Tag 中(第 5.8 节,见 [QUIC-TLS])。

在验证客户端地址之前,服务器 MUST NOT 发送超过其已接收字节数三倍的数据。 这限制了使用欺骗性源地址能够发起的任何放大攻击的规模。 为避免地址验证之前的放大,服务器 MUST 统计所有唯一归属于单个连接的数据报中收到的载荷字节。 这包括包含成功处理分组的数据报,以及包含的分组全部 被丢弃的数据报。

客户端 MUST 确保包含 Initial 分组的 UDP 数据报具有至少 1200 字节的 UDP 载荷,并按需添加 PADDING 帧。 发送填充数据报的客户端允许服务器在完成地址验证前 发送更多数据。

如果客户端没有发送额外的 Initial 或 Handshake 分组, 服务器的 Initial 或 Handshake 分组丢失可能导致死锁。当服务器达到 反放大限制,而客户端已经收到对其发送的所有数据的确认时, 可能出现死锁。在这种情况下,当客户端没有理由再发送额外分组时, 服务器将无法发送更多数据,因为它尚未验证客户端的地址。 为防止这种死锁,客户端 MUST 在探测超时(PTO)时发送分组; 见 第 6.2 节,见 [QUIC-RECOVERY]。 具体而言,如果客户端没有 Handshake 密钥,它 MUST 发送一个 包含至少 1200 字节的 UDP 数据报中的 Initial 分组;否则发送 Handshake 分组。

服务器可能希望在开始加密握手之前验证客户端地址。 QUIC 使用 Initial 分组中的令牌,以在完成握手之前提供 地址验证。该令牌会在连接建立期间通过 Retry 分组交付给 客户端(见 第 8.1.2 节),或在先前连接中使用 NEW_TOKEN 帧交付(见 第 8.1.3 节)。

除了地址验证之前施加的发送限制外,服务器还受到 拥塞控制器所设限制的约束。客户端仅受拥塞控制器约束。

8.1.1. 令牌构造

在 NEW_TOKEN 帧或 Retry 分组中发送的令牌 MUST 以一种方式构造, 使服务器能够识别它是如何提供给客户端的。这些 令牌承载在相同字段中,但要求服务器进行不同处理。

8.1.2. 使用 Retry 分组进行地址验证

收到客户端的 Initial 分组后,服务器可以通过发送 包含令牌的 Retry 分组(第 17.2.5 节)请求地址验证。客户端收到 Retry 分组后, 在该连接上发送的所有 Initial 分组中都 MUST 重复此令牌。

响应处理一个包含 Retry 分组中提供的令牌的 Initial 分组时,服务器不能发送另一个 Retry 分组;它只能拒绝 该连接或允许其继续。

只要攻击者不可能为自己的地址生成有效令牌 (见 第 8.1.4 节),并且客户端能够返回 该令牌,它就向服务器证明了其已收到该令牌。

服务器还可以使用 Retry 分组来推迟连接建立的状态和 处理成本。要求服务器提供不同的连接 ID,并结合 第 18.2 节中定义的 original_destination_connection_id 传输参数, 会迫使服务器证明它或与其协作的实体收到了来自客户端的原始 Initial 分组。提供不同的连接 ID 还使服务器能够在一定程度上 控制后续分组的路由方式。这可用于将连接引导到不同的服务器实例。

如果服务器收到包含无效 Retry 令牌但在其他方面有效的 客户端 Initial 分组,则它知道客户端不会接受另一个 Retry 令牌。 服务器可以丢弃这样的分组,并让客户端通过超时检测握手失败, 但这可能给客户端带来显著的延迟惩罚。相反,服务器 SHOULD 使用 INVALID_TOKEN 错误立即关闭(第 10.2 节) 连接。注意,此时服务器尚未为该连接建立任何状态,因此不会进入 关闭期。

使用 Retry 分组的流程显示在 图 9中。

客户端                                                  服务器

Initial[0]: CRYPTO[CH] ->

                                                <- Retry+Token

Initial+Token[1]: CRYPTO[CH] ->

                                 Initial[0]: CRYPTO[SH] ACK[1]
                       Handshake[0]: CRYPTO[EE, CERT, CV, FIN]
                                 <- 1-RTT[0]: STREAM[1, "..."]
图 9带 Retry 的 握手示例

8.1.3. 面向未来 连接的地址验证

服务器 MAY 在一个 连接期间向客户端提供可用于后续连接的地址验证令牌。 地址验证对 0-RTT 尤其重要,因为服务器可能会响应 0-RTT 数据向客户端发送大量数据。

服务器使用 NEW_TOKEN 帧(第 19.7 节)向客户端提供 可用于验证未来连接的地址验证令牌。在未来连接中,客户端在 Initial 分组中包含此令牌以提供地址验证。客户端 MUST 在它发送的所有 Initial 分组中包含该令牌,除非 Retry 用更新的令牌替换它。 客户端 MUST NOT 将 Retry 中提供的令牌用于未来连接。 服务器 MAY 丢弃任何未携带预期令牌的 Initial 分组。

不同于为 Retry 分组创建并立即使用的令牌, NEW_TOKEN 帧中发送的令牌可以在一段时间之后使用。 因此,令牌 SHOULD 有到期时间,这可以是 显式到期时间,也可以是可用于动态计算到期时间的 签发时间戳。服务器可以存储到期时间,也可以将其以加密形式 包含在令牌中。

使用 NEW_TOKEN 签发的令牌 MUST NOT 包含任何会让观察者将这些值与其签发所在连接相关联的信息。 例如,它不能包含先前的连接 ID 或地址信息,除非这些值经过加密。 服务器 MUST 确保它发送的每个 NEW_TOKEN 帧 在所有客户端之间都是唯一的,但为修复先前发送的 NEW_TOKEN 帧丢失而发送的除外。 允许服务器区分 Retry 和 NEW_TOKEN 令牌的信息 MAY 可被服务器以外的实体访问。

客户端端口号在两个不同连接中相同的可能性很低; 因此,验证端口不太可能成功。

在 NEW_TOKEN 帧中收到的令牌适用于该连接被认为 对其具有权威性的任何服务器(例如证书中包含的服务器名称)。 当连接到客户端保留有适用且未使用令牌的服务器时,客户端 SHOULD 在其 Initial 分组的 Token 字段中包含该令牌。 包含令牌可能使服务器无需额外往返即可验证客户端地址。客户端 MUST NOT 包含不适用于其正在连接服务器的令牌,除非 客户端知道签发令牌的服务器和客户端正在连接的服务器共同管理这些令牌。 客户端 MAY 使用来自到该服务器的任何先前连接的令牌。

令牌允许服务器将签发令牌的连接与使用令牌的任何连接之间的活动 相关联。希望打破与服务器之间身份连续性的客户端 可以丢弃使用 NEW_TOKEN 帧提供的令牌。相比之下, 在 Retry 分组中获得的令牌 MUST 在连接尝试期间立即使用, 且不能用于后续连接尝试。

客户端 SHOULD NOT 为不同的 连接尝试重用来自 NEW_TOKEN 帧的令牌。重用令牌允许网络路径上的 实体将连接关联起来;见 第 9.5 节

客户端可能在单个连接上收到多个令牌。除了 防止可关联性之外,任何令牌都可以在任何连接尝试中使用。 服务器可以发送额外令牌,以便为多次连接尝试启用地址验证, 或替换可能变得无效的旧令牌。对于客户端而言, 这种歧义意味着发送最近未使用的令牌最可能有效。虽然保存并使用 较旧令牌没有负面后果,但客户端可以认为较旧令牌对服务器用于 地址验证的可能性较低。

当服务器收到带有地址验证令牌的 Initial 分组时, 除非它已经完成地址验证,否则 MUST 尝试验证该令牌。 如果令牌无效,则服务器 SHOULD 按客户端没有 已验证地址的情况继续处理,包括可能发送 Retry 分组。 服务器可以区分 NEW_TOKEN 帧和 Retry 分组提供的令牌(见 第 8.1.1 节),并且后者可以更严格地验证。如果验证成功,服务器 SHOULD 随后允许握手继续。

在无状态设计中,服务器可以使用经过加密和认证的令牌 向客户端传递信息,服务器随后可以恢复这些信息并用于验证客户端地址。 令牌未集成到加密握手中,因此它们不被认证。 例如,客户端可能能够重用令牌。为避免利用此属性的攻击, 服务器可以将令牌的使用限制为仅包含验证客户端地址所需的信息。

客户端 MAY 将在一个连接上获得的令牌用于 使用相同版本的任何连接尝试。选择要使用的令牌时, 客户端不需要考虑所尝试连接的其他属性,包括可能的应用协议选择、 会话票据或其他连接属性。

8.1.4. 地址验证令牌 完整性

地址验证令牌 MUST 难以猜测。 在令牌中包含至少 128 比特熵的随机值将是足够的, 但这取决于服务器记住它发送给客户端的值。

基于令牌的方案允许服务器将与验证相关的任何状态 卸载到客户端。为使这种设计生效,令牌 MUST 受到完整性保护, 以防被客户端修改或伪造。如果没有完整性保护,恶意客户端 可能生成或猜测会被服务器接受的令牌值。只有服务器需要访问 令牌的完整性保护密钥。

令牌不需要单一的明确定义格式,因为生成令牌的服务器 也会消费它。Retry 分组中发送的令牌 SHOULD 包含允许服务器验证客户端分组中的源 IP 地址和端口保持不变的信息。

NEW_TOKEN 帧中发送的令牌 MUST 包含允许服务器验证客户端 IP 地址自令牌签发以来未发生变化的信息。 服务器可以在决定不发送 Retry 分组时使用 NEW_TOKEN 帧中的令牌, 即使客户端地址已经改变。如果客户端 IP 地址已经改变, 服务器 MUST 遵守反放大限制;见 第 8 节。注意,在存在 NAT 的情况下,此要求可能不足以保护共享该 NAT 的其他主机免受 放大攻击。

攻击者可能重放令牌,将服务器用作 DDoS 攻击中的放大器。为防止此类攻击,服务器 MUST 确保 令牌重放被阻止或受到限制。服务器 SHOULD 确保 Retry 分组中发送的令牌只在很短时间内被接受,因为客户端会立即返回它们。 NEW_TOKEN 帧(第 19.7 节)中提供的令牌需要有效更长时间, 但 SHOULD NOT 被多次接受。 鼓励服务器在可能的情况下只允许令牌使用一次;令牌 MAY 包含 关于客户端的附加信息,以进一步缩小其适用性或重用范围。

8.2. 路径验证

路径验证由两个对等方在连接迁移期间使用 (见 第 9 节),以在地址变化后验证可达性。 在路径验证中,端点测试特定本地地址与特定对等方地址之间的可达性, 其中地址是 IP 地址和端口组成的二元组。

路径验证测试在通向对等方的路径上发送的分组是否被该 对等方接收。路径验证用于确保从迁移的对等方收到的分组 不携带欺骗性源地址。

路径验证不会验证对等方能否在返回方向发送。 确认不能用于返回路径验证,因为其中包含的熵不足, 且可能被伪造。端点独立地确定路径每个方向上的可达性, 因此返回可达性只能由对等方建立。

路径验证可以由任一端点在任何时候使用。例如,端点 可能在一段静默期后检查对等方是否仍然拥有其地址。

路径验证并非设计为 NAT 穿越机制。尽管这里描述的 机制可能有效创建支持 NAT 穿越的 NAT 绑定,但其预期是 其中一个端点能够在未先在该路径上发送分组的情况下接收分组。 有效的 NAT 穿越需要这里未提供的额外同步机制。

端点 MAY 将其他帧与用于路径验证的 PATH_CHALLENGE 和 PATH_RESPONSE 帧一起包含。特别是,端点可以将 PADDING 帧与 PATH_CHALLENGE 帧一起包含,用于路径最大传输单元发现 (PMTUD);见 第 14.2.1 节。端点在发送 PATH_RESPONSE 帧时也可以包含自己的 PATH_CHALLENGE 帧。

端点对从新的本地地址发送的探测使用新的连接 ID; 见 第 9.5 节。探测新路径时,端点可以 确保其对等方有一个未使用的连接 ID 可用于响应。 如果对等方的 active_connection_id_limit 允许,在同一分组中发送 NEW_CONNECTION_ID 和 PATH_CHALLENGE 帧,可确保对等方在发送响应时 有一个未使用的连接 ID 可用。

端点可以选择同时探测多条路径。用于探测的 同时路径数量受到其对等方先前提供的额外连接 ID 数量限制, 因为用于探测的每个新本地地址都需要一个先前未使用的连接 ID。

8.2.1. 发起路径 验证

为发起路径验证,端点在要验证的路径上发送包含 不可预测载荷的 PATH_CHALLENGE 帧。

端点 MAY 发送多个 PATH_CHALLENGE 帧以防范分组丢失。不过,端点 SHOULD NOT 在单个分组中发送多个 PATH_CHALLENGE 帧。

端点 SHOULD NOT 以比发送 Initial 分组更高的频率 使用包含 PATH_CHALLENGE 帧的分组探测新路径。这确保 连接迁移在新路径上的负载不超过建立新连接。

端点 MUST 在每个 PATH_CHALLENGE 帧中使用 不可预测数据,以便它能够将对等方的响应与相应的 PATH_CHALLENGE 关联起来。

端点 MUST 将包含 PATH_CHALLENGE 帧的数据报 扩展到至少为允许的最小最大数据报大小 1200 字节,除非 该路径的反放大限制不允许发送此大小的数据报。发送这种大小的 UDP 数据报可确保从端点到对等方的网络路径可用于 QUIC;见 第 14 节

当端点因反放大限制而无法将数据报大小扩展到 1200 字节时, 路径 MTU 将不会被验证。为确保路径 MTU 足够大,端点 MUST 通过在至少 1200 字节的数据报中发送 PATH_CHALLENGE 帧来执行第二次路径验证。可以在成功收到 PATH_RESPONSE 后,或在该路径上收到足够字节使发送较大数据报 不会超过反放大限制时,执行此额外验证。

不同于数据报被扩展的其他情况,当数据报包含 PATH_CHALLENGE 或 PATH_RESPONSE 时,即使它们看起来过小,端点 MUST NOT 丢弃它们。

8.2.2. 路径验证 响应

收到 PATH_CHALLENGE 帧时,端点 MUST 通过在 PATH_RESPONSE 帧中回显 PATH_CHALLENGE 帧所含数据来响应。端点 MUST NOT 延迟发送包含 PATH_RESPONSE 帧的分组,除非受到拥塞控制约束。

PATH_RESPONSE 帧 MUST 在接收 PATH_CHALLENGE 帧的网络路径上发送。这确保对等方的路径验证只有在 该路径双向都可用时才会成功。发起路径验证的端点 MUST NOT 强制执行此要求,因为那会使针对迁移的攻击成为可能; 见 第 9.3.3 节

端点 MUST 将包含 PATH_RESPONSE 帧的数据报 扩展到至少为允许的最小最大数据报大小 1200 字节。这会验证 该路径能够在两个方向上承载这种大小的数据报。不过,如果结果数据 超过反放大限制,端点 MUST NOT 扩展包含 PATH_RESPONSE 的数据报。预期只有在收到的 PATH_CHALLENGE 并非在扩展数据报中 发送时才会出现这种情况。

端点 MUST NOT 响应一个 PATH_CHALLENGE 帧发送多个 PATH_RESPONSE 帧;见 第 13.3 节。预期对等方会按需发送更多 PATH_CHALLENGE 帧以引发额外的 PATH_RESPONSE 帧。

8.2.3. 成功的路径 验证

当收到包含先前 PATH_CHALLENGE 帧中所发送数据的 PATH_RESPONSE 帧时,路径验证成功。在任何网络路径上收到的 PATH_RESPONSE 帧都会验证发送 PATH_CHALLENGE 所用的路径。

如果端点在未扩展到至少 1200 字节的数据报中发送 PATH_CHALLENGE 帧,并且对它的响应验证了对等方地址, 则该路径被验证,但路径 MTU 未被验证。因此,端点现在可以 发送超过已接收数据量三倍的数据。不过,端点 MUST 使用扩展数据报发起另一次路径验证,以验证该路径支持所需 MTU。

收到对包含 PATH_CHALLENGE 帧的分组的确认 不足以作为验证,因为该确认可以由恶意对等方伪造。

8.2.4. 失败的路径 验证

只有在尝试验证路径的端点放弃其路径验证尝试时, 路径验证才会失败。

端点 SHOULD 基于定时器放弃路径验证。 设置此定时器时,实现需要注意新路径的往返时间可能长于原路径。 [QUIC-RECOVERY] 中定义的 当前 PTO 或新路径 PTO(使用 kInitialRtt)两者中较大者的三倍 是 RECOMMENDED 的值。

此超时允许多个 PTO 在路径验证失败之前到期, 因此单个 PATH_CHALLENGE 或 PATH_RESPONSE 帧的丢失 不会导致路径验证失败。

注意,端点可能在新路径上收到包含其他帧的分组, 但路径验证要成功,需要具有适当数据的 PATH_RESPONSE 帧。

当端点放弃路径验证时,它确定该路径不可用。 这不一定意味着连接失败——端点可以酌情继续通过其他路径发送分组。 如果没有可用路径,端点可以等待新路径变为可用,或关闭连接。 没有通向其对等方的有效网络路径的端点 MAY 使用 NO_VIABLE_PATH 连接错误发出信号, 但注意,只有当网络路径存在却不支持所需 MTU(第 14 节)时才可能这样做。

路径验证也可能因失败以外的其他原因被放弃。 这主要发生在旧路径上的路径验证仍在进行时, 发起了到新路径的连接迁移。

9. 连接迁移

使用连接 ID 允许连接在端点地址(IP 地址和端口)变化后继续存在, 例如端点迁移到新网络所导致的地址变化。本节描述端点迁移到 新地址的过程。

QUIC 的设计依赖端点在握手期间保持稳定地址。 如 第 4.1.2 节, 见 [QUIC-TLS] 中所定义, 在握手被确认之前,端点 MUST NOT 发起连接迁移。

如果对等方发送了 disable_active_migration 传输参数, 端点也 MUST NOT 从不同的本地地址向对等方在握手期间使用的地址 发送分组(包括探测分组;见 第 9.1 节), 除非端点已经根据对等方的 preferred_address 传输参数采取行动。 如果对等方违反此要求,端点 MUST 要么丢弃该路径上的 传入分组而不生成 Stateless Reset,要么继续进行路径验证并允许对等方迁移。 生成 Stateless Reset 或关闭连接会允许网络中的第三方通过伪造或以其他方式操纵 观察到的流量来导致连接关闭。

并非所有对等方地址变化都是有意的或主动的迁移。对等方 可能经历 NAT 重新绑定:即由于中间盒(通常是 NAT)为某个流分配新的 外向端口,甚至新的外向 IP 地址而导致的地址变化。 如果端点检测到对等方地址发生任何变化,则 MUST 执行路径验证 (第 8.2 节),除非它先前已验证过该地址。

当端点没有可用于发送分组的已验证路径时,它 MAY 丢弃连接状态。具有连接迁移能力的端点 MAY 在丢弃连接状态之前等待新路径变为可用。

本文档将连接迁移限制为迁移到新的客户端地址,但 第 9.6 节中描述的情况除外。 客户端负责发起所有迁移。服务器在从某个客户端地址看到非探测分组之前, 不会向该地址发送非探测分组(见 第 9.1 节)。 如果客户端收到来自未知服务器地址的分组,则客户端 MUST 丢弃这些分组。

9.1. 探测新路径

端点 MAY 在将连接迁移到新的本地地址之前, 使用路径验证(第 8.2 节) 从新本地地址探测对等方可达性。路径验证失败只意味着新路径 不可用于此连接。无法验证路径不会导致连接结束,除非 没有可用的有效替代路径。

PATH_CHALLENGE、PATH_RESPONSE、NEW_CONNECTION_ID 和 PADDING 帧是 “探测帧”,所有其他帧是“非探测帧”。只包含探测帧的分组是 “探测分组”,包含任何其他帧的分组是“非探测分组”。

9.2. 发起连接 迁移

端点可以通过从新的本地地址发送包含非探测帧的分组, 将连接迁移到该新本地地址。

每个端点在连接建立期间都会验证其对等方地址。 因此,迁移端点可以在知道对等方愿意在其当前地址接收时 向对等方发送。这样,端点就可以迁移到新的本地地址, 而无需先验证对等方地址。

为在新路径上建立可达性,端点在新路径上发起路径 验证(第 8.2 节)。端点 MAY 将路径验证推迟到对等方向其新地址发送下一个 非探测帧之后。

迁移时,新路径可能不支持端点当前的发送速率。 因此,端点会重置其拥塞控制器和 RTT 估计, 如 第 9.4 节所述。

新路径可能不具有相同的 ECN 能力。因此,端点 按 第 13.4 节所述验证 ECN 能力。

9.3. 响应连接 迁移

从新的对等方地址收到包含非探测帧的分组,表明 对等方已经迁移到该地址。

如果接收方允许迁移,它 MUST 将后续分组发送到新的对等方地址,并且如果验证尚未开始, MUST 发起路径验证(第 8.2 节) 以验证对等方对该地址的拥有。如果接收方没有来自对等方的未使用连接 ID, 则在对等方提供连接 ID 之前,它无法在新路径上发送任何内容;见 第 9.5 节

端点只会响应编号最大的非探测分组来更改其发送分组的地址。 这确保端点在收到重排序分组时不会向旧的对等方地址发送分组。

端点 MAY 向未验证的对等方地址发送数据, 但它 MUST 按第 9.3.19.3.2 节所述防范潜在攻击。端点 MAY 如果最近见过某个对等方地址,则跳过对该地址的验证。 特别是,如果端点在检测到某种形式的虚假迁移后返回 先前已验证的路径,跳过地址验证并恢复丢包检测和拥塞状态 可以降低攻击对性能的影响。

在更改其发送非探测分组的地址后,端点 可以放弃针对其他地址的任何路径验证。

从新的对等方地址收到分组可能是对等方发生 NAT 重新绑定的结果。

在验证新的客户端地址后,服务器 SHOULD 向客户端发送新的地址 验证令牌(第 8 节)。

9.3.1. 对等方地址 欺骗

对等方可能伪造其源地址,使端点向不愿接收的主机 发送过量数据。如果端点发送的数据明显多于该欺骗性对等方, 连接迁移可能被用于放大攻击者能够向受害者产生的数据量。

第 9.3 节所述,端点需要验证对等方的新地址, 以确认对等方拥有该新地址。在对等方地址被视为有效之前, 端点会限制其向该地址发送的数据量;见 第 8 节。 如果没有此限制,端点就有被用于对无辜受害者发起拒绝服务攻击的风险。

如果端点按上述方式跳过对对等方地址的验证,则 它不需要限制其发送速率。

9.3.2. 路径上地址 欺骗

路径上的攻击者可以通过复制分组并使用欺骗性地址转发, 使其先于原始分组到达,从而导致虚假的连接迁移。 带有欺骗性地址的分组会被视为来自迁移连接,而原始分组会被视为重复分组并 被丢弃。在虚假迁移之后,源地址验证将失败,因为 源地址处的实体没有读取或响应发送给它的 PATH_CHALLENGE 帧所需的 加密密钥,即使它想这样做也不行。

为保护连接不因这种虚假迁移而失败, 当新对等方地址验证失败时,端点 MUST 恢复使用最后一个 已验证的对等方地址。此外,从合法对等方地址收到具有更高分组编号的 分组会触发另一次连接迁移。这会导致放弃对虚假迁移地址的验证, 从而将攻击者注入单个分组发起的迁移限制住。

如果端点没有关于最后一个已验证对等方地址的状态, 它 MUST 通过丢弃所有连接状态静默关闭连接。 这会导致该连接上的新分组被通用处理。例如,端点 MAY 响应任何后续传入分组发送 Stateless Reset。

9.3.3. 路径外分组 转发

能够观察分组的路径外攻击者可能会将真实分组的副本 转发给端点。如果复制的分组先于真实分组到达, 这会表现为 NAT 重新绑定。任何真实分组都会作为重复分组被丢弃。 如果攻击者能够持续转发分组,它可能导致迁移到一条经过攻击者的路径。 这会使攻击者位于路径上,使其能够观察或丢弃所有后续分组。

这种攻击方式依赖攻击者使用一条与端点之间的直接路径 具有大致相同特征的路径。如果发送的分组相对较少,或分组丢失 与攻击尝试同时发生,则该攻击更可靠。

在原始路径上收到的、会增加最大已接收分组编号的 非探测分组,会导致端点移回该路径。 在该路径上引发分组会增加攻击不成功的可能性。 因此,缓解这种攻击依赖于触发分组交换。

响应表面上的迁移时,端点 MUST 使用 PATH_CHALLENGE 帧验证先前 活跃的路径。这会诱发在该路径上发送新分组。如果该路径不再可用, 验证尝试会超时并失败;如果该路径可用但不再需要, 验证会成功,但只会导致在该路径上发送探测分组。

在活跃路径上收到 PATH_CHALLENGE 的端点 SHOULD 发送非探测分组作为响应。 如果该非探测分组先于攻击者制作的任何副本到达, 这会使连接迁移回原始路径。任何后续迁移到另一条路径都会重新开始 整个过程。

这种防御并不完美,但这不被认为是严重问题。 如果经过攻击的路径在多次尝试使用原始路径后仍可靠地快于原始路径, 则无法区分攻击和路由改善。

端点也可以使用启发式方法改进对此类攻击的检测。 例如,如果最近在旧路径上收到过分组,NAT 重新绑定就不太可能; 同样,重新绑定在 IPv6 路径上很少见。端点还可以查找重复分组。 反过来,连接 ID 的变化更可能表示有意迁移而非攻击。

9.4. 丢包检测与 拥塞控制

新路径上的可用容量可能不同于旧路径。 在旧路径上发送的分组 MUST NOT 贡献于新路径的拥塞控制或 RTT 估计。

在确认对等方拥有其新地址后,端点 MUST 立即将新路径的拥塞控制器和往返时间估计器重置为初始值 (见 [QUIC-RECOVERY] 的附录 A.3B.3), 除非对等方地址唯一的变化是端口号。由于仅端口变化通常是 NAT 重新绑定或其他中间盒活动的结果,端点 MAY 在这些情况下保留其拥塞控制状态和往返时间估计,而不是 恢复到初始值。在从旧路径保留的拥塞控制状态用于具有显著不同 特征的新路径时,发送方可能在拥塞控制器和 RTT 估计器适应之前 发送得过于激进。通常建议实现谨慎地在新路径上使用先前值。

当端点在迁移期间从/向多个地址发送数据和探测时, 接收方可能出现表面上的重排序,因为产生的两条路径可能具有不同的往返时间。 在多条路径上接收分组的接收方仍会发送覆盖所有已接收分组的 ACK 帧。

虽然在连接迁移期间可能使用多条路径,但单个拥塞控制上下文和 单个丢包恢复上下文(如 [QUIC-RECOVERY] 中所述) 可能已经足够。例如,端点可能延迟切换到新的拥塞控制上下文, 直到确认旧路径不再需要(例如 第 9.3.3 节中描述的情况)。

发送方可以对探测分组作出例外,使其丢包检测独立, 且不会不适当地导致拥塞控制器降低发送速率。 端点可能在发送 PATH_CHALLENGE 时设置单独定时器,如果收到对应的 PATH_RESPONSE,则取消该定时器。如果在收到 PATH_RESPONSE 之前定时器触发, 端点可能发送新的 PATH_CHALLENGE,并以更长时间重新启动定时器。此定时器 SHOULD第 6.2.1 节,见 [QUIC-RECOVERY] 所述设置,且 MUST NOT 更激进。

9.5. 连接迁移的隐私 影响

在多条网络路径上使用稳定的连接 ID 会允许被动观察者 将这些路径之间的活动相关联。在网络之间移动的端点可能不希望 其活动被其对等方以外的任何实体关联,因此如 第 5.1 节所述, 从不同本地地址发送时会使用不同的连接 ID。为使这有效, 端点需要确保其提供的连接 ID 不能被任何其他实体关联。

在任何时候,端点 MAY 将其传输使用的 Destination Connection ID 更改为尚未在另一条路径上使用过的值。

端点 MUST NOT 在从多个本地地址 发送时重用连接 ID——例如,在按 第 9.2 节所述发起连接迁移时, 或在按 第 9.1 节所述探测新网络路径时。

类似地,端点在发送到多个目的地址时 MUST NOT 重用连接 ID。由于其对等方无法控制的网络变化,端点可能会 从新的源地址收到具有相同 Destination Connection ID 字段值的分组, 在这种情况下,只要仍从同一本地地址发送,它 MAY 继续对新的远端地址使用当前连接 ID。

这些关于连接 ID 重用的要求仅适用于发送分组, 因为路径无意变化时可能不会伴随连接 ID 变化。例如,在一段网络不活动之后, 当客户端恢复发送时,NAT 重新绑定可能导致分组在新路径上发送。 端点按 第 9.3 节所述响应此类事件。

在每条新网络路径上为两个方向发送的分组使用不同的连接 ID, 可以消除使用连接 ID 将同一连接在不同网络路径上的分组相关联。 报头保护确保分组编号不能用于关联活动。这并不阻止 使用分组的其他属性(例如时序和大小)来关联活动。

端点 SHOULD NOT 与请求零长度连接 ID 的 对等方发起迁移,因为新路径上的流量可能会被轻易关联到旧路径上的流量。 如果服务器能够将具有零长度连接 ID 的分组关联到正确连接, 这意味着服务器正在使用其他信息来解复用分组。例如,服务器 可能向每个客户端提供唯一地址——例如使用 HTTP 替代服务 [ALTSVC]。可能允许在多条网络路径上 正确路由分组的信息,也会允许对等方以外的实体关联这些路径上的活动。

客户端可能希望通过在一段不活动后发送流量时切换到新的连接 ID、 源 UDP 端口或 IP 地址(见 [RFC8981]), 来降低可关联性。同时更改其发送分组的地址可能导致服务器检测到连接迁移。 这确保支持迁移的机制即使对未经历 NAT 重新绑定或真实迁移的客户端也会被演练。 更改地址可能导致对等方重置其拥塞控制状态(见 第 9.4 节),因此地址 SHOULD 只应不频繁地更改。

耗尽可用连接 ID 的端点无法探测新路径或发起迁移, 也无法响应对等方的探测或迁移尝试。为确保迁移可行, 并且不同路径上发送的分组不能被关联,端点 SHOULD 在对等方迁移之前提供新的连接 ID; 见 第 5.1.1 节。如果对等方可能已经耗尽可用 连接 ID,迁移端点可以在新网络路径上发送的所有分组中包含 NEW_CONNECTION_ID 帧。

9.6. 服务器首选 地址

QUIC 允许服务器在一个 IP 地址上接受连接, 并在握手后不久尝试将这些连接转移到更首选的地址。 当客户端最初连接到多个服务器共享的地址,但更希望使用单播地址以确保 连接稳定性时,这尤其有用。本节描述将连接迁移到 首选服务器地址的协议。

本文档指定的 QUIC 版本不支持在连接中途将连接迁移到 新服务器地址。如果客户端在尚未发起向该地址迁移的情况下 从新服务器地址收到分组,则客户端 SHOULD 丢弃这些分组。

9.6.1. 传达首选 地址

服务器通过在 TLS 握手中包含 preferred_address 传输参数来传达首选地址。

服务器 MAY 传达每个地址族(IPv4 和 IPv6)的首选地址,以允许客户端选择最适合其网络附着点的地址。

一旦握手被确认,客户端 SHOULD 选择服务器提供的两个地址之一, 并发起路径验证(见 第 8.2 节)。客户端使用任何先前未使用的 活跃连接 ID 构造分组,该连接 ID 可取自 preferred_address 传输 参数或 NEW_CONNECTION_ID 帧。

一旦路径验证成功,客户端 SHOULD 开始使用新的连接 ID 将所有未来分组发送到新的服务器地址,并停止使用旧的服务器地址。 如果路径验证失败,客户端 MUST 继续将所有未来分组发送到服务器的 原始 IP 地址。

9.6.2. 迁移到 首选地址

迁移到首选地址的客户端 MUST 在迁移前验证它 选择的地址;见 第 21.5.3 节

服务器在接受连接后的任何时候都可能收到发往其 首选 IP 地址的分组。如果该分组包含 PATH_CHALLENGE 帧,服务器会按照 第 8.2 节发送包含 PATH_RESPONSE 帧的分组。 在服务器从客户端在其首选地址收到非探测分组且服务器已验证新路径之前, 服务器 MUST 从其原始地址发送非探测分组。

服务器 MUST 从其首选地址 在通向客户端的路径上进行探测。这有助于防范攻击者发起的虚假迁移。

一旦服务器完成其路径验证,并在其首选地址收到 带有新的最大分组编号的非探测分组,服务器就开始 专门从其首选 IP 地址向客户端发送非探测分组。服务器 SHOULD 丢弃此连接中在旧 IP 地址上收到的较新分组。 服务器 MAY 继续处理在旧 IP 地址上收到的延迟分组。

服务器在 preferred_address 传输参数中提供的地址 仅对提供它们的连接有效。客户端 MUST NOT 将这些地址用于其他连接, 包括从当前连接恢复的连接。

9.6.3. 客户端迁移与 首选地址的交互

客户端可能需要在迁移到服务器首选地址之前执行连接迁移。 在这种情况下,客户端 SHOULD 从客户端的新地址同时向原始服务器地址和 首选服务器地址执行路径验证。

如果服务器首选地址的路径验证成功,则客户端 MUST 放弃对原始地址的验证,并迁移到使用服务器的 首选地址。如果服务器首选地址的路径验证失败,但服务器原始地址的验证成功, 则客户端 MAY 迁移到其新地址,并继续向服务器的原始地址发送。

如果在服务器首选地址收到的分组具有与握手期间从客户端观察到的 不同源地址,则服务器 MUST 按第 9.3.19.3.2 节所述防范潜在攻击。除了有意的同时迁移外, 这也可能因为客户端的接入网络为服务器首选地址使用了不同的 NAT 绑定而发生。

服务器 SHOULD 在从不同地址收到 探测分组时,向客户端的新地址发起路径验证;见 第 8 节

迁移到新地址的客户端 SHOULD 为服务器使用来自同一地址族的 首选地址。

preferred_address 传输参数中提供的连接 ID 并不 特定于所提供的地址。提供此连接 ID 是为了确保客户端拥有 可用于迁移的连接 ID,但客户端 MAY 在任何路径上使用此连接 ID。

9.7. IPv6 流标签与 迁移的使用

使用 IPv6 发送数据的端点 SHOULD 按照 [RFC6437] 应用 IPv6 流标签,除非本地 API 不允许设置 IPv6 流标签。

流标签生成 MUST 被设计为 尽量降低与先前使用的流标签发生可关联性的可能性,因为稳定的流标签 会允许在多条路径上关联活动;见 第 9.5 节

[RFC6437] 建议使用伪随机函数派生 值来生成流标签。在生成流标签时,除源地址和目的地址外还包含 Destination Connection ID 字段,可以确保变化与其他可观察标识符的 变化同步。将这些输入与本地秘密组合的密码学哈希函数 是一种可能的实现方式。

10. 连接终止

已建立的 QUIC 连接可以通过三种方式之一终止:

如果端点没有可用于发送分组的已验证路径,则 MAY 丢弃连接状态;见 第 8.2 节

10.1. 空闲超时

如果任一端点在其传输参数中指定了 max_idle_timeout (第 18.2 节), 当连接保持空闲的时间超过两个端点通告的 max_idle_timeout 值中的较小者时, 连接会静默关闭并丢弃其状态。

每个端点都会通告 max_idle_timeout,但端点处的有效值 被计算为两个通告值中的较小者(或者如果只有一个端点通告非零值, 则为唯一通告值)。通过宣布 max_idle_timeout,端点承诺 如果它在有效值到期之前放弃连接,就会发起立即关闭 (第 10.2 节)。

当收到并成功处理来自对等方的分组时,端点会重新启动其空闲定时器。 如果自上次收到并处理分组以来没有发送过其他触发 ACK 的分组, 则端点在发送触发 ACK 的分组时也会重新启动其空闲定时器。 发送分组时重新启动此定时器,可确保在发起新活动后连接不会被关闭。

为避免空闲超时期过短,端点 MUST 将空闲超时期增加到 至少为当前探测超时(PTO)的三倍。这允许多个 PTO 到期, 因而在空闲超时之前发送并丢失多个探测。

10.1.1. 存活性测试

端点如果在接近有效超时时发送分组,就有这些 分组在对等方处被丢弃的风险,因为在这些分组到达对等方之前, 对等方的空闲超时期可能已经到期。

如果对等方可能很快超时,例如在一个 PTO 内, 端点可以发送 PING 或另一个触发 ACK 的帧来测试连接 存活性;见 第 6.2 节,见 [QUIC-RECOVERY]。如果任何可用的应用数据不能安全地重试, 这尤其有用。注意,由应用决定哪些数据可以安全重试。

10.1.2. 推迟空闲 超时

如果端点期望响应数据但没有或无法发送应用数据, 它可能需要发送触发 ACK 的分组以避免空闲超时。

QUIC 的实现可能向应用提供一个推迟空闲超时的选项。 当应用希望避免丢失与打开连接相关联的状态,但预计在一段时间内 不会交换应用数据时,可以使用此功能。使用此选项时, 端点可以周期性地发送 PING 帧(第 19.2 节), 这会使对等方重新启动其空闲超时期。如果这是自收到分组以来发送的第一个 触发 ACK 的分组,则发送包含 PING 帧的分组也会重新启动本端点的空闲超时。 发送 PING 帧会使对等方以确认响应,这也会重新启动该端点的空闲超时。

使用 QUIC 的应用协议 SHOULD 就何时适合推迟 空闲超时提供指导。不必要地发送 PING 帧可能会对性能产生不利影响。

如果在超过使用 max_idle_timeout 传输参数协商的时间内 没有发送或接收任何分组,连接就会超时; 见 第 10 节。不过,中间盒中的状态 可能更早超时。虽然 [RFC4787] 中的 REQ-5 建议使用 2 分钟超时间隔,但经验表明,为防止大多数中间盒丢失 UDP 流的状态,需要每 30 秒发送分组 [GATEWAY]

10.2. 立即关闭

端点发送 CONNECTION_CLOSE 帧(第 19.19 节)以立即 终止连接。CONNECTION_CLOSE 帧会使所有流立即变为关闭; 打开的流可以被认为已经被隐式重置。

发送 CONNECTION_CLOSE 帧后,端点立即进入 closing 状态;见 第 10.2.1 节。收到 CONNECTION_CLOSE 帧后,端点进入 draining 状态;见 第 10.2.2 节

协议违规会导致立即关闭。

立即关闭可以在应用协议已安排关闭连接后使用。 这可能是在应用协议协商优雅关闭之后。应用协议可以交换 两个应用端点都需要的消息,以同意连接可以关闭; 之后应用请求 QUIC 关闭连接。当 QUIC 因此关闭连接时, 会使用带有应用提供错误码的 CONNECTION_CLOSE 帧向对等方发出关闭信号。

closing 和 draining 连接状态的存在,是为了确保连接 干净关闭,并正确丢弃延迟或重排序的分组。 这些状态 SHOULD 至少持续当前 PTO 间隔的三倍, 如 [QUIC-RECOVERY] 中所定义。

在退出 closing 或 draining 状态之前处理掉连接状态, 可能导致端点在收到迟到分组时不必要地生成 Stateless Reset。 端点如果有其他方式确保迟到分组不会引发响应,例如能够关闭 UDP 套接字, MAY 提前结束这些状态以便更快回收资源。 保留打开套接字以接受新连接的服务器 SHOULD NOT 提前结束 closing 或 draining 状态。

一旦其 closing 或 draining 状态结束,端点 SHOULD 丢弃所有连接状态。端点 MAY 响应该连接所属的任何后续传入分组发送 Stateless Reset。

10.2.1. 关闭 连接状态

端点在发起立即关闭后进入 closing 状态。

在 closing 状态中,端点只保留足以生成包含 CONNECTION_CLOSE 帧的分组,以及识别属于该连接的分组的信息。 处于 closing 状态的端点,会响应任何被其归属于该连接的传入分组, 发送包含 CONNECTION_CLOSE 帧的分组。

端点 SHOULD 限制其在 closing 状态中生成分组的速率。例如,端点可以等待收到的分组数量或时间 逐步增加之后再响应收到的分组。

端点所选择的连接 ID 和 QUIC 版本是识别 closing 连接的分组所需的足够信息;端点 MAY 丢弃所有其他连接状态。正在关闭的端点无需处理任何收到的帧。 端点 MAY 保留传入分组的分组保护密钥, 以允许其读取和处理 CONNECTION_CLOSE 帧。

端点 MAY 在进入 closing 状态时 丢弃分组保护密钥,并响应收到的任何 UDP 数据报发送包含 CONNECTION_CLOSE 帧的分组。不过,丢弃分组保护密钥的端点无法识别并丢弃 无效分组。为避免被用于放大攻击,此类端点 MUST 将其发送的分组累计大小限制为收到并归属于该连接的分组累计大小的三倍。 为最小化端点为 closing 连接维护的状态,端点 MAY 响应任何收到的分组发送完全相同的分组。

处于 closing 状态时,端点可能从新的源地址收到分组, 这可能表示连接迁移;见 第 9 节。处于 closing 状态的端点 MUST 要么丢弃从未验证地址收到的分组, 要么将其发送到未验证地址的分组累计大小限制为从该地址收到分组大小的三倍。

端点在关闭时不预期处理密钥更新(第 6 节,见 [QUIC-TLS])。密钥更新可能阻止端点从 closing 状态转移到 draining 状态,因为端点将无法处理随后收到的分组, 但除此以外没有影响。

10.2.2. 排空 连接状态

一旦端点收到 CONNECTION_CLOSE 帧,就进入 draining 状态,这表明其对等方正在关闭或排空。虽然在其他方面与 closing 状态相同, 处于 draining 状态的端点 MUST NOT 发送任何分组。一旦连接进入 draining 状态, 就无需保留分组保护密钥。

收到 CONNECTION_CLOSE 帧的端点 MAY 在进入 draining 状态之前发送 单个包含 CONNECTION_CLOSE 帧的分组,并在适当时使用 NO_ERROR 代码。 端点 MUST NOT 发送更多分组。 这样做可能导致持续交换 CONNECTION_CLOSE 帧,直到其中一个端点退出 closing 状态。

如果端点在 closing 状态中收到 CONNECTION_CLOSE 帧,表明对等方也在关闭或排空,则端点 MAY 从 closing 状态进入 draining 状态。 在这种情况下,draining 状态会在 closing 状态本应结束时结束。 换言之,端点使用相同的结束时间,但停止在此连接上发送任何分组。

10.2.3. 握手期间 立即关闭

发送 CONNECTION_CLOSE 帧时,目标是确保对等方会 处理该帧。通常,这意味着在具有最高级别分组保护的分组中发送该帧, 以避免分组被丢弃。握手被确认后(见 第 4.1.2 节,见 [QUIC-TLS]),端点 MUST 在 1-RTT 分组中发送任何 CONNECTION_CLOSE 帧。不过,在确认握手之前, 对等方可能尚不可用更高级的分组保护密钥,因此另一个 CONNECTION_CLOSE 帧 MAY 被发送在使用较低分组保护级别的分组中。更具体地说:

  • 客户端始终知道服务器是否拥有 Handshake 密钥(见 第 17.2.2.1 节),但服务器可能不知道 客户端是否拥有 Handshake 密钥。在这些情况下,服务器 SHOULD 在 Handshake 和 Initial 分组中都发送 CONNECTION_CLOSE 帧, 以确保客户端至少能处理其中一个。
  • 在 0-RTT 分组中发送 CONNECTION_CLOSE 帧的客户端 无法确信服务器已经接受 0-RTT。在 Initial 分组中发送 CONNECTION_CLOSE 帧, 即使应用错误码可能不会被接收,也更可能使服务器收到关闭信号。
  • 在确认握手之前,对等方可能无法处理 1-RTT 分组,因此端点 SHOULD 在 Handshake 和 1-RTT 分组中都发送 CONNECTION_CLOSE 帧。服务器 SHOULD 还在 Initial 分组中发送 CONNECTION_CLOSE 帧。

在 Initial 或 Handshake 分组中发送类型为 0x1d 的 CONNECTION_CLOSE 可能暴露应用状态,或被用于改变应用状态。 在 Initial 或 Handshake 分组中发送该帧时,类型为 0x1d 的 CONNECTION_CLOSE MUST 被类型为 0x1c 的 CONNECTION_CLOSE 替换。否则,关于应用状态的信息可能被泄露。 端点 MUST 清空 Reason Phrase 字段的值,并且在转换为类型 0x1c 的 CONNECTION_CLOSE 时 SHOULD 使用 APPLICATION_ERROR 代码。

以多种分组类型发送的 CONNECTION_CLOSE 帧可以合并到 单个 UDP 数据报中;见 第 12.2 节

端点可以在 Initial 分组中发送 CONNECTION_CLOSE 帧。 这可能是为了响应在 Initial 或 Handshake 分组中收到的未经认证信息。 这种立即关闭可能使合法连接暴露于拒绝服务风险。QUIC 不包含 针对握手期间路径上攻击的防御措施;见 第 21.2 节。 不过,以减少对合法对等方错误反馈为代价,如果端点丢弃非法分组 而不是使用 CONNECTION_CLOSE 终止连接,某些形式的拒绝服务 对攻击者可能会更困难。因此,如果在缺乏认证的分组中检测到错误, 端点 MAY 丢弃分组而不是立即关闭。

尚未建立状态的端点,例如检测到 Initial 分组错误的服务器, 不会进入 closing 状态。没有连接状态的端点在发送 CONNECTION_CLOSE 帧时 不进入 closing 或 draining 期。

10.3. 无状态重置

无状态重置作为最后手段选项提供给无法访问连接状态的 端点。崩溃或故障可能导致对等方继续向无法正常继续连接的端点 发送数据。端点 MAY 响应收到无法与活跃连接关联的分组 发送 Stateless Reset。

无状态重置不适合用于指示活跃连接中的错误。 希望传达致命连接错误的端点如果能够使用 CONNECTION_CLOSE 帧, 就 MUST 使用它。

为支持此过程,端点会签发无状态重置令牌, 这是一个难以猜测的 16 字节值。如果对等方随后收到一个 Stateless Reset,即一个以该无状态重置令牌结尾的 UDP 数据报, 该对等方会立即结束连接。

无状态重置令牌特定于连接 ID。端点通过在 NEW_CONNECTION_ID 帧的 Stateless Reset Token 字段中包含该值来签发 无状态重置令牌。服务器还可以在握手期间签发 stateless_reset_token 传输参数,该参数适用于其在握手期间选择的连接 ID。 这些交换受加密保护,因此只有客户端和服务器知道其值。 注意,客户端不能使用 stateless_reset_token 传输参数,因为客户端的 传输参数没有机密性保护。

当关联的连接 ID 通过 RETIRE_CONNECTION_ID 帧 (第 19.16 节)退役时,令牌即失效。

收到无法处理的分组的端点会发送一个 采用以下布局的分组(见 第 1.3 节):

无状态重置 {
  固定位 (2) = 1,
  不可预测位 (38..),
  无状态重置令牌 (128),
}
图 10无状态重置

此设计确保 Stateless Reset 在可能范围内 与带有短报头的常规分组无法区分。

Stateless Reset 使用整个 UDP 数据报,从分组报头的前两个比特 开始。第一个字节的其余部分以及其后的任意数量字节被设置为 SHOULD 与随机值无法区分的值。数据报的最后 16 字节 包含无状态重置令牌。

对于预期接收方以外的实体,Stateless Reset 会表现为 带短报头的分组。为了使 Stateless Reset 表现为有效的 QUIC 分组,Unpredictable Bits 字段需要包含至少 38 比特数据 (或 5 字节,减去两个固定位)。

如果接收方要求使用连接 ID,则得到的最小大小 21 字节 并不能保证 Stateless Reset 难以与其他分组区分。为实现这一点, 端点 SHOULD 确保其发送的所有分组至少比 它请求对等方在其分组中包含的最小连接 ID 长度长 22 字节, 并按需添加 PADDING 帧。这确保对等方发送的任何 Stateless Reset 与发送给该端点的有效分组无法区分。响应 43 字节或更短分组 发送 Stateless Reset 的端点 SHOULD 发送比所响应分组短一个字节的 Stateless Reset。

这些值假定无状态重置令牌与分组保护 AEAD 的最小扩展长度相同。 如果端点可能已经协商出具有更大最小扩展的分组保护方案, 则需要额外的不可预测字节。

端点 MUST NOT 发送比其收到分组大三倍或更多的 Stateless Reset,以避免被用于放大。 第 10.3.3 节描述了对 Stateless Reset 大小的额外限制。

端点 MUST 丢弃过小而无法成为有效 QUIC 分组的分组。举例来说,对于 [QUIC-TLS] 中定义的 AEAD 函数集合, 小于 21 字节的短报头分组永远无效。

端点 MUST 发送格式为短报头分组的 Stateless Reset。不过,端点 MUST 将任何以有效无状态 重置令牌结尾的分组视为 Stateless Reset,因为其他 QUIC 版本可能允许使用长 报头。

端点 MAY 响应带长报头的分组发送 Stateless Reset。在无状态重置令牌可供对等方使用之前, 发送 Stateless Reset 不会有效。在此 QUIC 版本中,带长报头的分组 仅在连接建立期间使用。由于无状态重置令牌直到连接建立完成或接近完成时 才可用,因此忽略带长报头的未知分组可能与发送 Stateless Reset 一样有效。

端点无法从带短报头的分组确定 Source Connection ID; 因此,它无法设置 Stateless Reset 中的 Destination Connection ID。 因此,Destination Connection ID 将不同于先前分组中使用的值。 随机的 Destination Connection ID 会使该连接 ID 看起来像是迁移到 一个使用 NEW_CONNECTION_ID 帧提供的新连接 ID 的结果;见 第 19.15 节

使用随机化连接 ID 会导致两个问题:

  • 该分组可能无法到达对等方。如果 Destination Connection ID 对路由到对等方至关重要,则该分组可能被错误路由。 这也可能触发另一个 Stateless Reset 作为响应;见 第 10.3.3 节。未被正确路由的 Stateless Reset 是无效的错误检测和恢复机制。在这种情况下,端点 需要依赖其他方法——例如定时器——来检测连接已经失败。
  • 随机生成的连接 ID 可以被对等方以外的 实体用于识别这可能是 Stateless Reset。偶尔使用不同连接 ID 的端点 可能会对此引入一些不确定性。

这种无状态重置设计特定于 QUIC 版本 1。 支持多个 QUIC 版本的端点需要生成一个 Stateless Reset, 该重置会被支持该端点可能支持(或在丢失状态之前可能支持)的任何版本的 对等方接受。设计 QUIC 新版本的人需要意识到这一点,并且要么 (1)重用此设计,要么(2)使用分组最后 16 字节以外的部分承载数据。

10.3.1. 检测 无状态重置

端点使用 UDP 数据报末尾的 16 字节检测潜在的 Stateless Reset。端点会记住与其最近发送数据报的连接 ID 和远端地址 相关联的所有无状态重置令牌。这包括来自 NEW_CONNECTION_ID 帧的 Stateless Reset Token 字段值和服务器的传输参数,但不包括与 未使用或已退役连接 ID 相关联的无状态重置令牌。端点通过将 数据报最后 16 字节与在接收该数据报的远端地址相关联的所有无状态重置令牌 进行比较,来将收到的数据报识别为 Stateless Reset。

可以对每个入站数据报执行此比较。 如果数据报中的任何分组被成功处理,端点 MAY 跳过此检查。不过,当传入数据报中的第一个分组无法与连接关联, 或无法解密时,比较 MUST 被执行。

端点 MUST NOT 检查与其 未使用的连接 ID 或已退役连接 ID 相关联的任何无状态重置令牌。

将数据报与无状态重置令牌值进行比较时, 端点 MUST 在不泄露令牌值相关信息的情况下执行比较。例如,以常量时间执行此比较, 可以防止单个无状态重置令牌的值通过定时侧信道泄露。 另一种方法是存储并比较无状态重置令牌的转换值,而不是原始令牌值; 该转换被定义为使用秘密密钥的密码学安全伪随机函数 (例如分组密码、哈希消息认证码 (HMAC)[RFC2104])。端点不预期保护 关于某个分组是否被成功解密或有效无状态重置令牌数量的信息。

如果数据报最后 16 字节的值与无状态重置 令牌相同,则端点 MUST 进入 draining 期,并且不再在此 连接上发送任何分组。

10.3.2. 计算 无状态重置令牌

无状态重置令牌 MUST 难以猜测。 为创建无状态重置令牌,端点可以为其创建的每个连接随机生成 [RANDOM] 一个秘密。不过,当集群中有多个实例时, 这会带来协调问题;对于可能丢失状态的端点,则会带来存储问题。 无状态重置专门用于处理状态丢失的情况,因此这种方法并不理想。

可以通过使用一个伪随机函数,在同一端点的所有连接中 使用单个静态密钥生成证明;该函数以静态密钥和端点选择的连接 ID (见 第 5.1 节)作为输入。端点可以使用 HMAC [RFC2104](例如 HMAC(static_key, connection_id)),或基于 HMAC 的密钥派生函数(HKDF)[RFC5869] (例如,将静态密钥用作输入密钥材料,并将连接 ID 用作 salt)。 此函数的输出被截断为 16 字节,以生成该连接的无状态重置令牌。

丢失状态的端点可以使用相同方法生成有效的 无状态重置令牌。连接 ID 来自端点收到的分组。

此设计依赖对等方始终在其分组中发送连接 ID, 以便端点能够使用分组中的连接 ID 重置连接。使用此设计的端点 MUST 要么对所有连接使用相同的连接 ID 长度, 要么对连接 ID 的长度进行编码,使其无需状态即可恢复。此外, 它不能提供零长度连接 ID。

泄露无状态重置令牌会允许任何实体终止该连接, 因此一个值只能使用一次。这种选择无状态重置令牌的方法意味着 连接 ID 与静态密钥的组合 MUST NOT 用于另一个连接。 如果共享静态密钥的实例使用相同连接 ID,或者攻击者能导致分组被路由到 一个没有状态但拥有相同静态密钥的实例,就可能发生拒绝服务攻击; 见 第 21.11 节。通过泄露无状态重置令牌而 被重置连接的连接 ID MUST NOT 在共享静态密钥的节点上 被重新用于新连接。

同一个无状态重置令牌 MUST NOT 用于多个连接 ID。端点不要求将新值与所有先前值比较, 但重复值 MAY 被视为类型为 PROTOCOL_VIOLATION 的连接错误。

注意,Stateless Reset 没有任何密码学保护。

10.3.3. 循环

Stateless Reset 的设计使得在不知道无状态重置令牌的情况下, 它与有效分组无法区分。例如,如果服务器向另一个服务器发送 Stateless Reset,它可能会收到另一个 Stateless Reset 作为响应, 这可能导致无限交换。

端点 MUST 确保其发送的每个 Stateless Reset 都小于触发它的分组,除非它维护了足以防止循环的状态。 如果发生循环,这会导致分组最终变得过小而无法触发响应。

端点可以记住它已发送的 Stateless Reset 数量, 并在达到限制后停止生成新的 Stateless Reset。对不同远端地址使用单独限制, 可以确保当其他对等方或连接耗尽限制时,Stateless Reset 仍可用于关闭连接。

小于 41 字节的 Stateless Reset 可能会被观察者识别为 Stateless Reset,具体取决于对等方连接 ID 的长度。反过来, 不响应小分组发送 Stateless Reset,可能导致 Stateless Reset 在检测 仅发送很小分组的连接损坏情形时无用;此类失败可能只能通过其他方式检测, 例如定时器。

11. 错误处理

检测到错误的端点 SHOULD 向其对等方 发出该错误存在的信号。传输层和应用层错误都可能影响 整个连接;见 第 11.1 节。只有 应用层错误 可以隔离到单个流;见 第 11.2 节

发出错误信号的帧中 SHOULD 包含 最合适的错误码(第 20 节)。 在本规范标识错误条件的地方,也会标识所使用的错误码;虽然这些表述 为要求,但不同的实现策略可能导致报告不同的错误。特别是,端点 MAY 在检测到错误条件时使用任何适用的错误码;通用错误码 (例如 PROTOCOL_VIOLATION 或 INTERNAL_ERROR)始终可以用于替代 特定错误码。

无状态重置(第 10.3 节) 不适合任何可以用 CONNECTION_CLOSE 或 RESET_STREAM 帧发出信号的错误。 端点如果拥有在连接上发送帧所需的状态, MUST NOT 使用无状态重置。

11.1. 连接错误

导致连接不可用的错误,例如明显违反协议语义, 或影响整个连接的状态损坏,MUST 使用 CONNECTION_CLOSE 帧 (第 19.19 节)发出信号。

应用特定协议错误使用帧类型为 0x1d 的 CONNECTION_CLOSE 帧发出信号。特定于传输的错误,包括本文档描述的 所有错误,承载在帧类型为 0x1c 的 CONNECTION_CLOSE 帧中。

CONNECTION_CLOSE 帧可能在一个丢失的分组中发送。端点 SHOULD 准备好在终止的连接上收到更多分组时, 重传包含 CONNECTION_CLOSE 帧的分组。限制重传次数以及发送该最终分组的 时间,可以限制在已终止连接上耗费的工作量。

选择不重传包含 CONNECTION_CLOSE 帧的分组的端点,有其对等方错过第一个此类分组的风险。 对于继续接收已终止连接数据的端点,唯一可用的机制是 尝试无状态重置过程(第 10.3 节)。

由于 Initial 分组的 AEAD 不提供强认证,端点 MAY 丢弃无效的 Initial 分组。即使本规范在其他地方要求 连接错误,也允许丢弃 Initial 分组。端点只有在不处理该分组中的帧, 或回滚任何处理影响时,才可以丢弃分组。丢弃无效 Initial 分组可用于减少暴露于拒绝服务的风险;见 第 21.2 节

11.2. 流错误

如果应用层错误影响单个流,但在其他方面使 连接保持可恢复状态,则端点可以发送带有适当错误码的 RESET_STREAM 帧 (第 19.4 节),只终止受影响的 流。

在应用协议未参与的情况下重置流,可能导致 应用协议进入不可恢复状态。RESET_STREAM MUST 只能由使用 QUIC 的应用协议发起。

RESET_STREAM 中承载的应用错误码的语义 由应用协议定义。只有应用协议能够导致流被终止。 应用协议的本地实例使用直接 API 调用,远端实例使用 STOP_SENDING 帧, 后者触发自动 RESET_STREAM。

应用协议 SHOULD 定义处理由任一端点 过早取消的流的规则。

12. 分组与帧

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

此版本的 QUIC 在连接建立期间使用长分组报头; 见 第 17.2 节。带长报头的分组包括 Initial (第 17.2.2 节)、0-RTT(第 17.2.3 节)、Handshake(第 17.2.4 节) 和 Retry(第 17.2.5 节)。版本协商使用带长报头的、 与版本无关的分组;见 第 17.2.1 节

带短报头的分组设计为具有最小开销,并在 连接建立且 1-RTT 密钥可用后使用;见 第 17.3 节

12.1. 受保护分组

QUIC 分组根据分组类型具有不同级别的密码学保护。 分组保护的细节见 [QUIC-TLS];本节概述所提供的保护。

Version Negotiation 分组没有密码学保护;见 [QUIC-INVARIANTS]

Retry 分组使用 AEAD 函数 [AEAD] 来防止 意外修改。

Initial 分组使用 AEAD 函数,其密钥使用线上可见的 值派生。因此,Initial 分组不具有有效的机密性保护。 Initial 保护的存在是为了确保分组发送方位于网络路径上。 任何从客户端接收 Initial 分组的实体都可以恢复密钥, 这些密钥允许它们读取该分组内容,并生成会在任一端点成功认证的 Initial 分组。AEAD 也会保护 Initial 分组免遭意外修改。

所有其他分组都使用从加密握手派生的密钥进行保护。 加密握手确保只有通信端点接收 Handshake、0-RTT 和 1-RTT 分组对应的密钥。使用 0-RTT 和 1-RTT 密钥保护的分组具有强 机密性和完整性保护。

出现在某些分组类型中的 Packet Number 字段具有作为 报头保护一部分应用的替代机密性保护;详情见 第 5.4 节,见 [QUIC-TLS]。 底层分组编号会随着在给定分组编号空间中发送的每个分组而增加; 详情见 第 12.3 节

12.2. 合并分组

Initial(第 17.2.2 节)、0-RTT (第 17.2.3 节)和 Handshake (第 17.2.4 节)分组包含一个 Length 字段, 该字段决定分组的结尾。该长度包括 Packet Number 和 Payload 字段,这两个字段都受机密性保护,且初始长度未知。 一旦报头保护被移除,就会知道 Payload 字段的长度。

使用 Length 字段,发送方可以将多个 QUIC 分组合并到一个 UDP 数据报中。这可以减少完成加密握手并开始发送数据所需的 UDP 数据报数量。这也可用于构造路径最大传输单元(PMTU)探测; 见 第 14.4.1 节。接收方 MUST 能够处理合并分组。

按照加密级别递增的顺序合并分组(Initial、0-RTT、 Handshake、1-RTT;见 第 4.1.4 节,见 [QUIC-TLS]),会使接收方更可能 在一次遍历中处理所有分组。带短报头的分组不包含长度, 因此它只能是 UDP 数据报中包含的最后一个分组。端点 SHOULD 在帧要以相同加密级别发送时, 将多个帧包含在单个分组中,而不是在相同加密级别合并多个分组。

接收方 MAY 基于 UDP 数据报中 第一个分组的信息进行路由。发送方 MUST NOT 将具有不同连接 ID 的 QUIC 分组合并到单个 UDP 数据报中。接收方 SHOULD 忽略数据报中任何后续分组,如果其 Destination Connection ID 与第一个分组不同。

每个合并到单个 UDP 数据报中的 QUIC 分组都是独立且 完整的。合并 QUIC 分组的接收方 MUST 分别处理每个 QUIC 分组,并分别确认它们,就像它们是作为不同 UDP 数据报的 载荷收到一样。例如,如果解密失败(因为密钥不可用或出于其他任何原因), 接收方 MAY 丢弃或缓冲该分组以便稍后处理,并且 MUST 尝试处理剩余分组。

Retry 分组(第 17.2.5 节)、 Version Negotiation 分组 (第 17.2.1 节)以及带短报头的分组(第 17.3 节)不包含 Length 字段,因此在同一 UDP 数据报中不能后接其他分组。 还要注意,不存在 Retry 或 Version Negotiation 分组与另一个分组合并的情况。

12.3. 分组编号

分组编号是范围从 0 到 262-1 的整数。 该编号用于确定分组保护的密码学 nonce。每个 端点为发送和接收分别维护一个分组编号。

分组编号限制在此范围内,因为它们需要能完整表示在 ACK 帧(第 19.3 节)的 Largest Acknowledged 字段中。 不过,当存在于长报头或短报头中时,分组编号会被缩减并编码为 1 到 4 字节;见 第 17.1 节

Version Negotiation(第 17.2.1 节)和 Retry(第 17.2.5 节) 分组不包含分组编号。

QUIC 中的分组编号被划分为三个空间:

Initial 空间:

所有 Initial 分组(第 17.2.2 节)都在此空间中。

Handshake 空间:

所有 Handshake 分组(第 17.2.4 节)都在此空间中。

应用数据空间:

所有 0-RTT(第 17.2.3 节)和 1-RTT(第 17.3.1 节) 分组都在此 空间中。

[QUIC-TLS] 中所述,每种分组类型使用不同的保护密钥。

概念上,分组编号空间是分组可以被处理和确认的上下文。 Initial 分组只能使用 Initial 分组保护密钥发送,并且只能在同样为 Initial 分组的分组中被确认。类似地,Handshake 分组在 Handshake 加密 级别发送,并且只能在 Handshake 分组中被确认。

这会强制分隔不同分组编号空间中发送的数据的密码学保护。 每个空间中的分组编号都从分组编号 0 开始。 在同一分组编号空间中发送的后续分组 MUST 使分组 编号至少增加 1。

0-RTT 和 1-RTT 数据存在于同一分组编号空间中, 以便丢包恢复算法在这两种分组类型之间更容易实现。

QUIC 端点 MUST NOT 在一个连接的同一 分组编号空间内重用分组编号。如果发送的分组编号达到 262-1,发送方 MUST 在不发送 CONNECTION_CLOSE 帧或任何后续分组的情况下关闭连接;端点 MAY 响应其收到的后续分组发送 Stateless Reset(第 10.3 节)。

接收方 MUST 丢弃新解保护的 分组,除非它确定尚未处理来自同一分组编号空间且具有相同分组编号的 另一个分组。出于 第 9.5 节 ,见 [QUIC-TLS] 中描述的原因, 重复抑制 MUST 在移除分组保护后发生。

为检测重复而跟踪所有单独分组的端点, 有积累过多状态的风险。可以通过维护一个最小分组编号来限制 检测重复所需的数据,低于该编号的所有分组都会被立即丢弃。 任何最小值都需要考虑往返时间的大幅变化,包括对等方可能用 大得多的往返时间探测网络路径的可能性;见 第 9 节

发送方的分组编号编码和接收方的解码在 第 17.1 节中描述。

12.4. 帧与帧类型

移除分组保护后,QUIC 分组的载荷由一系列完整帧组成, 如 图 11所示。 Version Negotiation、Stateless Reset 和 Retry 分组不包含帧。

分组载荷 {
  帧 (8..) ...,
}
图 11QUIC 载荷

包含帧的分组载荷 MUST 至少包含一个帧,并且 MAY 包含多个帧和多种帧类型。端点 MUST 将收到不包含帧的分组视为类型为 PROTOCOL_VIOLATION 的连接错误。 帧始终适配在单个 QUIC 分组内,不能跨多个分组。

每个帧都以 Frame Type 开始,用于指示其类型, 随后是依赖具体类型的附加字段:

帧 {
  帧类型 (i),
  依赖类型的字段 (..),
}
图 12通用帧布局

表 3列出并汇总了 本规范中定义的每种帧类型的信息。表后包含对此摘要的说明。

表 3帧类型
类型值 帧类型名称 定义 分组 特殊规则
0x00 PADDING 第 19.1 节 IH01 NP
0x01 PING 第 19.2 节 IH01
0x02-0x03 ACK 第 19.3 节 IH_1 NC
0x04 RESET_STREAM 第 19.4 节 __01
0x05 STOP_SENDING 第 19.5 节 __01
0x06 CRYPTO 第 19.6 节 IH_1
0x07 NEW_TOKEN 第 19.7 节 ___1
0x08-0x0f STREAM 第 19.8 节 __01 F
0x10 MAX_DATA 第 19.9 节 __01
0x11 MAX_STREAM_DATA 第 19.10 节 __01
0x12-0x13 MAX_STREAMS 第 19.11 节 __01
0x14 DATA_BLOCKED 第 19.12 节 __01
0x15 STREAM_DATA_BLOCKED 第 19.13 节 __01
0x16-0x17 STREAMS_BLOCKED 第 19.14 节 __01
0x18 NEW_CONNECTION_ID 第 19.15 节 __01 P
0x19 RETIRE_CONNECTION_ID 第 19.16 节 __01
0x1a PATH_CHALLENGE 第 19.17 节 __01 P
0x1b PATH_RESPONSE 第 19.18 节 ___1 P
0x1c-0x1d CONNECTION_CLOSE 第 19.19 节 ih01 N
0x1e HANDSHAKE_DONE 第 19.20 节 ___1

每种帧类型的格式和语义在 第 19 节中有更详细说明。本节余下部分提供 重要且通用信息的摘要。

ACK、STREAM、MAX_STREAMS、STREAMS_BLOCKED 和 CONNECTION_CLOSE 帧中的 Frame Type 用于承载其他帧特定标志。对于所有 其他帧,Frame Type 字段仅标识该帧。

表 3中的“分组”列 列出每种帧类型可能出现在哪些分组类型中,并由以下字符表示:

I:

Initial(第 17.2.2 节

H:

Handshake(第 17.2.4 节

0:

0-RTT(第 17.2.3 节

1:

1-RTT(第 17.3.1 节

ih:

只有类型为 0x1c 的 CONNECTION_CLOSE 帧可以出现在 Initial 或 Handshake 分组中。

关于这些限制的更多细节,见 第 12.5 节。注意, 所有帧都可以出现在 1-RTT 分组中。端点 MUST 将收到 不允许的分组类型中的帧视为类型为 PROTOCOL_VIOLATION 的连接错误。

表 3中的“特殊规则”列 汇总了管理该帧类型处理或生成的任何特殊规则, 并由以下字符表示:

N:

仅包含带此标记的帧的分组不会 触发 ACK;见 第 13.2 节

C:

仅包含带此标记的帧的分组,在拥塞控制目的下不计入 在途字节;见 [QUIC-RECOVERY]

P:

仅包含带此标记的帧的分组,可用于 在连接迁移期间探测新的网络路径;见 第 9.1 节

F:

带此标记的帧的内容受流量控制; 见 第 4 节

表 3中的“分组”和“特殊规则”列不构成 IANA 注册表的一部分;见 第 22.4 节

端点 MUST 将收到未知类型的帧 视为类型为 FRAME_ENCODING_ERROR 的连接错误。

在此版本的 QUIC 中,所有帧都是幂等的。也就是说, 有效帧在被多次接收时不会导致不良副作用或错误。

Frame Type 字段使用可变长度整数编码(见 第 16 节),但有一个例外。为确保 帧解析实现简单且高效,帧类型 MUST 使用尽可能短的 编码。对于本文档定义的帧类型,这意味着单字节编码, 即使这些值可以编码为 2、4 或 8 字节的可变长度整数。 例如,虽然 0x4001 是值为 1 的可变长度整数的合法 两字节编码,PING 帧始终编码为值为 0x01 的单字节。 此规则适用于所有当前和未来的 QUIC 帧类型。端点 MAY 将收到使用比必要长度更长编码的帧类型 视为类型为 PROTOCOL_VIOLATION 的连接错误。

12.5. 帧与编号 空间

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

  • PADDING、PING 和 CRYPTO 帧 MAY 出现在任何分组编号空间中。
  • 发出 QUIC 层错误信号的 CONNECTION_CLOSE 帧(类型 0x1c)MAY 出现在任何分组编号空间中。发出应用错误信号的 CONNECTION_CLOSE 帧 (类型 0x1d)MUST 只能出现在应用数据分组 编号空间中。
  • ACK 帧 MAY 出现在任何 分组编号空间中,但只能确认出现在该分组编号空间中的分组。 不过,如下文所述,0-RTT 分组不能包含 ACK 帧。
  • 所有其他帧类型 MUST 只能在应用数据分组编号空间中发送。

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

13. 分组化与可靠性

发送方在 QUIC 分组中发送一个或多个帧;见 第 12.4 节

发送方可以通过在每个 QUIC 分组中包含尽可能多的帧, 来最小化每个分组的带宽和计算成本。发送方 MAY 等待一小段 时间以收集多个帧,然后再发送一个未最大化填充的分组, 从而避免发送大量小分组。实现 MAY 使用关于应用发送行为的 知识或启发式方法来决定是否等待以及等待多久。此等待期是实现决策, 实现应谨慎地保守延迟,因为任何延迟都可能增加应用可见 延迟。

流复用通过将来自多个流的 STREAM 帧交织到一个或多个 QUIC 分组中实现。单个 QUIC 分组可以包含来自一个或多个流的 多个 STREAM 帧。

QUIC 的好处之一是避免多个流之间的队头阻塞。 当发生分组丢失时,只有在该分组中有数据的流会被阻塞并等待 接收重传,而其他流可以继续推进。注意,当来自多个流的数据 包含在单个 QUIC 分组中时,该分组丢失会阻塞所有这些 流的推进。建议实现只在必要时将尽可能少的流包含在外发分组中, 同时不要因分组未填满而损失传输效率。

13.1. 分组处理

在成功移除分组保护并处理完该分组中包含的所有帧之前, 分组 MUST NOT 被确认。对于 STREAM 帧,这意味着数据已经入队,以准备由应用协议接收, 但不要求数据已被交付和消费。

一旦分组被完全处理,接收方通过发送一个或多个 包含所接收分组编号的 ACK 帧来确认接收。

端点如果能够检测到这种情况,SHOULD 将收到 对其未发送分组的确认视为类型为 PROTOCOL_VIOLATION 的连接错误。 关于这可能如何实现的进一步讨论,见 第 21.4 节

13.2. 生成确认

端点会确认其接收并处理的所有分组。不过,只有 触发 ACK 的分组才会导致在最大 ACK 延迟内发送 ACK 帧。 不触发 ACK 的分组仅在由于其他原因发送 ACK 帧时才会被确认。

无论出于何种原因发送分组时,如果最近没有发送过 ACK 帧, 端点 SHOULD 尝试包含 ACK 帧。 这样做有助于对等方及时检测丢包。

一般来说,接收方频繁反馈可以改善丢包和拥塞响应, 但这必须与接收方响应每个触发 ACK 分组而发送 ACK 帧所产生的 过大负载相平衡。下面给出的指导旨在取得这种平衡。

13.2.1. 发送 ACK 帧

每个分组 SHOULD 至少被确认一次, 触发 ACK 的分组 MUST 在端点通过 max_ack_delay 传输参数传达的最大延迟内至少被确认一次;见 第 18.2 节。max_ack_delay 声明了一个明确契约:端点承诺绝不会有意将触发 ACK 分组的确认 延迟超过所指示的值。如果这样做,任何超额部分都会累积到 RTT 估计中, 并可能导致来自对等方的虚假或延迟重传。发送方使用接收方的 max_ack_delay 值来确定基于定时器的重传超时, 如 第 6.2 节,见 [QUIC-RECOVERY] 中详述。

端点 MUST 立即确认所有 触发 ACK 的 Initial 和 Handshake 分组,并在其通告的 max_ack_delay 内确认所有触发 ACK 的 0-RTT 和 1-RTT 分组, 但以下例外。在握手确认之前,端点在收到 Handshake、0-RTT 或 1-RTT 分组时可能没有用于解密它们的分组保护密钥。 因此,它可能缓冲这些分组,并在所需密钥可用时确认它们。

由于仅包含 ACK 帧的分组不受拥塞控制, 端点 MUST NOT 响应收到一个 触发 ACK 的分组而发送多个这样的分组。

端点 MUST NOT 响应 非触发 ACK 的分组而发送非触发 ACK 的分组,即使在收到的分组之前存在 分组间隙也是如此。这避免了确认的无限反馈循环, 这种循环可能阻止连接进入空闲状态。非触发 ACK 分组最终会在端点响应其他事件发送 ACK 帧时得到确认。

只发送 ACK 帧的端点不会从其对等方收到确认, 除非这些确认被包含在具有触发 ACK 帧的分组中。 当有新的触发 ACK 分组需要确认时,端点 SHOULD 将 ACK 帧 与其他帧一起发送。当只有非触发 ACK 分组需要确认时,端点 MAY 选择在收到触发 ACK 分组之前,不随外发帧发送 ACK 帧。

只发送非触发 ACK 分组的端点可以选择 偶尔向这些分组添加一个触发 ACK 的帧,以确保收到确认; 见 第 13.2.4 节。 在这种情况下,端点 MUST NOT 在所有本来不会触发 ACK 的分组中 发送触发 ACK 的帧,以避免确认的无限反馈循环。

为了帮助发送方进行丢包检测,端点在收到触发 ACK 的分组且 满足以下任一条件时,SHOULD 无延迟地生成并发送 ACK 帧:

  • 收到的分组的分组编号小于另一个已经收到的触发 ACK 分组,或
  • 该分组的分组编号大于已收到的编号最大的 触发 ACK 分组,并且在该分组与此分组之间存在缺失分组。

类似地,IP 报头中标记为 ECN Congestion Experienced (CE) 码点的分组 SHOULD 被立即确认,以减少 对等方对拥塞事件的响应时间。

[QUIC-RECOVERY] 中的算法预期能抵御不遵循上述指导的接收方。 不过,实现只有在仔细考虑改变对本端点所建立连接以及网络其他用户的 性能影响之后,才应偏离这些要求。

13.2.2. 确认 频率

接收方决定响应触发 ACK 的分组发送确认的频率。 这一决定涉及权衡。

端点依赖及时确认来检测丢包;见 第 6 节,见 [QUIC-RECOVERY]。 基于窗口的拥塞控制器,例如 第 7 节,见 [QUIC-RECOVERY] 中描述的控制器,依赖确认来管理其 拥塞窗口。在这两种情况下,延迟确认都可能不利于 性能。

另一方面,降低仅承载确认的分组频率, 会降低两个端点上的分组传输和处理成本。它可以改善严重非对称链路上的 连接吞吐量,并减少使用返回路径容量的确认流量; 见 第 3 节,见 [RFC3449]

接收方 SHOULD 在接收至少两个触发 ACK 的分组后发送 ACK 帧。此建议本质上是通用的,并与 TCP 端点行为的 建议 [RFC5681] 一致。对网络条件的了解、 对对等方拥塞控制器的了解,或进一步研究和实验,可能会提出 性能特征更好的替代确认策略。

接收方 MAY 在决定是否 发送 ACK 帧作为响应之前,处理多个可用分组。

13.2.3. 管理 ACK 范围

发送 ACK 帧时,会包含一个或多个已确认分组范围。 包含对较旧分组的确认,可以降低因先前发送的 ACK 帧丢失而导致虚假重传的机会, 代价是 ACK 帧更大。

ACK 帧 SHOULD 始终确认最近收到的 分组,并且分组越乱序,越有必要快速发送更新后的 ACK 帧, 以防止对等方将分组声明为丢失并虚假重传其中包含的帧。 ACK 帧预期适配在单个 QUIC 分组中。如果不能,则省略较旧的范围 (具有最小分组编号的范围)。

接收方会限制其记住并在 ACK 帧中发送的 ACK Ranges (第 19.3.1 节)数量,以限制 ACK 帧大小并避免资源 耗尽。收到对 ACK 帧的确认后,接收方 SHOULD 停止跟踪那些已确认的 ACK Ranges。发送方可以预期 大多数分组会被确认,但 QUIC 不保证接收方处理的每个分组 都会收到确认。

保留许多 ACK Ranges 可能导致 ACK 帧变得 过大。接收方可以丢弃未确认的 ACK Ranges 来限制 ACK 帧大小, 代价是增加来自发送方的重传。如果 ACK 帧过大而无法适配在分组中, 这是必要的。接收方 MAY 进一步限制 ACK 帧大小, 以为其他帧保留空间,或限制确认所消耗的容量。

接收方 MUST 保留 ACK Range, 除非它能确保随后不会接受该范围中编号的分组。 维护一个随着范围被丢弃而增加的最小分组编号,是以最小状态实现这一点的一种方式。

接收方可以丢弃所有 ACK Ranges,但它们 MUST 保留已经成功处理的最大分组编号, 因为该编号用于从后续分组中恢复分组编号;见 第 17.1 节

接收方 SHOULD 在每个 ACK 帧中 包含一个包含最大已接收分组编号的 ACK Range。Largest Acknowledged 字段 用于发送方的 ECN 验证,并且包含比先前 ACK 帧中所含值更低的值, 可能导致 ECN 被不必要地禁用;见 第 13.4.2 节

第 13.2.4 节描述了 一种示例性方法,用于确定每个 ACK 帧中要确认哪些分组。 虽然该算法的目标是为处理的每个分组生成确认,但确认仍可能丢失。

13.2.4. 通过跟踪 ACK 帧限制范围

当发送包含 ACK 帧的分组时,可以保存该帧中的 Largest Acknowledged 字段。当包含 ACK 帧的分组被确认时, 接收方可以停止确认小于或等于所发送 ACK 帧中 Largest Acknowledged 字段的分组。

只发送非触发 ACK 分组(例如 ACK 帧)的接收方,可能在很长一段时间内不会收到确认。这可能导致 接收方在很长时间内维护大量 ACK 帧的状态,并且它发送的 ACK 帧 可能不必要地很大。在这种情况下,接收方可以偶尔发送 PING 或其他较小的触发 ACK 帧,例如每个往返一次,以诱发对等方发送 ACK。

在没有 ACK 帧丢失的情况下,此算法允许最少 1 个 RTT 的 重排序。在存在 ACK 帧丢失和重排序的情况下,这种方法不能保证 每个确认在不再包含于 ACK 帧之前都被发送方看到。 分组可能乱序接收,随后包含它们的所有 ACK 帧都可能丢失。 在这种情况下,丢包恢复算法可能导致虚假重传,但发送方仍会继续向前推进。

13.2.5. 测量并报告 主机延迟

端点测量在收到具有最大分组编号的分组的时间与发送确认的时间之间 有意引入的延迟。端点在 ACK 帧的 ACK Delay 字段中编码此确认延迟; 见 第 19.3 节。这允许 ACK 帧的接收方 对任何有意延迟进行调整,这对于在确认被延迟时获得更好的路径 RTT 估计很重要。

分组在处理之前可能被保留在操作系统内核或主机上的其他地方。 端点在填充 ACK 帧中的 ACK Delay 字段时, MUST NOT 包含其不控制的延迟。不过,端点 SHOULD 包含由于解密密钥不可用导致的缓冲延迟,因为这些延迟可能很大且 可能不会重复。

当测得的确认延迟大于其 max_ack_delay 时, 端点 SHOULD 报告测得的延迟。 此信息在握手期间延迟可能很大时尤其有用;见 第 13.2.1 节

13.2.6. ACK 帧与 分组保护

ACK 帧 MUST 只能承载在与被确认分组 具有相同分组编号空间的分组中;见 第 12.1 节。例如, 使用 1-RTT 密钥保护的分组 MUST 在同样使用 1-RTT 密钥保护的分组中被确认。

客户端使用 0-RTT 分组保护发送的分组 MUST 由服务器在受 1-RTT 密钥保护的分组中确认。 这可能意味着,如果服务器的加密握手消息被延迟或丢失,客户端无法使用这些确认。 注意,同样的限制也适用于服务器使用 1-RTT 密钥保护发送的其他数据。

13.2.7. PADDING 帧 消耗拥塞窗口

包含 PADDING 帧的分组在拥塞控制目的下被视为在途 [QUIC-RECOVERY]。 因此,仅包含 PADDING 帧的分组会消耗拥塞窗口,但不会生成 打开拥塞窗口的确认。为避免死锁,发送方 SHOULD 确保 除 PADDING 帧外还周期性地发送其他帧,以诱发接收方确认。

13.3. 信息重传

被确定为丢失的 QUIC 分组不会整体重传。 其中包含的帧也是如此。相反,帧中可能承载的信息会按需 在新的帧中再次发送。

新的帧和分组用于承载被确定已丢失的信息。 一般来说,当包含该信息的分组被确定为丢失时,信息会再次发送; 当包含该信息的分组被确认时,发送停止。

  • CRYPTO 帧中发送的数据按照 [QUIC-RECOVERY] 中的规则重传, 直到所有数据都被确认。Initial 和 Handshake 分组的 CRYPTO 帧中的数据会在相应分组编号空间的密钥被丢弃时被丢弃。
  • STREAM 帧中发送的应用数据会在新的 STREAM 帧中 重传,除非端点已经为该流发送 RESET_STREAM。一旦端点 发送 RESET_STREAM 帧,就不再需要更多 STREAM 帧。
  • ACK 帧承载最新一组确认,以及来自最大已确认分组的 确认延迟,如 第 13.2.1 节所述。延迟发送包含 ACK 帧的分组或重新发送旧 ACK 帧,可能导致对等方生成 膨胀的 RTT 样本或不必要地禁用 ECN。
  • 流传输的取消,如 RESET_STREAM 帧中承载的, 会一直发送,直到被确认或直到对等方确认所有流数据 (也就是说,流的发送部分达到“Reset Recvd”或“Data Recvd”状态)。 RESET_STREAM 帧的内容在再次发送时 MUST NOT 改变。
  • 类似地,请求取消流传输,如 STOP_SENDING 帧中编码的,会一直发送,直到流的接收部分进入 “Data Recvd”或“Reset Recvd”状态;见 第 3.5 节
  • 连接关闭信号,包括包含 CONNECTION_CLOSE 帧的分组,在检测到分组丢失时不会再次发送。重新发送这些 信号见 第 10 节
  • 当前连接最大数据在 MAX_DATA 帧中发送。如果包含最近发送的 MAX_DATA 帧的分组被声明为丢失, 或端点决定更新限制,则在 MAX_DATA 帧中发送更新后的值。 需要谨慎,避免过于频繁发送此帧,因为该限制可能频繁增加并导致 不必要地发送大量 MAX_DATA 帧;见 第 4.2 节
  • 当前最大流数据偏移在 MAX_STREAM_DATA 帧中发送。与 MAX_DATA 一样,当包含某个流最近的 MAX_STREAM_DATA 帧的分组丢失,或限制被更新时,会发送更新后的值, 同时注意防止该帧发送过于频繁。当流的接收部分进入 “Size Known”或“Reset Recvd”状态时,端点 SHOULD 停止发送 MAX_STREAM_DATA 帧。
  • 给定类型流的限制在 MAX_STREAMS 帧中发送。与 MAX_DATA 一样,当包含某个流类型最近 MAX_STREAMS 帧的分组被声明为丢失,或限制被更新时,会发送更新后的值, 同时注意防止该帧发送过于频繁。
  • 阻塞信号承载在 DATA_BLOCKED、 STREAM_DATA_BLOCKED 和 STREAMS_BLOCKED 帧中。DATA_BLOCKED 帧具有连接作用域, STREAM_DATA_BLOCKED 帧具有流作用域,STREAMS_BLOCKED 帧 的作用域限定为特定流类型。如果包含某个作用域最近帧的分组丢失, 且仅在端点仍受相应限制阻塞时,会发送新帧。 这些帧始终包含其传输时导致阻塞的限制。
  • 使用 PATH_CHALLENGE 帧进行的存活性或路径验证检查 会周期性发送,直到收到匹配的 PATH_RESPONSE 帧,或不再需要 进行存活性或路径验证检查。PATH_CHALLENGE 帧每次发送时都包含不同的载荷。
  • 使用 PATH_RESPONSE 帧进行的路径验证响应 只发送一次。预期对等方会按需发送更多 PATH_CHALLENGE 帧以引发 额外 PATH_RESPONSE 帧。
  • 新的连接 ID 在 NEW_CONNECTION_ID 帧中发送,并在包含它们的分组丢失时重传。 此帧的重传携带相同的序列号值。同样,退役的连接 ID 在 RETIRE_CONNECTION_ID 帧中发送,并在包含它们的分组丢失时重传。
  • 如果包含 NEW_TOKEN 帧的分组丢失, 则会重传 NEW_TOKEN 帧。除了直接比较帧内容外,不提供 用于检测重排序和重复 NEW_TOKEN 帧的特殊支持。
  • PING 和 PADDING 帧不包含信息, 因此丢失的 PING 或 PADDING 帧不需要修复。
  • HANDSHAKE_DONE 帧 MUST 重传,直到其被确认。

除非应用指定的优先级另有指示,否则端点 SHOULD 优先重传数据, 而不是发送新数据;见 第 2.3 节

即使鼓励发送方在每次发送分组时都组装包含最新信息的帧, 也不禁止重传丢失分组中的帧副本。重传帧副本的发送方 需要处理由于分组编号长度、连接 ID 长度和路径 MTU 变化导致的 可用载荷大小减少。接收方 MUST 接受 包含过时帧的分组,例如承载比旧分组中发现的最大数据值更小的 MAX_DATA 帧。

发送方 SHOULD 避免在分组被确认后 重传其中的信息。这包括在被声明为丢失后又被确认的分组, 这可能在网络重排序存在时发生。这样做要求发送方在分组被声明为丢失后 保留关于分组的信息。发送方可以在经过足以允许重排序的一段时间后丢弃此信息, 例如一个 PTO(第 6.2 节 ,见 [QUIC-RECOVERY]), 或基于其他事件,例如达到内存限制。

检测到丢包后,发送方 MUST 采取 适当的拥塞控制行动。 丢包检测和拥塞控制的细节在 [QUIC-RECOVERY] 中描述。

13.4. 显式拥塞 通知

QUIC 端点可以使用 ECN [RFC3168] 来检测并响应网络 拥塞。ECN 允许端点在 IP 分组的 ECN 字段中设置 ECN-Capable Transport (ECT) 码点。随后,网络节点可以通过在 ECN 字段中设置 ECN-CE 码点来指示 拥塞,而不是丢弃分组 [RFC8087]。 端点按照 [QUIC-RECOVERY] 中描述的方式,通过降低其发送速率来响应 所报告的拥塞。

为启用 ECN,发送 QUIC 的端点首先确定某条路径是否支持 ECN 标记,以及对等方是否报告收到的 IP 报头中的 ECN 值; 见 第 13.4.2 节

13.4.1. 报告 ECN 计数

使用 ECN 要求接收端点从 IP 分组中读取 ECN 字段,但并非所有平台都能做到这一点。如果端点未实现 ECN 支持, 或无法访问收到的 ECN 字段,则它不会报告其所收分组的 ECN 计数。

即使端点没有在其发送的分组中设置 ECT 字段, 如果收到的 ECN 标记可访问,端点也 MUST 提供关于这些标记的反馈。 未能报告 ECN 计数会导致发送方为此连接禁用 ECN 的使用。

收到带有 ECT(0)、ECT(1) 或 ECN-CE 码点的 IP 分组时,启用 ECN 的端点会访问 ECN 字段,并增加相应的 ECT(0)、ECT(1) 或 ECN-CE 计数。这些 ECN 计数包含在后续 ACK 帧中;见第 13.219.3 节。

每个分组编号空间维护单独的确认状态和单独的 ECN 计数。合并的 QUIC 分组(见 第 12.2 节)共享同一个 IP 报头,因此每个合并的 QUIC 分组都会使 ECN 计数增加一次。

例如,如果一个 Initial、一个 Handshake 和一个 1-RTT QUIC 分组合并到单个 UDP 数据报中,则所有三个分组编号空间的 ECN 计数 都会基于该单个 IP 报头的 ECN 字段各增加一次。

只有当收到的 IP 分组中的 QUIC 分组被处理时, ECN 计数才会增加。因此,重复的 QUIC 分组不会被处理,也不会 增加 ECN 计数;相关安全关注见 第 21.10 节

13.4.2. ECN 验证

故障网络设备可能损坏或错误丢弃 承载非零 ECN 码点的分组。为确保在存在此类设备时仍能连接, 端点会为每条网络路径验证 ECN 计数,并在检测到错误时 在该路径上禁用 ECN。

为新路径执行 ECN 验证:

  • 端点在发往对等方的新路径上的早期外发 分组的 IP 报头中设置 ECT(0) 码点 [RFC8311]
  • 端点监测所有带有 ECT 码点发送的分组 是否最终都被视为丢失(第 6 节,见 [QUIC-RECOVERY]),这表示 ECN 验证失败。

如果端点有理由预期带有 ECT 码点的 IP 分组可能被故障网络元素丢弃,则端点可以只为路径上的前十个外发分组 设置 ECT 码点,或设置三次 PTO 的一段时间(见 第 6.2 节,见 [QUIC-RECOVERY])。如果所有标记非零 ECN 码点的分组随后都丢失,则可以在假设标记导致丢失的情况下禁用标记。

因此,端点会尝试使用 ECN,并为每个 新连接、切换到服务器首选地址时以及主动连接迁移到新路径时验证这一点。 附录 A.4描述了一种可能的算法。

也可以使用其他方法探测路径的 ECN 支持, 以及使用不同的标记策略。实现 MAY 使用 RFC 中定义的其他方法; 见 [RFC8311]。使用 ECT(1) 码点的实现需要 使用报告的 ECT(1) 计数执行 ECN 验证。

13.4.2.1. 接收带 ECN 计数的 ACK 帧

网络错误应用 ECN-CE 标记可能导致 连接性能下降。因此,收到带 ECN 计数的 ACK 帧的端点在使用这些计数之前 会对其进行验证。它通过将新收到的计数与上一个成功处理的 ACK 帧中的计数进行比较来执行此验证。ECN 计数的任何增加 都基于 ACK 帧中新确认分组上所应用的 ECN 标记进行验证。

如果 ACK 帧新确认了端点发送时设置了 ECT(0) 或 ECT(1) 码点的分组,而 ACK 帧中不存在相应的 ECN 计数,则 ECN 验证失败。此检查可检测将 ECN 字段置零的网络 元素,或不报告 ECN 标记的对等方。

如果 ECT(0) 和 ECN-CE 计数增加量之和 小于最初使用 ECT(0) 标记发送的新确认分组数量, ECN 验证也会失败。类似地,如果 ECT(1) 和 ECN-CE 计数增加量之和 小于使用 ECT(1) 标记发送的新确认分组数量,ECN 验证也会失败。 这些检查可以检测网络对 ECN-CE 标记的重新标记。

当 ACK 帧丢失时,端点可能错过对某个分组的确认。 因此,ECT(0)、ECT(1) 和 ECN-CE 计数的总增加量 可能大于 ACK 帧中新确认的分组数量。这就是为什么允许 ECN 计数 大于已确认分组总数。

验证来自重排序 ACK 帧的 ECN 计数可能导致失败。 端点 MUST NOT 因处理未增加最大已确认分组编号的 ACK 帧而使 ECN 验证失败。

如果收到的 ECT(0) 或 ECT(1) 的总计数 超过使用每个相应 ECT 码点发送的分组总数,ECN 验证可能失败。 特别是,当端点收到与其从未应用过的 ECT 码点对应的非零 ECN 计数时,验证将失败。此检查可检测网络中分组被重新标记为 ECT(0) 或 ECT(1) 的情况。

13.4.2.2. ECN 验证结果

如果验证失败,则端点 MUST 禁用 ECN。它会停止在其发送的 IP 分组中设置 ECT 码点,并假定网络路径或对等方不支持 ECN。

即使验证失败,端点 MAY 在连接中任何较晚时间为同一路径 重新验证 ECN。端点可以继续周期性地尝试验证。

验证成功后,端点 MAY 继续在其后续发送的分组中设置 ECT 码点, 预期该路径具备 ECN 能力。网络路由和路径元素可能在连接中途变化; 如果之后验证失败,端点 MUST 禁用 ECN。

14. 数据报大小

一个 UDP 数据报可以包含一个或多个 QUIC 分组。数据报大小指的是 承载 QUIC 分组的单个 UDP 数据报的 UDP 载荷总大小。数据报大小包括 一个或多个 QUIC 分组报头和受保护载荷, 但不包括 UDP 或 IP 报头。

最大数据报大小定义为可使用单个 UDP 数据报 在网络路径上发送的最大 UDP 载荷大小。如果网络路径不能支持至少 1200 字节的最大数据报大小,则 MUST NOT 使用 QUIC。

QUIC 假定最小 IP 分组大小至少为 1280 字节。这是 IPv6 最小大小 [IPv6],并且也受大多数现代 IPv4 网络支持。假设 IPv6 的最小 IP 报头大小为 40 字节、IPv4 为 20 字节, 且 UDP 报头大小为 8 字节,则 IPv6 的最大数据报大小为 1232 字节, IPv4 的最大数据报大小为 1252 字节。因此,预期现代 IPv4 和所有 IPv6 网络路径都能够支持 QUIC。

任何大于 1200 字节的最大数据报大小,都可以使用路径 最大传输单元发现(PMTUD)(见 第 14.2.1 节)或 数据报分组化层 PMTU 发现(DPLPMTUD)(见 第 14.3 节)来发现。

强制执行 max_udp_payload_size 传输参数 (第 18.2 节)可能会作为 最大数据报大小的额外限制。一旦知道该值,发送方就可以避免超过此限制。 不过,在获知该传输参数值之前,如果端点发送大于 最小允许最大数据报大小 1200 字节的数据报,就有数据报丢失的风险。

UDP 数据报 MUST NOT 在 IP 层分片。在 IPv4 [IPv4] 中,如果可能,MUST 设置 Don't Fragment (DF) 位,以 防止路径上的分片。

QUIC 有时要求数据报不小于某个大小;示例见 第 8.1 节。不过,数据报大小并未 被认证。也就是说,如果端点收到某个大小的数据报,它无法知道 发送方是否以相同大小发送了该数据报。因此,端点在收到不满足 大小约束的数据报时 MUST NOT 关闭连接;端点 MAY 丢弃这样的数据报。

14.1. Initial 数据报大小

客户端 MUST 通过向 Initial 分组 添加 PADDING 帧,或通过合并 Initial 分组,将所有承载 Initial 分组的 UDP 数据报载荷扩展到至少最小允许最大数据报大小 1200 字节;见 第 12.2 节。Initial 分组甚至可以 与无效分组合并,接收方会丢弃这些无效分组。类似地,服务器 MUST 将所有承载触发 ACK 的 Initial 分组的 UDP 数据报载荷 扩展到至少最小允许最大数据报大小 1200 字节。

发送这种大小的 UDP 数据报可确保网络路径在两个方向上 都支持合理的路径最大传输单元(PMTU)。此外,客户端扩展 Initial 分组有助于降低由服务器响应未验证客户端地址而造成的 放大攻击幅度;见 第 8 节

如果发送方认为网络路径和对等方都支持其选择的大小, 则包含 Initial 分组的数据报 MAY 超过 1200 字节。

服务器 MUST 丢弃承载在 UDP 数据报中且 其载荷小于最小允许最大数据报大小 1200 字节的 Initial 分组。 服务器 MAY 也可以通过发送错误码为 PROTOCOL_VIOLATION 的 CONNECTION_CLOSE 帧立即关闭连接;见 第 10.2.3 节

服务器在验证客户端地址之前,还 MUST 限制其发送的字节数;见 第 8 节

14.2. 路径最大 传输单元

PMTU 是整个 IP 分组的最大大小,包括 IP 报头、 UDP 报头和 UDP 载荷。UDP 载荷包括一个或多个 QUIC 分组 报头和受保护载荷。PMTU 可能取决于路径特征,因此可能随时间变化。 端点在任意给定时间发送的最大 UDP 载荷称为该端点的 最大数据报大小。

端点 SHOULD 使用 DPLPMTUD(第 14.3 节)或 PMTUD(第 14.2.1 节)来确定 到目的地的路径是否会在不分片的情况下支持期望的最大数据报大小。 在缺少这些机制时,QUIC 端点 SHOULD NOT 发送 大于最小允许最大数据报大小的数据报。

DPLPMTUD 和 PMTUD 都会发送大于当前最大数据报大小的 数据报,称为 PMTU 探测。所有未在 PMTU 探测中发送的 QUIC 分组 SHOULD 调整为适配在最大数据报大小内,以避免 数据报被分片或丢弃 [RFC8085]

如果 QUIC 端点确定任何一对本地和远端 IP 地址之间的 PMTU 不能支持最小允许最大数据报大小 1200 字节,则它 MUST 立即停止在受影响路径上发送 QUIC 分组, 但 PMTU 探测中的分组或包含 CONNECTION_CLOSE 帧的分组除外。 如果无法找到替代路径,端点 MAY 终止连接。

每一对本地和远端地址都可能具有不同的 PMTU。 因此,实现任何类型 PMTU 发现的 QUIC 实现 SHOULD 为每个本地和远端 IP 地址组合维护最大数据报大小。

QUIC 实现 MAY 在计算最大数据报 大小时更加保守,以允许未知的隧道开销或 IP 报头选项/扩展。

14.2.1. PMTUD 对 ICMP 消息的处理

PMTUD [RFC1191] [RFC8201] 依赖接收 ICMP 消息(即 IPv6 Packet Too Big (PTB) 消息),这些消息指示 IP 分组 由于大于本地路由器 MTU 而被丢弃。DPLPMTUD 也可以选择使用 这些消息。ICMP 消息的这种使用可能容易受到无法观察分组但可能成功猜测 路径上所用地址的实体攻击。这些攻击可能将 PMTU 降低到 带宽效率低下的值。

端点 MUST 忽略声称 PMTU 已降至 QUIC 最小允许最大数据报大小以下的 ICMP 消息。

生成 ICMP 的要求 [RFC1812] [RFC4443] 规定, 引用的分组应在不超过相应 IP 版本最小 MTU 的情况下尽可能多地包含 原始分组。引用分组的大小实际上可以更小,或者信息不可理解,如 第 1.1 节,见 [DPLPMTUD] 中所述。

使用 PMTUD 的 QUIC 端点 SHOULD[RFC8201]第 5.2 节,见 [RFC8085] 中的规定验证 ICMP 消息,以防止分组 注入。此验证 SHOULD 使用 ICMP 消息载荷中提供的 引用分组,将该消息与相应传输连接关联起来(见 第 4.6.1 节,见 [DPLPMTUD])。ICMP 消息验证 MUST 包括匹配 IP 地址和 UDP 端口 [RFC8085], 并在可能时将连接 ID 匹配到 活跃的 QUIC 会话。端点 SHOULD 忽略所有验证失败的 ICMP 消息。

端点 MUST NOT 基于 ICMP 消息 增加 PMTU;见 第 3 节,见 [DPLPMTUD]中的条目 6。 响应 ICMP 消息而对 QUIC 最大数据报大小作出的任何降低 MAY 是临时的,直到 QUIC 的丢包检测 算法确定引用的分组确实已经丢失。

14.3. 数据报分组化 层 PMTU 发现

DPLPMTUD [DPLPMTUD] 依赖跟踪承载在 PMTU 探测中的 QUIC 分组的丢失或确认。使用 PADDING 帧的 DPLPMTUD PMTU 探测实现了 “使用填充数据进行探测”,如 第 4.1 节 ,见 [DPLPMTUD] 中所定义。

端点 SHOULD 将 BASE_PLPMTU(第 5.1 节,见 [DPLPMTUD])的初始值设置为与 QUIC 最小允许最大 数据报大小一致。MIN_PLPMTU 与 BASE_PLPMTU 相同。

实现 DPLPMTUD 的 QUIC 端点会为每个本地和远端 IP 地址组合维护 DPLPMTUD 最大分组大小(MPS)(第 4.4 节,见 [DPLPMTUD])。 这对应于最大数据报大小。

14.3.1. DPLPMTUD 与 初始连通性

从 DPLPMTUD 的角度看,QUIC 是一种已确认的 分组化层(PL)。因此,当 QUIC 连接握手完成后,QUIC 发送方 可以进入 DPLPMTUD BASE 状态(第 5.2 节,见 [DPLPMTUD])。

14.3.2. 使用 DPLPMTUD 验证网络路径

QUIC 是一种已确认的 PL;因此,QUIC 发送方在 SEARCH_COMPLETE 状态中不实现 DPLPMTUD CONFIRMATION_TIMER;见 第 5.2 节,见 [DPLPMTUD]

14.3.3. DPLPMTUD 对 ICMP 消息的处理

使用 DPLPMTUD 的端点在使用 PTB 信息之前, 需要验证任何收到的 ICMP PTB 消息,如 第 4.6 节,见 [DPLPMTUD] 所定义。 除 UDP 端口验证外,QUIC 还使用其他 PL 信息验证 ICMP 消息 (例如,验证任何收到的 ICMP 消息中引用分组内的连接 ID)。

如果 DPLPMTUD 使用这些消息,则 第 14.2.1 节中描述的 ICMP 消息处理考虑事项也 适用。

14.4. 发送 QUIC PMTU 探测

PMTU 探测是触发 ACK 的分组。

端点可以将 PMTU 探测的内容限制为 PING 和 PADDING 帧, 因为大于当前最大数据报大小的分组更可能被网络丢弃。 因此,承载在 PMTU 探测中的 QUIC 分组丢失并不是拥塞的可靠指示, 并且 SHOULD NOT 触发拥塞控制反应;见 第 3 节, 见 [DPLPMTUD]中的条目 7。 不过,PMTU 探测会消耗拥塞窗口,这可能延迟应用的后续传输。

14.4.1. 包含 Source Connection ID 的 PMTU 探测

依赖 Destination Connection ID 字段路由传入 QUIC 分组的端点,可能需要在 PMTU 探测中包含连接 ID, 以便将由此产生的任何 ICMP 消息(第 14.2.1 节)路由回正确端点。不过,只有长报头分组(第 17.2 节)包含 Source Connection ID 字段,并且一旦握手完成,长报头分组就不会被对等方解密或 确认。

构造 PMTU 探测的一种方式,是将一个带长报头的分组 (例如 Handshake 或 0-RTT 分组 (第 17.2 节))与一个短报头分组 合并(见 第 12.2 节)到单个 UDP 数据报中。 如果得到的 PMTU 探测到达端点,带长报头的分组会被忽略, 但短报头分组会被确认。如果 PMTU 探测导致发送 ICMP 消息, 探测的第一部分会在该消息中被引用。如果 Source Connection ID 字段位于 探测的引用部分内,则可用于路由或验证 ICMP 消息。

15. 版本

QUIC 版本使用 32 位无符号数标识。

版本 0x00000000 被保留用于表示版本协商。 本规范的此版本由编号 0x00000001 标识。

QUIC 的其他版本可能具有不同于此版本的属性。 保证在协议所有版本中保持一致的 QUIC 属性在 [QUIC-INVARIANTS] 中描述。

QUIC 的版本 0x00000001 使用 TLS 作为加密握手协议, 如 [QUIC-TLS] 中所述。

版本号最高有效 16 位清零的版本,被保留用于未来 IETF 共识文档。

符合模式 0x?a?a?a?a 的版本被保留,用于强制 演练版本协商——也就是所有字节低四位均为 1010(二进制)的任何版本号。 客户端或服务器 MAY 宣告支持这些保留版本中的任意版本。

保留版本号永远不会表示真实协议;客户端 MAY 使用 这些版本号之一,并预期服务器会发起版本协商;服务器 MAY 宣告支持这些版本之一,并可预期客户端会忽略该值。

16. 可变长度整数编码

QUIC 分组和帧通常对非负整数值使用可变长度编码。 这种编码确保较小的整数值需要较少字节进行编码。

QUIC 可变长度整数编码保留第一个字节的两个最高有效位, 用于编码整数编码长度(以字节为单位)的以 2 为底的对数。 整数值按网络字节序编码在剩余位上。

这意味着整数编码为 1、2、4 或 8 字节,并可分别编码 6、14、30 或 62 位值。表 4总结了 编码属性。

表 4整数编码摘要
2MSB 长度 可用位 范围
00 1 6 0-63
01 2 14 0-16383
10 4 30 0-1073741823
11 8 62 0-4611686018427387903

解码算法示例和编码样例见 附录 A.1

值不需要用必要的最小字节数进行编码, 唯一例外是 Frame Type 字段;见 第 12.4 节

版本(第 15 节)、报头中发送的分组编号 (第 17.1 节),以及长报头分组中连接 ID 的长度 (第 17.2 节)虽然使用整数描述, 但不使用此编码。

17. 分组格式

所有数值都按网络字节序(即大端序)编码, 所有字段大小都以位为单位。十六进制表示法用于描述 字段值。

17.1. 分组编号编码与 解码

分组编号是范围从 0 到 262-1 的整数 (第 12.3 节)。当存在于长分组报头或短分组 报头中时,它们编码为 1 到 4 字节。通过只包含分组编号的 最低有效位,表示分组编号所需的位数会被减少。

编码后的分组编号按 第 5.4 节,见 [QUIC-TLS]中的描述受到保护。

在收到某个分组编号空间的确认之前, MUST 包含完整分组编号;不能按下文所述截断。

在收到某个分组编号空间的确认后,发送方 MUST 使用一种分组编号大小,其可表示的范围大于最大已确认分组编号与正在发送的分组编号之间 差值的两倍。接收该分组的对等方随后会正确解码 分组编号,除非该分组在传输中被延迟,以至于在许多更高编号的分组 已经收到之后才到达。端点 SHOULD 使用足够大的 分组编号编码,以便即使该分组在随后发送的分组之后到达, 仍能恢复分组编号。

因此,分组编号编码的大小至少比连续未确认分组编号数量 (包括新分组)的以 2 为底的对数多一位。 分组编号编码的伪代码和示例见 附录 A.2

在接收方,分组编号保护会在恢复完整分组编号之前被移除。 随后,完整分组编号会基于存在的有效位数、这些位的值,以及在成功认证分组中 收到的最大分组编号进行重建。恢复完整分组编号是成功完成 分组保护移除所必需的。

一旦报头保护被移除,就通过查找最接近下一个预期分组的 分组编号值来解码分组编号。下一个预期分组是最高已接收分组编号 加一。分组编号解码的伪代码和示例见 附录 A.3

17.2. 长报头分组

长报头分组 {
  报头形式 (1) = 1,
  固定位 (1) = 1,
  长分组类型 (2),
  类型特定位 (4),
  版本 (32),
  Destination Connection ID 长度 (8),
  Destination Connection ID (0..160),
  Source Connection ID 长度 (8),
  Source Connection ID (0..160),
  类型特定载荷 (..),
}
图 13长报头分组格式

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

Header Form:

字节 0(第一个字节)的最高有效位(0x80)在 长报头中设置为 1。

Fixed Bit:

字节 0 的下一位(0x40)设置为 1,除非该分组是 Version Negotiation 分组。包含此位为零值的分组在此版本中不是 有效分组,并且 MUST 被丢弃。此位为 1 的值允许 QUIC 与其他协议共存;见 [RFC7983]

Long Packet Type:

字节 0 的接下来的两位(掩码为 0x30 的位)包含 分组类型。分组类型列在 表 5中。

Type-Specific Bits:

字节 0 的低四位(掩码为 0x0f 的位)的语义 由分组类型决定。

Version:

QUIC Version 是跟随第一个字节的 32 位字段。 该字段指示正在使用的 QUIC 版本,并决定如何解释协议的其余字段。

Destination Connection ID Length:

版本之后的字节包含后续 Destination Connection ID 字段的长度(以字节为单位)。此长度编码为 8 位 无符号整数。在 QUIC 版本 1 中,此值 MUST NOT 超过 20 字节。收到值大于 20 的版本 1 长报头的端点 MUST 丢弃该分组。为了正确形成 Version Negotiation 分组,服务器 SHOULD 能够读取来自 其他 QUIC 版本的更长连接 ID。

Destination Connection ID:

Destination Connection ID 字段跟在 Destination Connection ID Length 字段之后,后者指示此字段的长度。 第 7.2 节更详细地描述了 此字段的使用。

Source Connection ID Length:

Destination Connection ID 之后的字节包含后续 Source Connection ID 字段的长度(以字节为单位)。此长度编码为 8 位无符号整数。在 QUIC 版本 1 中,此值 MUST NOT 超过 20 字节。收到值大于 20 的版本 1 长报头的端点 MUST 丢弃该分组。为了正确形成 Version Negotiation 分组,服务器 SHOULD 能够读取来自其他 QUIC 版本的更长连接 ID。

Source Connection ID:

Source Connection ID 字段跟在 Source Connection ID Length 字段之后,后者指示此字段的长度。第 7.2 节 更详细地描述了此字段的使用。

Type-Specific Payload:

分组的剩余部分(如果有)是类型特定的。

在此版本的 QUIC 中,定义了以下带长报头的分组类型:

表 5长报头分组类型
类型 名称 章节
0x00 Initial 第 17.2.2 节
0x01 0-RTT 第 17.2.3 节
0x02 Handshake 第 17.2.4 节
0x03 Retry 第 17.2.5 节

长报头分组的报头形式位、Destination 和 Source Connection ID 长度、 Destination 和 Source Connection ID 字段,以及 Version 字段都与版本无关。 第一个字节中的其他字段是版本特定的。关于如何解释来自不同 QUIC 版本的分组,详见 [QUIC-INVARIANTS]

字段和载荷的解释特定于版本和分组类型。 虽然此版本的类型特定语义在以下各节中描述,但此版本 QUIC 中 若干长报头分组包含这些附加字段:

Reserved Bits:

字节 0 的两位(掩码为 0x0c 的位)在多种 分组类型中被保留。这些位使用报头保护进行保护;见 第 5.4 节,见 [QUIC-TLS]。 保护前包含的值 MUST 设置为 0。 端点在移除分组保护和报头保护后,如果收到这些位具有非零值的分组, MUST 将其视为类型为 PROTOCOL_VIOLATION 的连接错误。 仅移除报头保护后就丢弃这样的分组可能使端点暴露于攻击;见 第 9.5 节,见 [QUIC-TLS]

Packet Number Length:

在包含 Packet Number 字段的分组类型中,字节 0 的 最低有效两位(掩码为 0x03 的位)包含 Packet Number 字段的长度, 编码为无符号两位整数,其值比 Packet Number 字段以字节为单位的长度少一。 也就是说,Packet Number 字段的长度是此字段的值加一。 这些位使用报头保护进行保护;见 第 5.4 节,见 [QUIC-TLS]

Length:

这是分组剩余部分(即 Packet Number 和 Payload 字段)的长度(以字节为单位),编码为可变长度整数 (第 16 节)。

Packet Number:

此字段长度为 1 到 4 字节。分组编号使用报头 保护进行保护;见 第 5.4 节,见 [QUIC-TLS]。Packet Number 字段的长度编码在字节 0 的 Packet Number Length 位中;见上文。

Packet Payload:

这是分组载荷——包含一系列帧——并使用分组保护进行保护。

17.2.1. Version Negotiation 分组

Version Negotiation 分组本质上不是版本特定的。 客户端收到它时,会基于 Version 字段的值为 0 将其识别为 Version Negotiation 分组。

Version Negotiation 分组是对包含服务器不支持版本的 客户端分组的响应。它只由服务器发送。

Version Negotiation 分组的布局为:

Version Negotiation 分组 {
  报头形式 (1) = 1,
  未使用 (7),
  版本 (32) = 0,
  Destination Connection ID 长度 (8),
  Destination Connection ID (0..2040),
  Source Connection ID 长度 (8),
  Source Connection ID (0..2040),
  支持的版本 (32) ...,
}
图 14Version Negotiation 分组

Unused 字段中的值由服务器设置为任意值。 客户端 MUST 忽略此字段的值。在 QUIC 可能与 其他协议复用的地方(见 [RFC7983]), 服务器 SHOULD 将此字段最高有效位 (0x40)设置为 1,使 Version Negotiation 分组看起来具有 Fixed Bit 字段。注意,其他 QUIC 版本可能不会提出类似建议。

Version Negotiation 分组的 Version 字段 MUST 设置为 0x00000000。

服务器 MUST 在 Destination Connection ID 字段中 包含其收到分组的 Source Connection ID 字段值。Source Connection ID 的值 MUST 从收到分组的 Destination Connection ID 复制,该值最初由客户端随机选择。回显两个 连接 ID 会给客户端一定保证:服务器收到了该分组, 并且 Version Negotiation 分组不是由未观察到 Initial 分组的实体生成的。

未来的 QUIC 版本可能对连接 ID 的长度有不同要求。 特别是,连接 ID 可能具有更小的最小长度或更大的最大长度。 因此,连接 ID 的版本特定规则 MUST NOT 影响是否发送 Version Negotiation 分组的决定。

Version Negotiation 分组的剩余部分是服务器支持的 32 位版本列表。

Version Negotiation 分组不会被确认。它只会响应 指示不支持版本的分组而发送;见 第 5.2.2 节

Version Negotiation 分组不包含其他使用长报头形式的 分组中存在的 Packet Number 和 Length 字段。因此, Version Negotiation 分组会占用整个 UDP 数据报。

服务器 MUST NOT 响应单个 UDP 数据报发送多个 Version Negotiation 分组。

版本协商过程的描述见 第 6 节

17.2.2. Initial 分组

Initial 分组使用长报头,类型值为 0x00。它承载 客户端和服务器发送的用于执行密钥交换的第一个 CRYPTO 帧, 并在任一方向上承载 ACK 帧。

Initial 分组 {
  报头形式 (1) = 1,
  固定位 (1) = 1,
  长分组类型 (2) = 0,
  保留位 (2),
  分组编号长度 (2),
  版本 (32),
  Destination Connection ID 长度 (8),
  Destination Connection ID (0..160),
  Source Connection ID 长度 (8),
  Source Connection ID (0..160),
  令牌长度 (i),
  令牌 (..),
  长度 (i),
  分组编号 (8..32),
  分组载荷 (8..),
}
图 15Initial 分组

Initial 分组包含长报头以及 Length 和 Packet Number 字段;见 第 17.2 节。第一个字节 包含 Reserved 和 Packet Number Length 位;另见 第 17.2 节。在 Source Connection ID 和 Length 字段之间,有两个特定于 Initial 分组的附加字段。

Token Length:

一个可变长度整数,指定 Token 字段的长度(以字节为单位)。 如果不存在令牌,则此值为 0。服务器发送的 Initial 分组 MUST 将 Token Length 字段设置为 0;收到 Token Length 字段非零的 Initial 分组的客户端 MUST 丢弃该分组,或生成类型为 PROTOCOL_VIOLATION 的连接错误。

Token:

先前在 Retry 分组或 NEW_TOKEN 帧中提供的令牌值;见 第 8.1 节

为防止不了解版本的中间盒篡改,Initial 分组 使用连接和版本特定密钥(Initial 密钥)进行保护,如 [QUIC-TLS] 中所述。这种 保护不提供针对能够观察分组的攻击者的机密性或完整性, 但它可以防止不能观察分组的攻击者伪造 Initial 分组。

客户端和服务器对任何包含初始加密握手消息的分组 都使用 Initial 分组类型。这包括需要创建包含初始加密消息的新分组的 所有情况,例如收到 Retry 分组后发送的分组;见 第 17.2.5 节

服务器响应客户端 Initial 发送其第一个 Initial 分组。 服务器 MAY 发送多个 Initial 分组。加密密钥交换可能 需要多个往返或该数据的重传。

Initial 分组的载荷包括包含加密握手消息的 CRYPTO 帧 (或多个帧)、ACK 帧,或二者。也允许 PING、PADDING 和 类型为 0x1c 的 CONNECTION_CLOSE 帧。收到包含其他帧的 Initial 分组的端点,可以将该分组作为伪分组丢弃,或将其视为连接错误。

客户端发送的第一个分组始终包含一个 CRYPTO 帧, 其中包含第一个加密握手消息的开头或全部。发送的第一个 CRYPTO 帧始终从偏移量 0 开始;见 第 7 节

注意,如果服务器发送 TLS HelloRetryRequest(见 第 4.7 节,见 [QUIC-TLS]),客户端将发送另一系列 Initial 分组。这些 Initial 分组会继续加密握手,并包含 从与第一轮 Initial 分组中所发送 CRYPTO 帧大小相匹配的偏移量开始的 CRYPTO 帧。

17.2.2.1. 放弃 Initial 分组

客户端在发送其第一个 Handshake 分组时, 停止发送和处理 Initial 分组。服务器在收到其第一个 Handshake 分组时 停止发送和处理 Initial 分组。尽管分组可能仍在传输中或等待确认, 但在此点之后不再需要交换更多 Initial 分组。Initial 分组保护密钥会被丢弃 (见 第 4.9.1 节,见 [QUIC-TLS]),同时丢弃任何丢包恢复和 拥塞控制状态;见 第 6.4 节,见 [QUIC-RECOVERY]

当 Initial 密钥被丢弃时,CRYPTO 帧中的任何数据也会被丢弃 ——并且不再重传。

17.2.3. 0-RTT

0-RTT 分组使用长报头,类型值为 0x01, 后接 Length 和 Packet Number 字段;见 第 17.2 节。第一个字节包含 Reserved 和 Packet Number Length 位;见 第 17.2 节。0-RTT 分组 用于在第一轮发送中、握手完成之前,从客户端向服务器承载“早期”数据。 作为 TLS 握手的一部分,服务器可以接受或拒绝此早期数据。

关于 0-RTT 数据及其限制的讨论,见 第 2.3 节,见 [TLS13]

0-RTT 分组 {
  报头形式 (1) = 1,
  固定位 (1) = 1,
  长分组类型 (2) = 1,
  保留位 (2),
  分组编号长度 (2),
  版本 (32),
  Destination Connection ID 长度 (8),
  Destination Connection ID (0..160),
  Source Connection ID 长度 (8),
  Source Connection ID (0..160),
  长度 (i),
  分组编号 (8..32),
  分组载荷 (8..),
}
图 160-RTT 分组

0-RTT 受保护分组的分组编号使用与 1-RTT 受保护分组相同的空间。

客户端收到 Retry 分组后,0-RTT 分组很可能已经 被服务器丢失或丢弃。客户端在发送新的 Initial 分组后 SHOULD 尝试在 0-RTT 分组中重新发送数据。发送任何新分组时 MUST 使用新的分组编号;如 第 17.2.5.3 节所述, 重用分组编号可能破坏分组保护。

客户端只有在握手完成后才会收到对其 0-RTT 分组的确认, 如 第 4.1.1 节,见 [QUIC-TLS] 所定义。

客户端一旦开始处理来自服务器的 1-RTT 分组, 就 MUST NOT 发送 0-RTT 分组。 这意味着 0-RTT 分组不能包含对 1-RTT 分组中帧的任何响应。 例如,客户端不能在 0-RTT 分组中发送 ACK 帧,因为它只能确认 1-RTT 分组。对 1-RTT 分组的确认 MUST 承载在 1-RTT 分组中。

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

17.2.4. Handshake 分组

Handshake 分组使用长报头,类型值为 0x02, 后接 Length 和 Packet Number 字段;见 第 17.2 节。第一个字节包含 Reserved 和 Packet Number Length 位;见 第 17.2 节。它用于 承载来自服务器和客户端的加密握手消息和确认。

Handshake 分组 {
  报头形式 (1) = 1,
  固定位 (1) = 1,
  长分组类型 (2) = 2,
  保留位 (2),
  分组编号长度 (2),
  版本 (32),
  Destination Connection ID 长度 (8),
  Destination Connection ID (0..160),
  Source Connection ID 长度 (8),
  Source Connection ID (0..160),
  长度 (i),
  分组编号 (8..32),
  分组载荷 (8..),
}
图 17Handshake 受保护 分组

一旦客户端收到来自服务器的 Handshake 分组, 它就使用 Handshake 分组向服务器发送后续加密握手消息和确认。

Handshake 分组中的 Destination Connection ID 字段包含 由分组接收方选择的连接 ID;Source Connection ID 包含 分组发送方希望使用的连接 ID;见 第 7.2 节

Handshake 分组有自己的分组编号空间,因此服务器发送的 第一个 Handshake 分组包含分组编号 0。

此分组的载荷包含 CRYPTO 帧,并且可以包含 PING、PADDING 或 ACK 帧。Handshake 分组 MAY 包含 类型为 0x1c 的 CONNECTION_CLOSE 帧。端点 MUST 将收到包含其他 帧的 Handshake 分组视为类型为 PROTOCOL_VIOLATION 的连接错误。

与 Initial 分组一样(见 第 17.2.2.1 节),Handshake 分组的 CRYPTO 帧中的数据会在 Handshake 保护密钥被丢弃时被丢弃——并且不再重传。

17.2.5. Retry 分组

图 18所示, Retry 分组使用长分组报头,类型值为 0x03。它承载由服务器创建的 地址验证令牌。希望执行重试的服务器会使用它;见 第 8.1 节

Retry 分组 {
  报头形式 (1) = 1,
  固定位 (1) = 1,
  长分组类型 (2) = 3,
  未使用 (4),
  版本 (32),
  Destination Connection ID 长度 (8),
  Destination Connection ID (0..160),
  Source Connection ID 长度 (8),
  Source Connection ID (0..160),
  Retry 令牌 (..),
  Retry 完整性标签 (128),
}
图 18Retry 分组

Retry 分组不包含任何受保护 字段。Unused 字段中的值由服务器设置为任意值; 客户端 MUST 忽略这些位。除了来自 长报头的字段外,它还包含这些附加字段:

Retry Token:

服务器可用于验证客户端地址的不透明令牌。

Retry Integrity Tag:

定义见 [QUIC-TLS] 的第 5.8 节(“Retry 分组完整性”

17.2.5.1. 发送 Retry 分组

服务器用客户端在 Initial 分组的 Source Connection ID 中包含的连接 ID 填充 Destination Connection ID。

服务器在 Source Connection ID 字段中包含其选择的连接 ID。此值 MUST NOT 等于 客户端所发送分组的 Destination Connection ID 字段。客户端 MUST 丢弃包含与其 Initial 分组的 Destination Connection ID 字段相同的 Source Connection ID 字段的 Retry 分组。 客户端 MUST 在其随后发送分组的 Destination Connection ID 字段中使用 Retry 分组的 Source Connection ID 字段值。

服务器 MAY 响应 Initial 和 0-RTT 分组发送 Retry 分组。服务器可以丢弃或缓冲其收到的 0-RTT 分组。服务器在收到 Initial 或 0-RTT 分组时可以发送多个 Retry 分组。服务器 MUST NOT 响应单个 UDP 数据报发送多个 Retry 分组。

17.2.5.2. 处理 Retry 分组

客户端 MUST 对每次连接 尝试最多接受并处理一个 Retry 分组。在客户端已经收到并处理来自服务器的 Initial 或 Retry 分组后,它 MUST 丢弃其收到的 任何后续 Retry 分组。

客户端 MUST 丢弃 Retry Integrity Tag 无法 验证的 Retry 分组;见 第 5.8 节,见 [QUIC-TLS]。这会削弱攻击者注入 Retry 分组的能力, 并防止 Retry 分组的意外损坏。客户端 MUST 丢弃 Retry Token 字段为零长度的 Retry 分组。

客户端通过包含所提供 Retry 令牌的 Initial 分组响应 Retry 分组,以继续连接建立。

客户端将此 Initial 分组的 Destination Connection ID 字段 设置为 Retry 分组中 Source Connection ID 字段的值。更改 Destination Connection ID 字段也会导致用于保护 Initial 分组的密钥发生变化。 它还将 Token 字段设置为 Retry 分组中提供的令牌。客户端 MUST NOT 更改 Source Connection ID,因为 服务器可能将该连接 ID 作为其令牌验证逻辑的一部分;见 第 8.1.4 节

Retry 分组不包含分组编号,也不能由客户端显式确认。

17.2.5.3. Retry 后继续握手

客户端后续的 Initial 分组包含来自 Retry 分组的 连接 ID 和令牌值。客户端将 Retry 分组的 Source Connection ID 字段 复制到 Destination Connection ID 字段,并使用此值直到收到具有更新值的 Initial 分组;见 第 7.2 节。Token 字段的值 会复制到所有后续 Initial 分组;见 第 8.1.2 节

除更新 Destination Connection ID 和 Token 字段外,客户端发送的 Initial 分组受与第一个 Initial 分组相同的限制约束。客户端 MUST 使用其在该分组中 包含的相同加密握手消息。服务器 MAY 将包含不同 加密握手消息的分组视为连接错误,或将其丢弃。注意, 包含 Token 字段会减少加密握手消息可用的空间, 这可能导致客户端需要发送多个 Initial 分组。

客户端 MAY 在收到 Retry 分组后, 通过向服务器提供的连接 ID 发送 0-RTT 分组来尝试 0-RTT。

客户端在处理 Retry 分组后 MUST NOT 重置任何分组编号空间的分组编号。 特别是,0-RTT 分组包含机密信息,在收到 Retry 分组时很可能会被重传。 用于保护这些新的 0-RTT 分组的密钥不会因响应 Retry 分组而改变。 不过,这些分组中发送的数据可能不同于之前发送的数据。 使用相同分组编号发送这些新分组很可能破坏这些分组的分组保护, 因为相同密钥和 nonce 可能被用于保护不同内容。 如果服务器检测到客户端重置了分组编号, MAY 中止连接。

客户端和服务器之间交换的 Initial 和 Retry 分组中使用的 连接 ID 会被复制到传输参数中,并按 第 7.3 节所述进行验证。

17.3. 短报头分组

此版本的 QUIC 定义了单一一种使用短分组报头的分组类型。

17.3.1. 1-RTT 分组

1-RTT 分组使用短分组报头。它在版本和 1-RTT 密钥协商完成后使用。

1-RTT 分组 {
  报头形式 (1) = 0,
  固定位 (1) = 1,
  Spin Bit (1),
  保留位 (2),
  密钥阶段 (1),
  分组编号长度 (2),
  Destination Connection ID (0..160),
  分组编号 (8..32),
  分组载荷 (8..),
}
图 191-RTT 分组

1-RTT 分组包含以下字段:

Header Form:

字节 0 的最高有效位(0x80)在短报头中设置为 0。

Fixed Bit:

字节 0 的下一位(0x40)设置为 1。 包含此位为零值的分组在此版本中不是有效分组,并且 MUST 被丢弃。此位为 1 的值允许 QUIC 与其他协议共存;见 [RFC7983]

Spin Bit:

字节 0 的第三个最高有效位(0x20)是 延迟自旋位,按 第 17.4 节所述设置。

Reserved Bits:

接下来的两位(字节 0 中掩码为 0x18 的位) 被保留。这些位使用报头保护进行保护;见 第 5.4 节,见 [QUIC-TLS]。 保护前包含的值 MUST 设置为 0。端点 MUST 将在移除分组保护和报头保护后收到这些位 具有非零值的分组视为类型为 PROTOCOL_VIOLATION 的连接错误。 仅移除报头保护后就丢弃这样的分组可能使端点暴露于攻击;见 第 9.5 节,见 [QUIC-TLS]

Key Phase:

字节 0 的下一位(0x04)指示密钥阶段, 这允许分组接收方识别用于保护该分组的分组保护密钥。 详情见 [QUIC-TLS]。此位使用报头保护进行保护; 见 第 5.4 节,见 [QUIC-TLS]

Packet Number Length:

字节 0 的最低有效两位(掩码为 0x03 的位)包含 Packet Number 字段的长度,编码为无符号两位整数, 其值比 Packet Number 字段以字节为单位的长度少一。也就是说, Packet Number 字段的长度是此字段的值加一。 这些位使用报头保护进行保护;见 第 5.4 节,见 [QUIC-TLS]

Destination Connection ID:

Destination Connection ID 是由分组的 预期接收方选择的连接 ID。更多细节见 第 5.1 节

Packet Number:

Packet Number 字段长度为 1 到 4 字节。 分组编号使用报头保护进行保护;见 第 5.4 节,见 [QUIC-TLS]。Packet Number 字段的长度 编码在 Packet Number Length 字段中。详情见 第 17.1 节

Packet Payload:

1-RTT 分组始终包含 1-RTT 受保护载荷。

短报头分组的报头形式位和 Destination Connection ID 字段与版本无关。其余字段特定于所选 QUIC 版本。 关于如何解释来自不同 QUIC 版本的分组,详见 [QUIC-INVARIANTS]

17.4. 延迟自旋位

延迟自旋位定义用于 1-RTT 分组(第 17.3.1 节), 它使网络路径上的观察点能够在连接持续期间进行被动延迟监测。 服务器反射收到的自旋值,而客户端在一个 RTT 后“自旋”该值。 路径上的观察者可以测量两次自旋位切换事件之间的时间,以估计连接的端到端 RTT。

自旋位只存在于 1-RTT 分组中,因为可以通过观察握手来测量 连接的初始 RTT。因此,自旋位在版本协商和连接建立完成后可用。 关于路径上测量和延迟自旋位的使用,在 [QUIC-MANAGEABILITY] 中有进一步讨论。

自旋位是此版本 QUIC 的一个 OPTIONAL 特性。不支持此特性的端点 MUST 按下文定义禁用它。

每个端点单方面决定是否为某个连接启用或禁用自旋位。 实现 MUST 允许客户端和服务器的管理员 全局或按连接禁用自旋位。即使管理员未禁用自旋位,端点 MUST 在每 16 条网络路径中随机选择至少一条路径, 或每 16 个连接 ID 中随机选择一个,禁用其自旋位的使用, 以确保禁用自旋位的 QUIC 连接在网络上常见可见。 由于每个端点独立禁用自旋位,这确保自旋位信号在大约八分之一的 网络路径上被禁用。

当自旋位被禁用时,端点 MAY 将自旋位设置为 任意值,并且 MUST 忽略任何传入值。RECOMMENDED 端点将自旋 位设置为随机值,该值可为每个分组独立选择,也可为每个连接 ID 独立选择。

如果连接启用了自旋位,端点会为每条网络路径维护一个自旋 值,并在该路径上发送 1-RTT 分组时,将分组报头中的自旋位设置为 当前存储的值。自旋值在每个端点针对每条网络路径初始化为 0。 每个端点还会记住在每条路径上从其对等方看到的最高分组编号。

当服务器收到使其在给定网络路径上从客户端看到的最高 分组编号增加的 1-RTT 分组时,它会将该路径的自旋值设置为等于 收到分组中的自旋位。

当客户端收到使其在给定网络路径上从服务器看到的最高 分组编号增加的 1-RTT 分组时,它会将该路径的自旋值设置为收到分组中 自旋位的反值。

端点在更改某条网络路径上使用的连接 ID 时,会将该网络路径的 自旋值重置为 0。

18. 传输参数编码

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

Transport Parameters {
  Transport Parameter (..) ...,
}
图 20传输参数序列

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

Transport Parameter {
  Transport Parameter ID (i),
  Transport Parameter Length (i),
  Transport Parameter Value (..),
}
图 21传输参数编码

Transport Parameter Length 字段包含 Transport Parameter Value 字段的长度,以字节为单位。

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

18.1. 保留传输 参数

标识符形式为 31 * N + 27 的传输参数 (其中 N 为整数值)被保留,用于演练未知传输参数必须被忽略的 要求。这些传输参数没有语义,可以承载任意值。

18.2. 传输参数 定义

本节详细说明本文档中定义的传输参数。

这里列出的许多传输参数具有整数值。那些被标识为整数的 传输参数使用可变长度整数编码;见 第 16 节。 除非另有说明,如果传输参数不存在,则其默认值为 0。

定义了以下传输参数:

original_destination_connection_id (0x00):

此参数是客户端发送的第一个 Initial 分组中的 Destination Connection ID 字段值;见 第 7.3 节。此传输 参数仅由服务器发送。

max_idle_timeout (0x01):

最大空闲超时是以毫秒为单位的值, 编码为整数;见(第 10.1 节)。 当两个端点都省略此传输参数或指定值为 0 时,空闲超时被禁用。

stateless_reset_token (0x02):

无状态重置令牌用于验证无状态重置;见 第 10.3 节。此参数是 16 字节序列。 此传输参数 MUST NOT 由客户端发送,但 MAY 由服务器发送。 未发送此传输参数的服务器,不能对握手期间协商的连接 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):

此参数是一个整数值,指定本地发起的 双向流的初始流量控制限制。此限制适用于发送该传输参数的端点所打开的 新创建双向流。在客户端传输参数中,这适用于标识符最低有效两位 设置为 0x00 的流;在服务器传输参数中,这适用于最低有效两位 设置为 0x01 的流。

initial_max_stream_data_bidi_remote (0x06):

此参数是一个整数值,指定对等方发起的 双向流的初始流量控制限制。此限制适用于接收该传输参数的端点所打开的 新创建双向流。在客户端传输参数中,这适用于标识符最低有效两位 设置为 0x01 的流;在服务器传输参数中,这适用于最低有效两位 设置为 0x00 的流。

initial_max_stream_data_uni (0x07):

此参数是一个整数值,指定单向流的 初始流量控制限制。此限制适用于接收该传输参数的端点所打开的 新创建单向流。在客户端传输参数中,这适用于标识符最低有效两位 设置为 0x03 的流;在服务器传输参数中,这适用于最低有效两位 设置为 0x02 的流。

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 帧(第 19.3 节)中 ACK Delay 字段的指数。 如果此值不存在,则假定默认值为 3(表示乘数为 8)。 大于 20 的值无效。

max_ack_delay (0x0b):

最大确认延迟是一个整数值,指示端点将延迟发送 确认的最大时间量,以毫秒为单位。此值 SHOULD 包含接收方预计的闹钟触发延迟。例如,如果接收方设置 5ms 的定时器, 而闹钟通常最多晚触发 1ms,那么它应该发送 6ms 的 max_ack_delay。 如果此值不存在,则假定默认值为 25 毫秒。大于或等于 214 的值无效。

disable_active_migration (0x0c):

如果端点不支持在握手期间所用地址上的 主动连接迁移(第 9 节),则包含 disable active migration 传输参数。 收到此传输参数的端点 MUST NOT 在向对等方握手期间使用的 地址发送时使用新的本地地址。此传输参数不禁止客户端对 preferred_address 传输参数采取行动后的连接迁移。此参数是零长度值。

preferred_address (0x0d):

服务器的首选地址用于在握手结束时实现服务器地址变更, 如 第 9.6 节所述。此传输参数仅由服务器发送。服务器 MAY 选择只发送一个地址族的首选地址,方法是为另一个地址族 发送全零地址和端口(0.0.0.0:0 或 [::]:0)。IP 地址按网络字节序编码。

preferred_address 传输参数包含 IPv4 和 IPv6 的地址与端口。 四字节 IPv4 Address 字段后跟关联的两字节 IPv4 Port 字段。 随后是 16 字节 IPv6 Address 字段和两字节 IPv6 Port 字段。 在地址和端口对之后,Connection ID Length 字段描述后续 Connection ID 字段的长度。最后,16 字节 Stateless Reset Token 字段包含与该连接 ID 关联的无状态重置令牌。此传输参数的格式如下方 图 22 所示。

Connection ID 字段和 Stateless Reset Token 字段包含一个 序列号为 1 的替代连接 ID;见 第 5.1.1 节。 将这些值与首选地址一起发送,可以确保当客户端发起向首选地址的迁移时, 至少有一个未使用的活动连接 ID。

首选地址的 Connection ID 和 Stateless Reset Token 字段 在语法和语义上与 NEW_CONNECTION_ID 帧(第 19.15 节)的对应字段相同。选择零长度连接 ID 的服务器 MUST NOT 提供首选地址。类似地,服务器 MUST NOT 在此传输参数中包含零长度连接 ID。 客户端 MUST 将违反这些要求视为 类型为 TRANSPORT_PARAMETER_ERROR 的连接错误。

Preferred Address {
  IPv4 Address (32),
  IPv4 Port (16),
  IPv6 Address (128),
  IPv6 Port (16),
  Connection ID Length (8),
  Connection ID (..),
  Stateless Reset Token (128),
}
图 22首选地址格式
active_connection_id_limit (0x0e):

这是一个整数值,指定端点愿意存储的来自对等方的 最大连接 ID 数量。此值包括握手期间收到的连接 ID、 在 preferred_address 传输参数中收到的连接 ID,以及在 NEW_CONNECTION_ID 帧中收到的连接 ID。 active_connection_id_limit 参数的值 MUST 至少为 2。 收到小于 2 的值的端点 MUST 以 TRANSPORT_PARAMETER_ERROR 类型的错误关闭连接。 如果此传输参数不存在,则假定默认值为 2。如果端点签发零长度连接 ID, 它永远不会发送 NEW_CONNECTION_ID 帧,因此会忽略从对等方收到的 active_connection_id_limit 值。

initial_source_connection_id (0x0f):

这是端点在其为连接发送的第一个 Initial 分组的 Source Connection ID 字段中包含的值;见 第 7.3 节

retry_source_connection_id (0x10):

这是服务器在 Retry 分组的 Source Connection ID 字段中包含的值;见 第 7.3 节。 此传输参数仅由服务器发送。

如果存在,设置初始逐流流量控制限制的传输参数 (initial_max_stream_data_bidi_local、initial_max_stream_data_bidi_remote 和 initial_max_stream_data_uni)等同于在每个相应类型的流打开后立即 在该流上发送 MAX_STREAM_DATA 帧(第 19.10 节)。 如果传输参数不存在,该类型的流以 0 的流量控制限制开始。

客户端 MUST NOT 包含任何仅限服务器的 传输参数: original_destination_connection_id、preferred_address、 retry_source_connection_id 或 stateless_reset_token。服务器 MUST 将收到任何这些传输参数视为类型为 TRANSPORT_PARAMETER_ERROR 的连接错误。

19. 帧类型与格式

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

19.1. PADDING 帧

PADDING 帧(type=0x00)没有语义值。PADDING 帧可用于 增加分组大小。填充可用于将 Initial 分组增加到所需的最小大小, 或为受保护分组提供抵抗流量分析的保护。

PADDING 帧的格式如 图 23 所示,该图表明 PADDING 帧没有内容。也就是说,PADDING 帧由标识该帧为 PADDING 帧的单个字节组成。

PADDING Frame {
  Type (i) = 0x00,
}
图 23PADDING 帧格式

19.2. PING 帧

端点可以使用 PING 帧(type=0x01)验证其对等方是否仍然 存活,或检查到对等方的可达性。

PING 帧的格式如 图 24 所示,该图表明 PING 帧没有内容。

PING Frame {
  Type (i) = 0x01,
}
图 24PING 帧格式

PING 帧的接收方只需确认包含此帧的分组。

当应用或应用协议希望防止连接超时时,可以使用 PING 帧保持连接存活;见 第 10.1.2 节

19.3. ACK 帧

接收方发送 ACK 帧(类型 0x02 和 0x03),以通知发送方其已 收到并处理的分组。ACK 帧包含一个或多个 ACK Range。 ACK Range 标识被确认的分组。如果帧类型为 0x03,ACK 帧还包含 到目前为止在连接上接收到的带有关联 ECN 标记的 QUIC 分组累计计数。 QUIC 实现 MUST 正确处理这两种类型,并且如果它们已为 自己发送的分组启用 ECN,则 SHOULD 使用 ECN 部分中的信息 管理其拥塞状态。

QUIC 确认是不可撤销的。一旦被确认,即使某个分组不出现在 未来的 ACK 帧中,它仍保持被确认状态。这不同于 TCP 选择性确认 (SACK)的撤销 [RFC2018]

来自不同分组编号空间的分组可以使用相同的数值标识。 对分组的确认需要同时指示分组编号和分组编号空间。这通过让每个 ACK 帧只确认与承载该 ACK 帧的分组处于同一空间中的分组编号来完成。

Version Negotiation 和 Retry 分组不能被确认,因为它们不包含 分组编号。这些分组不依赖 ACK 帧,而是由客户端发送的下一个 Initial 分组隐式确认。

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 (..)],
}
图 25ACK 帧格式

ACK 帧包含以下字段:

Largest Acknowledged:

一个可变长度整数,表示对等方正在确认的最大分组编号; 这通常是对等方在生成 ACK 帧之前收到的最大分组编号。 不同于 QUIC 长报头或短报头中的分组编号,ACK 帧中的值不会被截断。

ACK Delay:

一个可变长度整数,以微秒为单位编码确认延迟; 见 第 13.2.5 节。它通过将字段中的值 乘以 ACK 帧发送方所发送的 ack_delay_exponent 传输参数的 2 次幂来解码;见 第 18.2 节。与简单地 将延迟表示为整数相比,这种编码允许在相同字节数内表示更大的 值范围,代价是分辨率较低。

ACK Range Count:

一个可变长度整数,指定帧中 ACK Range 字段的数量。

First ACK Range:

一个可变长度整数,指示 Largest Acknowledged 之前 连续被确认分组的数量。也就是说,通过从 Largest Acknowledged 字段减去 First ACK Range 值来确定该范围中被确认的最小分组。

ACK Ranges:

包含额外的分组范围,这些范围在未确认(Gap)和 已确认(ACK Range)之间交替;见 第 19.3.1 节

ECN Counts:

三个 ECN 计数;见 第 19.3.2 节

19.3.1. ACK 范围

每个 ACK Range 都由按分组编号降序排列、交替出现的 Gap 和 ACK Range Length 值组成。ACK Range 可以重复。 Gap 和 ACK Range Length 值的数量由 ACK Range Count 字段决定; ACK Range Count 字段中的每个值对应其中各一个值。

ACK Range 的结构如 图 26 所示。

ACK Range {
  Gap (i),
  ACK Range Length (i),
}
图 26ACK 范围

构成每个 ACK Range 的字段为:

Gap:

一个可变长度整数,指示前一个 ACK Range 中 最小分组编号再小一的分组之前连续未确认分组的数量。

ACK Range Length:

一个可变长度整数,指示由前一个 Gap 确定的 最大分组编号之前连续已确认分组的数量。

为提高效率,Gap 和 ACK Range Length 值使用相对整数编码。 尽管每个编码值都是正数,但这些值会被相减,因此每个 ACK Range 描述的是编号逐渐降低的分组。

每个 ACK Range 通过指示该范围中最大分组编号之前已确认分组的数量, 确认一个连续分组范围。值 0 表示仅确认最大分组编号。 更大的 ACK Range 值表示更大的范围,并对应范围中更低的最小分组编号。 因此,给定范围的最大分组编号后,最小值由以下公式确定:

   smallest = largest - ack_range

ACK Range 确认最小分组编号和最大分组编号之间的 所有分组,包括两端。

ACK Range 的最大值通过累计减去所有前面的 ACK Range Length 和 Gap 的大小来确定。

每个 Gap 表示一段未被确认的分组范围。 间隙中的分组数量比 Gap 字段的编码值大一。

Gap 字段的值使用以下公式为后续 ACK Range 建立最大分组编号值:

   largest = previous_smallest - gap - 2

如果任何计算出的分组编号为负数,端点 MUST 生成类型为 FRAME_ENCODING_ERROR 的连接错误。

19.3.2. ECN 计数

ACK 帧使用类型值的最低有效位 (即类型 0x03)来指示 ECN 反馈,并报告接收到了分组 IP 报头中带有关联 ECN 码点 ECT(0)、ECT(1) 或 ECN-CE 的 QUIC 分组。 ECN 计数仅在 ACK 帧类型为 0x03 时存在。

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

ECN Counts {
  ECT0 Count (i),
  ECT1 Count (i),
  ECN-CE Count (i),
}
图 27ECN 计数格式

ECN 计数字段为:

ECT0 Count:

一个可变长度整数,表示在 ACK 帧的分组编号空间中 带有 ECT(0) 码点接收的分组总数。

ECT1 Count:

一个可变长度整数,表示在 ACK 帧的分组编号空间中 带有 ECT(1) 码点接收的分组总数。

ECN-CE Count:

一个可变长度整数,表示在 ACK 帧的分组编号空间中 带有 ECN-CE 码点接收的分组总数。

ECN 计数为每个分组编号空间单独维护。

19.4. RESET_STREAM 帧

端点使用 RESET_STREAM 帧(type=0x04)突然终止 流的发送部分。

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

端点如果收到针对仅发送流的 RESET_STREAM 帧, MUST 以 STREAM_STATE_ERROR 错误终止连接。

RESET_STREAM 帧的格式如 图 28 所示。

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

RESET_STREAM 帧包含以下字段:

Stream ID:

被终止流的流 ID 的可变长度整数编码。

Application Protocol Error Code:

一个可变长度整数,包含指示为何关闭该流的 应用协议错误码(见 第 20.2 节)。

Final Size:

一个可变长度整数,指示 RESET_STREAM 发送方给出的 流最终大小,以字节为单位;见 第 4.5 节

19.5. STOP_SENDING 帧

端点使用 STOP_SENDING 帧(type=0x05)传达其正在根据应用请求 在接收时丢弃传入数据。STOP_SENDING 请求对等方停止在流上传输。

STOP_SENDING 帧可以为处于“Recv”或“Size Known”状态的流发送; 见 第 3.2 节。收到针对尚未创建的 本地发起流的 STOP_SENDING 帧,MUST 被视为 类型为 STREAM_STATE_ERROR 的连接错误。收到针对仅接收流的 STOP_SENDING 帧的端点 MUST 以 STREAM_STATE_ERROR 错误终止连接。

STOP_SENDING 帧的格式如 图 29 所示。

STOP_SENDING Frame {
  Type (i) = 0x05,
  Stream ID (i),
  Application Protocol Error Code (i),
}
图 29STOP_SENDING 帧格式

STOP_SENDING 帧包含以下字段:

Stream ID:

一个可变长度整数,承载被忽略流的流 ID。

Application Protocol Error Code:

一个可变长度整数,包含应用指定的发送方忽略该流的原因; 见 第 20.2 节

19.6. CRYPTO 帧

CRYPTO 帧(type=0x06)用于传输加密握手消息。 它可以在除 0-RTT 之外的所有分组类型中发送。CRYPTO 帧为 加密协议提供有序字节流。CRYPTO 帧在功能上与 STREAM 帧相同, 但它们不携带流标识符;不受流量控制;也不携带可选偏移量、 可选长度和流结束标记。

CRYPTO 帧的格式如 图 30 所示。

CRYPTO Frame {
  Type (i) = 0x06,
  Offset (i),
  Length (i),
  Crypto Data (..),
}
图 30CRYPTO 帧格式

CRYPTO 帧包含以下字段:

Offset:

一个可变长度整数,指定此 CRYPTO 帧中数据在流中的 字节偏移量。

Length:

一个可变长度整数,指定此 CRYPTO 帧中 Crypto Data 字段的长度。

Crypto Data:

加密消息数据。

每个加密级别中都有单独的加密握手数据流, 每个流都从偏移量 0 开始。这意味着每个加密级别都被视为 单独的 CRYPTO 数据流。

流上传递的最大偏移量——偏移量与数据长度之和——不能超过 262-1。收到超过此限制的帧 MUST 被视为类型为 FRAME_ENCODING_ERROR 或 CRYPTO_BUFFER_EXCEEDED 的连接错误。

不同于包含流 ID 来指示数据属于哪个流的 STREAM 帧, CRYPTO 帧为每个加密级别承载单个流的数据。该流没有显式结束, 因此 CRYPTO 帧没有 FIN 位。

19.7. NEW_TOKEN 帧

服务器发送 NEW_TOKEN 帧(type=0x07),为客户端提供一个 可在未来连接的 Initial 分组报头中发送的令牌。

NEW_TOKEN 帧的格式如 图 31 所示。

NEW_TOKEN Frame {
  Type (i) = 0x07,
  Token Length (i),
  Token (..),
}
图 31NEW_TOKEN 帧格式

NEW_TOKEN 帧包含以下字段:

Token Length:

一个可变长度整数,指定令牌的长度,以字节为单位。

Token:

客户端可与未来 Initial 分组一起使用的不透明 blob。 令牌 MUST NOT 为空。客户端 MUST 将收到 Token 字段为空的 NEW_TOKEN 帧视为 类型为 FRAME_ENCODING_ERROR 的连接错误。

如果错误地确定包含该帧的分组已经丢失,客户端可能收到 多个包含相同令牌值的 NEW_TOKEN 帧。客户端负责丢弃重复值, 这些重复值可能被用于关联连接尝试;见 第 8.1.3 节

客户端 MUST NOT 发送 NEW_TOKEN 帧。 服务器 MUST 将收到 NEW_TOKEN 帧视为 类型为 PROTOCOL_VIOLATION 的连接错误。

19.8. STREAM 帧

STREAM 帧隐式创建流并承载流数据。STREAM 帧中的 Type 字段 采用 0b00001XXX 形式(或从 0x08 到 0x0f 的值集合)。 帧类型的三个低位决定帧中存在的字段:

  • 帧类型中的 OFF 位(0x04)被设置时表示 存在 Offset 字段。当设置为 1 时,Offset 字段存在。当设置为 0 时, Offset 字段不存在,Stream Data 从偏移量 0 开始(也就是说, 该帧包含流的第一个字节,或包含无数据流的结束)。
  • 帧类型中的 LEN 位(0x02)被设置时表示 存在 Length 字段。如果此位设置为 0,则 Length 字段不存在, Stream Data 字段延伸到分组末尾。如果此位设置为 1, Length 字段存在。
  • FIN 位(0x01)表示该帧标记流的结束。 流的最终大小是此帧的偏移量与长度之和。

端点如果收到针对尚未创建的本地发起流或仅发送流的 STREAM 帧,MUST 以 STREAM_STATE_ERROR 错误终止连接。

STREAM 帧的格式如 图 32 所示。

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

STREAM 帧包含以下字段:

Stream ID:

一个可变长度整数,指示该流的流 ID;见 第 2.1 节

Offset:

一个可变长度整数,指定此 STREAM 帧中数据在流中的 字节偏移量。当 OFF 位设置为 1 时,此字段存在。 当 Offset 字段不存在时,偏移量为 0。

Length:

一个可变长度整数,指定此 STREAM 帧中 Stream Data 字段的长度。当 LEN 位设置为 1 时,此字段存在。 当 LEN 位设置为 0 时,Stream Data 字段会消耗分组中所有剩余字节。

Stream Data:

要交付的指定流中的字节。

当 Stream Data 字段长度为 0 时,STREAM 帧中的偏移量是 将发送的下一个字节的偏移量。

流中的第一个字节偏移量为 0。流上传递的最大偏移量 ——偏移量与数据长度之和——不能超过 262-1,因为无法为该数据提供流量控制额度。 收到超过此限制的帧 MUST 被视为类型为 FRAME_ENCODING_ERROR 或 FLOW_CONTROL_ERROR 的连接错误。

19.9. MAX_DATA 帧

MAX_DATA 帧(type=0x10)用于流量控制,以通知对等方 在整个连接上可以发送的最大数据量。

MAX_DATA 帧的格式如 图 33 所示。

MAX_DATA Frame {
  Type (i) = 0x10,
  Maximum Data (i),
}
图 33MAX_DATA 帧格式

MAX_DATA 帧包含以下字段:

Maximum Data:

一个可变长度整数,指示整个连接上可以发送的 最大数据量,以字节为单位。

STREAM 帧中发送的所有数据都计入此限制。 所有流上的最终大小之和——包括处于终止状态的流——MUST NOT 超过 接收方通告的值。如果端点接收的数据超过其已发送的最大数据值, 则端点 MUST 以类型为 FLOW_CONTROL_ERROR 的错误终止连接。 这包括 Early Data 中违反记住限制的情况;见 第 7.4.1 节

19.10. MAX_STREAM_DATA 帧

MAX_STREAM_DATA 帧(type=0x11)用于流量控制, 以通知对等方在某个流上可以发送的最大数据量。

MAX_STREAM_DATA 帧可以为处于“Recv”状态的流发送; 见 第 3.2 节。收到针对尚未创建的 本地发起流的 MAX_STREAM_DATA 帧,MUST 被视为 类型为 STREAM_STATE_ERROR 的连接错误。收到针对仅接收流的 MAX_STREAM_DATA 帧的端点 MUST 以 STREAM_STATE_ERROR 错误终止连接。

MAX_STREAM_DATA 帧的格式如 图 34 所示。

MAX_STREAM_DATA Frame {
  Type (i) = 0x11,
  Stream ID (i),
  Maximum Stream Data (i),
}
图 34MAX_STREAM_DATA 帧 格式

MAX_STREAM_DATA 帧包含以下字段:

Stream ID:

受影响流的流 ID,编码为可变长度整数。

Maximum Stream Data:

一个可变长度整数,指示在所标识流上可以发送的 最大数据量,以字节为单位。

在将数据计入此限制时,端点会考虑在流上发送或接收的数据的 最大已接收偏移量。丢包或重排序可能意味着,流上的最大已接收偏移量 可能大于该流上接收的数据总大小。收到 STREAM 帧 可能不会增加最大已接收偏移量。

在流上发送的数据 MUST NOT 超过 接收方通告的最大流数据值中的最大者。如果端点收到的数据超过 它已为受影响流发送的最大流数据值,则端点 MUST 以类型为 FLOW_CONTROL_ERROR 的错误终止连接。这包括 Early Data 中 违反记住限制的情况;见 第 7.4.1 节

19.11. MAX_STREAMS 帧

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

MAX_STREAMS 帧的格式如 图 35 所示。

MAX_STREAMS Frame {
  Type (i) = 0x12..0x13,
  Maximum Streams (i),
}
图 35MAX_STREAMS 帧格式

MAX_STREAMS 帧包含以下字段:

Maximum Streams:

在连接生命周期内可以打开的相应类型流的累计数量。 此值不能超过 260,因为无法编码大于 262-1 的流 ID。收到允许打开大于此限制的流的帧 MUST 被视为类型为 FRAME_ENCODING_ERROR 的连接错误。

丢包或重排序可能导致端点收到的 MAX_STREAMS 帧中流限制 低于先前收到的值。不增加流限制的 MAX_STREAMS 帧 MUST 被忽略。

端点 MUST NOT 打开超过其对等方设置的 当前流限制所允许的流。例如,收到单向流限制 3 的服务器 被允许打开流 3、7 和 11,但不能打开流 15。 如果对等方打开的流超过允许数量,端点 MUST 以类型为 STREAM_LIMIT_ERROR 的错误终止连接。这包括 Early Data 中 违反记住限制的情况;见 第 7.4.1 节

注意,这些帧(以及相应的传输参数)并不描述可以并发打开的 流数量。该限制包括已经关闭的流以及打开的流。

19.12. DATA_BLOCKED 帧

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

DATA_BLOCKED 帧的格式如 图 36 所示。

DATA_BLOCKED Frame {
  Type (i) = 0x14,
  Maximum Data (i),
}
图 36DATA_BLOCKED 帧格式

DATA_BLOCKED 帧包含以下字段:

Maximum Data:

一个可变长度整数,指示发生阻塞时的连接级限制。

19.13. STREAM_DATA_BLOCKED 帧

发送方希望发送数据但由于流级流量控制而无法发送时, SHOULD 发送 STREAM_DATA_BLOCKED 帧 (type=0x15)。此帧类似于 DATA_BLOCKED(第 19.12 节)。

端点如果收到针对仅发送流的 STREAM_DATA_BLOCKED 帧, MUST 以 STREAM_STATE_ERROR 错误终止连接。

STREAM_DATA_BLOCKED 帧的格式如 图 37 所示。

STREAM_DATA_BLOCKED Frame {
  Type (i) = 0x15,
  Stream ID (i),
  Maximum Stream Data (i),
}
图 37STREAM_DATA_BLOCKED 帧 格式

STREAM_DATA_BLOCKED 帧包含以下字段:

Stream ID:

一个可变长度整数,指示因流量控制而被阻塞的流。

Maximum Stream Data:

一个可变长度整数,指示发生阻塞时的流偏移量。

19.14. STREAMS_BLOCKED 帧

发送方希望打开流但由于其对等方设置的最大流限制而无法打开时, SHOULD 发送 STREAMS_BLOCKED 帧 (type=0x16 或 0x17);见 第 19.11 节。 类型为 0x16 的 STREAMS_BLOCKED 帧用于指示达到双向流限制, 类型为 0x17 的 STREAMS_BLOCKED 帧用于指示达到单向流限制。

STREAMS_BLOCKED 帧不会打开流,而是通知对等方需要新流, 但流限制阻止了该流的创建。

STREAMS_BLOCKED 帧的格式如 图 38 所示。

STREAMS_BLOCKED Frame {
  Type (i) = 0x16..0x17,
  Maximum Streams (i),
}
图 38STREAMS_BLOCKED 帧 格式

STREAMS_BLOCKED 帧包含以下字段:

Maximum Streams:

一个可变长度整数,指示发送该帧时允许的最大流数量。 此值不能超过 260,因为无法编码大于 262-1 的流 ID。收到编码了更大流 ID 的帧 MUST 被视为类型为 STREAM_LIMIT_ERROR 或 FRAME_ENCODING_ERROR 的连接错误。

19.15. NEW_CONNECTION_ID 帧

端点发送 NEW_CONNECTION_ID 帧(type=0x18),以向其对等方提供 替代连接 ID,这些连接 ID 可用于在迁移连接时破坏可关联性; 见 第 9.5 节

NEW_CONNECTION_ID 帧的格式如 图 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),
}
图 39NEW_CONNECTION_ID 帧 格式

NEW_CONNECTION_ID 帧包含以下字段:

Sequence Number:

发送方分配给连接 ID 的序列号,编码为 可变长度整数;见 第 5.1.1 节

Retire Prior To:

一个可变长度整数,指示哪些连接 ID 应该退役; 见 第 5.1.2 节

Length:

一个 8 位无符号整数,包含连接 ID 的长度。 小于 1 和大于 20 的值无效,并且 MUST 被视为 类型为 FRAME_ENCODING_ERROR 的连接错误。

Connection ID:

指定长度的连接 ID。

Stateless Reset Token:

一个 128 位值,当使用关联连接 ID 时, 该值将用于无状态重置;见 第 10.3 节

如果端点当前要求其对等方使用零长度 Destination Connection ID 发送分组,则它 MUST NOT 发送此帧。 将连接 ID 的长度更改为零长度或从零长度更改,会使识别连接 ID 值何时更改变得困难。 正在发送带零长度 Destination Connection ID 的分组的端点 MUST 将收到 NEW_CONNECTION_ID 帧视为 类型为 PROTOCOL_VIOLATION 的连接错误。

传输错误、超时和重传可能导致同一个 NEW_CONNECTION_ID 帧被多次接收。多次收到同一个帧 MUST NOT 被视为连接错误。 接收方可以使用 NEW_CONNECTION_ID 帧中提供的序列号来处理 多次收到同一个 NEW_CONNECTION_ID 帧的情况。

如果端点收到的 NEW_CONNECTION_ID 帧重复了先前签发的连接 ID, 但 Stateless Reset Token 字段值不同或 Sequence Number 字段值不同, 或者一个序列号被用于不同的连接 ID,则端点 MAY 将该接收视为类型为 PROTOCOL_VIOLATION 的连接错误。

Retire Prior To 字段适用于连接建立期间建立的连接 ID 和 preferred_address 传输参数;见 第 5.1.2 节。Retire Prior To 字段中的值 MUST 小于或等于 Sequence Number 字段中的值。 收到 Retire Prior To 字段中的值大于 Sequence Number 字段中的值时, MUST 被视为类型为 FRAME_ENCODING_ERROR 的连接错误。

一旦发送方指示了 Retire Prior To 值,后续 NEW_CONNECTION_ID 帧中发送的较小值就没有效果。接收方 MUST 忽略任何 不增加已接收最大 Retire Prior To 值的 Retire Prior To 字段。

端点如果收到 NEW_CONNECTION_ID 帧,其序列号小于先前收到的 NEW_CONNECTION_ID 帧的 Retire Prior To 字段,则 MUST 发送相应的 RETIRE_CONNECTION_ID 帧来退役新收到的连接 ID, 除非它已经为该序列号这样做过。

19.16. RETIRE_CONNECTION_ID 帧

端点发送 RETIRE_CONNECTION_ID 帧(type=0x19),以指示它 将不再使用其对等方签发的连接 ID。这包括握手期间提供的连接 ID。 发送 RETIRE_CONNECTION_ID 帧也作为请求对等方发送更多连接 ID 供未来使用; 见 第 5.1 节。新的连接 ID 可以使用 NEW_CONNECTION_ID 帧(第 19.15 节)交付给对等方。

退役连接 ID 会使与该连接 ID 关联的无状态重置令牌失效。

RETIRE_CONNECTION_ID 帧的格式如 图 40 所示。

RETIRE_CONNECTION_ID Frame {
  Type (i) = 0x19,
  Sequence Number (i),
}
图 40RETIRE_CONNECTION_ID 帧 格式

RETIRE_CONNECTION_ID 帧包含以下字段:

Sequence Number:

正在退役的连接 ID 的序列号;见 第 5.1.2 节

收到包含大于先前发送给对等方的任何序列号的 RETIRE_CONNECTION_ID 帧,MUST 被视为 类型为 PROTOCOL_VIOLATION 的连接错误。

RETIRE_CONNECTION_ID 帧中指定的序列号 MUST NOT 指向承载该帧的分组的 Destination Connection ID 字段。对等方 MAY 将其视为类型为 PROTOCOL_VIOLATION 的连接错误。

如果端点被其对等方提供了零长度连接 ID,则不能发送此帧。 提供零长度连接 ID 的端点 MUST 将收到 RETIRE_CONNECTION_ID 帧视为类型为 PROTOCOL_VIOLATION 的连接错误。

19.17. PATH_CHALLENGE 帧

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

PATH_CHALLENGE 帧的格式如 图 41 所示。

PATH_CHALLENGE Frame {
  Type (i) = 0x1a,
  Data (64),
}
图 41PATH_CHALLENGE 帧 格式

PATH_CHALLENGE 帧包含以下字段:

Data:

此 8 字节字段包含任意数据。

在 PATH_CHALLENGE 帧中包含 64 位熵,可确保接收该分组 比正确猜测该值更容易。

此帧的接收方 MUST 生成包含相同 Data 值的 PATH_RESPONSE 帧 (第 19.18 节)。

19.18. PATH_RESPONSE 帧

PATH_RESPONSE 帧(type=0x1b)是为响应 PATH_CHALLENGE 帧而发送的。

PATH_RESPONSE 帧的格式如 图 42 所示。PATH_RESPONSE 帧的格式 与 PATH_CHALLENGE 帧相同;见 第 19.17 节

PATH_RESPONSE Frame {
  Type (i) = 0x1b,
  Data (64),
}
图 42PATH_RESPONSE 帧 格式

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

19.19. CONNECTION_CLOSE 帧

端点发送 CONNECTION_CLOSE 帧(type=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 (..),
}
图 43CONNECTION_CLOSE 帧 格式

CONNECTION_CLOSE 帧包含以下字段:

Error Code:

一个可变长度整数,指示关闭此连接的原因。 类型为 0x1c 的 CONNECTION_CLOSE 帧使用 第 20.1 节中定义空间的错误码。 类型为 0x1d 的 CONNECTION_CLOSE 帧使用应用协议定义的错误码; 见 第 20.2 节

Frame Type:

一个可变长度整数,编码触发错误的帧类型。 当帧类型未知时,使用值 0(等同于提及 PADDING 帧)。 CONNECTION_CLOSE 的应用特定变体(type 0x1d)不包含此字段。

Reason Phrase Length:

一个可变长度整数,指定原因短语的长度,以字节为单位。 因为 CONNECTION_CLOSE 帧不能拆分到多个分组之间,所以任何分组大小限制 也会限制原因短语可用的空间。

Reason Phrase:

关于关闭的额外诊断信息。如果发送方选择不提供 超出 Error Code 值之外的详细信息,则其长度可以为零。 这 SHOULD 是 UTF-8 编码字符串 [RFC3629],不过该帧不承载语言标签等信息, 这些信息本可帮助除创建该文本的一方之外的任何实体理解。

CONNECTION_CLOSE 的应用特定变体(type 0x1d)只能使用 0-RTT 或 1-RTT 分组发送;见 第 12.5 节。当应用希望在握手期间放弃连接时,端点 可以在 Initial 或 Handshake 分组中发送类型为 0x1c 且错误码为 APPLICATION_ERROR 的 CONNECTION_CLOSE 帧。

19.20. HANDSHAKE_DONE 帧

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

HANDSHAKE_DONE 帧的格式如 图 44 所示,该图 表明 HANDSHAKE_DONE 帧没有内容。

HANDSHAKE_DONE Frame {
  Type (i) = 0x1e,
}
图 44HANDSHAKE_DONE 帧 格式

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

19.21. 扩展帧

QUIC 帧不使用自描述编码。因此,端点需要理解所有帧的语法, 才能成功处理分组。这允许高效编码帧,但也意味着端点不能向其对等方 发送未知类型的帧。

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

修改或替换核心协议功能(包括帧类型)的扩展,如果与其他 修改或替换相同功能的扩展组合,将难以结合使用,除非明确 定义该组合的行为。此类扩展 SHOULD 定义它们与 先前定义的、修改相同协议组件的扩展之间的交互。

扩展帧 MUST 受拥塞控制, 并且 MUST 导致发送 ACK 帧。 例外是替换或补充 ACK 帧的扩展帧。除非扩展中指定, 扩展帧不纳入流量控制。

IANA 注册表用于管理帧类型的分配;见 第 22.4 节

20. 错误码

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

20.1. 传输错误码

本节列出了已定义的 QUIC 传输错误码,这些错误码可用于 类型为 0x1c 的 CONNECTION_CLOSE 帧。这些错误适用于整个连接。

NO_ERROR (0x00):

端点将其与 CONNECTION_CLOSE 一起使用, 表示在没有任何错误的情况下突然关闭连接。

INTERNAL_ERROR (0x01):

端点遇到内部错误,无法继续该连接。

CONNECTION_REFUSED (0x02):

服务器拒绝接受新连接。

FLOW_CONTROL_ERROR (0x03):

端点收到的数据超过了其通告的数据限制; 见 第 4 节

STREAM_LIMIT_ERROR (0x04):

端点收到一个帧,其流标识符超过了 对应流类型的通告流限制。

STREAM_STATE_ERROR (0x05):

端点收到针对某个流的帧,而该流所处状态 不允许该帧;见 第 3 节

FINAL_SIZE_ERROR (0x06):

(1) 端点收到一个包含数据的 STREAM 帧, 其数据超过了先前建立的最终大小;(2) 端点收到一个 STREAM 帧或 RESET_STREAM 帧,其中包含的最终大小小于已经收到的流数据大小;或 (3) 端点收到一个 STREAM 帧或 RESET_STREAM 帧,其中包含的最终大小 与已经建立的最终大小不同。

FRAME_ENCODING_ERROR (0x07):

端点收到格式错误的帧——例如, 未知类型的帧,或 ACK 帧包含的确认范围数量超过了 分组剩余部分能够承载的数量。

TRANSPORT_PARAMETER_ERROR (0x08):

端点收到格式错误的传输参数,包含无效值, 省略了强制传输参数,包含了被禁止的传输参数,或存在其他错误。

CONNECTION_ID_LIMIT_ERROR (0x09):

对等方提供的连接 ID 数量超过了通告的 active_connection_id_limit。

PROTOCOL_VIOLATION (0x0a):

端点检测到协议合规性错误,而该错误未被 更具体的错误码覆盖。

INVALID_TOKEN (0x0b):

服务器收到的客户端 Initial 中包含无效的 Token 字段。

APPLICATION_ERROR (0x0c):

应用或应用协议导致连接被关闭。

CRYPTO_BUFFER_EXCEEDED (0x0d):

端点在 CRYPTO 帧中收到的数据超过了其 能够缓冲的数量。

KEY_UPDATE_ERROR (0x0e):

端点在执行密钥更新时检测到错误;见 第 6 节,见 [QUIC-TLS]

AEAD_LIMIT_REACHED (0x0f):

端点已经达到给定连接所使用 AEAD 算法的机密性或完整性限制。

NO_VIABLE_PATH (0x10):

端点已经确定网络路径无法支持 QUIC。 除非路径不支持足够大的 MTU,否则端点不太可能收到携带此代码的 CONNECTION_CLOSE 帧。

CRYPTO_ERROR (0x0100-0x01ff):

加密握手失败。保留 256 个值的范围, 用于承载所使用加密握手特有的错误码。当使用 TLS 进行加密握手时 发生错误的错误码,见 第 4.8 节,见 [QUIC-TLS]

有关注册新错误码的详细信息,见 第 22.5 节

定义这些错误码时应用了若干原则。可能要求接收方采取 特定操作的错误条件会被赋予唯一代码。表示常见条件的错误 会被赋予特定代码。如果不满足这两种条件,则错误码用于标识协议栈的 一般功能,例如流量控制或传输参数处理。最后,对于实现无法或不愿意 使用更具体代码的条件,提供通用错误。

20.2. 应用协议 错误码

应用错误码的管理留给应用协议。应用协议错误码用于 RESET_STREAM 帧(第 19.4 节)、 STOP_SENDING 帧(第 19.5 节),以及 类型为 0x1d 的 CONNECTION_CLOSE 帧(第 19.19 节)。

21. 安全考虑

QUIC 的目标是提供安全的传输连接。 第 21.1 节 概述了这些属性; 后续各节讨论与这些属性有关的约束和注意事项,包括 已知攻击和对策的描述。

21.1. 安全属性概述

QUIC 的完整安全分析不在本文档范围内。 本节以非正式方式描述期望的安全属性,以帮助实现者并指导协议分析。

QUIC 假定 [SEC-CONS] 中描述的威胁模型,并针对 该模型产生的许多攻击提供保护。

为此,攻击被分为被动攻击和主动攻击。被动攻击者具有 从网络读取分组的能力,而主动攻击者还具有向网络写入分组的能力。 然而,被动攻击也可能涉及具有能力导致路由变化或对构成连接的分组所走路径 进行其他修改的攻击者。

攻击者还被进一步分类为路径上攻击者或路径外攻击者。 路径上攻击者可以读取、修改或移除它观察到的任何分组,使该分组不再到达 其目的地;而路径外攻击者观察分组,但不能阻止原始分组到达其预期目的地。 两类攻击者也都可以发送任意分组。此定义与 第 3.5 节 ,见 [SEC-CONS] 中的定义不同,因为这里的 路径外攻击者能够观察分组。

握手、受保护分组和连接迁移的属性将分别考虑。

21.1.1. 握手

QUIC 握手纳入了 TLS 1.3 握手,并继承 附录 E.1,见 [TLS13] 中描述的 加密属性。QUIC 的许多安全属性依赖 TLS 握手提供这些属性。 对 TLS 握手的任何攻击都可能影响 QUIC。

任何破坏会话密钥保密性或唯一性、 或破坏参与对等方认证的 TLS 握手攻击,都会影响 QUIC 提供的 依赖这些密钥的其他安全保证。例如,迁移(第 9 节) 依赖机密性保护的有效性,这既包括使用 TLS 握手协商密钥, 也包括 QUIC 分组保护,以避免跨网络路径的可关联性。

对 TLS 握手完整性的攻击可能允许攻击者影响 应用协议或 QUIC 版本的选择。

除了 TLS 提供的属性外,QUIC 握手还为 抵御针对握手的 DoS 攻击提供了一些防护。

21.1.1.1. 抗放大

地址验证(第 8 节)用于验证声称拥有给定地址的实体 是否能够在该地址接收分组。地址验证将放大攻击目标限制为 攻击者能够观察分组的地址。

在地址验证之前,端点能够发送的内容受到限制。 端点不能向未经验证的地址发送超过从该地址接收数据三倍的数据。

21.1.1.2. 服务器端 DoS

为完整握手计算服务器的首个 flight 可能代价很高,需要签名和密钥交换计算。为防止计算型 DoS 攻击, Retry 分组提供了一种廉价的令牌交换机制,使服务器能够在进行任何昂贵计算之前 验证客户端的 IP 地址,代价是一个往返。成功握手后,服务器可以向客户端 签发新令牌,从而允许建立新连接而不产生此成本。

21.1.1.3. 路径上握手终止

路径上或路径外攻击者可以通过替换 Initial 分组或与其竞速来强制握手失败。一旦有效的 Initial 分组已经交换, 后续 Handshake 分组会由 Handshake 密钥保护,路径上攻击者除非丢弃分组 使端点放弃尝试,否则不能强制握手失败。

路径上攻击者还可以替换任一侧分组的地址, 因而使客户端或服务器对远端地址产生不正确的认识。这样的攻击 与 NAT 执行的功能无法区分。

21.1.1.4. 参数 协商

整个握手都受到加密保护,其中 Initial 分组 使用按版本确定的密钥加密,而 Handshake 及之后的分组使用 从 TLS 密钥交换派生的密钥加密。此外,参数协商被纳入 TLS transcript, 因此提供与普通 TLS 协商相同的完整性保证。攻击者可以观察 客户端的传输参数(只要它知道版本特定 salt),但不能观察 服务器的传输参数,也不能影响参数协商。

连接 ID 在所有分组中均未加密,但受到完整性保护。

此版本的 QUIC 未纳入版本协商机制; 不兼容版本的实现只会无法建立连接。

21.1.2. 受保护分组

分组保护(第 12.1 节)对除 Version Negotiation 分组之外的所有分组 应用认证加密,不过 Initial 和 Retry 分组由于使用版本特定密钥材料而保护有限; 更多细节见 [QUIC-TLS]。 本节考虑针对受保护分组的被动和主动攻击。

路径上和路径外攻击者都可以发起被动攻击, 保存观察到的分组,以便将来离线攻击分组保护;对任何网络上任何分组的任何观察者而言, 情况都是如此。

如果攻击者在无法观察连接有效分组的情况下注入分组, 则不太可能成功,因为分组保护确保有效分组只能由拥有握手期间建立的 密钥材料的端点生成;见第 7 节和 21.1.1 节。类似地,任何观察分组并 试图插入新数据或修改这些分组中现有数据的主动攻击者, 除 Initial 分组外,都不应能够生成被接收端点视为有效的分组。

在欺骗攻击中,主动攻击者会改写其转发或注入的分组中 未受保护的部分,例如源地址或目的地址;只有当攻击者能够将分组转发给 原始端点时,这种攻击才有效。分组保护确保分组载荷只能由完成握手的 端点处理,无效分组会被这些端点忽略。

攻击者还可以修改分组与 UDP 数据报之间的边界, 使多个分组合并到单个数据报中,或将合并分组拆分为多个数据报。 除了包含 Initial 分组的数据报需要填充外,修改分组在数据报中的排列方式 对连接没有功能性影响,尽管它可能改变某些性能特征。

21.1.3. 连接 迁移

连接迁移(第 9 节)为端点提供了在多条路径上的 IP 地址和端口之间 转换的能力,同时一次使用一条路径来传输和接收非探测帧。 路径验证(第 8.2 节)确定 对等方既愿意也能够接收发送到特定路径上的分组。这有助于通过限制 发送到伪造地址的分组数量来降低地址欺骗的影响。

本节描述在各种 DoS 攻击类型下 连接迁移的预期安全属性。

21.1.3.1. 路径上 主动攻击

能够使其观察到的分组不再到达预期目的地的 攻击者,被视为路径上攻击者。当攻击者位于客户端和服务器之间时, 端点需要通过该攻击者发送分组,以在给定路径上建立连通性。

路径上攻击者可以:

  • 检查分组
  • 修改 IP 和 UDP 分组报头
  • 注入新分组
  • 延迟分组
  • 重排序分组
  • 丢弃分组
  • 沿分组边界拆分和合并数据报

路径上攻击者不能:

  • 修改分组的认证部分并使接收方 接受该分组

路径上攻击者有机会修改其观察到的分组; 然而,对分组认证部分的任何修改都会导致接收端点将其作为无效分组丢弃, 因为分组载荷既经过认证又经过加密。

QUIC 旨在如下约束路径上攻击者的能力:

  1. 路径上攻击者可以阻止连接使用某条路径, 如果该连接无法使用不包含攻击者的其他路径,则导致连接失败。 这可以通过丢弃所有分组、修改它们使其解密失败或其他方法实现。
  2. 路径上攻击者可以通过使新路径上的路径验证失败, 来阻止迁移到攻击者也位于路径上的新路径。
  3. 路径上攻击者不能阻止客户端迁移到 攻击者不在路径上的路径。
  4. 路径上攻击者可以通过延迟分组或丢弃分组 降低连接吞吐量。
  5. 路径上攻击者不能使端点接受其已修改了 认证部分的分组。
21.1.3.2. 路径外 主动攻击

路径外攻击者不直接位于客户端与服务器之间的路径上, 但可能能够获得客户端和服务器之间发送的部分或全部分组副本。 它还能够向任一端点发送这些分组的副本。

路径外攻击者可以:

  • 检查分组
  • 注入新分组
  • 重排序注入的分组

路径外攻击者不能:

  • 修改端点发送的分组
  • 延迟分组
  • 丢弃分组
  • 重排序原始分组

路径外攻击者可以创建其观察到分组的修改副本, 并将这些副本注入网络,可能带有伪造的源地址和目的地址。

为了本讨论,假定路径外攻击者有能力将 某个分组的修改副本注入网络,并使该副本在攻击者观察到的原始分组到达之前 到达目的端点。换言之,攻击者有能力持续在与端点之间合法分组的竞速中 “获胜”,可能导致接收方忽略原始分组。

还假定攻击者拥有影响 NAT 状态所需的资源。 特别是,攻击者可以使端点丢失其 NAT 绑定,然后获得同一端口 用于自己的流量。

QUIC 旨在如下约束路径外攻击者的能力:

  1. 路径外攻击者可以与分组竞速,并试图成为“有限” 路径上攻击者。
  2. 只要路径外攻击者能够在客户端和服务器之间 提供改进的连通性,它就可以使源地址列为该路径外攻击者的 转发分组的路径验证成功。
  3. 握手完成后,路径外攻击者不能导致连接关闭。
  4. 如果路径外攻击者无法观察新路径, 它不能导致向新路径的迁移失败。
  5. 在迁移到攻击者也是路径外攻击者的新路径期间, 路径外攻击者可以成为有限路径上攻击者。
  6. 路径外攻击者可以通过影响共享 NAT 状态, 使其从客户端最初使用的相同 IP 地址和端口向服务器发送分组, 从而成为有限路径上攻击者。
21.1.3.3. 有限路径上主动攻击

有限路径上攻击者是一种路径外攻击者, 它通过复制并转发服务器与客户端之间的原始分组来提供改进的分组路由, 使这些分组先于原始副本到达,从而使原始分组被目的端点丢弃。

有限路径上攻击者与路径上攻击者的区别在于, 它不在端点之间的原始路径上,因此端点发送的原始分组仍会到达目的地。 这意味着,如果将复制分组比其原始路径更快地路由到目的地的能力在未来失败, 也不会阻止原始分组到达目的地。

有限路径上攻击者可以:

  • 检查分组
  • 注入新分组
  • 修改未加密分组报头
  • 重排序分组

有限路径上攻击者不能:

  • 延迟分组,使其晚于原始路径上发送的 分组到达
  • 丢弃分组
  • 修改分组中经过认证和加密的部分, 并使接收方接受该分组

有限路径上攻击者只能将分组延迟到 原始分组先于重复分组到达的程度,这意味着它不能提供比原始路径 延迟更差的路由。如果有限路径上攻击者丢弃分组,原始副本仍会到达 目的端点。

QUIC 旨在如下约束有限路径外攻击者的能力:

  1. 握手完成后,有限路径上攻击者不能导致连接关闭。
  2. 如果客户端首先恢复活动,有限路径上攻击者 不能导致空闲连接关闭。
  3. 如果服务器首先恢复活动,有限路径上攻击者 可以导致空闲连接被认为已经丢失。

注意,出于相同原因,这些保证与为任何 NAT 提供的保证相同。

21.2. 握手拒绝 服务

作为加密且经过认证的传输,QUIC 提供了一系列 抵御拒绝服务的保护。一旦加密握手完成,QUIC 端点会丢弃大多数 未经认证的分组,从而极大限制攻击者干扰现有连接的能力。

连接建立后,QUIC 端点可能接受某些未经认证的 ICMP 分组 (见 第 14.2.1 节),但这些分组的使用 极其有限。端点可能接受的唯一其他分组类型是无状态重置 (第 10.3 节),它依赖令牌在使用前 保持秘密。

在创建连接期间,QUIC 只提供针对网络路径外攻击的保护。 所有 QUIC 分组都包含证明:接收方看到了来自其对等方的前一个分组。

握手期间地址不能改变,因此端点可以丢弃在不同网络路径上 收到的分组。

Source 和 Destination Connection ID 字段是握手期间 防御路径外攻击的主要手段;见 第 8.1 节。它们必须与对等方设置的值匹配。 除 Initial 和 Stateless Reset 外,端点只接受包含 Destination Connection ID 字段且 该字段匹配端点先前选择值的分组。这是 Version Negotiation 分组所提供的唯一保护。

Initial 分组中的 Destination Connection ID 字段由客户端 选择为不可预测的值,这还有另一个目的。承载加密握手的分组使用 从该连接 ID 和特定于 QUIC 版本的 salt 派生的密钥进行保护。 这使端点能够使用与加密握手完成后相同的流程来认证其收到的分组。 无法认证的分组会被丢弃。以这种方式保护分组,强有力地保证了 分组发送方看到了 Initial 分组并理解了它。

这些保护并非旨在有效抵御能够在连接建立前接收 QUIC 分组的 攻击者。这样的攻击者可能发送会被 QUIC 端点接受的分组。 此版本的 QUIC 试图检测此类攻击,但预期端点会建立连接失败, 而不是恢复。大体而言,加密握手协议 [QUIC-TLS] 负责检测握手期间的篡改。

允许端点使用其他方法来检测并尝试从握手干扰中恢复。 无效分组可以使用其他方法识别和丢弃,但本文档不强制规定任何具体方法。

21.3. 放大攻击

攻击者可能能够从服务器接收地址验证令牌 (第 8 节),然后释放其用于获取该令牌的 IP 地址。稍后,攻击者可以通过伪造同一地址来与服务器发起 0-RTT 连接,而该地址现在可能指向不同的(受害)端点。因此,攻击者可能导致 服务器向受害者发送一个初始拥塞窗口大小的数据。

服务器 SHOULD 通过限制地址验证令牌的 使用和生命周期来缓解此攻击;见 第 8.1.3 节

21.4. 乐观 ACK 攻击

确认其未收到分组的端点,可能导致拥塞控制器允许以 超出网络支持能力的速率发送。端点 MAY 在发送分组时跳过 分组编号以检测这种行为。端点随后可以立即以 PROTOCOL_VIOLATION 类型的连接错误关闭连接;见 第 10.2 节

21.5. 请求伪造 攻击

请求伪造攻击发生在端点使其对等方向受害者发出请求, 且该请求由该端点控制的情况下。请求伪造攻击旨在让攻击者获得 其对等方所具备、而攻击者原本可能无法获得的能力。对于网络协议, 请求伪造攻击通常用于利用受害者因对等方在网络中的位置而授予该对等方的 任何隐式授权。

要使请求伪造有效,攻击者需要能够影响对等方发送什么分组 以及这些分组发送到哪里。如果攻击者能够使用受控载荷定位易受攻击的服务, 该服务可能执行归因于攻击者对等方但由攻击者决定的操作。

例如,Web 上的跨站请求伪造 [CSRF] 利用会导致客户端发出包含授权 cookie [COOKIE] 的请求,从而允许一个站点访问本应仅限于另一个站点的信息和操作。

由于 QUIC 运行在 UDP 之上,主要关注的攻击方式是: 攻击者能够选择其对等方发送 UDP 数据报的地址,并能控制这些分组中 一些未受保护的内容。由于 QUIC 端点发送的大部分数据受到保护, 这包括对密文的控制。如果攻击者能够使对等方向某个主机发送 UDP 数据报, 而该主机会基于数据报中的内容执行某些操作,则攻击成功。

本节讨论 QUIC 可能被用于请求伪造攻击的方式。

本节还描述了 QUIC 端点可以实现的有限对策。 这些缓解措施可由 QUIC 实现或部署单方面采用,而无需请求伪造攻击的潜在目标采取行动。 然而,如果基于 UDP 的服务未正确授权请求,这些对策可能不足。

由于 第 21.5.4 节中描述的迁移攻击非常强大且没有 足够的对策,QUIC 服务器实现应假定攻击者能够使其生成任意 UDP 载荷 并发送到任意目的地。QUIC 服务器 SHOULD NOT 部署在 未部署入口过滤 [BCP38] 且同时存在 安全性不足的 UDP 端点的网络中。

虽然通常无法确保客户端不与易受攻击的端点共处同一位置, 但此版本的 QUIC 不允许服务器迁移,因此防止了针对客户端的伪造迁移攻击。 任何允许服务器迁移的未来扩展 MUST 同时定义 针对伪造攻击的对策。

21.5.1. 端点的 控制选项

QUIC 为攻击者影响或控制其对等方发送 UDP 数据报的位置 提供了一些机会:

  • 初始连接建立(第 7 节),其中服务器能够选择 客户端发送数据报的位置——例如,通过填充 DNS 记录;
  • 首选地址(第 9.6 节),其中服务器能够选择 客户端发送数据报的位置;
  • 伪造连接迁移(第 9.3.1 节),其中客户端能够 使用源地址欺骗来选择服务器随后发送数据报的位置;以及
  • 导致服务器发送 Version Negotiation 分组的 伪造分组(第 21.5.5 节)。

在所有情况下,攻击者都可以使其对等方向 可能不理解 QUIC 的受害者发送数据报。也就是说,这些分组由对等方在 地址验证之前发送;见 第 8 节

在分组加密部分之外,QUIC 为端点提供了若干选项, 用于控制其对等方发送的 UDP 数据报内容。Destination Connection ID 字段 可直接控制对等方发送的分组中较早出现的字节;见 第 5.1 节。Initial 分组中的 Token 字段使服务器能够控制 Initial 分组的 其他字节;见 第 17.2.2 节

此版本的 QUIC 中没有任何措施可防止对分组加密部分的 间接控制。必须假定端点能够控制对等方发送的帧内容, 尤其是那些传递应用数据的帧,例如 STREAM 帧。尽管这在一定程度上 取决于应用协议的细节,但在许多协议使用上下文中都可能实现某些控制。 由于攻击者可以访问分组保护密钥,它们很可能能够预测对等方将如何 加密未来分组。随后,成功控制数据报内容只要求攻击者能够以一定可靠性 预测分组编号以及帧在分组中的位置。

本节假定限制对数据报内容的控制并不可行。 后续各节的缓解措施重点在于限制地址验证之前发送的数据报可被用于 请求伪造的方式。

21.5.2. 使用客户端 Initial 分组的请求伪造

充当服务器的攻击者可以选择其通告可用性的 IP 地址和端口, 因此假定来自客户端的 Initial 分组可用于此类攻击。握手中隐含的 地址验证确保——对于新连接——客户端不会向不理解 QUIC 或不愿意接受 QUIC 连接的目的地发送其他类型的分组。

Initial 分组保护(第 5.2 节,见 [QUIC-TLS]) 使服务器难以控制客户端发送的 Initial 分组内容。客户端选择不可预测的 Destination Connection ID,可确保服务器无法控制来自客户端的 Initial 分组中 任何加密部分。

然而,Token 字段受服务器控制,并且确实允许服务器 利用客户端发起请求伪造攻击。使用 NEW_TOKEN 帧提供的令牌 (第 8.1.3 节)是连接建立期间 请求伪造的唯一选项。

不过,客户端没有义务使用 NEW_TOKEN 帧。 如果服务器地址自收到 NEW_TOKEN 帧以来已经改变,客户端发送空的 Token 字段,则可以避免依赖 Token 字段的请求伪造攻击。

如果服务器地址发生变化,客户端可以避免使用 NEW_TOKEN。 然而,不包含 Token 字段可能对性能产生不利影响。服务器可能依赖 NEW_TOKEN 来启用超过三倍发送限制的数据发送;见 第 8.1 节。 特别是,这会影响客户端使用 0-RTT 向服务器请求数据的情况。

发送 Retry 分组(第 17.2.5 节)为服务器提供了更改 Token 字段的选项。发送 Retry 后,服务器还可以控制客户端后续 Initial 分组的 Destination Connection ID 字段。这也可能允许对 Initial 分组加密内容进行间接控制。 然而,Retry 分组交换会验证服务器地址,从而防止使用后续 Initial 分组进行 请求伪造。

21.5.3. 使用首选地址的 请求伪造

服务器可以指定首选地址,客户端随后在确认握手后 迁移到该地址;见 第 9.6 节。 客户端发送到首选地址的分组中的 Destination Connection ID 字段可用于请求伪造。

客户端在验证首选地址之前 MUST NOT 向该地址发送非探测帧;见 第 8 节。 这大大减少了服务器控制数据报加密部分的选项。

本文档未提供可由端点实现且特定于使用首选地址的 任何额外对策。第 21.5.6 节中描述的 通用措施可用作进一步缓解。

21.5.4. 使用伪造迁移的 请求伪造

客户端能够将伪造的源地址呈现为表面上的连接迁移的一部分, 以使服务器向该地址发送数据报。

服务器随后发送到该伪造地址的任何分组中的 Destination Connection ID 字段都可用于请求伪造。客户端也可能能够 影响密文。

在地址验证之前,如果服务器只向某个地址发送探测分组 (第 9.1 节),则攻击者对数据报加密部分 只有有限控制。然而,特别是对于 NAT 重新绑定,这可能对性能产生不利影响。 如果服务器发送承载应用数据的帧,攻击者可能能够控制数据报的大部分内容。

第 21.5.6 节中描述的通用措施外, 本文档未提供可由端点实现的具体对策。 然而,网络层面的地址欺骗对策——特别是入口过滤 [BCP38]——对于使用欺骗且 源自外部网络的攻击尤其有效。

21.5.5. 使用版本协商的 请求伪造

能够在分组上呈现伪造源地址的客户端,可以使服务器向该地址 发送 Version Negotiation 分组(第 17.2.1 节)。

未知版本分组的连接 ID 字段没有大小限制, 这增加了客户端从生成的数据报中控制的数据量。此分组的第一个字节不受客户端控制, 接下来的四个字节为零,但客户端能够控制从第五个字节开始的最多 512 字节。

没有为此攻击提供具体对策,不过通用保护 (第 21.5.6 节)可以适用。 在这种情况下,入口过滤 [BCP38] 也有效。

21.5.6. 通用请求伪造 对策

抵御请求伪造攻击最有效的防御是修改易受攻击的服务, 使其使用强认证。然而,这并不总是在 QUIC 部署的控制范围内。 本节概述 QUIC 端点可以单方面采取的其他步骤。这些额外步骤都是可酌情采用的, 因为取决于具体情况,它们可能干扰或阻止合法使用。

通过回环接口提供的服务通常缺乏适当认证。 端点 MAY 阻止连接尝试或迁移到回环地址。 如果同一服务先前可在不同接口上使用,或该地址由非回环地址上的服务提供, 端点 SHOULD NOT 允许连接或迁移到回环地址。 依赖这些能力的端点可以提供禁用这些保护的选项。

类似地,端点可以将从全局、唯一本地 [RFC4193] 或非私有地址 更改为链路本地地址 [RFC4291] 或私有使用范围中的地址 [RFC1918] 视为潜在的 请求伪造尝试。端点可以完全拒绝使用这些地址,但这会带来干扰合法使用的 显著风险。除非端点掌握关于网络的具体知识,表明向给定范围内未经验证地址 发送数据报不安全,否则端点 SHOULD NOT 拒绝使用某个地址。

端点 MAY 选择通过不在 Initial 分组中包含来自 NEW_TOKEN 帧的值,或在完成地址验证前仅在分组中发送探测帧, 来降低请求伪造风险。注意,这不能阻止攻击者使用 Destination Connection ID 字段进行攻击。

端点不应被期望拥有关于可能成为请求伪造攻击 易受攻击目标的服务器位置的具体信息。然而,随着时间推移,可能能够识别 常见攻击目标的特定 UDP 端口,或用于攻击的数据报中的特定模式。 端点 MAY 选择在验证目的地址之前,避免向这些端口发送 数据报,或不发送匹配这些模式的数据报。端点 MAY 在不使用的情况下退役包含已知问题模式的连接 ID。

21.6. Slowloris 攻击

通常称为 Slowloris [SLOWLORIS] 的攻击试图保持大量 到目标端点的连接处于打开状态,并尽可能长时间保持打开。 这些攻击可以通过生成避免因不活动而关闭所需的最少活动量, 针对 QUIC 端点执行。这可能涉及发送少量数据、逐渐打开流量控制窗口以 控制发送方速率,或制造模拟高丢包率的 ACK 帧。

QUIC 部署 SHOULD 为 Slowloris 攻击提供缓解措施, 例如增加服务器允许的最大客户端数量、限制单个 IP 地址允许建立的连接数量、 对连接允许的最小传输速度施加限制,以及限制端点允许保持连接的时长。

21.7. 流分片 与重组攻击

恶意发送方可能故意不发送流数据的某些部分, 使接收方为未发送的数据投入资源。这可能导致接收缓冲区内存占用不成比例, 和/或在接收方创建庞大且低效的数据结构。

恶意接收方可能故意不确认包含流数据的分组, 试图迫使发送方存储未确认的流数据以便重传。

如果流量控制窗口与可用内存相对应,则针对接收方的攻击会得到缓解。 然而,一些接收方会过度承诺内存,并通告总体上超过实际可用内存的 流量控制偏移量。当端点行为良好时,过度承诺策略可以带来更好性能, 但会使端点容易受到流分片攻击。

QUIC 部署 SHOULD 为流分片攻击提供缓解措施。 缓解措施可以包括避免过度承诺内存、限制跟踪数据结构的大小、 延迟 STREAM 帧重组、实现基于重组空洞年龄和持续时间的启发式方法, 或这些措施的组合。

21.8. 流承诺 攻击

恶意端点可以打开大量流,耗尽端点上的状态。 恶意端点可以在大量连接上重复此过程,其方式类似于 TCP 中的 SYN 洪泛攻击。

通常,客户端会按顺序打开流,如 第 2.1 节所述。 然而,当多个流在短时间间隔内发起时,丢包或重排序可能导致打开流的 STREAM 帧乱序接收。收到更高编号的流 ID 时,接收方需要打开同一类型中 所有中间流;见 第 3.2 节。 因此,在新连接上,打开流 4000000 会打开 1000001 个 客户端发起的双向流。

活动流的数量受 initial_max_streams_bidi 和 initial_max_streams_uni 传输参数限制,并由收到的任何 MAX_STREAMS 帧更新,如 第 4.6 节所述。如果选择得当, 这些限制可以缓解流承诺攻击的影响。然而,如果限制设置过低, 当应用期望打开大量流时可能影响性能。

21.9. 对等方拒绝服务

QUIC 和 TLS 都包含在某些上下文中有合法用途的帧或消息, 但这些帧或消息可能被滥用,使对等方消耗处理资源,而对连接状态没有任何 可观察影响。

消息也可以用于以很小或无关紧要的方式改变并恢复状态, 例如发送对流量控制限制的小幅增量。

如果处理成本相对于带宽消耗或对状态的影响来说过大, 那么这可能允许恶意对等方耗尽处理能力。

虽然所有消息都有合法用途,但实现 SHOULD 跟踪处理成本相对于进展的比例, 并将任何大量无生产性分组视为攻击迹象。端点 MAY 通过连接错误响应该条件,或通过丢弃分组来响应。

21.10. 显式拥塞 通知攻击

路径上攻击者可能操纵 IP 报头中 ECN 字段的值, 以影响发送方速率。[RFC3168] 更详细地讨论了这些操纵及其影响。

有限路径上攻击者可以复制并发送修改了 ECN 字段的分组, 以影响发送方速率。如果接收方丢弃重复分组,攻击者需要使重复分组 与原始分组竞速,才能在此攻击中成功。因此,除非 IP 分组中的至少一个 QUIC 分组被成功处理,否则 QUIC 端点会忽略该 IP 分组中的 ECN 字段; 见 第 13.4 节

21.11. 无状态重置 预言机

无状态重置会造成一种可能的拒绝服务攻击, 类似于 TCP 重置注入。如果攻击者能够导致为具有所选连接 ID 的连接生成 无状态重置令牌,则这种攻击是可能的。能够导致生成此令牌的攻击者 可以重置具有相同连接 ID 的活动连接。

如果分组可以被路由到共享静态密钥的不同实例——例如, 通过更改 IP 地址或端口——则攻击者可以导致服务器发送无状态重置。 为防御这种拒绝服务,针对无状态重置共享静态密钥的端点(见 第 10.3.2 节MUST 被安排为:具有给定连接 ID 的分组始终到达 具有连接状态的实例,除非该连接不再活动。

更一般地说,如果具有相应连接 ID 的连接可能在任何使用 相同静态密钥的端点上处于活动状态,则服务器 MUST NOT 生成无状态重置。

在使用动态负载均衡的集群中,可能在活动实例仍保留连接状态时 发生负载均衡器配置变化。即使实例保留连接状态,路由变化以及由此产生的 无状态重置仍会导致连接终止。如果分组没有机会被路由到正确实例, 则发送无状态重置比等待连接超时更好。然而,只有当路由不能被攻击者影响时, 这才是可接受的。

21.12. 版本降级

本文档定义了 QUIC Version Negotiation 分组 (第 6 节),可用于在两个端点之间 协商使用的 QUIC 版本。然而,本文档未指定此版本与后续未来版本之间如何 执行该协商。特别是,Version Negotiation 分组不包含任何防止版本降级攻击的 机制。使用 Version Negotiation 分组的未来 QUIC 版本 MUST 定义一种能够稳健抵御版本降级攻击的机制。

21.13. 通过路由进行的 定向攻击

部署应限制攻击者将新连接定向到特定服务器实例的能力。 理想情况下,路由决策独立于客户端选择的值,包括地址。一旦实例被选中, 可以选择一个连接 ID,使后续分组被路由到同一实例。

21.14. 流量分析

QUIC 分组的长度可能泄露这些分组内容长度的信息。 PADDING 帧使端点在一定程度上能够掩盖分组内容长度;见 第 19.1 节

击败流量分析具有挑战性,并且是活跃研究的主题。 长度并非信息可能泄露的唯一方式。端点还可能通过其他侧信道泄露敏感信息, 例如分组时序。

22. IANA 考虑事项

本文档建立了若干注册表,用于管理 QUIC 中的码点。 这些注册表按照 第 22.1 节中定义的一组 通用策略运行。

22.1. QUIC 注册表的 注册策略

所有 QUIC 注册表都允许码点的临时注册和永久注册。 本节记录这些注册表共有的策略。

22.1.1. 临时 注册

码点的临时注册旨在允许对 QUIC 扩展进行 私有使用和实验。临时注册只要求包含码点值和联系信息。 但是,临时注册可能被收回并重新分配给其他用途。

临时注册需要专家审查,如 [RFC8126] 的 第 4.5 节所定义。建议指定专家 仅拒绝占用剩余码点空间比例过大,或使用第一个未分配值 (见 第 22.1.2 节)的注册。

临时注册将包含 Date 字段,用来指示注册最后更新的时间。 可以在无需指定专家审查的情况下,请求更新任何临时注册的日期。

所有 QUIC 注册表都包含以下字段以支持临时注册:

Value:

分配的码点。

Status:

"permanent" 或 "provisional"。

Specification:

指向该值的公开可用规范的引用。

Date:

该注册最后更新的日期。

Change Controller:

负责该注册定义的实体。

Contact:

注册人的联系详情。

Notes:

关于该注册的补充说明。

临时注册 MAY 省略 Specification 和 Notes 字段,以及永久注册可能要求的任何 附加字段。请求注册时不要求提供 Date 字段,因为它会被设置为 创建或更新注册的日期。

22.1.2. 选择 码点

来自 QUIC 注册表的新码点请求 SHOULD 使用随机选择的码点,并排除 已有分配以及所选空间中的第一个未分配码点。多个码点的请求 MAY 使用连续范围。这可以最小化不同实现 将不同语义赋予同一码点的风险。

第一个未分配码点的使用保留给按照 Standards Action 策略进行的分配;见 [RFC8126] 的 第 4.9 节。早期码点分配流程 [EARLY-ASSIGN] 可用于这些值。

对于编码为可变长度整数的码点 (第 16 节),例如帧类型, 除非该用途对较长编码特别敏感,否则 SHOULD 使用编码为四字节或八字节的码点(也就是值 214 及以上)。

在 QUIC 注册表中注册码点的申请 MAY 在注册中包含请求的码点。 如果码点未分配且满足注册策略的要求,则 IANA MUST 分配所选码点。

22.1.3. 收回 临时码点

可以提出请求,从注册表中移除未使用的临时注册, 以回收某个注册表或注册表一部分中的空间(例如使用可变长度编码的 码点的 64-16383 范围)。这 SHOULD 仅针对记录日期最早的码点执行,并且在不到一年前更新过的条目 SHOULD NOT 被收回。

移除码点的请求 MUST 由指定专家审查。专家 MUST 尝试确定该码点 是否仍在使用。建议专家联系该注册中列出的联系人,并尽可能联系广泛的 协议实现者,以确定是否已知该码点有任何使用。还建议专家至少允许 四周的响应时间。

如果通过此搜索识别到码点的任何使用,或收到更新注册的请求, 则该码点 MUST NOT 被收回。相反, 注册日期会被更新。可以为该注册添加说明,记录了解到的相关信息。

如果未识别到该码点的使用,也没有提出更新注册的请求, 则该码点 MAY 从注册表中移除。

此审查和咨询流程也适用于将临时注册更改为永久注册的请求, 只是其目标不是确定该码点是否没有被使用,而是确定该注册准确反映了任何 已部署使用。

22.1.4. 永久 注册

除非另有说明,QUIC 注册表中的永久注册使用 Specification Required 策略 ([RFC8126] 的 第 4.6 节)。指定专家 验证是否存在规范且该规范易于访问。鼓励专家倾向于批准注册, 除非注册具有滥用性、轻率性或主动有害(而不仅仅是不美观或架构上可疑)。 创建注册表时 MAY 为永久注册指定 额外约束。

创建注册表时 MAY 标识一段码点范围,在该范围内注册由不同的注册策略管理。 例如,"QUIC Frame Types" 注册表(第 22.4 节) 对 0 到 63 范围内的码点采用更严格的策略。

永久注册的任何更严格要求并不阻止对受影响码点的 临时注册。例如,可以请求帧类型 61 的临时注册。

所有由 Standards Track 出版物作出的注册 MUST 是永久的。

本文档中的所有注册都被分配为永久状态, 并列出变更控制者为 IETF、联系人为 QUIC 工作组(quic@ietf.org)。

22.2. QUIC Versions 注册表

IANA 已在 "QUIC" 标题下添加了 "QUIC Versions" 注册表。

"QUIC Versions" 注册表管理一个 32 位空间;见 第 15 节。该注册表遵循 第 22.1 节中的注册策略。 该注册表中的永久注册使用 Specification Required 策略分配 ([RFC8126] 的 第 4.6 节)。

协议的码点 0x00000001 被以永久状态分配给 本文档中定义的协议。码点 0x00000000 被永久保留;该码点的说明表明 此版本保留用于版本协商。

所有符合模式 0x?a?a?a?a 的码点均被保留, MUST NOT 由 IANA 分配,并且 MUST NOT 出现在已分配值列表中。

22.3. QUIC Transport Parameters 注册表

IANA 已在 "QUIC" 标题下添加了 "QUIC Transport Parameters" 注册表。

"QUIC Transport Parameters" 注册表管理一个 62 位空间。 该注册表遵循 第 22.1 节中的注册策略。 该注册表中的永久注册使用 Specification Required 策略分配 ([RFC8126] 的 第 4.6 节), 但 0x00 到 0x3f(十六进制)之间的值(含两端)除外, 这些值使用 Standards Action 或 IESG Approval 分配,如 [RFC8126] 的第 4.9 节和第 4.10 节所定义。

第 22.1.1 节中列出的字段外,此注册表中的永久注册 MUST 包含以下字段:

Parameter Name:

参数的简短助记名。

此注册表的初始内容见 表 6

表 6初始 QUIC Transport Parameters 注册表条目
参数名称 规范
0x00 original_destination_connection_id 第 18.2 节
0x01 max_idle_timeout 第 18.2 节
0x02 stateless_reset_token 第 18.2 节
0x03 max_udp_payload_size 第 18.2 节
0x04 initial_max_data 第 18.2 节
0x05 initial_max_stream_data_bidi_local 第 18.2 节
0x06 initial_max_stream_data_bidi_remote 第 18.2 节
0x07 initial_max_stream_data_uni 第 18.2 节
0x08 initial_max_streams_bidi 第 18.2 节
0x09 initial_max_streams_uni 第 18.2 节
0x0a ack_delay_exponent 第 18.2 节
0x0b max_ack_delay 第 18.2 节
0x0c disable_active_migration 第 18.2 节
0x0d preferred_address 第 18.2 节
0x0e active_connection_id_limit 第 18.2 节
0x0f initial_source_connection_id 第 18.2 节
0x10 retry_source_connection_id 第 18.2 节

整数值 N 的所有形式为 31 * N + 27 的值 (即 27、58、89,...)均被保留;这些值 MUST NOT 由 IANA 分配,并且 MUST NOT 出现在已分配值列表中。

22.4. QUIC Frame Types 注册表

IANA 已在 "QUIC" 标题下添加了 "QUIC Frame Types" 注册表。

"QUIC Frame Types" 注册表管理一个 62 位空间。 该注册表遵循 第 22.1 节中的注册策略。 该注册表中的永久注册使用 Specification Required 策略分配 ([RFC8126] 的 第 4.6 节), 但 0x00 到 0x3f(十六进制)之间的值(含两端)除外, 这些值使用 Standards Action 或 IESG Approval 分配,如 [RFC8126] 的第 4.9 节和 4.10 节所定义。

第 22.1.1 节中列出的字段外,此注册表中的永久注册 MUST 包含以下字段:

Frame Type Name:

帧类型的简短助记名。

第 22.1 节中的建议外,新永久注册的规范 SHOULD 描述端点可能用来确定其能够发送 所标识帧类型的方式。预计大多数注册都伴随一个传输参数注册;见 第 22.3 节。 永久注册的规范还需要描述帧中任何字段的格式和所分配语义。

此注册表的初始内容列于 表 3。注意,该注册表不包括 表 3中的 "Pkts" 和 "Spec" 列。

22.5. QUIC Transport Error Codes 注册表

IANA 已在 "QUIC" 标题下添加了 "QUIC Transport Error Codes" 注册表。

"QUIC Transport Error Codes" 注册表管理一个 62 位空间。 该空间被拆分为三个范围,并由不同策略管理。该注册表中的永久注册 使用 Specification Required 策略分配 ([RFC8126] 的 第 4.6 节), 但 0x00 到 0x3f(十六进制)之间的值(含两端)除外, 这些值使用 Standards Action 或 IESG Approval 分配,如 [RFC8126] 的第 4.9 节和第 4.10 节所定义。

第 22.1.1 节中列出的字段外,此注册表中的永久注册 MUST 包含以下字段:

Code:

参数的简短助记名。

Description:

错误码语义的简要描述;如果提供了规范引用, 则该描述 MAY 是摘要。

此注册表的初始内容见 表 7

表 7初始 QUIC Transport Error Codes 注册表条目
代码 描述 规范
0x00 NO_ERROR 无错误 第 20 节
0x01 INTERNAL_ERROR 实现错误 第 20 节
0x02 CONNECTION_REFUSED 服务器拒绝连接 第 20 节
0x03 FLOW_CONTROL_ERROR 流量控制错误 第 20 节
0x04 STREAM_LIMIT_ERROR 打开的流过多 第 20 节
0x05 STREAM_STATE_ERROR 在无效流状态中收到帧 第 20 节
0x06 FINAL_SIZE_ERROR 最终大小发生变化 第 20 节
0x07 FRAME_ENCODING_ERROR 帧编码错误 第 20 节
0x08 TRANSPORT_PARAMETER_ERROR 传输参数中的错误 第 20 节
0x09 CONNECTION_ID_LIMIT_ERROR 收到的连接 ID 过多 第 20 节
0x0a PROTOCOL_VIOLATION 通用协议违规 第 20 节
0x0b INVALID_TOKEN 收到无效 Token 第 20 节
0x0c APPLICATION_ERROR 应用错误 第 20 节
0x0d CRYPTO_BUFFER_EXCEEDED CRYPTO 数据缓冲区溢出 第 20 节
0x0e KEY_UPDATE_ERROR 无效的分组保护更新 第 20 节
0x0f AEAD_LIMIT_REACHED 过度使用分组保护 密钥 第 20 节
0x10 NO_VIABLE_PATH 不存在可行的网络路径 第 20 节
0x0100-​0x01ff CRYPTO_ERROR TLS 警报代码 第 20 节

23. 参考文献

23.1. 规范性参考文献

[BCP38]
Ferguson, P. and D. Senie, "网络入口过滤:击败使用 IP 源地址欺骗的拒绝服务攻击", BCP 38, RFC 2827, .
<https://www.rfc-editor.org/info/bcp38>
[DPLPMTUD]
Fairhurst, G., Jones, T., Tüxen, M., Rüngeler, I., and T. Völker, "数据报传输的分组化层路径 MTU 发现", RFC 8899, DOI 10.17487/RFC8899, , <https://www.rfc-editor.org/info/rfc8899>.
[EARLY-ASSIGN]
Cotton, M., "Standards Track 码点的早期 IANA 分配", BCP 100, RFC 7120, DOI 10.17487/RFC7120, , <https://www.rfc-editor.org/info/rfc7120>.
[IPv4]
Postel, J., "互联网协议", STD 5, RFC 791, DOI 10.17487/RFC0791, , <https://www.rfc-editor.org/info/rfc791>.
[QUIC-INVARIANTS]
Thomson, M., "QUIC 的版本无关属性", RFC 8999, DOI 10.17487/RFC8999, , <https://www.rfc-editor.org/info/rfc8999>.
[QUIC-RECOVERY]
Iyengar, J., Ed. and I. Swett, Ed., "QUIC 丢包检测与拥塞控制", RFC 9002, DOI 10.17487/RFC9002, , <https://www.rfc-editor.org/info/rfc9002>.
[QUIC-TLS]
Thomson, M., Ed. and S. Turner, Ed., "使用 TLS 保护 QUIC", RFC 9001, DOI 10.17487/RFC9001, , <https://www.rfc-editor.org/info/rfc9001>.
[RFC1191]
Mogul, J. and S. Deering, "路径 MTU 发现", RFC 1191, DOI 10.17487/RFC1191, , <https://www.rfc-editor.org/info/rfc1191>.
[RFC2119]
Bradner, S., "RFC 中用于表示 要求级别的关键词", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC3168]
Ramakrishnan, K., Floyd, S., and D. Black, "向 IP 添加显式拥塞 通知(ECN)", RFC 3168, DOI 10.17487/RFC3168, , <https://www.rfc-editor.org/info/rfc3168>.
[RFC3629]
Yergeau, F., "UTF-8,ISO 10646 的 转换格式", STD 63, RFC 3629, DOI 10.17487/RFC3629, , <https://www.rfc-editor.org/info/rfc3629>.
[RFC6437]
Amante, S., Carpenter, B., Jiang, S., and J. Rajahalme, "IPv6 流标签规范", RFC 6437, DOI 10.17487/RFC6437, , <https://www.rfc-editor.org/info/rfc6437>.
[RFC8085]
Eggert, L., Fairhurst, G., and G. Shepherd, "UDP 使用指南", BCP 145, RFC 8085, DOI 10.17487/RFC8085, , <https://www.rfc-editor.org/info/rfc8085>.
[RFC8126]
Cotton, M., Leiba, B., and T. Narten, "编写 RFC 中 IANA 考虑事项章节的指南", BCP 26, RFC 8126, DOI 10.17487/RFC8126, , <https://www.rfc-editor.org/info/rfc8126>.
[RFC8174]
Leiba, B., "RFC 2119 关键词中 大写与小写的歧义", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.
[RFC8201]
McCann, J., Deering, S., Mogul, J., and R. Hinden, Ed., "IP 版本 6 的路径 MTU 发现", STD 87, RFC 8201, DOI 10.17487/RFC8201, , <https://www.rfc-editor.org/info/rfc8201>.
[RFC8311]
Black, D., "放宽对显式拥塞通知 (ECN)实验的限制", RFC 8311, DOI 10.17487/RFC8311, , <https://www.rfc-editor.org/info/rfc8311>.
[TLS13]
Rescorla, E., "传输层安全 (TLS)协议版本 1.3", RFC 8446, DOI 10.17487/RFC8446, , <https://www.rfc-editor.org/info/rfc8446>.
[UDP]
Postel, J., "用户数据报协议", STD 6, RFC 768, DOI 10.17487/RFC0768, , <https://www.rfc-editor.org/info/rfc768>.

23.2. 资料性参考文献

[AEAD]
McGrew, D., "认证加密的接口和算法", RFC 5116, DOI 10.17487/RFC5116, , <https://www.rfc-editor.org/info/rfc5116>.
[ALPN]
Friedl, S., Popov, A., Langley, A., and E. Stephan, "传输层安全(TLS)应用层协议协商 扩展", RFC 7301, DOI 10.17487/RFC7301, , <https://www.rfc-editor.org/info/rfc7301>.
[ALTSVC]
Nottingham, M., McManus, P., and J. Reschke, "HTTP 替代服务", RFC 7838, DOI 10.17487/RFC7838, , <https://www.rfc-editor.org/info/rfc7838>.
Barth, A., "HTTP 状态管理 机制", RFC 6265, DOI 10.17487/RFC6265, , <https://www.rfc-editor.org/info/rfc6265>.
[CSRF]
Barth, A., Jackson, C., and J. Mitchell, "跨站请求伪造的 稳健防御", 第 15 届 ACM 计算机与通信安全会议论文集 - CCS '08, DOI 10.1145/1455770.1455782, , <https://doi.org/10.1145/1455770.1455782>.
[EARLY-DESIGN]
Roskind, J., "QUIC:基于 UDP 的多路复用流 传输", , <https://docs.google.com/document/d/1RNHkx_VvKWyWg6Lr8SZ-saqsQx7rFV-ev2jRFUoVD34/edit?usp=sharing>.
[GATEWAY]
Hätönen, S., Nyrhinen, A., Eggert, L., Strowes, S., Sarolahti, P., and M. Kojo, "家庭网关特性的实验研究", 第 10 届 ACM SIGCOMM 互联网测量会议论文集 - IMC '10, DOI 10.1145/1879141.1879174, , <https://doi.org/10.1145/1879141.1879174>.
[HTTP2]
Belshe, M., Peon, R., and M. Thomson, Ed., "超文本传输协议 版本 2(HTTP/2)", RFC 7540, DOI 10.17487/RFC7540, , <https://www.rfc-editor.org/info/rfc7540>.
[IPv6]
Deering, S. and R. Hinden, "互联网协议,版本 6(IPv6)规范", STD 86, RFC 8200, DOI 10.17487/RFC8200, , <https://www.rfc-editor.org/info/rfc8200>.
[QUIC-MANAGEABILITY]
Kuehlewind, M. and B. Trammell, "QUIC 传输协议的可管理性", 进行中的工作, Internet-Draft, draft-ietf-quic-manageability-11, , <https://tools.ietf.org/html/draft-ietf-quic-manageability-11>.
[RANDOM]
Eastlake 3rd, D., Schiller, J., and S. Crocker, "安全性的随机性要求", BCP 106, RFC 4086, DOI 10.17487/RFC4086, , <https://www.rfc-editor.org/info/rfc4086>.
[RFC1812]
Baker, F., Ed., "IP 版本 4 路由器要求", RFC 1812, DOI 10.17487/RFC1812, , <https://www.rfc-editor.org/info/rfc1812>.
[RFC1918]
Rekhter, Y., Moskowitz, B., Karrenberg, D., de Groot, G. J., and E. Lear, "私有互联网的地址分配", BCP 5, RFC 1918, DOI 10.17487/RFC1918, , <https://www.rfc-editor.org/info/rfc1918>.
[RFC2018]
Mathis, M., Mahdavi, J., Floyd, S., and A. Romanow, "TCP 选择性确认选项", RFC 2018, DOI 10.17487/RFC2018, , <https://www.rfc-editor.org/info/rfc2018>.
[RFC2104]
Krawczyk, H., Bellare, M., and R. Canetti, "HMAC:用于消息 认证的密钥哈希", RFC 2104, DOI 10.17487/RFC2104, , <https://www.rfc-editor.org/info/rfc2104>.
[RFC3449]
Balakrishnan, H., Padmanabhan, V., Fairhurst, G., and M. Sooriyabandara, "网络路径非对称性的 TCP 性能影响", BCP 69, RFC 3449, DOI 10.17487/RFC3449, , <https://www.rfc-editor.org/info/rfc3449>.
[RFC4193]
Hinden, R. and B. Haberman, "唯一本地 IPv6 单播地址", RFC 4193, DOI 10.17487/RFC4193, , <https://www.rfc-editor.org/info/rfc4193>.
[RFC4291]
Hinden, R. and S. Deering, "IP 版本 6 地址架构", RFC 4291, DOI 10.17487/RFC4291, , <https://www.rfc-editor.org/info/rfc4291>.
[RFC4443]
Conta, A., Deering, S., and M. Gupta, Ed., "互联网协议版本 6 (IPv6)规范的互联网控制消息协议(ICMPv6)", STD 89, RFC 4443, DOI 10.17487/RFC4443, , <https://www.rfc-editor.org/info/rfc4443>.
[RFC4787]
Audet, F., Ed. and C. Jennings, "单播 UDP 的网络地址转换(NAT)行为要求", BCP 127, RFC 4787, DOI 10.17487/RFC4787, , <https://www.rfc-editor.org/info/rfc4787>.
[RFC5681]
Allman, M., Paxson, V., and E. Blanton, "TCP 拥塞控制", RFC 5681, DOI 10.17487/RFC5681, , <https://www.rfc-editor.org/info/rfc5681>.
[RFC5869]
Krawczyk, H. and P. Eronen, "基于 HMAC 的提取并扩展密钥派生函数(HKDF)", RFC 5869, DOI 10.17487/RFC5869, , <https://www.rfc-editor.org/info/rfc5869>.
[RFC7983]
Petit-Huguenin, M. and G. Salgueiro, "安全实时传输协议(SRTP)与数据报传输层安全 (DTLS)扩展的复用方案更新", RFC 7983, DOI 10.17487/RFC7983, , <https://www.rfc-editor.org/info/rfc7983>.
[RFC8087]
Fairhurst, G. and M. Welzl, "使用显式拥塞通知(ECN)的好处", RFC 8087, DOI 10.17487/RFC8087, , <https://www.rfc-editor.org/info/rfc8087>.
[RFC8981]
Gont, F., Krishnan, S., Narten, T., and R. Draves, "IPv6 无状态地址自动配置的临时地址扩展", RFC 8981, DOI 10.17487/RFC8981, , <https://www.rfc-editor.org/info/rfc8981>.
[SEC-CONS]
Rescorla, E. and B. Korver, "编写 RFC 安全考虑事项文本的指南", BCP 72, RFC 3552, DOI 10.17487/RFC3552, , <https://www.rfc-editor.org/info/rfc3552>.
[SLOWLORIS]
"RSnake" Hansen, R., "欢迎来到 Slowloris - 低带宽但贪婪且有毒的 HTTP 客户端!", , <https://web.archive.org/web/20150315054838/http://ha.ckers.org/slowloris/>.

附录 A. 伪代码

本节中的伪代码描述了示例算法。这些算法旨在 正确且清晰,而不是达到最优性能。

本节中的伪代码片段按 Code Components 许可;见 版权声明。

A.1. 可变长度 整数解码示例

图 45中的伪代码展示了如何 从字节流中读取可变长度整数。ReadVarint 函数接受一个参数—— 字节序列,它可以按网络字节序读取。

ReadVarint(data):
  // 可变长度整数的长度编码在第一个字节的
  // 前两个比特中。
  v = data.next_byte()
  prefix = v >> 6
  length = 1 << prefix

  // 一旦知道长度,就移除这些比特并读取任何
  // 剩余字节。
  v = v & 0x3f
  repeat length-1 times:
    v = (v << 8) + data.next_byte()
  return v
图 45可变长度 整数解码算法示例

例如,八字节序列 0xc2197c5eff14e88c 解码为十进制 值 151,288,809,941,952,652;四字节序列 0x9d7f3e7d 解码为 494,878,333;两字节序列 0x7bbd 解码为 15,293;单字节 0x25 解码为 37(两字节序列 0x4025 也是如此)。

A.2. 分组编号 编码算法示例

图 46中的伪代码展示了 实现如何为分组编号编码选择适当大小。

EncodePacketNumber 函数接受两个参数:

  • full_pn 是正在发送分组的完整分组编号。
  • largest_acked 是对等方在当前分组编号空间中 已确认的最大分组编号(如果有)。
EncodePacketNumber(full_pn, largest_acked):

  // 比特数必须至少比连续未确认分组编号数量的
  // 以 2 为底的对数多一,包括新分组。
  if largest_acked is None:
    num_unacked = full_pn + 1
  else:
    num_unacked = full_pn - largest_acked

  min_bits = log(num_unacked, 2) + 1
  num_bytes = ceil(min_bits / 8)

  // 编码整数值,并截断为 num_bytes 个
  // 最低有效字节。
  return encode(full_pn, num_bytes)
图 46分组编号 编码算法示例

例如,如果端点已经收到对分组 0xabe8b3 的确认, 并且正在发送编号为 0xac5c02 的分组,则有 29,519(0x734f)个 未完成的分组编号。为了表示至少两倍于该范围的值 (59,038 个分组,即 0xe69e),需要 16 位。

在相同状态下,发送编号为 0xace8fe 的分组会使用 24 位 编码,因为需要至少 18 位来表示该范围的两倍 (131,222 个分组,即 0x020096)。

A.3. 分组编号 解码算法示例

图 47中的伪代码包含一个 在移除报头保护后解码分组编号的示例算法。

DecodePacketNumber 函数接受三个参数:

  • largest_pn 是当前分组编号空间中已成功 处理的最大分组编号。
  • truncated_pn 是 Packet Number 字段的值。
  • pn_nbits 是 Packet Number 字段中的比特数 (8、16、24 或 32)。
DecodePacketNumber(largest_pn, truncated_pn, pn_nbits):
   expected_pn  = largest_pn + 1
   pn_win       = 1 << pn_nbits
   pn_hwin      = pn_win / 2
   pn_mask      = pn_win - 1
   // 传入分组编号应大于 expected_pn - pn_hwin,
   // 且小于或等于 expected_pn + pn_hwin
   //
   // 这意味着不能只是从 expected_pn 中剥离尾部比特
   // 并加上 truncated_pn,因为这样可能会得到
   // 窗口之外的值。
   //
   // 以下代码计算一个候选值,并确保它位于
   // 分组编号窗口内。
   // 注意额外检查以防止溢出和下溢。
   candidate_pn = (expected_pn & ~pn_mask) | truncated_pn
   if candidate_pn <= expected_pn - pn_hwin and
      candidate_pn < (1 << 62) - pn_win:
      return candidate_pn + pn_win
   if candidate_pn > expected_pn + pn_hwin and
      candidate_pn >= pn_win:
      return candidate_pn - pn_win
   return candidate_pn
图 47分组编号 解码算法示例

例如,如果最高的成功认证分组的分组编号为 0xa82f30ea,则包含 16 位值 0x9b32 的分组将被解码为 0xa82f9b32。

A.4. ECN 验证 算法示例

每当端点开始在新的网络路径上发送时,它会确定该路径 是否支持 ECN;见 第 13.4 节。如果路径 支持 ECN,目标就是使用 ECN。端点也可以定期重新评估先前被判定为 不支持 ECN 的路径。

本节描述一种测试新路径的方法。此算法旨在展示 如何测试路径是否支持 ECN。端点可以实现不同方法。

路径被分配一个 ECN 状态,其值为 "testing"、"unknown"、 "failed" 或 "capable" 之一。在状态为 "testing" 或 "capable" 的路径上, 端点发送带 ECT 标记的分组——默认是 ECT(0);否则,端点发送 未标记的分组。

要开始测试路径,ECN 状态被设置为 "testing", 并将现有 ECN 计数记为基线。

测试周期运行若干分组或一段有限时间,由端点决定。 目标不是限制测试周期的持续时间,而是确保发送足够多的标记分组, 以便收到的 ECN 计数能够清楚表明路径如何处理标记分组。 第 13.4.2 节建议将其限制为十个分组 或三倍 PTO。

测试周期结束后,路径的 ECN 状态变为 "unknown"。 从 "unknown" 状态开始,对 ACK 帧中 ECN 计数的成功验证 (见 第 13.4.2.1 节)会使路径的 ECN 状态变为 "capable",除非没有已标记分组被确认。

如果 ECN 计数验证在任何时候失败,受影响路径的 ECN 状态 变为 "failed"。如果带标记分组全部被声明为丢失,或者它们全部被标记为 ECN-CE,则端点也可以将路径的 ECN 状态标记为 "failed"。

遵循此算法可确保对于正确支持 ECN 的路径,ECN 很少被禁用。 任何错误修改标记的路径都会导致 ECN 被禁用。对于那些罕见的、标记分组被路径丢弃的情况, 测试周期的短持续时间限制了产生的丢包数量。

贡献者

本协议背后的原始设计和基本原理显著借鉴了 Jim Roskind 的工作 [EARLY-DESIGN]

IETF QUIC 工作组得到了许多人的大量支持。 以下人员为本文档作出了实质性贡献:

作者地址

Jana Iyengar(编辑
Fastly
Martin Thomson(编辑
Mozilla