欢迎访问我的个人网站获取更好的阅读排版体验: [译] QUIC Wire Layout Specification - Frame Types and Formats | QUIC协议标准中文翻译(4) 帧类型和格式 | yoko blog (https://pengrl.com/p/47156/)

目录

  • Frame Types | 帧类型
  • STREAM Frame | 流类型帧
  • ACK Frame | ACK帧
  • STOP_WAITING Frame | 停止等待帧
  • WINDOW_UPDATE Frame | 窗口更新帧
  • BLOCKED Frame | 阻塞信息帧
  • CONGESTION_FEEDBACK Frame | 拥塞反馈帧
  • PADDING Frame | 填充帧
  • RST_STREAM Frame | 流重置帧
  • PING frame | PING帧
  • CONNECTION_CLOSE frame | 连接关闭帧

Frame Types and Formats | 帧类型和格式

QUIC的帧类型包由一个或多个帧组成。帧有一个字节大小的帧类型,有自己的类型解释,后面跟着该帧类型的字段。一个或多个帧只能包含在一个QUIC包中,没有帧可以跨越多个QUIC包。

Frame Types | 帧类型

一共有两种帧类型:特殊帧类型和常规帧类型,它们的解析是不一样的。特殊帧类型在帧类型的字节中同时编码了帧类型和相应的标志位,而常规帧的帧类型字节只用来标识类型。

当前定义的特殊帧类型有:

--- src
   +------------------+-----------------------------+
   | Type-field value |     Control Frame-type      |
   +------------------+-----------------------------+
   |     1fdooossB    |  STREAM                     |
   |     01ntllmmB    |  ACK                        |
   |     001xxxxxB    |  CONGESTION_FEEDBACK        |
   +------------------+-----------------------------+
---

当前定义的常规帧类型有:

--- src
   +------------------+-----------------------------+
   | Type-field value |     Control Frame-type      |
   +------------------+-----------------------------+
   | 00000000B (0x00) |  PADDING                    |
   | 00000001B (0x01) |  RST_STREAM                 |
   | 00000010B (0x02) |  CONNECTION_CLOSE           |
   | 00000011B (0x03) |  GOAWAY                     |
   | 00000100B (0x04) |  WINDOW_UPDATE              |
   | 00000101B (0x05) |  BLOCKED                    |
   | 00000110B (0x06) |  STOP_WAITING               |
   | 00000111B (0x07) |  PING                       |
   +------------------+-----------------------------+
---

STREAM Frame | 流类型帧

流类型帧用于隐式创建流和在流上发送数据,格式为:

--- src
     0        1       …               SLEN
+--------+--------+--------+--------+--------+
|Type (8)| Stream ID (8, 16, 24, or 32 bits) |
|        |    (Variable length SLEN bytes)   |
+--------+--------+--------+--------+--------+

  SLEN+1  SLEN+2     …                                         SLEN+OLEN
+--------+--------+--------+--------+--------+--------+--------+--------+
|   Offset (0, 16, 24, 32, 40, 48, 56, or 64 bits) (variable length)    |
|                    (Variable length: OLEN  bytes)                     |
+--------+--------+--------+--------+--------+--------+--------+--------+

  SLEN+OLEN+1   SLEN+OLEN+2
+-------------+-------------+
| Data length (0 or 16 bits)|
|  Optional(maybe 0 bytes)  |
+------------+--------------+
---

流类型帧的头部字段含义如下:

  • 帧类型: 8bit,包含可变的标志(1fdooossB):
    • 最左边的bit必须设置为1标志这是一个流类型帧。
    • 'f' bit是Fin bit。如果设置为1,标识发送端完成了这条流上的发送并且希望进入半关闭状态(后续再讨论细节)。
    • 'd' bit标识当前流的头中是否包含数据长度。如果设置为0,标识这个流类型帧的大小为包的大小。
    • 接下来的'ooo' 3个bit编码了头中偏移的长度,比如0,16,24,32,40,48,56,64bit长。
    • 接下来的'ss'两个bit编码了头中流ID的长度,比如8,16,24,32bit长。
  • 流ID: 一个可变长度的无符号整型用于唯一标识这条流
  • 偏移: 一个可变长度的无符号整型标识这块数据在流上的偏移
  • 数据长度: 可选的16bit无符号整型标识这个类型帧的数据长度。当这个包的长度是一个完全大小时可以省略这个字段,用于避免填充的风险。

流类型帧必须是非0大小的数据长度或者设置了Fin标志。

ACK Frame | ACK帧

发送ACK帧用于通知对端哪些包已经被接收,换句话说,通知对端哪些包接收端没有收到(丢失包的内容可能需要重新发送)。ACK帧包含了1到256个ack块。ack块是已确认的包的区间,类似于TCP的SACK的块,但是不同于TCP的不可撤销的ack,因为QUIC重传时使用了新的包序号。

为了限制对于那些对端还没有接收到的ACK的块,数据发送端周期性发送STOP_WAITING帧来通知接收端停止ack小于一个指定序号的包。使得接收端产生一个"最小还没ack"的包序号。于是ACK帧的发送者值需要报告那些最小还没ack与收到的最大序号之间的ACK块。建议ACK帧的发送者在ACK帧中发送最近最大已ack的包,就像STOP_WAITING帧的最小还没ack一样。

不同于TCP的SACK,QUIC的ACK块是不可撤销的,如果一个包被ack了,那么即使它不再出现在之后的ACK帧中,依然认为它已经被ack了。

作为QUIC已弃用的熵信息的替代方案,发送端可以有意的跳过包序号来将熵信息注入连接中。发送端如果收到一个没有发送过的包序号对应的ack,应该关闭这个连接,这个机制可以自动预防任何潜在的攻击者。ack用于表示丢失包的块的格式是高效的。

各区域偏移

0: ack帧的开始 T: 时间区域的偏移 A: ack块区域的偏移 N: 最大ack的长度

--- src
     0                            1  => N                     N+1 => A(aka N + 3)
+---------+-------------------------------------------------+--------+--------+
|   Type  |                   Largest Acked                 |  Largest Acked  |
|   (8)   |    (8, 16, 32, or 48 bits, determined by ll)    | Delta Time (16) |
|01nullmm |                                                 |                 |
+---------+-------------------------------------------------+--------+--------+


     A             A + 1  ==>  A + N
+--------+----------------------------------------+
| Number |             First Ack                  |
|Blocks-1|           Block Length                 |
| (opt)  |(8, 16, 32 or 48 bits, determined by mm)|
+--------+----------------------------------------+

  A + N + 1                A + N + 2  ==>  T(aka A + 2N + 1)
+------------+-------------------------------------------------+
| Gap to next|              Ack Block Length                   |
| Block (8)  |   (8, 16, 32, or 48 bits, determined by mm)     |
| (Repeats)  |       (repeats Number Ranges times)             |
+------------+-------------------------------------------------+
     T        T+1             T+2                 (Repeated Num Timestamps)
+----------+--------+---------------------+ ...  --------+------------------+
|   Num    | Delta  |     Time Since      |     | Delta  |       Time       |
|Timestamps|Largest |    Largest Acked    |     |Largest |  Since Previous  |
|   (8)    | Acked  |      (32 bits)      |     | Acked  |Timestamp(16 bits)|
+----------+--------+---------------------+     +--------+------------------+
---

ACK帧中的字段含义如下:

  • 帧类型: 8bit,包含了可变内容的标志(01nullmmB)。
    • 前面两个bit必须设置为01来标识这是一个ACK帧。
    • 'n'标识这个帧是否包含一个以上ack区间
    • 'u'没有被使用
    • 'll'这2bit编码了Largest Observed字段的长度,可以是1,2,4,6字节
    • 'mm'这2bit编码了Missing Packet Sequence Number Delta字段的长度,可以是1,2,4,6字节
  • 最大已确认包序号: 一个可变长度的无符号数标识对端收到的最大包序号
  • 最大已确认差值时间: 一个16bit无符号浮点数,协议中的低11bit用于标识尾数,高5bit用于标识指数。该字段用于标识该ack帧的发送时间减去最大已确认包的接收时间,单位微妙。格式的定义近似IEEE754。举例,0x1表示1微妙,指数为0,在高5bit中表示,尾数为1,在低11bit中表示。当协议中的指数部分大于0,尾数隐含的第12bit默认为1。举例,协议中值为0x800的浮点数有一个显式的指数为1,有一个隐式的尾数为0,由于协议中的指数大于1,所以我们认为有一个有效的尾数值4096(尾数隐含的第12位被认为是1)。此外,真正的指数比协议中的指数小1,所以这个值标识4096微秒。任何大于可表示范围的值都被限定为0xFFFF。
  • Ack块区域:
    • 块数量: 一个可选的8bit无符号值,等于ack块的数量减一。只有在'n'标志设置为1时存在。
    • 块大小: 一个可变的包序号差值。对于第一个丢包区间,ack块从最大已ack的序号开始。对于非第一个块,0标识超过256个包丢失了。
    • 下一个块的间隔: 一个8bit无符号值标识ack块之间包的数量。
  • 时间戳区域:
    • 时间戳数量: 一个8bit无符号值标识了ACK帧中有多个时间戳。后面会有多个包序号,时间戳的对
    • 与最大已观察到的差值: 8bit无符号值,标识第一个时间戳和最大已观察到的差值。所以,包号等于最大已观察到的包号减去这个差值。
    • 第一个时间戳: 32位无符号值,标识以微妙为单位的时间差值,等于最大已观察到的包的接收时间减去最大已观察到的差值。
    • 与最大已观察到的差值(重复的): (和上面描述的一样。)
    • 与前一个时间戳的差值(重复的): 16bit无符号值,标识与前一个时间戳的差值。编码方式和Ack延时时间相同。

STOP_WAITING Frame | 停止等待帧

发送停止等待帧来通知对端不再等待小于指定包序号的包。包序号可以被编码为1,2,4,6字节。编码方式和QUIC帧类型包的公共标志中包序号字段一样。帧格式如下:

--- src
     0        1        2        3         4       5       6
+--------+--------+--------+--------+--------+-------+-------+
|Type (8)|   Least unacked delta (8, 16, 32, or 48 bits)     |
|        |                       (variable length)           |
+--------+--------+--------+--------+--------+--------+------+
---

停止等待帧的字段含义如下:

  • 帧类型: 8bit,必须设置成0x06。
  • 最小还没确认序号差值: 一个可变长度的包序号和使用同样长度的包头序号的差值。用包序号减去这个值得到最小还没ack的包序号。最小还没ack的包序号是发送端还在等待ack的所有包序号中最小的序号。如果接收端丢失了任何比这个序号还要小的包,那么接收端应该认为这些包永久丢失了。

WINDOW_UPDATE Frame | 窗口更新帧

窗口更新帧用来告知对端本端的一次流量控制接收窗口的增长。流ID为0时标识这个窗口更新帧作用于连接层面的流量控制窗口,> 0标识一个指定的流需要增长它的流量控制窗口。这个帧如下定义:

指定一个绝对的字节偏移,窗口更新帧的接收端可能在指定的流上最多发送指定的字节。违反流量控制发送更多的数据会导致数据接收端关闭这个连接。

如果一个流上收到了多个窗口更新帧,只需要关注最大的字节偏移。

流和连接的窗口开始的默认值是16KB,但是一般会在握手阶段增长。为了达到这个效果,一端需要在握手阶段协商SFCW(流上的流量控制窗口)和CFCW(连接/会话上的流量控制窗口)参数。流层面和连接层面的初始窗口大小需分开指定。

帧格式如下:

--- src
    0         1                 4        5                 12
+--------+--------+-- ... --+-------+--------+-- ... --+-------+
|Type(8) |    Stream ID (32 bits)   |  Byte offset (64 bits)   |
+--------+--------+-- ... --+-------+--------+-- ... --+-------+
---

窗口更新帧的字段含义如下:

  • 帧类型: 8bit,必须设置为0x04
  • 流ID: 需要更新哪个流的流量控制窗口,设置为0则更新连接层面的流量控制窗口。
  • 字节偏移: 64bit无符号整型,标识指定流上可以被发送绝对字节偏移的数据。如果是连接层面的流量控制,那么是连接上当前所有打开的流的总和。

BLOCKED Frame | 阻塞信息帧

阻塞信息帧用来通知对端本端已经准备好发送数据(并且有数据需要发送),但是当前被流量控制所阻塞。这是一个对调试极其有用的纯信息帧。阻塞信息帧的接收者应该简单的把这个帧丢弃掉(比如再打印一条有用的日志信息之后)。帧格式如下:

--- src
     0        1        2        3         4
+--------+--------+--------+--------+--------+
|Type(8) |          Stream ID (32 bits)      |
+--------+--------+--------+--------+--------+
---

阻塞信息帧中的字段含义如下:

  • 帧类型: 8bit,必须设置为0x05标志这是一个阻塞信息帧。
  • 流ID: 32bit无符号数表示哪个流被流量控制阻塞了。非0的流ID标识特定的流。流ID为0标识连接在连接层面被流量控制阻塞了。

CONGESTION_FEEDBACK Frame | 拥塞反馈帧

拥塞反馈帧时一个实验性质的帧当前没有被使用。目的是在标准的ack帧范围之外提供额外的拥塞反馈信息。拥塞反馈帧的帧类型的头3bit必须设置为001,后5bit保留用于未来使用。

PADDING Frame | 填充帧

填充帧在包中填充一个0x00字节。当遇到了填充帧,该包的剩余部分都是填充帧。填充帧包含0x00的字节,并且填充在QUIC包的末尾。填充帧只有一个8bit大小设置为0x00的帧类型的字段。

RST_STREAM Frame | 流重置帧

流重置帧用于异常关闭一个流。当由流创建者发送时,标识了创建者希望取消这个流。当由流接收端发送,标识了一个错误或者接收端不希望接受这个流,所以这个流应该被关闭。这个帧的格式如下:

--- src
     0        1            4      5              12     8             16
+-------+--------+-- ... ----+--------+-- ... ------+-------+-- ... ------+
|Type(8)| StreamID (32 bits) | Byte offset (64 bits)| Error code (32 bits)|
+-------+--------+-- ... ----+--------+-- ... ------+-------+-- ... ------+
---

流重置帧的字段含义如下:

  • 帧类型: 8bit,必须设置为0x01。
  • 流ID: 32bit,被关闭的流ID。
  • 字节偏移: 64bit无符号整型标识这条流上结束数据的绝对字节偏移。
  • 错误码: 32bit错误码标识流为什么被关闭。错误码的定义在文档后续部分。

PING frame | PING帧

PING帧用于验证对端是否还存活。PING没有负载信息。PING帧的接收端需要返回这个包的确认包。当流被打开时,PING帧需要用来保证连接活跃。默认静止15秒后发送,这比大部分NAT超时的时间要短得多。PING帧只有帧类型字段,8bit的帧类型必须设置为0x07。

CONNECTION_CLOSE frame | 连接关闭帧

连接关闭帧用于通知连接被关闭。如果还有流在传输,所有这些流都被隐式关闭(理论上,一个GOAWAY帧可以被发送以留有足够时间用于关闭所有流)。帧格式如下:

--- src
     0        1             4        5        6       7
+--------+--------+-- ... -----+--------+--------+--------+----- ...
|Type(8) | Error code (32 bits)| Reason phrase   |  Reason phrase
|        |                     | length (16 bits)|(variable length)
+--------+--------+-- ... -----+--------+--------+--------+----- ...
---

连接关闭帧的字段含义如下:

帧类型: 8bit,必须设置为0x02 错误码: 32bit,标识关闭链接原因的错误码 描述信息长度: 16bit无符号数,标识描述信息的长度。可以为0如果发送端认为在错误码之上不需要给出更详细的信息 描述信息: 可选字段,可读字符串用于解释连接关闭的原因

英文原文链接

QUIC Wire Layout Specification - Google 文档

本文作者: yoko 本文链接: http://www.pengrl.com/p/47156/ 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!

04-18 08:16