关注网络安全
和行业未来

通往QUIC之路

QUIC(快速UDP互联网连接)协议是一种新的默认加密的互联网通信协议,它提供了许多改进,旨在加速HTTP通信,同时使其变得更加安全,其最终目的是在web上代替TCP和TLS协议。在这篇博客中,我们将对QUIC协议的一些内容进行概述,这些内容包括该协议的一些关键特性以及这些特性会如何使web受益,还包括支持这种激进的新协议过程中遇到的一些挑战。

事实上,有两种协议共用这个名称,一种是“Google QUIC”(简称gQUIC),它是几年前由谷歌公司的工程师设计的原始协议,经过多年的试验,该协议现在已经被IETF(互联网工程任务组)采纳为标准协议。
另一种是“IETF QUIC”(从现在开始叫作“QUIC”),它已经与gQUIC有了很大的不同,以至于它可以被认为是一个单独的协议。从数据包的有线格式,到握手机制和HTTP的映射,通过许多机构和个人的开放的合作,QUIC协议改进了最初的gQUIC设计,实现了让互联网更快、更安全的共同目标。

那么,QUIC带来的改进是什么呢?

内置的安全性(以及性能)

QUIC有一个更彻底的背离现在脆弱的TCP协议的特点,即它所声称的设计目的是提供一个默认安全的传输协议。通过提供一些安全方面的特性,比如身份验证和加密等,QUIC协议自身实现了这一目的。而这些工作通常本应是由更高层次的协议(如TLS协议)来完成的。

最初的QUIC握手机制将TCP协议使用的典型的三次握手方式,与TLS1.3协议的握手机制结合起来,它提供了端点的身份验证以及加密参数的协商等机制。对于那些熟悉TLS协议的人们来说,QUIC用它自己的框架格式代替了TLS协议的记录层,同时保留了与原来相同的TLS握手消息。

这不仅能够确保连接总能实现身份验证和加密,而且,由此使得初始连接的建立速度更快了:相比于TCP和TLS1.3相结合的握手机制需要客户端和服务器之间两次消息往返来说,典型的QUIC握手只需要一次往返就可以完成。

基于TCP+TLS的HTTP请求:

基于QUIC的HTTP请求:

但是QUIC走得更远,它还对额外连接的元数据进行加密,这些元数据可能被一些中间件所利用,从而对连接产生干扰。例如,当连接迁移的漏洞被利用时,路途中的被动攻击者可以利用数据包编号来发现多条网络路径上的用户活动之间的联系(详见下文)。通过加密数据包编号,QUIC可以确保,除了通信连接的端点之外,没有任何实体能够使用数据包编号来发现用户活动之间的联系。

加密也可以是一种有效的避免僵化的补救措施。所谓僵化是指,由于在部署时作出了错误的假设,导致某种协议原本内置的调节机制(例如,原本该协议与其自身的几个版本之间都可以进行协商)实际上无法奏效(僵化正是这么长时间以来TLS1.3的部署仍然被拖延的原因,TLS1.3旨在防止僵化的中间件错误地阻止该协议的修正,而只有当该协议做出几项重要变更后,才可能被广泛部署)。

队首阻塞

HTTP/2所带来的主要改进之一是,它能够将不同的HTTP请求多路传输到同一个TCP连接上。这使得HTTP/2应用程序能够并发地处理请求,并更好地利用它们可用的网络带宽。

这比原来的情况有了很大的改进,当时的要求是,如果应用程序想要并发地处理多个HTTP/1.1请求(例如,当浏览器需要同时获取CSS和Javascript资源以呈现web页面时),就需要初始化多个TCP+TLS连接。创建新的连接需要多次重复最初的握手,同时还要经历最初的拥塞窗口缓慢增大的过程,这意味着web页面的呈现速度被减慢了。多路复用HTTP交换则避免了所有这些问题。

然而,这有一个缺点:由于多个请求/响应通过同一个TCP连接传输,它们都同样受到数据包丢失(例如,由于网络拥塞所造成的)的影响,即使丢失的数据只涉及单个请求。这被称为“队首阻塞”。

QUIC走得更远一些,它为多路复用提供一流的支持,这样,不同的HTTP流反过来可以映射到不同的QUIC通信流,然而,尽管它们仍然共享同一QUIC连接因而不需要额外的握手,而且拥塞状态也是共享的,但QUIC流的传输却是独立的,因此在大多数情况下,数据包丢失只影响一个通信流,而不会影响其他人。

这可以极大地减少所需的时间,例如,呈现完整的web页面(带有CSS、Javascript、图片以及其它类型的资源)的时间,特别是在通信需要跨越高度拥挤的网络并伴随着很高的丢包率的情况下。

真的就那么简单?

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

QUIC被设计为在UDP数据报的顶端进行传输,这样可以简化部署,并避免来自网络设备的一些问题(一些网络设备会自动丢弃来自未知协议的数据包),这是因为大多数设备已经支持UDP协议。这也使得QUIC可以在用户那一端进行部署,例如,浏览器将能够实现新的协议功能,并将它们发送给用户,而不必等待操作系统的更新。

然而,尽管目标是避免连接受损,但它也给防范入侵以及如何正确地将数据包路由到正确的端点带来了更多的挑战。

一个NAT把它们都带了出来,然后在暗中把它们捆绑起来

典型的NAT路由器能够对通过自身的TCP连接进行追踪,这可以使用传统的四元组(源IP地址、源端口、目标IP地址、目标端口)来实现,而通过观察在网络中传输的TCP数据包中的SYN、ACK和FIN标记位,这些路由器可以检测到新连接的建立和终止。这使得它们能够精确地管理NAT绑定的生命周期、内部IP地址和端口之间的联系以及外部IP地址。

而对于QUIC来说,这现在还是不可能的,因为目前部署在外部网络的NAT路由器还并不了解QUIC,所以它们通常会退回到默认的和不太精确的UDP流处理机制,这通常涉及到使用任意长度(有时非常短)的超时,而这可能会影响那些长时间运行的连接。

当一个NAT重新绑定发生时(例如由于超时),位于NAT边界之外的通信端点会看到来自另一个源端口的数据包,该端口与连接最初建立时被观察到的源端口并不相同,这样,就不可能通过使用传统的四元组来追踪连接。

而且不仅仅是NAT!QUIC想要带来的特性之一是“连接迁移”,它将允许QUIC端点将连接任意地迁移到不同的IP地址和网络路径。例如,当一个已知的WiFi网络变得可用时(比如,当用户进入他们最喜欢的咖啡厅时),移动客户端将能够在蜂窝数据网络和WiFi网络之间迁移QUIC连接。

QUIC试图通过引入连接ID的概念来解决这个问题,连接ID是指,一个由QUIC数据包携带的,任意的,不透明的,可变长度的blob对象,它可以用来标识网络连接。终端可以使用这个ID来追踪它们所负责的连接,而不需要检查四元组(实际上,可能会有多个ID来标识相同的连接,例如,可以用来避免在连接迁移时连接到不同的路径,但这种行为是由终端而不是中间件来控制的)。

然而,这也给使用任意播寻址方式和ECMP路由的网络运营商带来了问题,在ECMP路由中,单个目标IP地址可能会指向数百台甚至数千台服务器。因为这些网络所使用的边缘路由器也还不知道如何处理QUIC通信,可能会发生这样的情况,属于同一QUIC连接(这指的是具有同一连接ID的QUIC连接)但却具有不同的四元组(这是由于NAT重新绑定或连接迁移所造成的)的UDP数据包可能会被路由到不同的服务器,从而最终导致连接断开。

为了解决这个问题,网络运营商可能需要使用更加智能的第四层负载均衡解决方案,这种解决方案可以在软件中实现,并且在不需要接触边缘路由器的情况下部署(例如,Facebook的Katran项目)。

QPACK

HTTP/2所带来的另一个好处就是头部压缩(或叫作HPACK),它使得HTTP/2端点可以通过删除HTTP请求和响应的冗余来减少通过网络传输的数据量。

特别是,相比其他各项技术,HPACK所使用的充满了从此前的HTTP请求(或响应)发送(或接收)的报头的动态表,使得端点可以在新的请求(或响应)中引用以前遇到过的头部信息,而不必再一次传输它们。

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

基于TCP的HTTP/2的这种同步是透明的,因为传输层(TCP)负责以与报文被发送到该层时相同的顺序来传输HTTP请求和响应,对表进行更新的指令可以直接作为请求(或响应)本身的一部分,由解码器进行发送,这使编码变得非常简单。但对于QUIC来说,情况则更为复杂。

QUIC可以在不同的流中独立地传递多个HTTP请求(或响应),这意味着,虽然对于单个流而言,它可以按顺序传输数据,但在多个流当中,却无法保证正确的传输顺序。

例如,如果一个客户端在QUIC流A中发送HTTP请求A,然后在QUIC流B中发送请求B,那么,由于在网络中数据包会重新排序或丢失,因此可能会发生服务器在收到请求A之前就收到请求B的情况,而如果请求B被编码了因而需要参考请求A的头部信息,那么由于服务器还未收到请求A,它将无法解码请求B。

在gQUIC协议中,这个问题通过在gQUIC流中简单地对所有HTTP请求和响应的头部(而不是主体)进行序列化而得到了解决,这意味着无论如何都要按顺序传输头部。这是一个非常简单的方案,它使得程序可以重复使用大量现有的HTTP/2代码,但另一方面,它也增加了队首阻塞的情况,而这本来是QUIC设计时想要减少的。因此,IETF QUIC工作组设计了一个在HTTP和QUIC(“HTTP/QUIC”)之间的新映射关系,同时还设计出一个名为“QPACK”的新头部压缩方案。

在最新的HTTP/QUIC映射和QPACK规范草案中,每个HTTP请求/响应交换都使用它自己的双向QUIC流,因此就没有队首阻塞的情况。此外,为了支持QPACK,每个对等节点会创建两个额外的单向QUIC流,一个用于向另一个对等节点发送QPACK表更新,而另一个则用于确认另一方收到的更新。通过这种方式,一个QPACK编码器只能在被解码器明确地承认后才能使用动态表引用。

使反射攻击偏转方向

在基于UDP的一些协议中,一个常见的问题是,它们容易受到反射攻击。这种攻击是这样进行的,为了对目标主机进行攻击,攻击者假冒发往服务器的数据包的源IP地址,使得这些数据包看起来像是由目标主机发过来的,从而欺骗了服务器,这样,服务器就会误认为目标主机请求了大量数据,于是就会将这些数据发送过去。

当服务器发出的响应比它接收到的请求更大时,这种攻击可能是非常有效的,在这种情况下,我们讨论的是“放大”。

这种攻击通常不使用TCP连接,因为在握手过程中传输的初始数据包(SYN、SYN+ACK、……)具有相同的长度,因此它们不会有任何被放大的可能。

QUIC的握手方式则是非常不对称的:就像对于TLS一样,在它的第一次旅程中,QUIC服务器通常会发送自己的证书链,它可能非常大,而客户端则只需要发送几个字节(将TLS的客户端问候消息嵌入到QUIC包中)。基于这个原因,客户端发送的初始QUIC包必须被填充到特定的最小长度(即使包的实际内容要小得多)。然而,这种缓和措施仍然是不够的,因为典型的服务器响应会跨越多个数据包,因此仍然比填充的客户端数据包大得多。

QUIC协议还定义了一个显式的源地址验证机制,在这种机制中,服务器只发送一个小得多的“重试”数据包,而不会发送较长的响应。这个小的数据包包含一个唯一的加密令牌,然后,客户端将必须在一个新的初始数据包中对服务器进行响应。通过这种方式,服务器将会更加信任客户端,相信客户端并未使用假冒的源IP地址(这是因为客户端收到了重试数据包),这样就可以完成握手了。这种缓和措施的缺点是,它增加了最初的握手时间,从一次往返变成了两次往返。

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

UDP的性能

QUIC的一个反复出现的问题在于,部署在外部网络的现有硬件和软件无法理解它。我们已经研究了QUIC如何试图解决像路由器这样的网络中间件的问题,但是,另一个潜在的问题是,在QUIC端点上使用UDP协议发送和接收数据的性能如何。多年以来,人们做了很多工作来尽可能地优化TCP应用,包括在软件(如操作系统)和硬件(如网络接口)上建立卸载功能,但是目前UDP协议还不支持这些功能。

然而,QUIC应用可以使用这些功能将只是个时间问题。例如,最近人们致力于在Linux系统中为UDP协议实现通用分段卸载(Generic Segmentation Offloading)功能,这将使应用程序可以在用户空间和内核空间的网络协议栈之间以一个UDP段(或非常接近)的开销捆绑传输多个UDP段,以及为Linux加入零复制套接字支持的UDP段,这可以使应用程序免去从用户空间向内核空间复制信息的开销。

结语

与HTTP/2和TLS1.3一样,QUIC将提供许多新的特性,以提高web站点的性能和安全性,它也会提供许多其它的基于互联网的属性。IETF工作组计划于今年年底前交付QUIC规范的第一个版本,让我们拭目以待!

稿源:Cloudflare blog

评论 1

  1. #1

    加密也可以是一种有效的避免僵化的补救措施。所谓僵化是指,由于在部署时作出了错误的假设,导致某种协议原本内置的调节机制(例如,原本该协议与其自身的几个版本之间都可以进行协商)实际上无法奏效(僵化正是这么长时间以来TLS1.3的部署仍然被拖延的原因,TLS1.3旨在防止僵化的中间件错误地阻止该协议的修正,而只有当该协议做出几项重要变更后,才可能被广泛部署)???

    匿名3个月前 (09-14)回复