5. 连接
5. 连接 (Connections)
QUIC connection 是 client 和 server 之间共享的状态.
每个 connection 都从 handshake 阶段开始, 在此期间两个 endpoint 使用加密 handshake 协议 [QUIC-TLS] 建立 shared secret, 并协商应用协议. handshake (第 7 节) 确认两个 endpoint 都愿意通信 (第 8.1 节), 并建立 connection 的参数 (第 7.4 节).
应用协议可以在 handshake 阶段使用 connection, 但会受到一些限制. 0-RTT 允许 client 在收到 server 响应之前发送应用数据. 但是, 0-RTT 不提供针对 replay attack 的保护; 见 [QUIC-TLS] 第 9.2 节. server 也可以在收到允许其确认 client 身份和活性的最终加密 handshake 消息之前, 向 client 发送应用数据. 这些能力允许应用协议选择用部分安全保证换取更低 latency.
使用 connection ID (第 5.1 节) 允许 connection 迁移到新的网络路径, 无论这是 endpoint 的直接选择, 还是 middlebox 变化所迫. 第 9 节描述与 migration 相关的安全和隐私问题的缓解措施.
对于不再需要或不再希望保留的 connection, client 和 server 有多种方式可以终止 connection, 如第 10 节所述.
5.1 Connection ID
每个 connection 都拥有一组 connection identifier, 或 connection ID, 其中每一个都可以标识该 connection. connection ID 由 endpoint 独立选择; 每个 endpoint 选择其 peer 使用的 connection ID.
connection ID 的主要功能是确保较低协议层 (UDP, IP) 的地址变化不会导致 QUIC connection 的 packet 被递送到错误的 endpoint. 每个 endpoint 使用特定于实现 (也可能特定于部署) 的方法选择 connection ID, 该方法允许带有该 connection ID 的 packet 被路由回该 endpoint, 并在收到时被该 endpoint 识别.
使用多个 connection ID 是为了让 endpoint 能够发送在没有 endpoint 配合时观察者无法识别为属于同一 connection 的 packet; 见第 9.5 节.
connection ID MUST NOT 包含任何可由外部观察者 (即不与发行方合作的观察者) 用来将其与同一 connection 的其他 connection ID 关联起来的信息. 举一个简单例子, 这意味着同一个 connection ID MUST NOT 在同一个 connection 上发行超过一次.
带有 long header 的 packet 包含 Source Connection ID 和 Destination Connection ID 字段. 这些字段用于为新 connection 设置 connection ID; 详情见第 7.2 节.
带有 short header 的 packet (第 17.3 节) 只包含 Destination Connection ID, 并省略显式长度. endpoint 预期知道 Destination Connection ID 字段的长度. 使用基于 connection ID 路由的 load balancer 的 endpoint, 可以与 load balancer 约定 connection ID 的固定长度, 或约定一种编码方案. 固定部分可以编码显式长度, 从而允许整个 connection ID 的长度变化, 同时仍能被 load balancer 使用.
Version Negotiation (第 17.2.1 节) packet 会回显 client 选择的 connection ID, 既用于确保正确路由到 client, 也用于证明该 packet 是对 client 所发送 packet 的响应.
当不需要 connection ID 来路由到正确 endpoint 时, 可以使用零长度 connection ID. 但是, 在同一本地 IP address 和 port 上复用 connection 且使用零长度 connection ID, 会在存在 peer connection migration, NAT rebinding 和 client port reuse 时导致失败. endpoint MUST NOT 将同一个 IP address 和 port 用于多个并发且使用零长度 connection ID 的 connection, 除非它确定这些协议特性不会被使用.
当 endpoint 使用非零长度 connection ID 时, 它需要确保 peer 拥有可供选择并用于发往该 endpoint 的 packet 的 connection ID 供应. 这些 connection ID 由 endpoint 使用 NEW_CONNECTION_ID frame (第 19.15 节) 提供.
5.1.1 发行 Connection ID (Issuing Connection IDs)
每个 connection ID 都有一个关联的 sequence number, 用于辅助检测 NEW_CONNECTION_ID 或 RETIRE_CONNECTION_ID frame 是否引用同一个值. endpoint 发行的初始 connection ID 在 handshake 期间通过 long packet header (第 17.2 节) 的 Source Connection ID 字段发送. 初始 connection ID 的 sequence number 为 0. 如果发送了 preferred_address transport parameter, 所提供 connection ID 的 sequence number 为 1.
额外的 connection ID 使用 NEW_CONNECTION_ID frame (第 19.15 节) 传达给 peer. 每个新发行 connection ID 上的 sequence number MUST 增加 1. client 为其发送的第一个 Destination Connection ID 字段选择的 connection ID, 以及 Retry packet 提供的任何 connection ID, 都不会被分配 sequence number.
endpoint 发行 connection ID 时, MUST 在 connection 持续期间接受携带该 connection ID 的 packet, 或者直到其 peer 通过 RETIRE_CONNECTION_ID frame (第 19.16 节) 使该 connection ID 失效. 已发行且未退役的 connection ID 被视为 active; 任何 active connection ID 都可在当前 connection 的任何时候, 任何 packet 类型中有效使用. 这包括 server 通过 preferred_address transport parameter 发行的 connection ID.
endpoint SHOULD 确保其 peer 拥有足够数量的可用且未使用 connection ID. endpoint 使用 active_connection_id_limit transport parameter 通告其愿意维护的 active connection ID 数量. endpoint MUST NOT 提供超过 peer limit 的 connection ID. 如果 NEW_CONNECTION_ID frame 也通过在 Retire Prior To 字段中包含足够大的值来要求退役任何超额 connection ID, endpoint MAY 发送暂时超过 peer limit 的 connection ID.
当 peer 退役某个 connection ID 时, endpoint SHOULD 提供新的 connection ID. 如果 endpoint 提供的 connection ID 少于 peer 的 active_connection_id_limit, 则当它收到带有先前未使用 connection ID 的 packet 时, MAY 提供新的 connection ID. endpoint MAY 限制为每个 connection 发行的 connection ID 总数, 以避免 connection ID 耗尽的风险; 见第 10.3.2 节.
发起 migration 且要求非零长度 connection ID 的 endpoint SHOULD 确保其 peer 可用的 connection ID 池允许 peer 在 migration 时使用新的 connection ID, 因为如果该池耗尽, peer 将无法响应.
在 handshake 期间选择零长度 connection ID 的 endpoint 无法发行新的 connection ID. 对于发往此类 endpoint 的所有网络路径上的所有 packet, 都会使用零长度 Destination Connection ID 字段.
5.1.2 使用和退役 Connection ID (Consuming and Retiring Connection IDs)
endpoint 可以在 connection 期间随时将其为 peer 使用的 connection ID 更改为另一个可用的 connection ID. endpoint 会响应迁移中的 peer 而消耗 connection ID; 更多细节见第 9.5 节.
endpoint 维护从其 peer 收到的一组 connection ID, 其中任意一个都可在发送 packet 时使用. 当 endpoint 希望移除某个 connection ID 不再使用时, 它会向其 peer 发送 RETIRE_CONNECTION_ID frame. 发送 RETIRE_CONNECTION_ID frame 表示该 connection ID 将不再使用, 并请求 peer 使用 NEW_CONNECTION_ID frame 将其替换为新的 connection ID.
如第 9.5 节所讨论, endpoint 将 connection ID 的使用限制为从单个本地地址发往单个目的地址的 packet. 当 endpoint 不再主动使用该 connection ID 曾用于的本地地址或目的地址时, SHOULD 退役 connection ID.
endpoint 在某些情况下可能需要停止接受先前发行的 connection ID. 这样的 endpoint 可以通过发送带有增大 Retire Prior To 字段的 NEW_CONNECTION_ID frame, 使其 peer 退役 connection ID. endpoint SHOULD 继续接受先前发行的 connection ID, 直到它们被 peer 退役. 如果 endpoint 无法再处理所指示的 connection ID, 它 MAY 关闭 connection.
收到增大的 Retire Prior To 字段后, peer MUST 停止使用对应的 connection ID, 并在将新提供的 connection ID 加入 active connection ID 集合之前, 使用 RETIRE_CONNECTION_ID frame 退役这些 connection ID.
endpoint SHOULD 限制其本地已退役但对应 RETIRE_CONNECTION_ID frame 尚未被 acknowledged 的 connection ID 数量. endpoint SHOULD 允许发送和跟踪至少为 active_connection_id_limit transport parameter 值两倍数量的 RETIRE_CONNECTION_ID frame.
在收到退役先前 Retire Prior To 值所指示的所有 connection ID 的 RETIRE_CONNECTION_ID frame 之前, endpoint SHOULD NOT 发行 Retire Prior To 字段的更新.
5.2 将 Packet 匹配到 Connection (Matching Packets to Connections)
收到传入 packet 时会对其进行分类. packet 可以与现有 connection 关联, 或者对于 server 而言, 可能创建新的 connection.
endpoint 尝试将 packet 与现有 connection 关联. 如果 packet 具有非零长度 Destination Connection ID, 且该值对应现有 connection, QUIC 会相应地处理该 packet. 注意, 一个 connection 可以关联多个 connection ID; 见第 5.1 节.
如果 Destination Connection ID 为零长度, 且 packet 中的地址信息与 endpoint 用来识别零长度 connection ID 的 connection 的地址信息匹配, QUIC 会将该 packet 作为该 connection 的一部分处理. endpoint 可以只使用目的 IP 和 port 来识别, 或同时使用源地址和目的地址来识别, 尽管如第 5.1 节所述, 这会使 connection 变得脆弱.
endpoint 可以对无法归属于现有 connection 的任何 packet 发送 Stateless Reset (第 10.3 节). Stateless Reset 允许 peer 更快识别 connection 何时变得不可用.
如果匹配到现有 connection 的 packet 与该 connection 的状态不一致, 则会被丢弃. 例如, 如果 packet 指示的协议版本不同于该 connection 的版本, 或者在预期密钥可用后移除 packet protection 失败, packet 会被丢弃.
缺少强完整性保护的无效 packet, 例如 Initial, Retry 或 Version Negotiation, MAY 被丢弃. 如果 endpoint 在发现 error 之前处理了这些 packet 的内容, 则 MUST 生成 connection error, 或完全回滚该处理期间所做的任何更改.
5.2.1 Client Packet 处理 (Client Packet Handling)
发送给 client 的有效 packet 总是包含与 client 所选择值匹配的 Destination Connection ID. 选择接收零长度 connection ID 的 client 可以使用本地地址和 port 来识别 connection. 与现有 connection 不匹配的 packet 会被丢弃, 匹配依据为 Destination Connection ID, 或者在该值为零长度时依据本地 IP address 和 port.
由于 packet 重排序或丢失, client 可能收到使用尚未计算出的密钥加密的 connection packet. client MAY 丢弃这些 packet, 也 MAY 缓冲它们, 以等待后续允许其计算密钥的 packet.
如果 client 收到使用不同于其最初选择版本的 packet, 它 MUST 丢弃该 packet.
5.2.2 Server Packet 处理 (Server Packet Handling)
如果 server 收到指示不支持版本的 packet, 且该 packet 足够大, 能够为任何受支持版本发起新的 connection, server SHOULD 按第 6.1 节所述发送 Version Negotiation packet. server MAY 限制其使用 Version Negotiation packet 响应的 packet 数量. server MUST 丢弃指定不支持版本的较小 packet.
不支持版本的第一个 packet 可以对任何 version-specific field 使用不同语义和编码. 特别是, 不同版本可能使用不同的 packet protection key. 不支持某个特定版本的 server 不太可能解密该 packet 的 payload 或正确解释结果. 只要 datagram 长度足够, server SHOULD 使用 Version Negotiation packet 响应.
带有受支持版本或没有 Version 字段的 packet, 会使用 connection ID 匹配到某个 connection; 对于零长度 connection ID 的 packet, 使用本地地址和 port 进行匹配. 这些 packet 会使用选定的 connection 处理; 否则, server 按下面所述继续处理.
如果该 packet 是完全符合规范的 Initial packet, server 会继续进行 handshake (第 7 节). 这使 server 承诺使用 client 所选择的版本.
如果 server 拒绝接受新的 connection, 它 SHOULD 发送包含 CONNECTION_CLOSE frame 且 error code 为 CONNECTION_REFUSED 的 Initial packet.
如果该 packet 是 0-RTT packet, server MAY 缓冲有限数量的此类 packet, 以等待迟到的 Initial packet. client 在收到 server 响应之前无法发送 Handshake packet, 因此 server SHOULD 忽略任何此类 packet.
在所有其他情况下, server MUST 丢弃传入 packet.
5.2.3 简单 Load Balancer 的考虑事项 (Considerations for Simple Load Balancers)
server 部署可以只使用源和目的 IP address 与 port 在 server 之间进行 load-balance. client 的 IP address 或 port 变化可能导致 packet 被转发到错误的 server. 当 client 地址变化时, 此类 server 部署可以使用以下方法之一来保持 connection continuity.
-
server 可以使用 out-of-band 机制, 基于 connection ID 将 packet 转发到正确的 server.
-
如果 server 能够使用专用 server IP address 或 port, 即不同于 client 初始连接的地址或 port, 它们可以使用 preferred_address transport parameter 请求 client 将 connection 移动到该专用地址. 注意, client 可以选择不使用 preferred address.
如果 server 部署未实现当 client 地址变化时维护 connection continuity 的解决方案, server SHOULD 使用 disable_active_migration transport parameter 指示不支持 migration. disable_active_migration transport parameter 不禁止 client 已经根据 preferred_address transport parameter 行动之后的 connection migration.
使用这种简单形式 load balancing 的 server 部署 MUST 避免创建 stateless reset oracle; 见第 21.11 节.
5.3 Connection 上的操作 (Operations on Connections)
本文档不定义 QUIC API; 它改为定义一组应用协议可以依赖的 QUIC connection 功能. 应用协议可以假定 QUIC 实现提供一个接口, 其中包含本节描述的操作. 为特定应用协议设计的实现可能只提供该协议使用的操作.
在实现 client 角色时, 应用协议可以:
-
打开 connection, 这会开始第 7 节所述的交换;
-
在 Early Data 可用时启用它; 并且
-
获知 Early Data 是否已被 server 接受或拒绝.
在实现 server 角色时, 应用协议可以:
-
监听传入 connection, 这会为第 7 节所述的交换做准备;
-
如果支持 Early Data, 在发送给 client 的 TLS resumption ticket 中嵌入由应用控制的数据; 并且
-
如果支持 Early Data, 从 client 的 resumption ticket 中取回由应用控制的数据, 并基于该信息接受或拒绝 Early Data.
在任一角色中, 应用协议可以:
-
配置每种类型允许的初始 stream 数量的最小值, 如 transport parameter (第 7.4 节) 中传达的那样;
-
通过为 stream 和 connection 设置 flow control limit 来控制 receive buffer 的资源分配;
-
识别 handshake 是已经成功完成还是仍在进行;
-
防止 connection 静默关闭, 方式是生成 PING frame (第 19.2 节), 或请求 transport 在 idle timeout 过期之前发送额外 frame (第 10.1 节); 并且
-
立即关闭 (第 10.2 节) connection.