队头阻塞解决:
TCP采用正面确认和超时重传保证可靠传输。一个包发送后,必须收到ACK确认它已经接收成功,才会发送下一个包。因此一个包持续没有收到ACK,不停的超时就会不停的重新传这个包,这就导致后面要发送的包阻塞住了。这是形象的理解,真实的情况是,TCP一次可以发送一个窗口的包,但需要这个窗口的包都收到确认,才会滑动窗口,发送下一个窗口。http2用复用tcp来解决它,但对于堵住那一路,还是有对头阻塞。
quic的解决:首先它没有滑动窗口,只有拥塞控制窗口,他为了两种窗口,一种是connect级别,一种是stream级别,每个stream有自己的窗口大小限制,每个connect大小就是所有stream加起来的大小;接下来的发送和接收端都是发送端:因为要发送的数据包的packet number是递增的,即使是重传包,丢掉了就后续重新传递,因此没有被ack的包不会阻塞拥塞控制窗口的滑动。tcp是丢的包必须被ack才往下走,quic是直接往下走,如果知道了哪些包已经丢失,那么把这些包放入重传队列,这些包后续将要被重传。接收端:这里需要对流进行整理排序,quic一条链路上有不同的流,每个流在发送和接收端都维护自己的拥塞控制窗口,其中一个流有包丢失在接收端拥塞控制窗口向右滑动时,就会引起队头阻塞,但不会阻塞其他流的拥塞控制窗口。他们管这叫做解决队头阻塞,这其实是极大缓解了。只要是可靠传输就不能没有队头阻塞,它是可靠传输的保证。
精准的rtt计算:
包含在服务器的delay时间,可以分清楚原始包ack和重传包ack,虽然packet number不同但是frame里的offer set相同;像tcp就无法区分,因为它的number是一样的。如果把重传包ack当作原始包ack,那么rtt偏大。如果把原始包ack当作重传包的ack,则rtt偏小,这里是ack还没到就重传,结果先后收到原始包ack和重传包ack。
流量控制:
通过window_update帧告诉对端自己可以接收的字节数,这样发送方就不会发送超过这个数量的数据。
通过BlockFrame告诉对方由于流量被控制阻塞了,无法发送数据。
quic的流量控制分为stream级别和connect级别;
stream级别限制单个流的窗口,防止它消耗了整个connect的窗口。在握手时通过传输参数(transport parameters)设置stream的初始值。如果发送方到达了限制值,则通过STREAM_DATA_BLOCKED帧通知接收方。如果接收方可以扩展更大的窗口,则通过MAX_STREAM_DATA帧通知对方增加。如果发送方违反流量控制的限制,接收方可以关闭连接听返回FLOW_CONTROL_ERROR错误。
connect级别,限制connect中所有streams相加起来的总字节数,防止发送方超过connection的窗口。在握手时接收方通过传输参数(transport parameters)设置发送方的connection的初始限制。发送方计算所有streams的窗口,与初始值进行比对,如果达到限制则发送STREAM_BLOCKED帧告知接收方。接收方通过MAX_DATA帧通知发送方增加。
连接迁移:
tcp使用源IP,目的IP,源端口,目的端口,协议号这五元组唯一标识一个连接。对于quic,客户端发送初始包时,会填充自己的源ID,此时不知道目标ID,会填充一个随机值。直到接收到服务器的包时,才会拿到其中的源ID,作为后续发送的目标ID。服务器那边,接收到客户端的包后,拿到其中的源ID,作为后续发包的目的ID。这些ID不会再改变,后续只有当接收到 NEW_CONNECTION_ID 帧时,才允许对目标连接ID进行更改。
对于客户端无论源IP如何切换,源ID是不变的,服务器是感受不到的,这叫连接迁移。但真正迁移完成后客户端需要提供新的源ID。
1.客户端的ip发生变化,发送信息通知服务器,告知进行连接迁移。
2.客户端启动路径验证,验证新路径可达性(reachability)。
2.1客户端发送PATH_CHALLENGE帧的探测包(Probing Packet),PATH_CHALLENGE帧里包含一个不可预测的随机值。
2.2服务器响应PATH_RESPONSE帧里包含前一步PATH_CHALLENGE接收到的随机值,发送相应探测包。
2.3客户端接收到服务器的PATH_RESPONSE,验证payload里面的值是否正确。
3.之后服务器也需要路径验证。
新路径的链路容量和路由,可能与旧的路径都不同,因此已经发出去的包不能用于新路径的拥塞控制窗口统计,这些包的RTT也不能用于新路径。新的路径的必须重置拥塞控制窗口,rtt重新计算。除非新路径只改了端口号,这一般是由于NAT重新绑定或者其他中间设备活动的结果。
连接迁移原理
低连接延时:
传统http2+tls1.2,有tcp三次握手,tls三次握手,就有3个rtt。http3+tls1.3,quic+tls虽然有四次握手,但后两次握手已经开始传输数据,因此只有1rtt。后续虽然有两次握手但是都传输数据,称为0rtt握手。
0RTT原理:
服务器随机生成一个Ks_pri作为私钥,然后生成质数p和整数g,通过前三个数算出Ks_pub,将{g, p, Ks_pub}三元组打包成config,等待client连接。
1.client首次发起连接,简单发送client hello给server。
2.server将已经生成好的config返回给client。
3.client随机生成一个Kc_pri作为私钥,并根据config中的p和g计算出出公钥Kc_pub。
4.client计算通信使用的密钥K=算法(Kc_pri, Ks_pub)
5.client用k加密需要发送的业务数据,并带上自己的Kc_pub一起发送给server。
6.server计算K=算法(Ks_pri, Kc_pub),根据笛福赫尔曼密钥交换的原理可以证明两端计算的K是一样的。
首次建立连接是从4开始已经使用加密通信了,因此是1个RTT。
二次建立连接,因为client端已经有config配置了,所以直接从3开始,所以建立连接是0RTT,直接加密发送业务数据,并带上自己的Kc_pub。
地址验证:
地址验证的主要目的是为了防止放大攻击。服务器生成一个令牌(token)并通过重试包(Retry packet)响应给客户端,客户端必须再后续的初始包(Initial packet)带上这个令牌,以证明其不是攻击者。路径验证是地址可达性验证,它是地址验证的一部分。
Quic的ACK Frame 支持256个NACK 区间,相比于TCP的SACK(Selective Acknowledgment)更弹性化
tcp的sack是否和Nack机制相同,quic有nack?
断网重新?
quic能实现向前冗余纠错,比如握手消息丢包后能够根据冗余的信息还原出。
如图一个连接中的每个流在接收端都有自己的拥塞控制窗口,当已读取数据大于总的缓存的1/2,那么开始向右滑动,把已读数据上交给应用层,在服务器端增加缓存来抗乱序,丢包,延时应该是在给这个拥塞控制窗口加缓存。
放大攻击:攻击者把包的源地址伪装成受害者地址,发向服务器,这些请求往往流量很小,但服务器却成倍的返回流量,造成服务器过载,其他请求无法响应;同时受害者地址也被大量流量攻击,造成其过载。
ALPN(Application-Layer Protocol Negotiation)是TLS握手过程中的一个扩展,用于协商应用层协议。它像sdp是一种协议协商选择的协议,ALPN允许客户端和服务器在TLS握手阶段就确定要使用的高层协议,比如HTTP/1.1、HTTP/2或HTTP/3。
设置客户端支持的ALPN协议列表
context.set_alpn_protocols([‘http/1.1’, ‘h2’])
0RTT原理,加密交互过程