Tim Wang Tech Blog

QUIC的发展之路

本文是 the-road-to-quic 的中文翻译版本,内容有删减。

QUIC(快速 UDP Internet 连接)是一种新式使用默认加密的 Internet 传输协议,它提供了许多旨在加速 HTTP 流量并使其更安全的改进,其最终目前是最终取代 TCP 和 TLS 。在这篇博文中,我们将概述QUIC的一些关键功能以及它们如何使网络受益,与此同时还有这种激进新协议带来的一些挑战。

事实上,有两个协议共享:“Google QUIC”(简称“gQUIC”)这个名称。它是谷歌工程师几年前设计的原始协议,经过多年的实验,现在已经被 IETF(互联网工程任务组)采用并进行标准化。

“IETF QUIC”(从现在开始只是“QUIC”)已经与gQUIC有很大不同,因此我们已经可以将其视为一个单独的协议333。从数据包的格式,到HTTP的握手和映射,QUIC改进了原始的gQUIC设计,其共同目标是使互联网更快,更安全。

那么,QUIC提供了哪些方面的改进?

内置安全性和性能 Built-in security (and performance)

QUIC与现在古老的TCP的一个更根本的区别是,QUIC在是提供默认安全传输的目标下进行设计的。QUIC 通过提供安全功能(如身份验证和加密)来实现这一点,这些功能通常由传输协议之上更高层协议(如 TLS)处理。

起初,QUIC 握手是通过类比TCP的三次握手与 TLS 1.3 握手相结合实现的,TLS 1.3 握手提供端点身份验证以及加密参数协商。对于那些熟悉TLS协议的人来说,QUIC用自己的帧格式替换TLS记录层,达到保持相同的TLS握手消息的目的。

这不仅确保了连接始终经过身份验证和加密,而且还使初始连接建立速度更快:QUIC 握手只需要客户端和服务器之间的一轮往返即可完成,而 TCP 和 TLS 1.3 握手需要两轮往返

但 QUIC 其实更进一步,它还加密了可能被中间设备滥用,来干扰连接的其他数据。例如,当采用连接迁移时,被动中间人攻击者可以使用数据包编号来关联用户在多个网络路径上的活动(见下文)。通过加密数据包编号,QUIC 可确保它们不能用于关联连接中端点以外的任何实体的活动。

但加密有时也会导致协议的是使用更僵化,因为它使得协议中内置的灵活性(例如能够协商该协议的不同版本)在实践中无法使用,例如因为做出了错误的假设,TLS 1.3的大规模使用被推迟数次。

队头阻塞(Head-of-line blocking)

HTTP/2 的主要改进之一就是能够将不同的HTTP请求多路复用到同一个TCP连接上。这允许HTTP / 2应用程序并发处理请求,可以更好地利用可用的网络带宽。

这是对现状的一大改进,此前如果应用程序需要同时处理多个HTTP / 1.1请求(例如,当浏览器需要获取CSS和Javascript资产来呈现网页时),那需要启动多个TCP + TLS连接。而创建新连接意味着需要多次重复初始握手,并经历初始拥塞窗口的加速,它意味着网页的呈现速度会变慢。HTTP交换的多路复用解决了这些问题。

然而,HTTP/2多路复用也带来了另一个缺点:由于多个请求/响应通过同一TCP连接传输,当在网络拥塞时,所有的请求均会受到数据包丢失的影响,即使丢失的数据仅涉及单个请求。这种现象被称为“队头阻塞”。

QUIC 则更深入一些,它为多路复用提供了一流的支持,它将不同的 HTTP 流依次映射到不同的 QUIC 传输流。HTTP 流仍然共享相同的 QUIC 连接,因此不需要额外的握手,并且拥塞状态是共享的。由于QUIC 流是独立交付的,因此在大多数情况下,一个流的数据包丢失不会影响其他流。

这可以大大减少渲染完整网页(使用 CSS、Javascript、图像和其他类型的资产)所需的时间,因为跨高度拥塞的网络传输数据时,数据包丢失率很高。

事实远非这么简单(That easy, uh?)

为了实现目的,QUIC协议需要打破许多网络应用程序认为理所当然的一些假设,这可能会使QUIC的实施和部署更加困难。

QUIC 旨在通过在 UDP 数据报进行交付,以简化部署并避免网络设备丢弃来自未知协议的数据包的问题,因为大多数设备已经支持 UDP。这也允许 QUIC 实现更贴近用户,例如浏览器已经支持通过新的协议功能并将其发送给用户,而无需等待操作系统更新。

然而,尽管QUIC的预期目标是避免数据破损,但它也使得防止滥用和将数据包路由到正确的端点更具挑战性。

NAT 陷阱

典型的 NAT 路由器可以使用传统的 4 元组(源 IP 地址和端口以及目标 IP 地址和端口)来跟踪通过它们的 TCP 连接,通过观察通网络传输的 TCP SYN、ACK 和 FIN 数据包,它们可以检测何时建立新连接以及何时终止。这使他们能够精确管理 NAT 绑定的生存期、内部 IP 地址和端口与外部 IP 地址和端口之间的关联。

对于QUIC来说,这是不可能的。因为今天广泛部署NAT路由器尚不支持QUIC。因此这些NAT路由器通常会退回到默认值和传统的不精确的UDP,因为涉及使用 arbitrary, and at times very short, timeouts,可能会影响长期连接。

发生 NAT 重新绑定时(例如由于超时),NAT 外围的端点将收到与最初建立连接时观察到的源端口不同端口的数据包,这使得仅使用 4 元组无法跟踪连接。

而且不仅仅是 NAT!QUIC旨在提供的功能之一称为“连接迁移”,它将允许QUIC随意将连接迁移到不同的IP地址和网络。例如,当已知的WiFi网络可用时(例如,当用户进入他们最喜欢的咖啡店时),移动客户端将能够在蜂窝数据网络和WiFi之间迁移QUIC连接。

QUIC 试图通过引入连接 ID 的概念来解决此问题:该ID由 QUIC 由数据包的长度构成,可用于标识连接。端点可以使用此 ID 来跟踪它们负责的连接,而无需检查 4 元组(实际上,可能有多个 ID 标识同一连接,例如,避免在使用连接迁移时链接不同的路径,但该行为由端点而不是中间框控制)。

然而,这也给使用任播寻址和ECMP routing的网络运营商带来了问题,因为单个IP地址可以潜在地包含数百甚至数千台服务器。由于这些网络使用的边缘路由器还不知道如何处理 QUIC 流量,因此属于同一 QUIC 连接(即具有相同连接 ID)但具有不同 4 元组(由于 NAT 重新绑定或连接迁移)的 UDP 数据包最终可能会路由到不同的服务器,从而中断连接。

为了解决这个问题,网络运营商可能需要采用更智能的第4层负载平衡解决方案,这些解决方案可以在软件中实现并部署,而无需接触边缘路由器(例如参考Facebook的Katran项目)。

头部压缩(QPACK)

HTTP/2引入的另一个好处是头部压缩(或HPACK),它允许HTTP / 2通过删除HTTP请求和响应中的冗余来减少通过网络传输的数据量。

HCPACK 使用动态表填充,可以从之前的 HTTP 请求(或响应)中提取头部,进而允许端点引用新请求(或响应)中以前的头部,而不必重新传输它们。

HPACK 的动态表需要在编码器(发送 HTTP 请求或响应的一方)和解码器(接收它们的一方)之间同步,否则解码器将无法解码它接收到的内容。

基于 TCP 的 HTTP/2,这种同步是透明的,因为传输层协议 (TCP) 以发送的顺序传递 HTTP 请求和响应;更新表的指令可以由编码器作为请求的一部分发送,使编码非常简单。但对于QUIC来说,这更复杂。

QUIC可以独立地通过不同的流传递多个HTTP请求(或响应),这意味着虽然它负责按单个流的顺序传递数据,但跨多个流没有排序保证。

例如,如果客户端通过 QUIC 流 A 发送 HTTP 请求 A,并通过流 B 发送请求 B,如果此时发生网络中的数据包重新排序或丢失,服务器会先收到B请求,然后收到A请求,如果B的编码使其引用了A 中的头部,服务器将会无法对其进行解码,因为它尚未收到请求 A。

在 gQUIC 协议中,这个问题是通过序列化同一个 gQUIC 流上的所有 HTTP 请求和响应头部(而不是正文)来解决的,这意味着无论如何头部都会按顺序交付。这是一个非常简单的方案,它允许重用大量现有的HTTP / 2代码,但另一方面,它增加了QUIC旨在减少的头部阻塞。因此,IETF QUIC工作组设计了HTTP和QUIC(HTTP/QUIC)之间的新映射,以及一个名为QPACK的新标头压缩方案。

在最新的HTTP/QUIC映射和QPACK规范的草案中,每个HTTP请求/响应交换都使用自己的双向QUIC流,因此没有头部阻塞。此外,为了支持 QPACK,每个对等方创建两个额外的单向 QUIC 流,一个将 QPACK 表更新发送到另一个对等方,另一个用于确认另一方收到的更新。这样,QPACK 编码器只有在解码器显式确认动态表引用后才能使用动态表引用。

偏转反射(Deflecting Reflection)

基于 UDP 协议中的一个常见问题是它们容易受到 反射攻击,攻击者通过欺骗针对服务器的数据包的源 IP 地址,使其看起来像来自受害者,从而欺骗原本无辜的服务器向第三方受害者发送大量数据。

当服务器发送的响应恰好大于它收到的请求时,这种攻击会非常有效,我们一般把这种情况叫做“扩大”。

此类攻击一般不适用于TCP,因为TCP在握手期间传输的初始数据包(SYN,SYN + ACK等)具有相同的长度,它们不提供任何放大潜力。

另一方面,QUIC的握手是非常不对称的:与TLS一样,在第一次握手中,QUIC服务器通常会发送自己的证书链,该证书链可能非常大,而客户端只需要发送几个字节(嵌入到QUIC数据包中的TLS ClientHello消息中)。因此,客户端发送的初始 QUIC 数据包必须具有特定的最小长度(即使数据包的实际内容要小得多)。但是这种缓解措施仍然不够,因为典型的服务器响应请求可能跨越多个数据包,因此最终的数据包仍可能远远大于填充的客户端数据包。

QUIC协议还定义了明确的源地址验证机制,此时服务器不发送长响应,只发送一个小得多的“重试”数据包,该数据包中包含一个唯一的加密令牌,然后客户端必须在新的初始数据包中显示地回服务器。这样服务器可以更确信客户端不会欺骗自己的源 IP 地址(因为它收到了重试数据包),并且可以完成握手。然而这个缓解措施也有缺点,它将初始握手持续时间从单次往返增加到两次。

另一种解决方案是将服务器的响应降低到反射攻击变得不那么有效的程度,例如通过使用ECDSA证书(通常比RSA证书小得多)。我们也一直在尝试一种机制,使用现成的压缩算法(如zlib和brotli)来压缩TLS证书,这是最初由gQUIC引入的功能,但目前在TLS中不可用。

UDP性能(UDP performance)

QUIC反复出现的问题之一是当前广泛部署的硬件和软件无法理解它。我们已经了解了 QUIC 如何尝试处理路由器等网络中间设备,但另一个潜在的问题领域是UDP在 QUIC 端点上发送和接收数据的性能。多年来,TCP在优化实现方面已经做了大量工作,包括在软件(如操作系统)和硬件(如网络接口)中构建卸载功能,但目前这些都不适用于UDP。

但是,QUIC实现这些功能只是时间问题。例如,最近实现的 Generic Segmentation Offloading for UDP on LInux,它允许应用程序在用户态和内核态的网络堆栈之间传输多个UDP段;以及添加 zerocopy socket support also on Linux,这将允许应用程序避免将用户态内存复制到内核态。

结论(Conclusion)

与HTTP / 2和TLS 1.3一样,QUIC将提供许多新功能,旨在提高网站的性能和安全性,以及其他基于Internet的属性。IETF工作组目前定于今年年底(本文著于2018年)前交付QUIC规范的第一版。

其他相关文章