HTTP/3 已经箭在弦上,你准备好了吗?

去年的这个时候,国内的 web 网络环境开始普及和部署 HTTP/2. 时隔一年,HTTP/2 的普及程度有了显著提升,而各大CDN厂商普及的广度和速度一直走在行业前列。甚至有不少CDN厂商在直播以及部分HTTP场景还引入了 QUIC.

在拙文《当我们在谈论HTTP队头阻塞时,我们在谈论什么?》中,我们提到,HTTP/2 over QUIC 是当前唯一应用落地解决了传输层队头阻塞问题的HTTP实现。那个时候,无论是 HTTP/2 over TCP 还是 HTTP/2 over QUIC(UDP) 都被我们认为是 HTTP/2,只是传输层使用的协议不一样。这种略带暧昧的模糊叫法在2018年11月成为了历史:

在2018年10月28日的邮件列表讨论中,互联网工程任务组(IETF) HTTP和QUIC工作组主席Mark Nottingham提出了将HTTP-over-QUIC更名为HTTP/3的正式请求,以“明确地将其标识为HTTP语义的另一个绑定……使人们理解它与QUIC的不同”,并在最终确定并发布草案后,将QUIC工作组继承到HTTP工作组。在随后的几天讨论中,Mark Nottingham的提议得到了IETF成员的接受,他们在2018年11月给出了官方批准,认可HTTP-over-QUIC成为HTTP/3。

虽然看起来像是之前的 HTTP/2 over QUIC 换了一个名称(从我个人角度理解,取名为 HTTP/2.1也许更合适),但是其背后却体现了 IETF 对 HTTP 未来标准的态度和方向,也许几年以后来看这次名称的确立会更加明白其重要意义。

HTTP/3 与 HTTP/2 over QUIC 的区别

QUIC 将成为一个通用安全传输层协议

当前阶段,Google 实现的 QUIC 与 IETF 实现的 QUIC 是不兼容的。Google 版 QUIC 只能用于 HTTP/2,且在协议层面与 HTTP/2 有一些强绑定。如 QUIC 帧映射 HTTP/2 frame. 这就导致很多大厂都没有跟进 QUIC,使得 HTTP/2 over QUIC 基本只能在 Google 自家的 Chrome, Gmail 等软件中普及使用,一度给行业造成“只有Google在弄”的错觉。

纳入 IETF 以后,显然 Google 就不能这么玩了。QUIC 定位为一个通用安全传输层协议:

可以近似的认为 QUIC over UDP 将成为下一代(或替代)TLS over TCP. 也就是说, QUIC 将能应用于任何应用层协议中,只是当前阶段将优先在 HTTP 中进行应用和验证。

统一使用 TLS 1.3 作为安全协议

2018年,有几个重要的WEB标准终于尘埃落定,其中一个便是 RFC 8446 TLS 1.3. 这个标准对于降低延迟,改善用户体验,尤其是移动端的体验有非常重要的意义。在拙文《TLS1.3/QUIC 是怎样做到 0-RTT 的》中,我们提到 虽然 TLS 1.3和 QUIC 都能做到 0-RTT,从而降低延迟,但是 QUIC 却自顾自地实现了一套安全协议。主要是因为当时 TLS 1.3 标准还没有发布,而 QUIC 又需要一套安全协议:

The QUIC crypto protocol is the part of QUIC that provides transport security to a connection. The QUIC crypto protocol is destined to die. It will be replaced by TLS 1.3 in the future, but QUIC needed a crypto protocol before TLS 1.3 was even started.

如今,TLS 1.3 标准已经发布,而 HTTP/3 也纳入 IETF,因此 QUIC 也就顺理成章的使用 TLS 1.3 作为其安全协议。Google 在这些方面倒是从来都不鸡贼和墨迹,点赞。

使用 QHPACK 头部压缩代替 HPACK

其实,QPACK与HPACK的设计非常类似,单独提出QPACK主要是更好的适配QUIC,同时也是 Google 将 QUIC 从与 HTTP/2 的耦合中抽离出来,与 IETF 标准完成统一的必要一步。

HTTP/3 问题与挑战

UDP 连通性问题

几乎所有的电信运营商都会“歧视” UDP 数据包,原因也很容易理解,毕竟历史上几次臭名昭著的 DDoS 攻击都是基于 UDP 的。国内某城宽带在某些区域更是直接禁止了非53端口的UDP数据包,而其他运营商及IDC即使没有封禁UDP,也是对UDP进行严格限流的。这点上不太乐观,但是我们相信随着标准的普及和推广落地,运营商会逐步改变对UDP流量的歧视策略。国外的情况会稍好一些,根据Google的数据,他们部署的QUIC降级的比例不到10%。

QUIC 不支持明文传输

对于用户来说,这是一个优势,并不是问题。对于国内内容审查环境来说是个不可忽视的坎。但QUIC以后毕竟也是基于TLS协议的,国内HTTPS都能普及下来,QUIC的普及也许会更乐观一些。

UDP 消耗资源多

当前阶段,UDP消耗的CPU资源多,且处理速度慢。这是不争的事实,但是我相信随着UDP应用的增多,内核和硬件的优化一定会跟上,直至达到或超过TCP的性能。而 QUIC 因为实在应用层实现,因此迭代速度更快,部署和更新难度和代价更小,能够一定程度缓解如TCP那样的协议僵化问题。

进一步了解 HTTP/3

如果希望全面的了解 HTTP/3,推荐 Daniel Stenberg (CURL 作者)的 HTTP/3 explained, 如果不想看英文,可以翻阅 Yi Bai 同学翻译了中文版本HTTP/3详解

QUIC 存在 UDP 反射 DDoS 攻击漏洞吗?

今年年初,360信息安全部发布了一篇关于利用 UDP 反射 DDoS 的分析报告:Memcache UDP反射放大攻击技术分析。报告一出,引起了业界的普遍关注。根据文中所述,光是Qrator Labs 在 medium.com 上 批露的一次DDoS攻击看,其攻击流量峰值达到 480Gbps。而360信息安全团队本身也监测和确认有更大的攻击已经实际发生,只是未被公开报道。

而就在这个这个事件纰漏没多久,我把博客升级为支持基于 UDP 的 QUIC 协议来改善小站的访问体验:本站开启支持 QUIC 的方法与配置。本着小站没几人访问的蜜汁自信,当时也没太纠结 QUIC 是否也存在 UDP 反射漏洞。前几天,看到著名博主,阮一峰同学网站被 DDoS 攻击,心里咯噔一下:出来混迟早是要还的,还是填坑为安吧。

什么是 UDP 反射 DDoS 攻击

简单讲,就是攻击者利用IP网络不做真实源地址检查的“设计缺陷“,向提供基于 UDP 服务的服务器发送伪造源地址(一般为被攻击者的主机IP)的 UDP 报文请求,使得这些 UDP 报文的响应数据都会发送给被攻击者主机,这种攻击我们称之为 UDP 反射 DDoS 攻击。

之所以要通过被利用的服务器反射流量到被攻击的服务器,是因为被利用的服务器一般存在流量放大效应。即一个伪造IP的 UDP 请求发送到到被利用服务器后,被利用服务器会发送比请求更多的数据到被攻击者服务器。

被利用服务器输出流量与输入流量的比值我们称之为放大系数。这个系数与被利用服务器所提供的 UDP 服务有关。之前提到的利用 Memcache 漏洞的 DRDoS 攻击,可以获得稳定的 60000 倍放大系数。而我们日常使用的 DNS 则可以轻松的获得 50 倍的放大系数。

由放大系数反推,我们可以知道,如果一个 UDP 服务被利用以后,放大系数小于等于1的话,则不存在利用价值,因为这个时候,只从带宽流量方面考虑的话,还不如直接利用攻击主机对被攻击服务器进行攻击效率高。

QUIC 存在 UDP 反射攻击漏洞吗

按照蛤乎惯例,照顾猴急的同学,先给结论:可以。

QUIC 主要通过以下机制来解决该问题:

  1. 对于首次发起建立 QUIC 连接的客户端,服务端要求其初始化的 hello 数据包必须完全填充。这个包在 IPv4 下一般是 1370 字节,在 IPv6 下是 1350 字节。在 QUIC 协议中,服务器和客户端数据交互的基本单位是就是 UDP 数据包,而一个全填充的数据包已经达到了数据包大小的上限,因此服务器的响应数据包一定是小于等于这个 hello 数据包的。显然,放大系数小于等于1. 因此,新连接的建立没有反射利用的价值。
  2. 建立 QUIC 连接后,客户端发出的数据包就不会全填充了。这个时候,如果被 UDP 反射利用,放大系数是大于1的。因此, QUIC 引入了源地址token (source address token):在成功建立 QUIC 连接后,服务器会用发放一个源地址token给客户端,并要求客户端在后续的数据包中带上这个token;服务器只对源地址token有效的数据包进行处理。源地址token中一般包含客户端的源地址和服务器的时间。因此这是一个客户端证明其IP所有权的凭证。
  3. 由于源地址token可能会被网络中的攻击者嗅探收集,因此 QUIC 设计了一套源地址token的过期和刷新机制。另一方面,每次客户端发送的数据包如果都带上源地址token的话,不仅客户端流量大,服务器验证token也是额外的开销,使得协议延迟变高。因此 QUIC 协议允许客户端按照一个动态策略选择是否在数据包中夹带源地址token:服务器端收集和统计源地址的数据包,当统计到源地址数据包交互响应异常的数量超过阈值时,要求该源地址的客户端必须夹带源地址token, 对于无法提供合法源地址的token的请求进行 reject 处理。

扩展阅读

本站开启支持 QUIC 的方法与配置

在越来越讲究用户体验的今天,网络带宽的提高已经很难有显著的页面加载改善,而低延迟的优化往往能够起到意想不到的效果。在《TLS1.3/QUIC 是怎样做到 0-RTT 的》中我们分析了TLS1.3和QUIC在低延迟方面的原理和低延迟优势。在从源代码编译 nginx docker 镜像开启 TLS 1.3中我们已经把玩了TLS1.3,没有理由不把玩一下QUIC,对吧?

起初以为,在普及程度上,QUIC因为主要是Google主导,会曲高和寡。但是,查了一下,发现腾讯早在2017年就在生产环境应用了QUIC:让互联网更快的协议,QUIC在腾讯的实践及性能优化. 结果显示:

灰度实验的效果也非常明显,其中 quic 请求的首字节时间 (rspStart) 比 http2 平均减少 326ms, 性能提升约 25%; 这主要得益于 quic 的 0RTT 和 1RTT 握手时间,能够更早的发出请求。

此外 quic 请求发出的时间 (reqStart) 比 h2 平均减少 250ms; 另外 quic 请求页面加载完成的时间 (loadEnd) 平均减少 2s,由于整体页面比较复杂, 很多其它的资源加载阻塞,导致整体加载完成的时间比较长约 9s,性能提升比例约 22%。

既然大厂都已经发车,我司也就可以考虑跟进了。稳妥起见,决定先在自己的博客开启QUIC,然后再逐步在线上业务进行推广。

方案概览

方案非常简单:不支持QUIC的浏览器依旧通过nginx tcp 443访问;支持QUIC的浏览器通过caddy udp 443访问。

由于nginx近期没有支持QUIC的计划, 作为一名gopher, 因此这里选择caddy作为QUIC的反向代理。后面会介绍caddy的具体安装和配置方法。

对于支持QUIC的浏览器来说,第一次访问支持QUIC的网站时,会有一次服务发现的过程。服务发现的流程在QUIC Discovery
有详细介绍。概括来说,主要有以下几步:

  1. 通过TLS/TCP访问网站,浏览器检查网站返回的http header中是否包含alt-svc字段。
  2. 如果响应中含有头部:alt-svc: 'quic=":443"; ma=2592000; v="39"',则表明该网站的UDP 443端口支持QUIC协议,且支持的版本号是draft v39; max-age为2592000秒。
  3. 然后,浏览器会发起QUIC连接,在该连接建立前,http 请求依然通过TLS/TCP发送,一旦QUIC连接建立完成,后续请求则通过QUIC发送。
  4. 当QUIC连接不可用时,浏览器会采取5min, 10min的间隔检查QUIC连接是否可以恢复。如果无法恢复,则自动回落到TLS/TCP。

这里有一个比较坑的地方:对于同一个域名,TLS/TCP和QUIC必须使用相同的端口号才能成功开启QUIC。没有什么特殊的原因,提案里面就是这么写的。具体的讨论可以参见Why MUST a server use the same port for HTTP/QUIC?

从上面QUIC的发现过程可以看出,要在网站开启QUIC,主要涉及两个动作:

  1. 配置nginx, 添加alt-svc头部。
  2. 安装和配置QUIC反向代理服务。

配置nginx, 添加alt-svc头部

一行指令搞定:

安装QUIC反向代理服务器caddy

上面我们提到对于同一个域名,TLS/TCP和QUIC必须使用相同的端口号才能成功开启QUIC。然而,caddy服务器的QUIC特性无法单独开启,必须与TLS一起开启,悲剧的是TLS想要使用的TCP 443端口已经被nginx占用了😂

场面虽然有点尴尬,但是我们有docker:将caddy安装到docker中,然后只把本地的UDP 443端口映射到容器中即可。

于是我们创建了一个docker-caddy项目。Dockerfile 10行内搞定:

caddy 服务配置文件/conf/blog.conf:

启动docker:

开启Chrome浏览器QUIC特性

chrome://flags/中找到Experimental QUIC protocol, 设置为Enabled. 重启浏览器生效。

测试QUIC开启状态

重新访问本站https://liudanking.com, 然后在浏览器中打开:chrome://net-internals/#quic。如果你看到了QUIC sessins,则开启成功:

当然,你也可以给Chrome安装一个HTTP/2 and SPDY indicator(An indicator button for HTTP/2, SPDY and QUIC support by each website) 更加直观的观察网站对http/2, QUIC的支持情况。