一、背景
Quick UDP Internet Connection(QUIC)协议是Google公司提出的基于UDP的高效可靠协议。自 2015 年以来,QUIC 协议开始在 IETF 进行标准化并被国内外各大厂商相继落地。鉴于 QUIC 具备“0RTT 建联”、“支持连接迁移”等诸多优势,并将成为下一代互联网协议:HTTP3.0 的底层传输协议。
二、QUIC 是什么?
简单来说,QUIC (Quick UDP Internet Connections) 是一种基于 UDP 封装的安全 可靠传输协议,他的目标是取代 TCP 并自包含 TLS 成为标准的安全传输协议。下图是 QUIC 在协议栈中的位置,基于 QUIC 承载的 HTTP 协议进一步被标准化为 HTTP3.0。
三、为什么是 QUIC ?
在 QUIC 出现之前,TCP 承载了 90% 多的互联网流量,似乎也没什么问题,那又为何会出现革命者 QUIC 呢?这主要是因为发展了几十年的 TCP 面临 “协议僵化问题”,表现在几方面:
网络设备支持 TCP 时的僵化,表现在:对于一些防火墙或者 NAT 等设备,如果 TCP 引入了新的特性,比如增加了某些 TCP OPTION 等,可能会被认为是攻击而丢包,导致新特性在老的网络设备上无法工作。
网络操作系统升级困难导致的 TCP 僵化,一些 TCP 的特性无法快速的被演进。
除此之外,当应用层协议优化到 TLS1.3、 HTTP2.0 后, 传输层的优化也提上了议程,QUIC 在 TCP 基础上,取其精华去其糟粕具有如下的硬核优势:
TCP | QUIC | |
---|---|---|
握手延迟 | TCP需要三次握手+TLS握手 | 传输层握手和TLS握手放到一起,真正实现0RTT |
头阻塞问题 | TCP使用连接级别的seq保序,丢失一个报文,影响所有的应用数据包。 | 基于UDP封装传输层Stream,Stream内部保序,Stream之间相互不影响,无头阻塞。 |
传输控制 | 存在Seq二义性,SACK限制,RTT计算不准确等。 | 吸TCP精华,去TCP糟粕,重新设计完善拥塞控制算法,流量控制算法,前向纠错算法。 |
连接迁移 | 内核在五元组位置session,所以五元组变了需要重新建联 | UDP 不面向连接,QUIC基于CID的标识连接,五元组发送变化,CID不变,Session状态可以依然维持。 |
特性迭代速度 | TCP是在内核实现的,迭代速度很慢 | 在用户态实现,不用修改内核,可以快速度演进新特性 |
安全性 | TCP Header 是完全明文传输 | 除去一些必要的字段,Header也可以被加密 |
四、QUIC 生态圈发展简史
五、挑战点
1. 支持连接迁移能力
先说连接迁移面临的问题,上文有提到,QUIC 有一项比较重要优势是支持连接迁移。这里的连接迁移是指:如果客户端在长连接保持的情况下切换网络,比如从4G切换到Wifi , 或者因为 NAT Rebinding 导致五元组发生变化(PS:必须是五元组发送变化,移动访问切换基站五元组有可能不发生变化),QUIC 依然可以在新的五元组上继续进行连接状态。
QUIC之所以能支持连接迁移,一个原因是QUIC底层是基于无连接的UDP,另一个重要原因是因为 QUIC 使用唯一的CID来标识一个连接,而不是五元组。
如下图所示,是QUIC支持连接的一个示意图,当客户端出口地址从 A 切换成 B 的时候,因为 CID 保持不变,所以在 QUIC 服务器上,依然可以查询到对应的 Session 状态。
然而,理论很丰满,落地却很艰难,在端到端的落地过程中,因为引入了负载均衡设备,会导致在连接迁移时,所有依赖五元组 Hash 做转发导致关联 Session 的机制失效。以 LVS 为例,连接迁移后, LVS 依靠五元组寻址会导致寻址的服务器存在不一致。
即便 LVS 寻址正确,当报文到达服务器时,如果是多进程的Server(如 Nginx),内核会根据五元组关联进程,依然会寻址出错。同时,IETF Draft 要求,连接迁移时 CID 需要更新掉,这就为仅依靠 CID 来转发的计划同样变的不可行。
1、在四层或者七层负载均衡上,需要重新设计QUIC LoadBalancer 的机制
2、若是QUIC 服务器多进程工作模式上,也需要进行相应的改造
2. 所谓的0RTT
前文我们介绍过,QUIC 支持传输层握手和安全加密层握手都在一个 0RTT 内完成。TLS1.3 本身就支持加密层握手的 0RTT,所以不足为奇。
而 QUIC 如何实现传输层握手支持 0RTT 呢?
我们先看下传输层握手的目的,即:服务端校验客户端是真正想握手的客户端,地址不存在欺骗,从而避免伪造源地址攻击。在 TCP 中,服务端依赖三次握手的最后一个 ACK 来校验客户端是真正的客户端,即只有真正的客户端才会收到 Sever 的 syn_ack 并回复。
QUIC 同样需要对握手的源地址做校验,否则便会存在 UDP 本身的 DDOS 问题,那 QUIC 是如何实现的?依赖 STK(Source Address Token) 机制。
这里我们先声明下,跟 TLS 类似,QUIC 的 0RTT 握手,是建立在已经同一个服务器建立过连接的基础上,所以如果是纯的第一次连接,仍然需要一个 RTT 来获取这个STK。
理论上说,只要客户端缓存了这个 STK,下次握手的时候带过来,服务端便可以直接校验通过,即实现传输层的 0RTT。但是真实的场景却存在如下两个问题:
- 因为 STK 是服务端加密的,所以如果下次这个客户端路由到别的服务器上了,则这个服务器也需要可以识别出来。
- STK 中 encode 的是上一次客户端的地址,如果下一次客户端携带的地址发生了变化,则同样会导致校验失败。此现象在移动端发生的概率非常大,尤其是 IPV6 场景下,客户端的出口地址会经常发生变化。
所以,需要解决上面两个问题才能真正实现0RTT
3. UDP的劣势
QUIC 是基于 UDP 的,而 UDP 相比于 TCP 在运营商的支持上并非友好,表现在:
- 在带宽紧张的时候,UDP 会经常被限流。
- 一些防火墙对于 UDP 包会直接 Drop。
- NAT 网关针对 UDP 的 Session 存活时间也较短。
- Android系统Kernel层面对UDP优化的力度
4. 文件上传和下载
目前还没有做文件上传和下载验证,但是就现状来看,文件下载和支持还是使用HTTP2,TCP较为靠谱
5. 无完善的生态和成熟的商业化框架
QUIC Server目前大都是单纯基于QUIC协议实现HTTP3,是否能够达到Tomcat Apache Jetty这些Server容器的稳定性还并未得到验证。
客户端目前Android已经移植成功一个Client,但是是否能够达到OKHTTP的性能目前也并没有得到验证
目前现状来看,QUIC只适合作为网关Server使用。
若是作为普通业务Server,就必须要上游的流量网关和业务网关对于QUIC进行支持。
六、其他
quic不同语言不同版本的实现
参考文档
datatracker.ietf.org/doc/rfc9000…
datatracker.ietf.org/doc/rfc8446…