总体上QUIC的重传机制吸取了大量TCP重传机制的经验,本章介绍的算法和机制基本上在TCP里都有对应的版本。但是QUIC和 TCP协议上的不同也体现在这些算法/机制上,其中最重要的一个不同点是,QUIC中数据传输和数据应用交付在协议上分开的,传输是发生在packet级别的,交付则是发生在stream级别,TCP中两者都是发生在byte stream级别。QUIC packet单调递增的seq number避免了重传歧义,这在TCP里是个比较麻烦的问题。
在正式介绍QUIC的重传机制前,我觉得最好先回顾下TCP对应的重传机制,这部分内容主要来源于《TCP/IP Illustrated》这本书,Richard Stevens大牛已经将TCP协议的big picture总结的明明白白,我们这样的后来人翻翻书就能避免钻进IETF的TCP“故纸堆”中迷失自我了。
TCP在认为发生丢包时通过重传来解决,因此我们说的重传机制其实最核心的就是TCP在什么情况下认为某个segment丢失了——即loss detection,至于怎么重传在TCP里面是个相对直观的问题,即原样重传那些认为丢失的segment即可,所以有时候会把重传算法和丢包检测算法这两个概念混用。另外TCP重传往往会牵扯到CC算法控制,这里先不做展开。
总的来说TCP经典的重传机制有两个:基于时间的重传(RTO recovery)和基于ACK结构的重传(fast recovery)
QUIC协议的RFC大概是目前为止我个人读过的最复杂的RFC文档,光是RFC文档都分为了4篇,分别是:
RFC 9001在之前的blog中已经做了解读,这篇是继QUIC handshake之后QUIC连接相关的协议解读,主要内容来自于RFC 9000中的第5-第10章节,并按照连接建立、连接迁移、连接终止顺序做了一些内容上的重新组织。另外RFC9000包含的信息量非常大,对很多QUIC设计和行为规定也没有做必要解释,这也造成了阅读体验上枯燥和难懂,因此这篇blog 里尽量对一些我个人认为比较难以理解的点做些motivation上的补充说明。看完并理解这部分内容的话,读者就可以理解QUIC的整个连接建立过程和连接终止过程的工作细节。
PS: QUIC Connection中有很多地方设计上参考了DTLS,了解下DTLS是怎么做的个人觉得有助于理解QUIC,这部分具体可以参考DTL1.2和DTLS1.3。
阅读前提:具备密码学基础,并了解TLS1.2、DTLS1.2、TLS1.3的工作细节
主要内容来源:draft-ietf-tls-dtls13-43
DTLS1.3 总体上和DTLS1.2的设计原则区别不大,主要区别分为两方面:
一方面是为了适配TLS1.3 对TLS1.2的升级,除了和DTLS1.2一样要解决TLS在握手阶段、应用层数据传输阶段的有序性/可靠性依赖问题外,还要解决类似比如TLS1.3中引入了key/IV update支持握手完成后的密钥更新,DTLS1.2中就已经引入的epoch 字段重要性上来了,用于标识key phase;再比如TLS1.3支持0-rtt data,在DTLS1.3使用epoch来标识0-rtt data的开始和结束,而非依赖end of early data。
另一方面是DTL1.3对自身的优化,这里借鉴吸收了不少QUIC的经验,首先是connection ID成为协议原住民了、并且支持ConnectionID的更新(类似QUIC);再比如record的header不再是固定长度了(对应QUIC里的long header & short header);再比如record header里的sequence number也需要加密了(对应QUIC的header encrption); 最后一点很重要,优化了DTLS1.2中粗糙的handshake消息重传机制,引入了ACK进行细粒度的重传,解决之前一个分片丢失,整个flight所有消息都需要重传的问题(个人觉得这基于属于DTLS1.2的设计缺陷了,使得对handshake的协议分片设计变得没有意义)