TCP系列51—拥塞控制—14、TLP、ER与拥塞控制
一、概述
这里的重点是介绍TLP、ER与拥塞控制并不是介绍TLP和ER本身,因此TLP和ER的详细内容请翻前文。
在TLP与拥塞控制的交互中有几个点需要注意
1、TLP触发的重传后,TCP仍然处于Open状态,TLP重传也不会更新lost_out等状态变量,TLP重传发出的是探测报文并不是因为当前确定丢包而重传。
2、TLP与ER/FACK是相互组合的,TLP触发的FACK重传与之前介绍的FACK下快速恢复一致。TLP和ER的耦合更深一些,TLP只能触发延迟ER,而ER定时器超时,延迟ER重传将TCP切换到Recovery状态的时候并不会更新cwnd,但是同样会更新prior_ssthresh=max(ssthresh,3/4*cwnd), ssthresh=max(cwnd/2,2),prior_cwnd=cwnd,prr_delivered=0, prr_out=0, high_seq=snd.nxt,cwnd不变。但是随后切换到Recovery状态后cwnd的更新流程就与之前文章介绍的一致了。
我们还要额外说一下TLP的loss dection,在重传部分介绍TLP的时候,提到过TLP的丢包探测,TLP在发出loss probe报文的时候,是假设当前没有发生拥塞的,即没有丢包,TCP的状态继续停留在Open态。因而TLP需要一种方法来检测是否真的发生了丢包,当检测到真的发生了丢包的时候,进行拥塞窗口的调整。当TLP发出的loss probe报文重传的TCP数据包时候,会记录一个状态变量tlp_high_seq=snd.nxt,tlp_high_seq这个字段在TCP进入Recovery、CWR或者RTO超时场景下都会重置为0。当TLP收到ACK确认包的时候,会按照下面的流程进行loss dection:
1、当tlp_high_seq==0,或者acknumber<tlp_high_seq时候,结束处理,不再执行下面其他步骤。
2、如果这个ACK报文带有DSACK信息,那么说明原始报文和TLP重传报文都到达了对端,更新tlp_high_seq=0,结束处理,不再执行下面其他步骤。
3、如果acknumber>tlp_high_seq,说明原始传输的报文或者TLP重传报文有丢包,因此需要调整拥塞窗口。首先先初始化拥塞窗口削减过程,设置tlp_high_seq=0, high_seq=0, cwnd_cnt=0, prr_delivered=0, prr_out=0, ssthresh=max(cwnd/2,2),临时把TCP的拥塞状态设置为CWR然后进行CWR状态下的拥塞削减结束过程,更新cwnd=ssthresh,最后在根据当前sacked_out等情况决定是切换到Open状态或者是Disorder状态,结束处理,不再执行下面其他步骤。
4、如果这个ACK报文没有传输TCP数据(pure ACK),而且ack number或者SACK块没有确认新数据,也不是窗口更新消息,(这个ACK称呼为TLP dup ACK),说明原始报文和TLP重传报文都到达了对端,更新tlp_high_seq=0。
5、如果上面的条件都不满足,那么TLP结束对这个ACK的处理,等待随后的ACK。
如果不理解上面的这个loss dection流程,那么可以参考之前重传部分TLP的文章或者TLP的官方文档,或者参考下面的示例来理解。
二、wireshark示例
1、TLP&增强ER与拥塞控制
在执行本示例前,如下设置TCP参数
******@Inspiron:~$sudo ip route add local 127.0.0.2 dev lo congctl reno initcwnd 3 ssthresh lock 20 #参考本系列destination metric文章
******@Inspiron:~$ sudo ethtool -K lo tso off gso off #关闭tso gso以方便观察cwnd变化
业务场景:server端与client建立连接后,先休眠1000ms,接着以3ms为间隔连续发送5个数据包,其中No10和No11这两个数据包丢失,以触发loss probe及增前ER,client对于收到的每个数据包都会回复一个ACK确认包,最终TCP的交互如wireshark截图所示。
No1-No13:不解释了,最终收到No13确认包后, ssthresh=20, cwnd=6,cwnd_cnt=0, packets_out=2, sacked_out=0, lost_out=0, retrans_out=0, fackets_out=0,server端处于Open状态,PTO定时器定时时间大约为100ms。
No14:PTO定时器超时,触发loss probe,重传最后发出的一个数据包(即No11)。注意这里虽然是tcp重传,但是并不会更新lost_out等参数,server端TCP仍然会停留在Open状态, ssthresh=20, cwnd=6,cwnd_cnt=0, packets_out=2, sacked_out=0, lost_out=0, retrans_out=0, fackets_out=0。重传后设置RTO定时器,RTO超时时间大约为250ms。
No15:No15是client收到No14数据包后回复的ACK确认包,通过SACK选项确认了No14报文,更新sacked_out=1,fackets_out=2。此时packets_out=2<4,且缓存中没有待发送数据,因此满足触发延迟ER的条件,取消RTO定时器并启动ER定时器,定时时间为RTT/4,大约为13ms。接着因为收到dup ACK,server端TCP从Open状态切换到Disorder状态。
No16:ER定时器超时,重传No10报文,server端TCP从Disorder状态切换到Recovery状态,注意这里ER重传触发TCP进入Disorder状态的时候,只会更新prior_ssthresh=max(ssthresh,3/4*cwnd)=20, ssthresh=max(cwnd/2,2)=3, prior_cwnd=cwnd=6,prr_delivered=0, prr_out=0, high_seq=251,而cwnd在触发ER重传的时候并不会更新,这是与之前介绍的FACK、SACK、SACK关闭场景下的触发的快速重传的重要区别。除了ER重传进入Recovery状态时候初始化与之前介绍的快速重传不一样外,在进入Recovery状态后的各种处理都是一样的了。
No17:server端收到对应No16的ACK确认包,同时这个确认包的ack number=251=high_seq,此时SACK处于打开的状态,因此更新cwnd=ssthresh=3,TCP从Recovery状态切换到Open状态,接着进行reno的拥塞避免过程,更新cwnd_cnt=2。
2、TLP Loss Detection与拥塞控制
在执行本示例前,如下设置TCP参数
******@Inspiron:~$sudo ip route add local 127.0.0.2 dev lo congctl reno initcwnd 3 ssthresh lock 20 #参考本系列destination metric文章
******@Inspiron:~$ sudo ethtool -K lo tso off gso off #关闭tso gso以方便观察cwnd变化
业务场景:server端与client建立连接后,先休眠1000ms,接着以3ms为间隔连续发送5个数据包,其中No11数据包丢失,以触发loss probe,接着休眠353ms写入50bytes的数据。client对于收到的每个数据包都会回复一个ACK确认包,最终TCP的交互如wireshark截图所示。
No1-No14:这个过程不解释了,注意No6-No14报文一直在重设TLP定时器,server在收到No14报文后,发现当前packets_out=1,因此更新PTO =max(2*SRTT, 1.5*SRTT+WCDelAckT)≈275,PTO=min(PTO, RTO)≈250,因此No14后设置PTO定时器定时时间约为250ms, ssthresh=20, cwnd=7,cwnd_cnt=0, packets_out=1, sacked_out=0, lost_out=0, retrans_out=0, fackets_out=0,server端TCP处于Open状态。
No15:PTO定时器超时后触发尾包重传,即No15,TLP超时触发的尾包重传是假设当前没有发生网络拥塞的,因此并不会将TCP切换到Loss状态或者Recovery状态,也不会更新lost_out字段,发出No15后, ssthresh=20, cwnd=7,cwnd_cnt=0, packets_out=1, sacked_out=0, lost_out=0, retrans_out=0, fackets_out=0,tlp_high_seq=251,server端TCP处于Open状态。
No16:server端休眠唤醒后,进行一次write操作,写入50bytes的数据,更新packets_out=2。
No17:client收到No15后,回复一个确认包,server收到No17后首先更新packets_out=1。这个确认包的acknumber=251,根据概述里面的流程描述,TLP并不会处理这个ACK确认包,而是会等待下一个ACK。接着进行reno的慢启动处理,更新cwnd=8。最终 ssthresh=20, cwnd=8,cwnd_cnt=0, packets_out=1, sacked_out=0, lost_out=0, retrans_out=0, fackets_out=0,tlp_high_seq=251,server端TCP处于Open状态
No18:client收到No16后回复No18,server端收到No18后更新packets_out=0,TLP收到这个ACK报文后,发现满足概述里面ACK处理流程的第3步骤,因此更新tlp_high_seq=0,ssthresh=max(cwnd/2,2)=4,cwnd=ssthresh=4。最终ssthresh=4, cwnd=4,cwnd_cnt=0, packets_out=0, sacked_out=0, lost_out=0, retrans_out=0, fackets_out=0,tlp_high_seq=0,server端TCP处于Open状态。
这里假如No11没有丢包,而只是RTT时延突然变大导致server端同样触发TLP流程,那么server端应该会首先收到一个类似No17的报文(client对No11的确认包),TLP不处理这个ACK报文,然后会在收到一个TLP dup ACK(client对于No15的确认包),TLP按照概述里面的第4步更新tlp_high_seq=0,因而也就不会触发ssthresh和cwnd的削减过程。
补充说明:
1、延迟ER处理tcp_resume_early_retransmit
2、TLP ACK处理tcp_process_tlp_ack