(译)Cloudflare: 通往 QUIC 之路(The Road to QUIC)

做者: Alessandro Ghedini,原文地址: https://blog.cloudflare.com/the-road-to-quic/

QUIC (Quick UDP Internet Connections) 是一种新的默认加密Internet传输协议,它提供了许多改进,旨在加速 HTTP 通讯并使其更加安全,其目标是最终取代 web 上 的 TCP 和 TLS。在这篇博文中,咱们将概述 QUIC 的一些关键特性,它们如何为 web 带来好处,以及支持这个新协议所面临的一些挑战。html

事实上,有两个协议同名:“Google QUIC”(简称 gQUIC),是Google工程师几年前设计的原始协议,通过多年的试验,如今已经被 IETF(互联网工程任务组)采用进行标准化。git

“IETF-QUIC”(从如今起就是“QUIC”)已经与 gQUIC 有至关大的不一样,所以能够将其视为一个单独的协议。从数据包的格式,到握手(handshake)和 HTTP 映射,QUIC 改进了最初的 gQUIC 设计,这得益于许多组织和我的的开放协做,共同的目标是使互联网更快、更安全。github

那么,QUIC提供了哪些改进?web

内置安全性(和性能)

QUIC 与如今受人尊敬的 TCP 的一个更根本的差异是,它的设计目标是提供一个默认安全的传输协议。QUIC 经过提供安全特性来实现这一点,好比身份验证和加密,这些特性一般由比传输协议自己更高层的协议(如TLS)处理。算法

QUIC 初始握手合并了 TCP 的典型三次握手和 TLS 1.3 握手,提供了端点的身份验证和密码参数的协商。对于那些熟悉 TLS 协议的人,QUIC 用本身的帧格式替换 TLS 记录层,同时保持相同的TLS握手消息。浏览器

这不只能够确保链接始终通过身份验证和加密,并且还能够加快初始链接的创建:与 TCP 和 TLS 1.3 握手组合起来所需的 2次 RTT(round-trip) 相比,典型的 QUIC 握手只需在客户端和服务器之间进行1 次 RTT。安全

 

但 QUIC 更进一步,它还加密了额外的链接元数据(metadata),这些元数据可能被中间设备(middle-boxes)滥用来干扰链接。例如,当使用链接迁移(connection migration)时,被动路径攻击者可以使用数据包编号关联多个网络路径上的用户活动(见下文)。经过加密数据包编号,QUIC 确保除了链接中的端点以外,任何人都不能使用它们来关联用户的活动。服务器

加密也能够是对僵化(ossification)的一种有效的补救措施,它使得协议中内置的灵活性(例如可以协商(negotiate)该协议的不一样版本)。因为实现中的错误假设而没法在实践中使用(僵化(ossification)是致使 TLS 1.3 长时间没法部署的缘由,这只有在几回修改后才可能实现,这些修改旨在防止僵化的中间设备(middle-boxes)错误地阻塞 TLS 协议的新修订)。网络

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

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

与当时的现状相比,这是一个很大的改进,即若是应用程序但愿同时处理多个 HTTP/1.1 请求,则须要启动多个 TCP+TLS 链接(例如,当浏览器须要同时获取 CSS 和 Javascript 资源来呈现网页时)。初始的握手会减慢初始页面的传输速度,这意味着须要屡次刷新新页面。多路复用(Multiplexing)HTTP 避免了全部这些问题。

然而,这也有一个缺点:因为多个请求/响应是经过同一个 TCP 链接传输的,所以它们都会受到数据包丢失(例如,因为网络拥塞)的影响,即便丢失的数据只涉及单个请求。这被称为“队头阻塞”(head-of-line blocking)。

QUIC 更深刻一点,为多路复用提供一流的支持,这样不一样的 HTTP 流能够映射到不一样的 QUIC 传输流,可是它们仍然共享相同的 QUIC 链接,所以不须要额外的握手和共享拥塞状态。可是 QUIC流是独立的,这样在大多数状况下,一个流的包丢失不会影响其余流。

例如,这能够显著减小呈现完整网页(使用CSS、Javascript、图片和其余类型的资源)所需的时间,尤为是在穿越高度拥挤的网络、具备高数据包丢失率的网络时。

这么简单,呃?

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

QUIC 被设计在 UDP 数据报之上传输,以便于部署,并避免网络设备丢弃未知协议的数据包,由于大多数设备已经支持UDP。这也容许 QUIC 实如今用户态(user-space)中生存,所以,例如浏览器将可以实现新的协议特性并将其发送给用户,而没必要等待操做系统的更新。(而 TCP 协议的更新受到网络设备和操做系统内容的约束,这也是 QUIC 设计在 UDP 之上的缘由之一)

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

一个 NAT 把他们都带来,在黑暗中捆绑他们(One NAT to bring them all and in the darkness bind them)

典型的 NAT 路由器可使用传统的4元组(源 IP 地址和端口、目的 IP 地址和端口)来跟踪通过它们的TCP链接,并经过观察在网络上传输的 TCP SYN、ACK 和 FIN 包,来检测新链接什么时候创建和什么时候终止。这使它们可以精确地管理 NAT 绑定的生命周期(lifetime),内部 IP 地址和端口之间的关联,以及外部 IP 地址和端口之间的关联。

对于QUIC,这还不可能,由于如今普遍部署的 NAT 路由器还不了解 QUIC,因此它们一般会退回(fallback)到默认和不太精确的处理 UDP 流,这一般涉及使用任意的,有时很是短的超时,这可能会影响长时间运行的链接。

当 NAT 从新绑定发生时(例如因为超时),NAT 外围外部的端点将看到来自不一样源端口的数据包,而不是最初创建链接时观察到的源端口,这使得仅使用4元组没法跟踪链接。

不只仅是 NAT!QUIC 打算提供的特性之一称为“链接迁移”(connection migration),它容许 QUIC 端点随意将链接迁移到不一样的IP地址和网络路径。例如,当已知的 WiFi 网络可用时(好比当用户进入他们最喜欢的咖啡店时),移动客户端将可以在蜂窝数据网络和 WiFi 之间迁移 QUIC 链接。

QUIC 试图经过引入链接ID(connection ID)的概念来解决这个问题:一个由 QUIC 包携带的可变长度的不透明blob,能够用来标识链接。端点可使用这个 ID 来跟踪它们负责的链接,而不须要检查4元组(实际上,同一个链接可能有多个 ID 标识,例如,在使用链接迁移(connection migration)时,为了不连接不一样的路径,可是这种行为由端点控制而不是中间设备控制)。

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

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

QPACK

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

特别是,在其余技术中,HPACK 使用动态表填充了从之前的 HTTP 请求(或响应)发送(或接收)的头,容许端点在新的请求(或响应)中引用之前遇到的头,而没必要从新传输它们。

HPACK 的动态表须要在编码器 encoder(发送HTTP请求或响应的一方)和解码器 decoder(接收它们的一方)之间同步,不然解码器 decoder 将没法解码它接收到的内容。

对于 TCP 之上的 HTTP/2,这种同步是透明的,由于传输层(TCP)负责以相同顺序发送 HTTP 请求和响应,更新表的指令能够由编码器 encoder 做为请求(或响应)自己的一部分发送,从而使编码很是简单。但对 QUIC 来讲,这更复杂。

QUIC 能够在不一样的流上独立地传递多个 HTTP 请求(或响应),这意味着,尽管它负责按单流的顺序传递数据,但在多个流之间不能保证排序。

例如,若是客户端经过 QUIC stream A 发送 HTTP 请求 A,而经过 stream B 发送请求 B,则因为网络中的数据包从新排序或丢失,服务器可能会在请求A 以前接收到请求 B,而且若是请求 B 被用来自请求 A 的报头编码,服务器将没法解码,由于它尚未看到请求 A。

在 gQUIC 协议中,这个问题只需在同一个 gQUIC 流上序列化全部 HTTP 请求和响应头(而不是主体),这意味着不管发生什么,消息头都将按顺序传递。这是一个很是简单的方案,它容许实现(implementations)重用大量现有的 HTTP/2 代码,但另外一方面,它增长了QUIC 本来设计减小的队头阻塞( head-of-line blocking )。IETF QUIC 工做组所以设计了 HTTP 和 QUIC 之间的新映射(“HTTP/QUIC”),以及一种称为“QPACK”的头压缩新方案。

在 HTTP/QUIC 映射和 QPACK 规范的最新草案中,每一个 HTTP 请求/响应交换都使用本身的双向 QUIC 流,所以没有队头阻塞(head-of-line blocking)。另外,为了支持 QPACK,每一个 peer 方建立两个e额外的单向 QUIC 流,一个用于向另外一个 peer 方发送 QPACK 表更新,另外一个用于确认另外一方接收到的更新。这样,QPACK 编码器 encoder 只有在解码器 decoder 显式地确认了动态表引用以后才能使用它。

偏转反射(Deflecting Reflection)

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


当服务器发送的响应刚好大于它接收到的请求时,这种攻击很是有效,在这种状况下,咱们称之为“放大”。

TCP 一般不用于此类攻击,由于在其握手 (SYN, SYN+ACK, …) 过程当中传输的初始数据包具备相同的长度,所以它们不会提供任何潜在放大攻击的风险。

另外一方面,QUIC 的握手是很是不对称的:就像 TLS 同样,在它的第一次传输中,QUIC 服务器一般发送本身的证书链(certificate chain),它可能很是大,而客户端只需发送几个字节(TLS ClientHello消息嵌入 QUIC 包中)。所以,客户端发送的初始 QUIC 包必须填充到特定的最小长度(即便包的实际内容要小得多)。然而,这种缓解仍然不够,由于典型的服务器响应跨越多个数据包,所以仍然能够远远大于填充的客户端数据包。

QUIC协议还定义了一个显式的源地址验证(source-address verification)机制,在该机制中,服务器不发送长响应,只发送一个很小的“重试(retry)”包,其中包含一个惟一的加密令牌,而后客户端必须在新的初始包中向服务器回显。这样,服务器就有了更高的信心,即客户机不会欺骗本身的源IP地址(由于它收到了重试数据包(retry packet)),而且能够完成握手。这种缓解的缺点是,它将初始握手持续时间从 1 次 RTT 增长到 2 次。

另外一种解决方案包括减小服务器对反射攻击(reflection attack)变得不那么有效的响应,例如使用 ECDSA 证书(一般比 RSA 证书小得多)。咱们还一直在尝试使用现成的压缩算法(如 zlib 和 brotli)压缩 TLS 证书的机制,这是 gQUIC 最初引入的一个特性,但目前在 TLS 中尚未。

UDP 性能

QUIC 常常出现的一个问题是如今普遍部署的硬件和软件没法理解它。咱们已经在另外一个C端的路由器上测试了数据的传输性能。咱们已经了解了 QUIC 如未尝试处理像路由器这样的网络中间设备,可是另外一个潜在的问题是在 QUIC 端点上经过 UDP 发送和接收数据的性能。多年来,人们作了大量的工做来尽量地优化 TCP 的实现,包括在软件(如操做系统)和硬件(如网络接口)中构建卸载功能,但目前尚未一种方法可用于UDP。

然而,等QUIC实现时也能利用这些功能,这只是时间问题。例如,最近在Linux上实现UDP通用分段卸载的工做,它容许应用程序以单个UDP段(或足够接近)的代价在用户态(user-space)和内核态(kernel-space)网络堆栈之间捆绑和传输多个 UDP 段,除了在Linux上添加零拷贝套接字支持以外,还容许应用程序避免将用户态(user-space )内存复制到内核空间的成本。

结论

与 HTTP/2 和 TLS 1.3同样,QUIC 将提供许多新特性,这些特性旨在提升web站点的性能和安全性,以及其余基于Internet的资产。IETF 工做组目前正准备在今年年末发布QUIC规范的初版,Cloudflare的工程师们已经在努力工做,为咱们全部的客户提供 QUIC 的好处。

相关文章
相关标签/搜索