QUIC 目前由 IETF 工作组 起草进行标准化设计,预计 2021 年初提交 RFC。IETF 工作组在设计过程中发布了多个版本的草案,目前最新的草案版本是 2020-10-20 发布的 draft-32。另外 QUIC 是在用户侧(User space)实现的,版本迭代会比较方便和快速,市面上的 QUIC 协议的实现 可能基于不同的版本(比如 draft-29, draft-30, ...)。这意味着客户端(client)和服务端(server)支持的 QUIC 协议版本可能不一样,它们建立连接时需要先进行版本协商(Version Negotiation),使用双方都支持的一个版本。

客户端和服务端创建连接时,客户端在首次发起请求时需要带上它支持的协议版本号。

发送版本协商数据包

当服务端收到新连接的数据包时,它会检查是否支持客户端的协议版本:

  • 如果服务端可以支持客户端的版本, 服务端将为连接的整个生命周期使用这个协议版本。
  • 如果服务端不支持该版本,服务端就响应版本协商包(Version Negotiation packet),附上它所支持的版本集合,这将增加 1-RTT(Round-Trip Time) 的延迟开销。

注意事项

  • 为了减少放大攻击(amplification attacks),QUIC 协议要求客户端发送的初始数据包大小(Initial Datagram Size)最少为 1200 字节。如果初始数据包小于 1200 字节,需要使用 PADDING frame 填充,不然该数据包会被服务端丢弃。
  • 只有服务端可以发送版本协商包(Version Negotiation packet),客户端不能发送。
  • 服务端识别到 0-RTT 数据包(之前有成功连接过),可以选择不发送版本协商包,以减少额外的 1-RTT 版本协商延迟。
  • 服务端响应发送的初始(Initial)数据包或版本协商数据包可能丢失,客户端可以继续发送新的数据包、直到它成功接收到服务端响应,或者放弃连接尝试。

处理版本协商数据包

客户端收到版本协商包后,从服务端所支持的版本集合里面挑选它所支持的版本。

  • 如果所有的版本都不支持,则客户端需要丢弃连接。
  • 如果有匹配到支持的版本,客户端尝试使用该版本创建新连接。新连接必须使用一个新的随机目标连接ID(Destination Connection ID)。

注意事项

  • 如果客户端已接收并成功处理了任何其他包(包括早期的版本协商包),则客户端必须丢弃它后来新收到的版本协商包。
  • 客户端需要检查协商数据包的连接ID是否匹配,确保不是攻击者伪造发送的。
  • 当前版本没有防止降级攻击(downgrade attacks)的机制,不能在草案(draft)版本之外。如何防止版本降级攻击(downgrade attacks)将放在以后的版本里面定义。

版本号

QUIC 版本使用 32 位无符号数字标识。版本号 0x00000000 被保留用来表示版本协商。版本号 0x00000001 是为作为 RFC 发布的协议版本保留的。

0x?a?a?a?a 格式的版本号被保留(reserved)用于强制执行版本协商。也就是说,所有字节的低 4 位是 1010(二进制)的任何版本号。保留的版本号永远不会代表真正的协议,客户端可以使用其中一个保留版本号,并期望服务端启动版本协商,测试是否能正确处理不支持的协议版本。

0xff000000 用于标识 IETF 草案版本号。例如,draft-ietf-quic-transport-13 将被标识为 0xff00000d。

QUIC 协议实现者可以在 https://link.zhihu.com/?target=https%3A//github.com/quicwg/base-drafts/wiki/QUIC-Versions 注册用于私有实验的QUIC版本号。

版本协商数据包(Version Negotiation packet)

在客户端接收数据包时(packet),它将基于 Version 字段的值为 0 来标识版本协商包。

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

Unused:由服务端随机选择,客户端必须忽略此字段的值。服务端应将此字段的最高有效位(0x40)设置为1,以便版本协商数据包具有固定位(Fixed Bit)字段。

Version:必须设置为 0x00000000

Destination Connection ID:是服务端接收到的数据包(packet)里面的 Source Connection ID。

Source Connection ID:是服务端接收到的数据包(packet)里面的 Destination Connection ID,这个 ID 最初是由客户端随机选择的。

回显这两个连接 ID 可以让客户端确信服务端收到了数据包,并且版本协商数据包不是由攻击者生成的。

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

Supported Version:服务端支持的 32 位版本号集合

注意事项

  • 版本协商包不需要 ACK
  • 版本协商包没有 Packet Number 和 Length 字段。因此,它将使用整个 UDP 数据报(datagram)。
  • 服务端不能在单个 UDP 数据报(datagram)里面发送多个版本协商包。

QUIC 的全称是 Quick UDP Internet Connections protocol,由 Google 设计提出,目前由 IETF 工作组推动进展,其设计的目标是替代 TCP 成为 HTTP/3 的数据传输层协议。熹乐科技在物联网(IoT)和边缘计算(Edge Computing)场景也一直在打造底层基于 QUIC 通讯协议的边缘计算微服务框架YoMo,长时间关注 QUIC 协议的发展,本系列文章总结了学习 QUIC 协议时的知识点。

在线社区:discord/quic

维护者:YoMo

05-03 22:25