Quic Protocal Part 2:连接和流

2019-11-19 231 Words Quic Web

QUIC Protocal —— Part 2

QUIC 流和连接是有状态的,因此发送方和接收方需要建立对应的状态机,也需要完成一定的握手和关闭动作。

一、QUIC Stream

1.1 流的基本结构

QUIC 流是有状态的。每一个 QUIC 连接中可以复用多个流,每个流有一个独立的 stream id 来标记不同的流。其中 stream id 的最低有效位决定流是客户端建立的还是服务器建立的;次低有效位决定了流是单向的还是双向的。

同一个连接中,stream id 不允许复用。

QUIC 的流就像 TCP 协议一样,上层应用对于流的感知是连续的消息流,没有明显的边界(也就是说上层应用对于报文要有自然分割,否则可能发生粘包)。

1.2 流的帧表示

一个 stream 可能使用如下一些 QUIC 帧(frame):

  • STREAM 帧: 发送数据
  • RST_STREAM: 接收流,收到对应 ACK 后关闭对应流。
  • STREAM_DATA_BLOCK: 阻塞
  • FIN: 发送结束
  • MAX_STREAM_DATA: 提示数据大小
  • STOP_SENDING: 接收方提示不再接收数据,后续可能被忽略

二、流量控制

QUIC 协议使用了基于 stream 和基于 connection 的两层流量控制。

2.1 基于字节的流量控制

接收方可以发送 MAX_STREAM_DATA 或者 MAX_DATA 来提示流和连接的字节最大偏移量。当达到发送最大字节的时候,发送方发送 STREAM_DATA_BLOCK 或 DATA_BLOCK 来提示阻塞。

2.2 基于stream 数量的流量控制

通信双方可以通过 MAX_STREAMS 帧来告知对端最大可以建立的 stream 的个数,以此来进行流量控制。

三、Connection (连接)

QUIC 协议本身并不依赖于底层的 udp 或者 ip 协议。这意味着其可以在任意的非 TCP/IP 网络中使用。此时没有 ip 和端口号作为五元组来表示一个连接,因此需要单独的 connection id 字段来表示一个连接。

当在使用 TCP/IP 网络中的时候,端口和ip地址足以表示一个连接的时候,可以使用一个 0 长度的 connection id。

使用 connection id 的另外一个好处是可以处理 ip、port 变化时候,依然复用之前的连接。

在一个连接中,connection id 可以使用 NEW_CONNECT_ID 以及 RETIRE_CONNECT_ID 帧来变更。

3.1 加密握手协议概述

QUIC 使用 CRYPTO 帧来进行加密握手协商,并支持 0-RTT。在后续文章中详解其内容。

3.1.1 Connection ID 的协商

QUIC 有两种报文头,长报文头中有 目的连接ID 和 源连接ID。而短报文头中只有 目的连接ID。

连接的选取方式是:

每个终端使用源连接ID字段指定发送给它们的数据包中目标连接ID字段中使用的连接ID。 在接收到数据包时,每个终端设置它发送的目标连接ID, 以匹配它们接收到的源连接ID的值。

当一个初始数据包由之前没有从服务器接收重试数据包的客户机发送时, 它将用一个不可预知的值填充目标连接ID字段。 它的长度必须至少为8字节。

3.1.2 握手连接参数

其中主要包括了拥塞控制,流量控制的相关参数。

3.2 地址验证和路径验证

3.2.1 地址验证

QUIC 协议使用地址验证来防止 DOS 等攻击。传统 IP 协议中,由于没有对源地址进行认证,因此可能会发生此种攻击。将由接收方发起对于源地址的挑战应答,以此确定源地址有效。

客户端                                               服务端

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, "..."]

3.2.2 路径验证

可以由一方发起地址验证 PATH_CHALLENGE 帧,对端如果接收,必须返回 PATH_RESPONSE,否则超时则可能路径失效。

3.3.3 连接迁移

由于通信中一方可能发生地址变化(NAT)等情况,此时可以动态的进行连接迁移。

!但是要注意可能因此特性发起一些地址欺骗攻击

3.4 连接终止

一个连接有一下一些情况发生连接中止:

  • 闲置超时
  • 立即关闭
  • 无状态终止

其中有状态的终止包括 closing peroid 和 draining period。

3.4.1 闲置超时

当启动闲置超时的时候,超时则会自动隐式结束连接,丢弃所以状态。

3.4.2 立即关闭

正常使用 CONNECTION_CLOSE 包来结束结束连接并重置所有流。发送方进入 closing peroid,对此后所有数据包回应 CONNECTION_CLOSE。

接收方接收到 CONNECION_CLOSE 后进入 draining peroid 状态。并不再发送任何包。

3.4.3 无状态重置

此方式是为了无法连接连接时,所使用的的关闭连接报文。是一种特殊的 QUIC 报文。此报文占据整个 UDP 报文空间,仅在致命错误并无法使用有连接关闭时使用。

连接建立过程中 NEW_CONNECTION_ID 帧中包含了一个 stateless_reset_token, 且只有服务器指定(保护机密性),而后使用此 token 来重置连接。

四、错误处理

  • 连接错误:此时发送 CONNECTION_CLOSE 并包含对应错误,关闭连接。
  • 流错误: 发送 RESET_STREAM 并包含对应错误,重置流。