QUIC(Quick UDP Internet Connection)是一个新的基于UDP的管线化技术和安全传输协议。
QUIC提供:
QUIC完全运行在用户空间。
因为运行在用户空间,QUIC能够更快的创新,而现存的TCP的更新周期要明显更长。
这篇文档只是描述了QUIC的概念设计和wire specification。
QUIC-CRYPTO:提供了关于加密和握手有关的介绍。
draft-quic-loss-recovery:提供了有关超时重传和拥塞控制有关的介绍。
所有的QUIC中使用的数值,包括长度length,版本version,类型type等都是小段字节序而不是网络字节序(大端)。
下面是一些文档中使用的各种定义:
下面简单列举了一些QUIC的关键技术和优点。QUIC在功能上等价于TCP+TLS+HTTP/2。QUIC优于TCP+TLS+HTTP/2的点有:
简单来说,client第一次和server简历连接的时候,会发送一个空的client hello(CHLO),然后server回复一个rejection(REJ),并且附带上一些建立连接需要的信息(和建立安全连接有关)。当下一次client发送CHLO的时候,client可以利用上次收到的信息(安全证书credentials),直接发送加密(encrypted)之后的请求。
所以说大多数时候,QUIC协议的client只需要zero roundtrips(也就是不需要提前建立连接)就可以直接发送数据。
详细的内容参考QUIC Crypto design
QUIC包含一个插件式的拥塞控制方式和richer signaling than TCP,这样使QUIC能够提供更丰富的拥塞控制信息。现在,QUIC的拥塞控制协议是TCP Cubic的实现,正在尝试使用另外的方式。
One example of richer information is that each packet, both original and retransmitted, carries a new packet sequence number. This allows a QUIC sender to distinguish ACKs for retransmissions from ACKs for original transmissions, thus avoiding TCP's retransmission ambiguity problem. QUIC ACKs also explicitly carry the delay between the receipt of a packet and its acknowledgment being sent, and together with the monotonically-increasing packet numbers, this allows for precise roundtrip-time (RTT) calculation.
QUIC的ACK帧允许最多256个NACK范围,所以在乱序到达的情况下,QUIC的表现要优于TCP连接。
QUIC提供流和链接两个层面的流量控制,类似于HTTP/2。
QUIC的流(stream)层面的流量控制和TCP的滑动窗口流量控制方式相似。
QUIC的链接层面的流量控制和流层面的也相似,只是滑动窗口的大小是所有流的总和。
HTTP中的,允许在第一个请求没有完成前直接发送第二个请求,以便能够使用同一个链接同时发送多个请求的数据,这叫管线化技术。
但是这会引入一个问题:如果第一个请求的消息发生了数据丢失,TCP中为了保证消息的按顺序到达,就会发生阻塞直到重传丢失的数据。在这个过程中,哪怕第二个请求与第一个请求并没有关系而且数据没有丢失,也会导致第二个请求被迫发生阻塞。这就叫head-of-line blocking(线头阻塞)
在QUIC中,一个流的数据发生丢失最多只会影响当前流,对于整个链接的影响不大,每一个流的帧到达都会立马被相应的流处理。
Caveat: QUIC currently compresses HTTP headers via HTTP/2 HPACK header compression, which imposes head-of-line blocking for header frames only.
为了不用等到重传就可以恢复丢失的数据包,QUIC现在使用一种简单的XOR-based(异或) FEC模式。
注:具体的原理还不是很了解,但是大致的效果就是能够通过之后的数据包计算出丢失的数据包,类似于可以纠错的汉明码
QUIC使用由client随机生成的64位connection ID来标识一个连接。
传统的TCP连接由server ip、server port、client ip、client port四项来表示,当client的IP地址发生变化(从WiFi切换到4G网络),之前建立的连接将失效,必须建立新的连接。
而QUIC协议在client的IP地址发生变化之后,依旧可以使用之前使用的connection ID来继续传输数据。
QUIC有Special Packets 和 Regular Packets两种数据包
特殊包:
1. 版本协商包 Version Negotiation Packet
2. Public Reset Packets
普通包:
1. 帧数据包 Frame Packet
2. FEC 包(在3.6中提到过,用来实现前向纠错)
QUIC每种包都应该被分割为适应传输路径上的MTU的大小,避免IP fragmentation。Path MTU 发现算法现在还在研究中。
现在的QUIC实现中,IPv6使用1350的maximum QUIC packet size,IPv4使用1370;
所有的QUIC数据包前面都包含一个2~19字节的public header,格式如下:
0 1 2 3 4 8
+--------+--------+--------+--------+--------+--- ---+
| Public | Connection ID (0, 8, 32, or 64) ... | ->
|Flags(8)| (variable length) |
+--------+--------+--------+--------+--------+--- ---+
9 10 11 12
+--------+--------+--------+--------+
| QUIC Version (32) | ->
| (optional) |
+--------+--------+--------+--------+
13 14 15 16 17 18
+--------+--------+--------+--------+--------+--------+
| Packet Number (8, 16, 32, or 48) |
| (variable length) |
+--------+--------+--------+--------+--------+--------+
Public Flags:
Connection ID
一个由client静态随机生成的8字节随机数,用来标识一个connection,这就是前面提到用QUIC用来实现Connection Migration的方式,如果可以的话,可以使用8字节的一部分来标识这个链接,这由public Flags中的第三第四位来设置。
QUIC Verison
大小为32比特,用来表示QUIC协议的版本。只有在Public Flags中FLAG_VERSION位置位的时候才会出现在首部中(public_flags & FLAG_VERSION != 0)。
Packet Number
client和server交换的第一个Regular Packet的序列号应该为1,之后发送的每一个包的序列号都自增一。
Public Flags的处理流程如下图所示:
Check the public flags in public header
|
|
V
+--------------+
| Public Reset | YES
| flag set? |---------------> Public Reset Packet
+--------------+
|
| NO
V
+------------+ +-------------+
| Version | YES | Packet sent | YES
| flag set? |--------->| by server? |--------> Version Negotiation
+------------+ +-------------+ Packet
| |
| NO | NO
V V
Regular Packet Regular Packet with
QUIC Version present in header
只有服务器会发送版本协商包,版本协商包的格式如下图所示,其中Public Flags中的PUBLIC_FLAG_VERSION位必须置1。
0 1 2 3 4 5 6 7 8
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
| Public | Connection ID (64) | ->
|Flags(8)| |
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
9 10 11 12 13 14 15 16 17
+--------+--------+--------+--------+--------+--------+--------+--------+---...--+
| 1st QUIC version supported | 2nd QUIC version supported | ...
| by server (32) | by server (32) |
+--------+--------+--------+--------+--------+--------+--------+--------+---...--+
TODO
这里涉及到对数据的两种操作:认证和加密。还有另一个概念是数字签名,关于这几个概念的定义可以看这篇文章数字证书原理
TODO Private Header的格式
除了Private Header,Frame Packet的负载是下图所示的格式,Type的具体格式在后面定义
+--------+---...---+--------+---...---+
| Type | Payload | Type | Payload |
+--------+---...---+--------+---...---+
TODO
QUIC连接的建立由client发起。
版本协商:
client发送给server的最初几个数据包中,必须设置verison flag标记,然后表明自己使用的版本号。client发送的所有数据包都必须设置version flag,直到client收到来自于server的置零version flag的数据包(server通过置零version flag表示同意client的版本请求)。当server收到来自于client的version flag置零的数据包后,任何之后的version flag置1的数据包将被丢弃(delay)。
对于server收到一个链接建立请求的包之后(新的connection ID,置1 version flag),server会比较包中的verison和它支持的所有版本号,如果这个版本是支持的,那么整个链接的生命周期里都将使用这个版本来发送数据。之后,server发送的所有数据都会清零version flag
如果这个server不支持当前的版本号,那么会引入一个额外的RTT来进行协商,server会发送一个版本协商包给client。
如果客户端收到一个版本协商包,那么会从中选择一个可以接受的版本号,然后从新发送一个连接建立请求。最后,client会收到第一个来自server的Regular Packet。
加密握手通过一个特定的加密流(crypto stream)来进行,Stream ID 为 1
QUIC提供可靠性连接、拥塞控制和流量控制。QUIC的流量控制方式类似于HTTP/2的流量控制。可靠连接和拥塞控制在其他文件中描述。
A QUIC connection uses a single packet sequence number space for shared congestion control and loss recovery across the connection.
所有的数据除了ACK 确认包,都是使用链接内的流来传输的。
这个部分描述了使用流在QUIC链接内传输的理论上的描述。涉及到的各种帧格式在Frame Types and Formats章节中描述。
Stream是被切割成stream frame的双向数据流。client和server都可以创建和销毁流,并且每个流之间的数据传输互相没有影响。QUIC流的生命周期和HTTP/2中的流[RFC7540]非常相似。
Stream流可以通过发送对应流的STREAM帧来隐式的创建。为了避免ID碰撞,由server创建的Stream-ID为偶数,由client创建的Stream-ID则为奇数。Stream-ID从1开始记数,并且客户端创建的第一个流——Stream 1专用于crypto handshake(加密握手)。当使用基于HTTP/2的QUIC协议时,Stream 3专用于传输其他流的压缩头部,用来保证头部的按序到达和处理。
Stream-ID在client或者server端都必须单调的增加。例如Stream 2可以在Stream 3之后创建,但是Stream 7一定不可以在Stream 9之后创建。注意,client或者server或许会收到乱序的stream。例如client或许会在收到任何Stream 7的消息之前收到了Stream 9的消息,这可能是因为乱序到达的原因导致的,client需要有一定的机制,支持乱序stream-ID(TODO:chromium中是如何解决这个问题的)。
如果终端收到了一个它不愿意接收的STREAM帧,它可以立刻回复一个RST_STREAM帧(之后会介绍)。注意,即使发起这个stream的另一端在这个帧中已经发送了一些数据,这些数据也必须被丢掉。
一旦流被成功创建,它就可以被用来发送和接收数据。这意味着直到流被关闭,否则终端可以一直发送stream帧数据。
QUIC流的任何一端都可以正常的关闭流。流的关闭有三种方式:
正常关闭
因为流是双向的,流可以被“半关闭”或者“关闭“。当一端发送带有FIN标志为true的帧之后,这个流被认为在那个方向半关闭了,FIN意味着发送者之后不会再发送数据。当QUIC流的两端都发送和接收了带有FIN标志的帧之后,意味着这个流关闭了。
异常关闭
client或者server在任何时候都可以发送RST_STREAM帧。RST_STREAM帧中携带了错误代码来表示发生错误的原因(文章后面会给出错误代码的列表),当stream的发起者发送了RST_STREAM帧,这表示发生了无法正确完整数据传输的错误,并且不会再有其他数据。当stream的接收者发送了RST_STREAM帧,stream发起者收到之后就应该停止发送数据。
如在下一节中将描述的,链接关闭之后其中的流也会关闭。
当链接空闲超过一个之前约定好的时限之后,就可以关闭链接。当server关闭链接的时候,应该避免告知client,这样可以避免唤醒移动设备上的无线设备。有两种方式能够关闭链接:
显式关闭:
终端可以发送CONNECTION_CLOSE 帧给另一端来表示关闭链接。另外,终端也可以在发送CONNECTION_CLOSE 帧之前,发送GOAWAY帧给另一端来表示链接将要被关闭。GOAWAY帧意味着正在活动的流将会继续处理,但是GOAWAY帧的发送端不会再发起或者接收任何新的流。如果一端在还有活动流(没有FIN为true或者RST_STREAM帧发送)的时候,发送了CONNECTION_CLOSE 帧,那么另一端将会认为这些流传输的数据不完整,并且异常中断了。
隐式关闭:
默认的QUIC链接空闲时间为30秒,而且空闲时间是在建立链接时商量的一个参数(“ICSL”)。最大时间是10分钟。当没有任何数据传输的时间超过了空闲时间限制,这个链接将会被关闭。默认情况下,将会发送CONNECTION_CLOSE帧。当显式关闭代价较高时(例如对于移动设备,这将会唤醒移动设备的无线电),可以选择激活静默式关闭。
终端可以在任何时候发送PUBLIC_RESET 帧来表示活动链接的异常终止。在QUIC中PUBLIC_RESET 等价于TCP中的RST。
QUIC帧包由帧填充(应该理解为帧包就是由包头部加上帧组成的)。它有一个帧类型字节,后跟类型相关的帧头字段。所有帧都包含在单个QUIC包中,没有帧可以跨越QUIC包边界(不存在TCP中的IP数据分包)。
这里有两种帧类型:特殊类型(Special Frame Type)和普通类型(Regular Frame Type)。在特殊帧的帧类型字节中,同时包含了表示类型和一些flag的字段,而普通帧的帧类型字段的使用方式非常简。
现在定义的特殊帧类型包括:
+------------------+-----------------------------+
| Type-field value | Control Frame-type |
+------------------+-----------------------------+
| 1fdooossB | STREAM |
| 01ntllmmB | ACK |
| 001xxxxxB | CONGESTION_FEEDBACK |
+------------------+-----------------------------+
现在定义的普通帧类型包括:
+------------------+-----------------------------+
| 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帧可以同时用来隐式的创建流和在上面发送数据,它有下面的格式:
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) |
+------------+--------------+
STREAM帧头部中的各个部分如下所示:
帧类型(Frame Type):8个bit,包含了各种标志位(1fdooossB):
Stream ID:可变长度的Stream ID。
Offset:指明了当前数据部分的在数据流中的偏移量。
Data length:一个可选的无符号整数,表示了当前STREAM帧中数据部分的长度。只有当前是“full-sized“包时,才可以省略Data length。
一个STREAM帧要么有长度非0的数据,要么FIN设置为1。
ACK帧用来通知对方收到了哪些packet,还有哪些packet是还没有收到的(这些被认为还没有收到的packet可能需要重发)。QUIC的ACK中包含了接收到的最大的packet number和一系列当前packet number下丢失的包的范围。To limit the NACK ranges to the ones that haven't yet been communicated to the peer(这句不是很懂),终端会周期性的发送STOP_WAITING帧,来表式不要继续等待小于一个特定number的packet,提高对方的“least unacked” packet number(这应该是作为返回ack帧时候的一个参数)。
帧的格式如下所示:
0 1 N
+--------+--------+---------------------------------------------------+
| Type |Received| Largest Observed |
| (8) |Entropy | (8, 16, 32, or 48 bits) |
+--------+--------+---------------------------------------------------+
N+1 N+2 N+3 N+4 N+8
+--------+--------+---------+--------+--------------------------------+
| Ack Delay | Num | Delta | First Timestamp |
| Time (16) |Timestamp|Largest | (32 bits) |
| | (8) |Observed| |
+--------+--------+---------+--------+--------------------------------+
N+9 N+11 - X
+--------+-------------------+
| Delta | Time Since |
|Largest | Previous Timestamp| <-- Repeat (NumTimestamp - 1) times
|Observed| (16 bits) |
+--------+-------------------+
X X+1 - Y Y+1
+--------+-------------------------------------------------+--------+
| Number | Missing Packet Sequence Number Delta | Range |
| Ranges | (8, 16, 32, or 48 bits) | Length |
| (opt) | (repeats Number Ranges times) |(Repeat)|
+--------+-------------------------------------------------+--------+
Y+2 Y+3 - Z
+--------+-----------------------------------------------------+
| Number | Revived Packet Number |
| Revived| (8, 16, 32, or 48 bits, same as Largest Observed) |
| (opt) | (repeats Number Revived times) |
+--------+-----------------------------------------------------+
ACK帧中的属性如下所示:
帧类型:帧类型长度为8-bit,包含各种标志位(01nt11mmB).
Received Entropy(熵):一个8bit的无符号整数,表示了所有收到的packet的熵的哈希值得累积(cumulative hash of entropy)。Entropy accumulation将会在后面讲解。
Largest Observed:是一个变长的无符号整数,表示收到的最大的packet编号。
When an ACK frame is truncated, it indicates a packet number greater than the specified largest observed has been received, but information about those additional receptions can't fit into this frame (typically due to packet size restrictions).
Ack Delay Time:
16-bit的无符号浮点数,表示从接收到编号最大的packet到当前ack帧发送出去的时间,单位是微秒。任何超过表示范围的时间都表示成0xFFFF;
Timestamp Section:(这部分包含了已经收到的包的信息)
Missing Packet Section:
<丢失数据的起始地址偏移量,丢失数据的长度>
,这个数据对会重复Num Ranges次,第一次出现时,偏移量是相对Largest Observed Number,之后的偏移量都是相对于前一个数据对,如果偏移量为0,表示丢失数据的区间超过255,所以分为了两个连续的数据对表示。逻辑上和上面的Timestamp Section很相似。Revived Packet Section:格式和Timestamp Section 和 Missing Packet Section很相似,不同之处在于这里面记录了通过FEC恢复的包的信息。
STOP_WAITING帧的作用是通知对方不要继续等待编号低于一个特定值的包。这个特定值是边长的,长度和包头部中包编号的长度相同,可以是1,2,4或者6字节。
0 1 2 3 4 5 6 7
+--------+--------+--------+--------+--------+--------+-------+-------+
|Type (8)|Sent | Least unacked delta (8, 16, 32, or 48 bits) |
| |Entropy | (variable length) |
+--------+--------+--------+--------+--------+--------+-------+-------+
STOP_WAITING帧的属性如下所示:
WINDOW_UPDATE Frame用于通告对端接收窗口增加了,如果Stream ID 等于0,表示这是连接级别的流量控制,否则是流级别的流量控制,表示在指定的流上应该增加其流量控制窗口。帧格式如下:
0 1 4 5 12
+--------+--------+-- ... --+-------+--------+-- ... --+-------+
|Type(8) | Stream ID (32 bits) | Byte offset (64 bits) |
+--------+--------+-- ... --+-------+--------+-- ... --+-------+
Frame Type:8bit,必须设置成0x40,表示他是WINDOW_UPDATE Frame
Stream ID: 0表示连接级别的流量控制,非0 指定的流
Byte offset: 64bit 表示指定流上数据的绝对偏移。如果是连接级别的流量控制,表示所有流总数据的绝对偏移
byte offset 表示WINDOW_Update Frame 帧的接收方 在指定的stream 上只能发送这么多的数据,如果发多了,对端可能会关闭连接。如果再指定的stream id 上收到了多个WINDOW_UPDATE帧,接收方只需要记录最大最大byteoffset的帧。流和连接级别的流量控制窗口初始时都是16KB,在握手阶段会增加。在握手的过程中双方会协商SFCW(Stream Flow Control Wind) 和 CFCW(Connection Flow Control Wind)参数。
BLOCK Frame 告诉对端,我有数据要发送,但是被阻塞了,它纯粹是一个通知信息帧,在调试的时候有用。接收方收到BLOCK Frame 后会打印一些日志信息,然后直接丢弃这个包。其帧结构如下:
0 1 2 3 4
+--------+--------+--------+--------+--------+
|Type(8) | Stream ID (32 bits) |
+--------+--------+--------+--------+--------+
这是一个正在试验中的帧类型,现在还没有正式使用。它的目的是为了提供除了标准ack帧以外的关于拥塞控制的额外信息。
RST_STREAM Frame可以异常终止一条流,当该帧又流的创建者发送时,表示创建者希望取消这条流,当接收方发送该帧时,表示出现了错误,或者接收者不想接收这条流,应该这条流应该被关闭。帧格式如下:
0 1 4 5 12 8 16
+-------+--------+-- ... ----+--------+-- ... ------+-------+-- ... ------+
|Type(8)| StreamID (32 bits) | Byte offset (64 bits)| Error code (32 bits)|
+-------+--------+-- ... ----+--------+-- ... ------+-------+-- ... ------+
RST_STREAM各个字段如下:
PING帧可以用来确定对方是否还存在。在链接建立之后,PING帧可以用来保持链接打开。默认情况下链接活动静默15秒钟之后会发送一个PING帧。
PING帧只有一个Frame Type字段,8-bit长,设置为0x07。
CONNECTION_CLOSE帧可以用来通知对方链接已经关闭,如果还有一写流数据没有传输完成,这些流也会被隐式的关闭。(理想情况下,会首先发送一个GOAWAY帧,预留足够的时间让所有的流先关闭)。帧格式如下所示:
0 1 4 5 6 7
+--------+--------+-- ... -----+--------+--------+--------+----- ...
|Type(8) | Error code (32 bits)| Reason phrase | Reason phrase
| | | length (16 bits)|(variable length)
+--------+--------+-- ... -----+--------+--------+--------+----- ...
GOAWAY帧可以用来通知对方链接应该停止使用,而且未来很有可能被中断。所有正在活动的流依旧会正常处理,但是GOAWAY帧的发送者不会再发起新的流,而且也不会接受新的流。GOAWAY帧格式如下:
0 1 4 5 6 7 8
+--------+--------+-- ... -----+-------+-------+-------+------+
|Type(8) | Error code (32 bits)| Last Good Stream ID (32 bits)| ->
+--------+--------+-- ... -----+-------+-------+-------+------+
9 10 11
+--------+--------+--------+----- ...
| Reason phrase | Reason phrase
| length (16 bits)|(variable length)
+--------+--------+--------+----- ...
握手过程的职责是协商一系列QUIC链接的传输参数。
QuicErrorCode到数字的映射关系现在定义在chromium源码文件src/net/quic/quic_protocol.h
文件中
手机扫一扫
移动阅读更方便
你可能感兴趣的文章