Reading Notes for QUIC

2017-09-09

Posted by 戴统宇

MOTIVATION:

QUIC的整体目标是降低网络服务的时延,努力方向是主要解决以下几个问题:

Protocol Entrenchment(协议固化):

像防火墙和NAT这样的中间设备增加了时延:比如防火墙会阻挡不熟悉的包,NAT为了安全还要重写包头,这些都加大了网络时延,也使很多被修改定制的传输协议无法广泛部署。

Implementation Entrenchment(实现固化):

由于网络演变或者遭遇攻击的问题,传输机制有时会需要快速部署到客户端。但是TCP是内嵌到OS内核中的,想要更新TCP的传输机制,就必须升级内核。众所周知,内核的升级是十分谨慎的,这种耦合关系极大地限制了传输机制部署和迭代的速度。

Handshake Delay(握手时延):

由于握手的机制,TCP在传输真正的应用数据之前,需要浪费至少一个RTT的时间用于握手,而TLS的存在又增加了两个RTT。但是对于Web应用来说,绝大多数都是短连接短传输,这种握手的开销增加了不少时延。

Head-of-line Blocking Delay(队首阻塞时延):

       为了降低多路TCP连接的时延和花费,HTTP/1.1限制了每个客户端的TCP连接数量,HTTP/2使用了多路复用。但是TCP要求必须在丢包重传后才能发送新的帧,这个机制增加了等待时延(transaction latency)。

 

QUIC做法:

      QUIC与HTTPS栈的对应关系如下图:

1

为了解决上述问题,它使用了以下几个做法:

  • 单连接中多流复用:每个流之间互不影响。
  • 加密认证数据包:避免中间件影响,防止协议僵化。
  • 使用独特的数据包编号和精确的RTT测量:改善丢包恢复,避免重传歧义。
  • 使用ID定义连接而不是IP和端口号:使连接可以跨IP迁移。
  • 使用单流限制:防止一个数据流耗尽收端的缓冲区。

 

QUIC的设计模块:

3.1Connection Establishment(建立连接):

       Initial handshake客户端发送inchoate client hello (CHLO)给服务器,并期待reject (REJ)回复。REJ包含了:服务器配置(包含了长期密钥交换公共值)、验证服务器的认证、源地址令牌等信息。一旦客户端收到了服务器的配置,它会通过验证证书链和署名来鉴定配置,然后发送完整的CHLO。
Final (and repeat) handshake: 对于一个连接,所有的key都是建立在秘钥交换上的。在发送完一个完整的CHLO后,客户端可以根据服务器的长期秘钥交换公共值和自己的短期秘钥交换私有值来计算出初始秘钥。此时,客户端就可以随意发送应用数据了。

如果一个握手成功了,服务器会返回一个server hello (SHLO)消息,这个消息使用初始化秘钥加密,并且包含了服务器短期的秘钥交换公共值。当持有这个短期公共秘钥时,两端就可以计算出final or forward-secure keys。这个forward-secure key用于对发送的包进行加密。

QUIC的加密主要包含两部分:最初的客户端数据使用初始化秘钥加密,随后的客户端数据和所有的服务器数据都使用forward-secure key加密。

客户端会存储服务器配置和源地址令牌,这样在连接同一个源时,不必等待服务器响应,可以直接发送加密的数据。当然,服务器配置和源地址令牌可能会到期,或者其他的问题导致握手失败,这时服务器会返回一个REJ信号,就像在最初收到CHLO那样。

Version Negotiation: QUIC的服务器和客户端在连接建立时完成版本对照。客户端在第一个包中提出一个版本,若服务器不支持,会返回给客户端一个包,包含所能支持的版本信息。这个机制可以在客户端的版本被服务器接受时节省一个RTT,同样也激励服务器更新版本。

 

3.2Stream Multiplexing(流复用):

应用程序通常都会在TCP的单连接中复用传输多个包。但为了避免TCP序列化传输造成的队首阻塞,QUIC在一个连接中多路传输多个流,这样数据包的丢失不会影响其他包的传输。

如下图所示,一个QUIC包包含了公共的包头,以及许多数据帧。这些数据帧就是用来实现多流复用的,一个QUIC包可以承载着许多来自不同流的数据帧。

2

 

3.3Authentication and Encryption(验证和加密):

除了刚开始的一些包以外,QUIC绝大多数包都会验证加密。在QUIC包的结构中,那些没有被加密的包头部分是用于路由转发和解密。

 

3.4Loss Recovery(丢包恢复):

TCP的序列号确实可以促进可靠性,但是同样也因为重传包与原始包的序列号相同,产生一种“重传模糊”的问题。收到的ACK也无法确定是来自于原始包还是重传的包,在QUIC中使用了一个新的包号去解决这个问题,这种机制能够区分开原始包与重传包,还有利于丢包检测。

 

3.5Flow Control(流量控制):

流量控制限制了QUIC的接收缓冲区大小。为了防止单个流占满缓冲区,进而影响到其他流的传输,QUIC不仅限制了所有流能够共同占用的缓冲区大小,也对每一条流可以消耗的缓冲区进行了限定。在具体的控制方法上,QUIC的做法与HTTP/2比较相似,使用window update frames去控制每个流中发送的数据量。

 

3.6Congestion Control(拥塞控制):

QUIC没有明确的指定拥塞控制算法,在本文中,QUIC和TCP都使用了Cubic作为拥塞控制。由于QUIC在单连接中使用了多流复用,它采用了mulTCP的一个变体实现公平性。

 

3.7NAT Rebinding and Connection MigrationNAT重连和连接迁移):

QUIC使用Connection ID定义每一个连接,这可以使连接不受到客户端IP或端口改变的影响。

 

3.8QUIC Discovery for HTTPSQUICHTTPS的发现):

一个客户端无法先验的知道服务器是否支持QUIC,因此它第一次会使用TCP/TLS发送一个HTTP请求给服务器,服务器使用一个HTTP响应告诉客户端可以支持QUIC。接下里的请求中,客户端便都会优先使用QUIC连接,在其连接失败或者QUIC握手包大于链路的MTU等情况下,也会重新转回使用TLS/TCP连接。

 

 

QUIC的开源实现代码(C++:https://cs.chromium.org/chromium/src/net/quic/.