英文原文:https://lwn.net/Articles/814522/

中文原文:https://mp.weixin.qq.com/s/jp9509IHPnboHM0I3fh6Og

HTTP(Hypertext Transfer Protocol,超文本传输协议)是互联网的核心基础之一。它也在不断演进,增加了诸如加密这些新功能。不过它和相关整套协议栈的缺点在现在看来也越来越明显了。Daniel Stenberg在FOSDEM 2020会议上进行的演讲介绍了一组新的协议,名为HTTP/3。这个协议尚在开发中,不过已经看到有许多大变动了,比如不再用TCP了,而是使用一个全新的传输层协议QUIC用来改善性能和供新功能所用。

HTTP/1 and HTTP/2

每个HTTP session都需要依赖底层的TCP连接,因此也就需要先进行3次握手才能建立。在连接建立之后,我们就可以进行可靠数据传输了。TCP传输的数据都是明文,任何人都能看懂,因此不加密的HTTP传输的话也都是明文传输。今时今日,其实80%的HTTP请求都是使用加密传输了,也就是HTTPS(Hypertext Transfer Protocol Secure),这个比例是根据Mozilla和Google的Firefox/Chrome用户统计出来的。Stenberg认为互联网的加密已经越来越普及了。HTTPS使用了TLS(Transport Layer Security),TLS是在现有的传输协议栈之上增加的安全层,所以现有协议栈就变成了(按从底到上的顺序):IP,TCP,TLS,HTTP。TLS新引入了一次握手,会额外增加网络延迟,不过相应的回报也很明显,就是私密性、安全性,能确保你的网络请求是跟正确的服务器进行的。

HTTP/1协议要求客户端针对每个对象都建立一条新的TCP连接,也就是说浏览器发起每次HTTP request的时候都会建立一个连接,发出request,读回response,然后关闭连接。Stenberg认为“TCP在建立阶段其实效率很低”。TCP连接建立之后数据传输一开始很慢,然后随着两端软件摸清楚这个链路质量如何而逐渐增加速度。如果建立一次连接之后只能取得一个对象,那么传输完成前TCP的速度都还没有来得及提升起来。通常来说一个典型的网页包括许多元素,例如Javascript文件,图像文件,stylesheet(指CSS)文件等等。每次只能取一个对象的话就太慢了,因此浏览器的实现都是会同时并行创建多个连接。

这样一来大大增加了服务器这边需要同时处理的连接数量,因此通常来说每个客户端能分配到的连接数量都是有限的。浏览器就需要仔细权衡接下来的对象应该用哪条连接来获取,从而导致了“head-of-line blocking”(队首被卡住)问题。就像是在超市里排队收银时,你可能会选那个看起来最短的队伍,不过却发现自己前面的那位顾客在收银时碰到问题,导致卡了很久。1997年的时候我们就在HTTP/1.1里面增加了一个重大改动,提升TCP效率:已经打开的TCP连接可以直接给其他HTTP request用。这样就大大减少了slow-start(慢启动)问题,不过head-of-line blocking问题还是没有解决,甚至可能会碰到更糟的情况。

2015年的HTTP/2则规定每个host都使用一个单独的连接,这样TCP就可以把速度提起来了。不过,head-of-line blocking问题在TCP连接层变得更严重了。在HTTP/1里面的问题是一个耗时很长的request会导致其他等待这个连接的request被阻塞,而在HTTP/2里面,这唯一的一个连接会承载几百条HTTP stream。这样一来,如果我们发生了丢包,那么就是几百条HTTP stream在等待这一个TCP包的重传。这是因为TCP只有在网络协议栈弄清楚丢失的是哪个packet之后才能重传这个丢失的packet,并且只有在丢失的包被收到之后,协议栈才会把已经收到的后续众多packet传给上层application。

The “boxes”

Stenberg还介绍了一下“protocol ossification”:互联网上充满了各种路由器、网关之类的黑盒子(通常称为“middlebox”)。许多都是很早以前安装的,只能处理当时的网络协议,也只能确保它是可以正常支持当时的互联网协议的。在这些黑盒看来,如果某个packet的头部里面的一个flag当时定义只有0,那么就绝对不会想到外来要处理其他取值。更糟的是,这些黑盒也一直没有升级。它们就好像时间暂停了一样,一直活在过去的时代。而服务器和网页浏览器则不同,通常都会定期升级。

因为这些黑盒的存在,开发新的HTTP协议的时候都不得不束手束脚。举例来说,因为HTTP/1.1使用了TCP的80端口,作为不加密的数据传输,现代所有的浏览器都不会用明文方式来跟80端口传输HTTP/2.0数据了。他说:“之前浏览器一直试图想用明文在80端口传输HTTP/2.0协议,最终实在做不到,只好放弃了。”这是因为这些黑盒依据它们所认识的HTTP/1协议而修改了或者丢弃了这些基于HTTP/2的网络packet。

还有一种改进协议的方法,就是能在TCP连接建立的时候尽早开始发送数据,这就是所谓的TCP fast open (TFO)。这个功能就能让浏览器可以在TCP握手packet里面就发送HTTP request数据。Stenberg指出,大家花了5~7年的时间才让所有kernel都能支持这个TCP fast open功能,然后浏览器开始试着使用这个功能,结果……没法正常工作。因为这些网络链路中的黑盒会把TFO packet直接丢掉。目前没有一个浏览器缺省打开TFO功能。Brotli compression也是类似的故事。因为中间黑盒只认识gzip,所以他们会导致传输Brotli压缩数据的链路被破坏。目前Brotli亚索只用于HTTPS传输中。他也断定我们没法引入新的传输层协议,因为“你的家用路由器可能只能支持TCP和UDP”。

The definition of HTTP/3

HTTP里面难于创新,也是IETF在2016年建立QUIC working group的原因之一。QUIC本身并不是一个缩写。许多公司对它感兴趣。IETF group的工作是基于Google QUIC的实验版本的,这是2013年第一次部署的一个协议。这个实验版本中使用UDP来传输HTTP request,用到的都是普通大众所用的客户端和web服务程序。这个实验里面有大量的HTTP传输,这些工作都送给了IETF,以此为基础建立了这个工作组。目前IETF开发版本跟当初Google的版本已经非常不同了,它包括了一个新增的传输层协议以及用户程序层。

IETF的QUIC解决了head-of-line blocking问题,也允许类似TCP fast open这样的尽早传输数据的行为。它内置加密,QUIC已经完全不支持明文传输了。基于QUIC实现的HTTP/3比起HTTP/2来说也用了更少的明文消息。

开发QUIC的过程中,工作组也解决了其他一些现代网络常见的问题。TCP定义是完全跟IP地址绑定的。当代的设备可能会有多个IP地址,用户换个地方使用的时候IP地址也会变。用TCP的话,就必须要重新建立一个连接了,毕竟网络接口的地址变了。而QUIC则使用了session identification,而不是使用IP地址,就解决了这个问题。

QUIC使用了UDP,不过用法非常克制,基本上就像是在使用IP而不是UDP协议一样。传输层是在QUIC的高层级,在UDP之上,增加了连接的概念,确保了可靠性,也包含了flow control和安全措施。QUIC跟TCP的一大区别在于如何管理一个连接内的多个stream。QUIC可以在一个连接内发送多个stream,可以是入站,也可以是出站方向。这些stream是完全独立的,由server或者client端来发起。丢包时,QUIC的实现机制可以知道哪个stream会受影响,因此只有这个stream才需要等重传结束。这些stream自己都是可靠传输,并且内部的数据包也是按顺序传输的。

Application位于QUIC协议之上。协议的定义是从HTTP开始的,其他协议如DNS等会逐个进行。其他的应用层协议估计会在QUIC发布之后开始进行。

HTTP over QUIC(基于QUIC的HTTP)跟此前的HTTP有相同点也有不同点。仍然会有GET命令,大多数读者应该都很了解了。不过这个命令传输的方式则跟此前不同了。Stenberg解释了HTTP的历史:HTTP/1是ASCII,HTTP/2是多路复用二进制数据流(binary multiplexed),HTTP/3是使用了TLS1.3的、基于多路复用QUIC的二进制数据流(binary over multiplexed QUIC, with TLS 1.3)。

HTTP/3比较快,主要来源于握手方面的改善。此前的实验中,70%的连接都没有RTT (round-trip-time)delay,因为这个连接早就已经建立好了。协议允许数据早发送(early data),因此哪怕在连接没有提前建立的情况下也有更小的延迟。各个stream之间互相独立,也对那些质量很差的网络有不少帮助。他说暂时还不能展示实验数据,因为协议还没有完成。不过大家可以认为能得到的提升在“a little better”到”much better”之间。

Deployment

HTTPS URL现在到处都是。如果不重写整个互联网的话,不可能直接把它替代掉。HTTPS代表了在TCP端口443上使用TLS。迁移到HTTP/3就会需要能首先连接到一个传统https服务上,支持HTTP/3的网站会提供一个Alt-svc header来告知应该连接哪个服务器。浏览器看到之后就会再建立一个连接,或者直接同时尝试两种协议进行访问。他说:“There will be a lot of probing(翻译成侦测?)”。DNS系统也会有相应的支持,是通过新增一个HTTPSSVC类型的记录,来提供连接参数信息。实用角度考虑,应该先询问DNS来了解是否该用HTTP/3。

还有其他一些挑战,其中之一就是许多公司都缺省关闭了UDP传输,从而避免DDoS(分布式拒绝服务)攻击。使用UDP传输的时候,约3~7%的连接会失败,就是因为网络中某些地方block了UDP。这种时候客户端就需要能自动退回到此前的协议,避免让用户感觉到不变。这就引入了另一个问题:既然反正都能回退回旧协议来访问,那人们就不会有动力来放开对UDP端口的block。

目前为止,QUIC协议栈还是实现在user space的,这样方便测试。不过Stenberg提醒:“大家需要固定使用一种library,因为目前还没有标准API。”目前QUIC已经有十多种实现了,各种语言的都有。每个月都有一些Interoperability test(互操作测试),目前2020年3月份最新版本的协议是draft 27。

HTTP/3占用的CPU资源会是此前协议下相同数据量的2~3倍。这一点就可能会在短期内阻碍它的广泛应用。原因之一是目前Linux的UDP实现不是最优化的,用他的话说:“毕竟TCP已经被打磨多年了”。目前UDP还没有用在大量数据传输上,也没有针对QUIC的hardware offload(硬件加速)。此外,性能也有影响,毕竟协议栈实现在user space会导致许多kernel和user space的切换开销。现在他也不确定是否QUIC会被移入kernel。目前有些人在做这个工作,不过这还需要在kernel里面实现一个新版的TLS。

QUIC里面的TLS使用方式跟此前不同,所以现有的offload功能都无法使用。TLS协议使用“TLS records”来传输数据,这些record可能会包含一个或多个TLS message,每个message都可以跨越record边界。基于TCP的TLS中,records和message都用到了。不过基于QUIC的TLS中则只会有message,而不再需要record了。这会改变TLS library的使用方式以及相关的API。

因为TLS library在TCP和QUIC上的使用方式不一样了,那么就需要增加新的API。有一个OpenSSL的pull request(PR 8797)正在讨论,就是用来新加QUIC API的。这个工作预计会花不少时间。等它合入了之后,还要等段时间才能有OpenSSL的release和应用。

因为修改了传输层协议,也导致相关的工具需要更新。比如tcpdump目前就还没有准备好。现在能理解QUIC协议的就是Wireshark以及另外两个QUIC特有的工具qlog和qvis。Stenberg是curl的作者,curl就已经支持了最新的协议草案(在他演讲的2020年2月还是draft version 25),不过尚未支持协议回退(fallback)功能,他认为fallback功能还是比较tricky。总之,他认为现有的工具还是太少了,需要许多工作来完善。

浏览器方面,目前Chrome和Firefox的nightly build都已经可以打开HTTP/3了。想要试一下的话,需要打开一些特定选项。Firefox的nightly builds包含了HTTP/3支持,用户只要去about:config里面把network.http.http3.enabled改为true即可。Chrome Canary (不是Linux版本)则需要在运行的时候加一些特定参数:–enable-quic和–quic-version=h4-25(这是针对演讲时的draft 25版本)。在服务器侧,有一个NGINX patch增加了quiche(一个QUIC的实现library)的试验性支持。不过,其他的知名服务器软件,包括Apache, IIS,以及NGINX的官方版本,都还没有支持。目前Safari浏览器也尚未支持。

最终协议发布的日期还没有确定,因为工作组希望能把事情做好做对,不要太赶时间。他希望能在2020年7月发布。目前的library都是在alpha版本。等到规范定下来之后,这些library就可以发布了。浏览器需要更新这些TLS library。协议的广泛部署应用肯定需要更多时间,他认为会比HTTP/2的推广速度要慢,不过长久来说HTTP/3还是非常必要的。

等到协议定稿之后,人们就可以去给QUIC增加新功能了,包括multipath(利用多个网络连接来访问相同网站),forward error correction(前向编码纠错,常用于卫星电视场景的数据纠错),unreliable and partially reliable stream(针对video场景)。当然,其他的应用场景也会慢慢显现出来。QUIC的开发重心,在第一版本发布之后就会马上开始移到第二版本上去。

资源

Slides: https://fosdem.org/2020/schedule/event/http3/attachments/slides/4056/export/events/attachments/http3/slides/4056/http3_fosdem_2020.pdf

Video: https://video.fosdem.org/2020/Janson/http3.mp4