OVERVIEW
QUIC像使用DTLS进行加密,基于UDP传输的SCTP。
MOTIVATIONS
动机:减少网络时延,实现更快的用户交互响应。
TCP和TLS(SSL),IP和端口对是有限资源,多路复用传输可以统一流量,减少端口使用数。
SPDY SUPPORT MOTIVATIONS
SPDY是一个基于TCP的多路复用流协议。它可以将所有请求同时发出,减少时延;可以压缩冗余的流量以减少带宽消耗。但是有以下问题:
- 队头阻塞:一个包的延迟将导致多个SPDY流的阻塞,好的多路复用传输应该是当一个包延迟时只会导致所在流阻塞。
- 多路复用,使得一个SPDY连接取代了K个TCP连接,但由于TCP的拥塞避免,会造成SPDY连接窗口大小减少50%,但如果使用TCP连接的话,K的连接的窗口总和只会下降1/2K。
- 由于部署了TLS,因此连接建立至少需要多一个RTT。
- TLS会导致加密依赖,对一个包的加密必须在上一个包加密完成后。
GOALS
- 可广泛部署:可以通过中间件、防火墙;无需改变内核或特殊权限;
- 减少队头阻塞:一个包延迟时只会导致所在流阻塞;
- 低时延:主要减少连接建立与重连时延;
- 支持移动设备;
- 像TCP一样友好的拥塞控制;
- 像TLS一样的隐私保障,但无需按序加密或传输;
- 服务端和客户端都具有安全可靠的可扩展性;
- 减少带宽消耗,增加信道响应能力;
- 减少包数量;
- 支持可靠传输;
- 代理高效的调制解调;
- 利用现有的协议,如uTP、DCCP、TCP等。
JUSTIFICATIONS AND SOME IMPLICATIONS
与上述目标对应:
- 除了TCP和UDP外,其他包格式都会被中间件和防火墙阻塞,因此只考虑TCP或UDP;
- 若通过改进TCP来避免队头阻塞,需要修改TCP协议,还需要改变内核,并且这样的改变可能会导致包被中间件阻塞;
- 对于包丢失,减少时延的方法更好是错误纠正而非重传,若使用TCP则需要修改TCP标准;
- 随着移动客户端的增加,对绘画会话连续性来说,减少返回数至关重要;
- 提供一个能与TCP兼容的拥塞避免协议;
- 避免中间件误解基于UDP或TCP的新协议的最好方法是将其负载和控制信息尽可能加密,因此Quic可以部署与TLS类似的安全元素,如防篡改、隐私、重放保护和身份验证;
- Quic的报头压缩将与SPDY类似,但支持其他方法;
- 隐私机制需要对齐加密块的包界限。
WHY NOT USE SCTP over DTLS?
SCTP提供了流多路复用,DTLS提供了基于UDP的SSL加密和验证。但Quic使用基于DTLS的SCTP,因为无法解决连接延迟问题,而且可能会有带宽效率问题。
CONNECTION ESTABLISHMENT LATENCY OF SCTP over DTLS
SCTP协商前,DTLS连接必须建立。SCTP连接需要一次往返的连接建立过程,而DTLS需要3个往返,总的就是2RTT的时间。
EFFICIENT UTILIZATION OF BANDWIDTH FOR SCTP over DTLS
Expected API Elements
API CONCEPTS
提供一个流多路复用的API有很高的复杂度,最需要解决的是在一个连接里边增加流的机制,以及单独分离多个读写流的方法。
STREAM CHARACTERISTICS
不同的流有不同的传输特性,并且可以被应用程序设置和修改,这些特性包括:
- 可调冗余水平
- 可调优先级水平
控制信道,也可以是带外流,将一直保持可用并且用来通知其他流状态变化。控制信道将包含控制帧和保留流,以供加密协商。
IN-SEQUENCE DATA DELIVERY
提供一个API,仿照TCP的可靠性和按序传输,以适配已有的应用。该API类似SPDY提供的接口,但是掩蔽内部包丢失,并使得每个流中的数据传输各自独立。
CONNECTION STATUS
传统网络中将应用与连接分离使得对连接的使用更加困难。例如有时应用将数据发送后立刻关闭,而数据还在本地发送缓冲区中未通过连接发送出去,造成无法预测的后果。
以下数据可以帮助使得应用与连接的联系紧密:
- RTT (current smoothed estimate)
- Packet size (including all overhead; also excluding overhead, only including
payload) - Bandwidth (current smoothed estimate across of entire connection)
- Peak Sustained Bandwidth (across entire connection)
- Congestion window size (expressed in packets)
- Queue size (packets that have been formed, but not yet emitted over a wire)
- Bytes in queue
- Per-stream queue size (either bytes per stream, or unsent packets, both??)
PROTOCOL PHILOSOPHY
四个阶段:建立、稳定、空闲条目、空闲断开。
挑战:
- 减少连接建立时延,特别是重连;
- 确保在稳定状态下高效且低延迟的性能,当大量流保持整个连接在拥塞避免允许的情况下是满载的;
- 有效地、低延迟地确保平稳过渡到空闲状态(当前没有流提供数据,但连接已完全建立)。
CONNECTING VIA CONNECTIONLESS UDP: OVERCOMING NATs
如何用UDP建立一个连接是一个问题,因为中间件和防火墙NAT服务很可能会阻碍这个连接。例如NAT不再支持这个连接,若是TCP的话,发送数据还会收到RST响应;若是UDP,则客户端收不到任何响应,而且若此时客户端再次发送数据给服务器,可能会重新绑定服务器其他端口,而被认为是两个不同的连接。
目前NAT中的UDP端口映射的存储期限大概在30-120s。另外,Chrome上约0.5%的HTTP GET请求会在60s后才收到响应。
CID: THE KEY TO CONNECTION IDENTIFICATION
NAT能够通过解绑和重绑改变连接的源端口,因此源端口和IP并不能有效定义一个连接。采用一个连接ID(CID,Connection IDentifier)可以解决以上问题。
Nonce:在密码学中是一个只被使用一次的任意或非重复的随机数值。
CID是一个64位的nonce。通常放在客户端发送给服务器的第一个UDP数据包中,并显性或隐性地放在该连接接下来的包中。
NAT BINDING KEEP-ALIVE
NAT端口绑定可能会被NAT服务撤销,因此需要重新发送从客户端到服务器的连接,以激活NAT端口映射。这个保活过程代价很大,因此:
- 什么时候需要保活? 若是服务器向客户端主动发送的连接,必须保活。
- 多久一次探测包? 实际数值需要通过实验得出基准值,并根据环境变化。
保活过程:服务器每隔一段时间发送探测包给客户端;
客户端将维护一个相应的计时器,当时间过期并经过一些额外的时间时,将发出警报;
客户端发送ACK或者NACK,并发送一个包给服务器,表明NAT绑定是可用的;
服务器端缩短(NACK)或延长(ACK)保活时间间隔。
UDP PACKET FRAGMENTATION
UDP包的长度如果超过MTU,则会在IP层被分段。若一个UDP包被分段,则第一个包将包含源目的端口和CID,而其他分段则没有这些信息,接收端无法通过这些信息对分段进行重组,因此只能由IP层的identification域进行重组。identification域有16位,即如果接收端如果超过2^16(256)个包,则会发生碰撞,无法重组,造成接收端的带宽和计算时间浪费。
目前操作系统没有提供检测数据包发生分段的API。如果能得到操作系统的支持,便可以测出数据包是否发生分段,并使数据包长度限制在MTU内,则没有上述分段问题。
CONNECTION ESTABLISHMENT and RESUMPTION
为了减少连接建立时延,并加快响应速度,Quic最初的包将包含会话协商信息(加密证书、连接凭证等)以及一或多个请求。若服务器拒绝接受证书,则需要多加一些协商。
STARTUP DDOS ATTACKS
连接建立时可能会遇到DDOS攻击。当服务器被攻击或超载,Quic将使用源IP的所有权证明以及回退到三次握手。
SECURITY CREDENTIALS
客户端需要保留服务器的信息:足以支持模拟TLS会话恢复的信息、服务器最新使用的公钥。
HIGH LEVEL OVERVIEW OF CONNECTION SCENARIO
Quic连接的过程如下:
Quic连接建立
初始化连接:通常为1RTT,有时2RTT
1、初始化连接时,客户端向服务器发送初始化Hello消息
该消息可能包含随机性,这个随机性能加快会话协商过程。该消息将放在一个UDP包中,并填充一些字节,以确保服务器回复一个满载的响应包,响应包比客户端的初始化包小。
2、服务器回复一个包,包含服务器证书链hash和类似SYN cookie的信息
服务器证书链大小通常为1-1.5KB。
3、客户端解析服务器证书链hash并进行验证
若服务器证书链hash无法解析或者验证不通过,则客户端进入2RTT的阶段,发送给一个请求(包含SYN cookie)要求服务器回复完整的析服务器证书链(得用多个包传输)。
在验证通过之后,客户端进入0RTT阶段,并被认为是可信任的客户端IP和端口拥有者。
重连:通常为0RTT,有时1RTT,少数情况下2RTT
在重连阶段,客户端认为服务器证书仍然有效,并可能拥有IP和端口的控制权。
客户端发送hello消息,包含:
- 使用SSL Snap Start技术构造类似于TLS会话主机密钥的组件
- 客户端IP地址控制权证明
- Quic协商项,如拥塞避免算法
hello消息会加密,保证攻击者不能使用该证明。该消息包含在一个UDP包中。
服务器收到hello消息后将评估信息,若服务器的公钥已经改变,那么服务器将拒绝该消息中的信息,并把该消息当作初始化连接。
发送完hello消息后,客户端便可以加密、验证和发送数据,以打开流或发送请求等。为了防止包丢失,以上的包可能被多次传输。
PROOF OF OWNERSHIP OF CLIENT IP ADDRESS
客户端IP的拥有权证明的作用不是很充分。
GENERATING PROOF OF OWNERSHIP/CONTROL OF CLIENT IP ADDRESS
在一个连接中,一个Quic服务器可能创建和传输一个客户端IP和端口的声明,当作0RTT重连的证明。服务器会更新该声明。一个客户端可能会拥有多个不同IP的证明清单,如Wifi、4G情况下,IP地址不同。
STEADY STATE
Quic连接中的每个流都有唯一的标志号。
CONNECTION STRUCTURE
流中的数据分成多个帧放置在UDP包中。任何特定包的负载都尽可能地放在一个流中,减少包丢失的影响。
SECURITY: TAMPER RESISTANCE, PRIVACY, AUTHENTICITY
需要一个方法来识别一个包的加密上下文,这个加密上下文与UDP包中的CID有关。
数据包将使用AEAD(使用相关数据进行身份验证加密)进行保护,AEAD根据密码上下文中持有的共享秘密进行加密。每个包需要一些nonce的初始化向量(IV,Initialization Vector)字节用于加密,每个包的nonce都是唯一的。
在任何隔离包加密模式中,都应该避免在延迟包必须的延迟之外增加加密的延迟。例如解压一个包需要其他包的信息,那么就没有隔离包加密特性。
PACKET LOSS
据统计,互联网中大概有1%-2%的包丢失,主要是由于拥塞使得路由采取交换操作和超过缓存导致的。
丢失包处理策略:包级纠错码和丢失数据重传。当其他方法都不可行时才会采取重传丢失数据的方法。而且当Quic重传数据时,会将数据放置在一个新的包中进行发送。
对于较短的流,可以增加冗余的信息来部署错误纠正和减少重传的功能,以减少时延;对于较长的流,序列化纠错时延会占总时延较大比例,而数据重传时延占总时延比例很小,那么将减少使用FEC或者取消。
拥塞控制
包丢失是连接上发生拥塞的标示。
- 若接收端认为发生了包丢失,会向拥塞连接的源头发送NACK。
- 发送端接收到NACK后,会调节其发送速率,如降低拥塞窗口或增加等到传输时间。
缓解乐观ACK攻击
乐观ACK攻击:恶意客户端连续确认所有的包,使得服务端认为可以加大数据输出,淹没ISP,造成DOS攻击。
Quic防止乐观ACK攻击的方法是:客户端必须证明其真的接收到数据包,服务器才增加输出。
在每个包中增加一个位的熵(一个不可预测的值)在加密负载中,可以提高客户端提供证明的可靠性。因为一个包中,除了发送时间,其他数据都是可以推导的,这样容易被攻击者模仿。而增加一个熵可以增加数据包的预测难度,提高其可靠性。
客户端的证明与ACK一起发送到服务器,并提供校验和以供服务器验证。若校验和不通过,服务器会认为客户端是恶意的并终止连接。
有一个问题,客户端的ACK中需要包含所有丢失包的清单(不包含在校验和中),这是一个没有上限的清单。为了解决这个问题,服务器在发送一个assertion说其不再重传第K个包之前的数据包的同时,还提供了一个校验和,它期望与接收到的直到包K的所有包(包括包K)相关联。
纠错模式
块级纠错方案是发送N个包负载并附上一个校验和,这就能纠正一个包丢失的错误,而无需重传。若N比较小,则校验和意味着高冗余;若N比较大,则意味着校验和能用很少的带宽来减少冗余。另外发送端可以决定在没有数据传输时传送校验和包(N比较小)还是在流的结尾发送校验和包(N比较大)。N值越大越好,但是N的值不能超过【RTT*单向传输带宽/平均包长度】,也就是说接收端收到校验和包的时间不能超过重传包的时间(1RTT)。
若发送2个校验和包,则可能可以纠错2个包丢失的情况。
调整包发送速度以减少包丢失
实验证明,调整包发送速度经常可以减少包丢失,因为速度调整减少数据流的波动,因而减少基于拥塞的包丢失。
现有的拥塞避免都是基于带宽估计调整包速度的,可能需要更好的在操作系统层面的方法来部署更加精确的包速调整。
包丢失重传
若包丢失已经超过了纠错协议的限制,包重传将被执行。
主动推测重传
对于特定的数据包,如初始化加密协商包对于连接建立时延,是至关重要的。为了防止这类数据包的丢失带来的影响,这类数据包可能会在没有发生丢失之前重传。FEC包是按序发送的,而重传包则没有顺序。
BUFFER BLOAT
缓冲膨胀是连接的一种状态,从一个源到一个目的路由的缓冲区已经变得很大。
对于要求及时传输的流,缓冲膨胀是一个重要的问题。需要为应用程序提供尽可能多的可见性,并尽可能地将包即时放置到本地发送队列中,以及从应用程序接收包的即时接收。这种需求将驱动围绕连接状态和通知的几个API元素。
一个基本的问题是,并发TCP连接以及通过路由器或连接共享的流可能会将缓冲区的大小提高到最大。如果检测到缓冲膨胀就减小流量,那么将逐步使用包含通信量的更小比例的膨胀缓冲区;如果像TCP那样传输,则会成为导致缓冲膨胀的元凶。
STREAM-BASED FLOW CONTROL
流量控制使得接收端在超过缓冲的情况下,可以请求发送端减少甚至暂停传输。对于多路复用协议,可能一个流的接收端接受数据很慢甚至不接受数据,这样虽然不会阻塞其他流,但还是会耗尽接收端的资源。
IDLE ENTRY
有时,所有的连接会空闲,此时丢失的尾随包可能会导致基于时间的重传,造成严重的时延,需要使用错误纠正来减少这个可能性。
当连接即将进入空闲状态时,如果此时连接正使用错误纠正模式则无需进行任何操作;否则需要开始使用错误纠正模式。
IDLE DEPARTURE
一段时间的空闲可能会导致连接的结束或者中间件撤销连接状态,需要低时延地解决状态撤销的情况。
NAT TABLE RESET
NAT盒子中存储的条目可能会过期(空闲超过30-120s就会过期),导致底层的UDP连接断开。若NAT中的条目过期导致连接断开,那么服务器将无法发送任何数据给客户端,知道客户端重连连接。
CONTINUATION WITH FULL CONNECTION STATE
客户端在建立过的连接上(空闲一段时间后)继续发送数据可能会导致连接断开。
Quic协议中,连接重连可以使用之前的证书,这个过程只需0RTT。但一开始只计划让一个连接存活约30s。这能避免NAT重新绑定(重新记录端口)的复杂性。考虑到电池寿命,没必要让接收端一直保持活跃状态,只需要协商好连接两端希望连接空闲后还需要维持多长时间。若在这段时间内恢复连接,那么连接上的加密元素、会话状态将保持有效。
CRYPTOGRAPHIC ELEMENTS
加密安全和经过身份验证的通道的初始协商将模仿TLS的过程,包括加密参数的协商、证书链的交换等,这个过程需要1RTT。客户端会记录协商结果。
RESERVED CRYPTOGRAPHIC COMMUNICATIONS STREAM
所有的加密交换都需要按序传输一系列消息,这对协议解析和纠正证明是至关重要的。因此,Quic模仿TLS,保留了一个流作为加密流,作为密码元信息通信的信道。加密流ID为1,因为这个流是由客户端发起的,因此必须为奇数(客户端发起的连接CID为奇数,服务端发起的连接CID为偶数)。使用保留的流ID将促进QUIC流代码的重用,促进加密交换。
加密流上的数据包丢失将导致一或多个并发流上的队头阻塞问题,有两个方法来避免:
- 关键包的冗余传输:例如初始密码连接建立包通常需要多次传输。冗余的传输包可以在初始传输包发送后一段时间再发送,以降低相关包都丢失的可能性。
- 关注密码规范更改的时间点,如密钥更改:新密钥将被异步使用,新密钥的验证意味着向新解密密钥的转换。发送方通常会等到收到加密流中更新加密密钥的接收方的数据已收到的确认后,才继续使用新密钥。该方法部署后,将不会有队头阻塞的问题,接收端在处理完密钥更新前的所有带序列号的包后便可以将旧密钥删除。
ENCRYPTION AND AUTHENTICATION
我们将使用AEAD来保护每个UDP数据包的大部分。通过为每个包从UDP包的序列号中衍生出一个IV,可以避免序列化解码。我们故意划分数据,每个独立的解密表节(使用IV)完全属于一个UDP包,而在每个UDP包中只有一个这样的节。
SESSION KEY UPGRADE
在一个连接的生命周期中,有多种改变会话密钥的因素:
- 从NULL加密(仅使用已知密钥进行身份验证)传输。客户端发送的初始化包不加密,而接下来的包加密。
- 在投机性连接上,它可能依赖于一个会话密钥,对于该密钥,不能确保完全的正向保密。在该连接建立后,需要更改会话密钥来保证完全的正向保密。
在密钥转变的过程中,相对于发送TLS修改密码消息(有混乱和队头阻塞的风险),我们依赖于尝试解密。当接收端意识到更换密钥的可能性,它可以尝试同时使用新旧两个密钥直到密钥更换成功。如果接收端接收到对端有新密钥的确认,它可以只接受用新密钥解密的包。这样,密钥转变的代价将最小,第三方也不会检测到这个过程。
PROTOCOL DETAILS: SPECIFICATION RATIONALE
DEPLOYMENT ISSUES
实验证明90-95%的用户能够使用充足的UDP连接来实现Quic连接。至于5%的用户连接受阻的主要原因是LAN防火墙。为了弥补这一点,所有的Quic连接建立时都会与TCP(或TLS)连接竞争,一旦Quic激活,所有的请求都会通过Quic连接。
ALTERNATE PROTOCOL HEADER
目前没有场景要求HTTP或HTTPS必须使用Quic连接,因此使用替代协议头来传递对Quic的支持,客户端和服务器在HTTP的替代协议头部使用Quic的Server Advertisement。当一个服务器收到一个非Quic请求,但该请求可以通过Quic服务,那么服务器会在响应的流中增加一个替代协议头部,如:
// 指定Quic为80端口上的替代协议
Alternate-Protocol: 80:quic
在UDP端口80(HTTP)和443(HTTPS)上支持Quic协议。
INITIAL (CONNECTION ESTABLISHMENT) PACKET DEFAULTS
所有的包都使用AEAD进行加密,第一个包使用默认NULL加密密钥,AEAD只是用来排除意外篡改(当作高质量的校验和)。
PROTOCOL OVERVIEW OF ELEMENTS
每个Quic连接(与一个UDP包集合相关)都有一个CID。每个数据包按顺序编号,包括数据包、控制包、FEC包和ACK包。
Quic连接能够快速恢复,当连接有超时的危险时,发送端能够发送一个连接建立包,而无需多余的往返时间。
FRAMING
QUIC PACKET FRAMING OVERVIEW
UDP包是Quic传输的基本单元,所有的数据会被分成多个块分别放置在一个UDP包中。
所有Quic包都由头部和负载两部分组成,其中,每个包中的负载是一个AEAD加密块:
- 数据包的负载包含一系列帧
- FEC包的负载包含冗余信息
如上图,Quic包的头部包含:
- Public:1字节,详述头部其他信息的布局。
- CID:8字节,连接的标志,冲突概率很低。
- 对服务器来说,当同时处理2^32个并发连接时,有50%的可能会冲突,此时用户可以暂时自动回退到使用TCP连接。服务器可能会跟客户端协商在一个指定的IP和端口上继续连接,此时服务器可以指示客户端说能接受值较小的CID。
- 对于客户端来说,其只会在它发起的单序号的Quic连接上接收到包,因此服务器无需在每个包中包含CID。当服务器收到客户端无需CID的请求时,服务器可以在Public Flags的第4位(值为8的位)标志CID被忽略了。
- Quic version:4字节,在连接建立的第一个包中提供,以使服务器知道客户端所用的协议版本号。连接建立后,该域将被忽略。
- Packet Sequence Number:除了对数据包进行排序、监视副本和通信丢失的包之外,这个数字也是加密的关键部分,其构成了用于解密每个数据包的IV的基础。由于一个连接中的数据包序号不能重复,因此该值理论上会比较大,但通常情况下不会太大。在任何时刻,只有少数包序号未被确认,因为发送端的缓存区需要缓冲需要打包的数据包,而其大小有限。另外,当需要重传数据时,Quic发送端选择将数据在一个新的包中重传,并使用STOP WAITING包让接收端无需再等待丢失的数据。
Payload Framing Overview
Quic包在头部之后是一个由AEAD算法进行验证的负载块,该块由一些冗余的身份验证位组成,并连接到一个与有效负载的明文大小相同的字节字符串。
解密后,负载块的组成如下:
-
Private Flags:1字节
Private Flags由密钥加密,对窃听者是不可见的。该标志中编码的一个值是FEC组的大小,并且说明了负载与帧开始位置的偏移量。另外该标志中还有两个位,一位是随机熵,另一位用于确定FEC组的最后一个包(即冗余FEC包,包含FEC组中所有负载文本的异或值)。
随机熵用于防止乐观ACK攻击,当接收端发送ACK时需要提供随机熵的哈希值以证明其真的收到了包。有一个问题是当一个包丢失了,其随机熵位就看不到了,为了避免接收端创建无穷无尽的丢失包列表,发送端定期发送一个更新说明到特定包之前的hash值。
FEC Final Bit,用于标志FEC组的最后一个包。目前FEC方法是将FEC包放在一系列由FEC保护的包后面,拥有该标志的包的负载需要单独处理,其值是FEC组其他负载的异或和
-
FEC Group number
互联网中通常会有1%+的丢包率,因此需要限制FEC组中包的数量不超过255个,实验建议由一个FEC包来保护10-20个数据包。而且过大的FEC组可能在减少时延方面与重传数据相比没有改进。
当发送端决定使用FEC方法时,便使用Private Flag来说明确实有一个嵌入的FEC组号字节,FEC组中的每个部分都有一个FEC组号偏移量以找到FEC组的第一个包。这个方法允许延迟决定FEC组的结尾,即无需知道组中有多少个包,只需要在没有数据传输时加上最后的FEC包就行。
-
Series of self-identifying Frames
每个QUIC包的大部分都是一个被称为帧的数据连接列表。每个帧的头几个字节标志了该帧,并且说明了该帧的格式以及内容。
Frames Within the Payload
每个帧都以一个帧类型字节开头,一般该字节的前几个位用于表明该帧的类型,其余位对帧内的格式进行编码。
特殊帧类型 常规帧类型Stream Frame
Stream Frame首部:
Stream Frame首部
每个Quic连接能够多路复用传输一些流,每个流帧为一个流传输应用数据。流帧也可以用于暗示打开一个流,流帧中包含一个标志位用于说明该帧是否是所属流的最后一部分。流帧的头部包含了:
- Type:说明帧类型,1fdoooss表示流帧;
- 流ID:说明该帧属于那个流;
- 偏移量:该流帧中的数据距离该流开始处的偏移量;
- 数据长度:该流帧中数据长度,该值可选,因为有时为了传输大文件,一个流帧会填满整个Quic包的负载,此时没必要说明数据长度,帧中是否包含数据长度字段由Type中的第6位(d)标识。
ACK Frame
ACK Frame:
ACK Frame
ACK帧是用于协调包丢失恢复的,类似与TCP中的ACK包。QUIC中的ACK总是累积的,因为新的ACK包含了足够的信息,任何先前的ACK都应该被丢弃,因此如果一个包含ACK帧的包丢失了,没必要重传ACK帧。
上图中,字段如下:
-
Largest Acked字段表示目前已接收的最大包序号。
-
Number Blocks-1、First Ack Block Length、Gap to next Block、Ack Block Length等字段从反方向(即包序号大到包序号小)列出哪些包已经接收,哪些包未被接收。
ACK帧中包含接收端认为丢失的包的NACK列表,因此ACK帧拥有丢失包的计数和列表。丢失包能够简洁地表示,但是对于一个包中能够列举的NACK大小有一定的限制(几百)。因此,当遇到NACK数太多这种少数情况,只有最老(序号最低)的丢失包被列举,这样能将有效的飞行窗口限制在10000个包内,这种情况称为截断ACK帧。为了解决这种情况,ACK包中的Largest Acked字段的值应该在NACK包列表中。例如发送端发送了序号为1-1000的1000个包,接收端只接受到包1和包1000,包2-999丢失了,假设ACK帧中最多只有200个NACK,即NACK列表为2-201,此时便需要截断ACK帧。可是如果该截断ACK帧的Largest Acked字段的值如果为1000,那么就像说包202-999已经接收了,因此该截断ACK帧的Largest Acked字段的值需要为201。但是这个情况将很快被自动解决,因为发送端收到该截断ACK帧后,会发送一个请求给服务端说无需再考虑序号为201以前的包。
-
另外,ACK帧中有一个域来标志最小未接收的包序号,以说明该序号之前的包无需再关注。
-
ACK帧中最后的元素与随机熵hash有关,用于派出乐观ACK攻击。
通常,一直只包含ACK帧的包是不会被确认的。但一个只包含ACK帧的包丢失的话是会被NACK的,因为接收端不知道包中的内容。当发送端接收到一个包含NACK的ACK帧时,会响应一个ACK帧告诉接收端停止NACK这个包。
与TCP一样,当发送端在一段时间(与RTT有关)未收到某个包的ACK,会重传该包的内容。重传的过程采用二进制退避算法,类似TCP,重传的包不受拥塞窗口限制。
CONGESTION CONTROL FRAME
拥塞控制帧用于传输相关信息以确定拥塞控制算法。在连接建立时,加密证书的协商过程也包括了拥塞控制算法的协商,Quic既支持类似TCP的拥塞控制算法,也支持其他算法。
拥塞控制帧可能包含包被接受的时间或接受包后多久发送ACK等信息,以协助计算RTT。
拥塞控制帧的具体域由部署的拥塞控制算法决定。
RST STREAM FRAME
RST FrameRST帧用于异常结束一个流,例如接收端想提前拒绝发送端发送更多的数据,或者发送已经没有数据要发送了。
CONNECTION CLOSE FRAME
连接关闭帧连接关闭帧用于快速关闭一个连接,并隐式关闭该连接中所有的流。为了实现连接快速断开,连接关闭帧经常跟着一个ACK帧,用于沟通连接断开时的状态,如连接断开前接收到的最后一个包序号。
GOAWAY STREAM FRAME
GOAWAY帧GOAWAY帧用于优雅地关闭一个连接,该连接上不会产生新的流,只有现有的流并且即将结束。
CONNECTION RESET ALTERNATIVES
Quic连接的两端都可以重置该连接。连接重置意味着该连接将永久弃用或断开,不会有通信发送。不像TCP只要注入一个RST包就能断开连接,Quic连接需要当足够的包丢失了并且没有收到ACK,中间件才能重置该连接。
沟通重置一个Quic连接的方法:
-
发送用一个包含GOAWAY帧或RST帧的标准包,该包与其他包一样需要进行验证。
-
使用单向的预先安排的重置nonce进行重置,这是一个Public Reset Packet,第三方能够检测该包的存在但无法伪造。
每个连接的重置nonce是在连接初期由连接两端指定的(可选的),生成该重置nonce的一端可以使用该nonce快速重置连接。例如服务器可以构造一个重置nonce(可以构造为CID的MAC),当连接的会话密钥失效或丢失了可以重置连接。会话密钥会在连接断开后不久失效,或者会在服务器重启时丢失。
当接收端接收到Public Reset Packet,它会验证该包的拥有权,并同意重置连接,开始构造一个新的连接。注意,nonce不是明文传输的,而是传输了一个有时间限制的拥有nonce的证明。
快速重置连接是必要的,假设在一个已经废弃的连接上收到一个包(不是连接建立包),如果直接忽略该包,那么发送端只能慢慢地舍弃该连接。
MALICIOUS RETURN ADDRESS REWRITING
通常NAT会重写源IP和端口,所以这些信息是没被AEAD加密的,恶意的中间件可以重写源IP或端口,导致接收端不知道向哪里发送响应。恶意的改写使得连接迁移的算法更加复杂。解决上述问题的方法是继续向旧的源IP发送数据,直到新的源IP发送足够的响应。例如发送所有的数据包到旧的IP、所有FEC包到新的IP,通过验证ACK来判断是否可以完全切换到新IP。
还有一种攻击,攻击者(客户端或中间件)建立一个连接,想服务器请求大量资源,并恶意修改接受响应地址,服务器会将大量的数据发送到响应地址,不知不觉间泛洪攻击了该地址。阻止这种攻击的自然方法是避免大量使用替换的源IP,直到一个包至少进行一次往返。
中间件可能会恶意攻击的事实,暗示了移动端连接迁移的速度可能会有限制。如果客户端能够意识到连接迁移即将到来,并发送一个加密的提示。
CRYPTOGRAPHIC STARTUP OVERVIEW
0RTT连接重连是由客户端在服务器的公钥还未更改的时间内建立的,这时客户端可以使用服务器公钥加密传输会话密钥等信息,在连接建立包可以跟上用初始化会话密钥加密的数据包。在这个过程连接传输的信息能被加密和验证,但没办法提供完美的正向加密。
在初始化会话密钥的保护下,加密HELLO消息可以交换足够的信息以生成改进的会话密钥,以提供完美的正向加密。之后每个部分就都可以使用改进的会话密钥。
为了减少无法预测的连接丢失的可能性,连接的两端需要指定连接空闲后需要维持会话状态多长时间。
1-RTT Fallback
0RTT重连是在服务器公钥还未更改时实现的,但若服务器公钥已经改变或服务器需要客户端地址额外的验证信息,服务器会拒绝客户端的重连请求,这时需要多加1RTT的验证过程,这个过程需要包括nonce,类似TLS的完整HELLO消息交换过程。
客户端响应了HELLO消息后,可能需要使用完整协商的会话密钥来加密重传先前的数据(0RTT重连时,客户端会向服务器发送数据,这些数据可能被服务器忽略了)。
若客户端第一次跟服务器建立连接,那么客户端会向服务器发送一个类似TLS HELLO消息。服务器会响应一个寻址挑战,或者响应类似TLS协议的HELLO消息(寻址挑战nonce可选)。有时扩展HELLO序列包含一个证书链可能一个UDP包装不下,这时可以用更大的流来传输。
2-RTT Worst Case Fallback
如果服务器需要响应一个广泛的证书链,服务器可能拒绝向客户端发送多包负载,直到客户端能够证明它控制了特定的源地址,这时HELLO消息互换需要2RTT。在这个最糟糕的情况:
- 客户端发送HELLO消息;
- 服务器拒绝HELLO消息,但会响应一个token证明对客户端地址的拥有权以及一个服务器证书,但不发送证书链(太大,放不下一个UDP包);
- 客户端在第二个HELLO消息使用token,但得等到收到完整的证书链才会信任证书;
- 服务器拒绝第二个HELLO消息,但至少会发送一个完全充实的证书链(需要使用几个UDP包发送);
- 客户端再次发送HELLO消息,同时也可以发送数据包,如0RTT情况。
网友评论