一、概述
FRTO虚假超时重传检测我们之前重传章节的文章已经介绍过了,这里不再重复介绍,针对后面的示例在说明两点
1、FRTO只能用于虚假超时重传的探测,不能用于虚假快速重传的探测。
2、延迟ER重传触发的进入Recovery状态时候,并不会立即更新cwnd。
本篇在演示FRTO的同时,还会涉及到ER超时重传、TLP探测、SACK关闭场景下的拥塞撤销,后面或者前面都会有针对这些场景的专门介绍文章。
一、wireshark示例
1、FRTO与ER
我们通过一个示例看一下关闭SACK时候,tcp_sack=0,FRTO虚假超时探测和拥塞撤销的处理,同时我们需要设置关闭TSopt选项,否则Eifel探测会先于FRTO探测到虚假超时,tcp_timestamps=0,后面我们会介绍到Eifel探测。如下设置相关参数
******@Inspiron:~$sudo ip route add local 127.0.0.2 dev lo congctl reno initcwnd 3 ssthresh lock 15 #参考本系列destination metric文章
******@Inspiron:~$ sudo ethtool -K lo tso off gso off #关闭tso gso以方便观察cwnd变化
业务场景:server端在与client端建立连接后休眠1s,然后写入50bytes数据,接着休眠46ms,然后以3ms为间隔连续写入6次,每次写入50bytes。client在收到第一个数据包并回复ACK确认包后,server与client之间的RTT突然变大(RTT大约从50ms突变为300ms,例如移动终端切换场景),然后server端触发虚假RTO超时。最终TCP流交互如下图所示。
No1-No9:server端慢启动过程,不再解释,注意因为关闭了SACK功能,因此TLP功能也不会使能,因此server端在收到No7数据包的时候会重新启动RTO定时器,后面也不会被改写为TLP定时器。发出No9后ssthresh=15, cwnd=4, packets_out=4, sacked_out=0, lost_out=0, retrans_out=0。
No10:RTO超时后,server端从Open状态切换为Loss状态,更新prior_ssthresh=max(ssthresh, cwnd*3/4)=15, ssthresh=max(cwnd/2, 2)=2, cwnd=1,并把No5、No6、No8、No9四个数据包标记为lost,lost_out=4,接着重传No5数据包,retrans_out=1。并且当前满足使能FRTO的条件。
No11-No13:No11报文为client对No5的确认包,server端收到No11报文后,更新packets_out=3, lost_out=3,retrans_out=0,发现当前使能了FRTO功能,并且No11这个报文的ack number确认了新的数据包,因此server端FRTO尝试发出新的未发送数据包,此时cwnd=1,in_flight=3-(3+0)+0=0,因此发出一个数据包即No12,FRTO对这个ACK报文的处理结束。接着reno的慢启动更新cwnd=2,又发出一个新数据包即No13。最终packets_out=5。
No14:server端收到No14报文后,更新packets_out=4,lost_out=2,发现No14这个确认包相比No11又确认了新的数据包,而且新确认的数据包是未曾重传过的No6数据包,因此server端FRTO认定之前的No10重传为虚假超时重传,接着进行拥塞撤销,更新cwnd=max(cwnd,2*ssthresh)=4,ssthresh = max(ssthresh,prior_ssthresh)=15,并取消No8、No9数据包的lost标记,更新lost=0,server端从Loss状态切换为Open状态。接着reno的慢启动更新cwnd=5。
No15-No16:同样是延迟到达的ACK报文,最终ssthresh=15, cwnd=7, packets_out=2, sacked_out=0, lost_out=0, retrans_out=0。
No17:No17是No10的确认包,是一个dup ACK,server端收到No17后更新sacked_out=1,并从Open状态切换为Disorder状态,此时满足
sacked_out>0, packets_out >= (sacked_out + 1) , packets_out < 4这几个条件且没有待发送的新数据,因此触发延迟ER,设置ER定时器为RTT/4(大约为35ms)。处理完No17后ssthresh=15, cwnd=7, packets_out=2, sacked_out=1, lost_out=0, retrans_out=0。
No18:ER定时器超时后,server端从Disorder状态切换到Recovery状态,更新high_seq=351, prior_ssthresh=max(ssthresh, cwnd*3/4)=15, ssthresh=max(cwnd/2, 2)=3,并把No12数据包标记为lost,更新lost_out=1,接着重传这个数据包,即发出No18,并更新retrans_out=1, prr_out=1。最终ssthresh=3, cwnd=7, packets_out=2, sacked_out=1, lost_out=1, retrans_out=1。注意ER定时器超时重传的时候并没有直接削减cwnd。
No19-No20:No19是一个partial ACK,更新packets_out=1, sacked_out=0, lost_out=0, retrans_out=0,SACK关闭场景下收到partial ACK,会立即把partial ACK的ack number对应的数据包(即No13)标记为lost,因而又更新lost_out=1,接着进入更新cwnd的流程,注意这里newly_acked_sacked=0, 因此并不会更新cwnd ,接着重传标记为lost的数据包即No20,更新retrans_out=1,prr_out=2。
No21:No21是No13数据包的确认包,server端收到No21的时候,更新packets_out=0,lost_out=0,retrans_out=0,Ack=351=high_seq,即正好到达Recovery point,但是因为此时SACK处于关闭状态,因此server端并不会从Recovery状态切换到Open状态,更新cwnd=min(cwnd, in_flight+dupthresh)=3, 最终处理完No21后,ssthresh=3, cwnd=3, packets_out=0, sacked_out=0, lost_out=0, retrans_out=0, prr_delivered=0, prr_out=2。(No21数据包的处理请参考介绍SACK关闭场景拥塞撤销处理的文章)。
No22-No23:server端收到虚假重传的dup ACK的确认包,最终处理完No23后,server端处于Recovery状态,ssthresh=3, cwnd=3, packets_out=0, sacked_out=0, lost_out=0, retrans_out=0, prr_delivered=0, prr_out=2。
2、FRTO与TLP
我们会在DSACK拥塞撤销的文章中用示例演示了server与client协商好TSopt后,如果收到不带有TSopt选项的数据包,虽然协议建议静默丢弃这种报文,但是linux仍然正常接收处理。这个示例我们先来看一下client如果与server协商好SACK选项后,而client侧代码bug或者其他原因导致收到乱序包后回复的dup ACK没有携带SACK信息时候,server端会如何处理。同样在执行示例前如下设置相关TCP参数:
******@Inspiron:~$sudo ip route add local 127.0.0.2 dev lo congctl reno initcwnd 5 ssthresh lock 4 #参考本系列destination metric文章
******@Inspiron:~$ sudo ethtool -K lo tso off gso off #关闭tso gso以方便观察cwnd变化
业务场景:server端在与client端建立连接后休眠1000ms,接着以3ms为间隔,连续发送10个数据包,每个数据包的大小为50bytes,其中高亮标出的No7数据包在传输中丢失。client对于收到的乱序的报文回复的dup ACK确认包并不带有SACK选项。最终如下图所示:
No1-No12:连接建立后的拥塞避免过程,最终发出No23后, ssthresh=4, cwnd=4,cwnd_cnt=1, packets_out=5, sacked_out=0, lost_out=0, retrans_out=0, fackets_out=0,server端处于Open状态,并设置了TLP定时器,PTO=2*RTT,大约为100ms。
No13:这个dup ACK是对No8报文的回复,但是可以看到里面并没有SACK选项,但是在三次握手的时候client和server端是协商了SACK的,因此server端在收到这种不带有SACK选项的dup ACK的时候,并不会更新sacked_out,因此server端也并不会进入Disorder状态。但是server端在收到这个dup ACK的时候,会先取消PTO定时器并设置成RTO定时器,然后检查收到报文的ack number是否能取消RTO定时器,因为收到的报文ack number并没有完整确认数据,因此仍然保留有RTO定时器的设置,接着server端在检查如果当前有设置RTO定时器,就会尝试取消RTO定时器设置为PTO定时器。因此收到No13后,server端最终又会重新设置PTO定时器为2*RTT,大约为100ms。
No14-No16:这几个报文的处理与No13类似,最终收到No16后,重设PTO定时器为100ms。
No17:PTO定时器超时,触发loss probe过程,此时缓存中有待发送的新数据,因此发出No17数据包,更新packets_out=6,并取消PTO定时器设置成RTO定时器。
No18:server端收到No18后又会把RTO定时器重设为PTO定时器,定时时间大约为100ms。
No19-No24:这几个数据包的处理与No17、No18类似。收到No24后,重设PTO定时器,定时时间为100ms。此时 ssthresh=5, cwnd=4,cwnd_cnt=1, packets_out=9, sacked_out=0, lost_out=0, retrans_out=0, fackets_out=0,server端处于Open状态,注意此时cwnd=4,in_flight=9,可以看到loss probe报文并不会受到拥塞控制cwnd的限制。
No25:当PTO定时器再次超时的时候,此时server端的缓存中已经没有待发送的新数据包,因此重传最后发送的数据包(即No23),注意虽然No25发生了数据包的重传,但是TLP重传后,server端TCP仍然会停留在Open状态,而且并不会更新lost_out等字段,因此重传完No25后, ssthresh=4, cwnd=4,cwnd_cnt=1, packets_out=9, sacked_out=0, lost_out=0, retrans_out=0, fackets_out=0,server端TCP处于Open状态,并且当前设置有RTO定时器。
No26:server端在收到No26这个dup ACK后的处理与No13类似,最终会把RTO定时器重设为PTO定时器,定时时间大约为100ms。
No27:No26设置的PTO定时器超时后发现当前已经有了一个TLP超时重传(即No25),因此不会在进行TLP尾包重传,而是设置RTO定时器,定时时间大约为250ms,最终RTO超时触发No27重传,此时server端由Open状态切换为Loss状态,发出No27后以指数回退重设RTO定时器,相关状态变量如下,prior_ssthresh=4, ssthresh=2, cwnd=1,cwnd_cnt=1, packets_out=9, sacked_out=0, lost_out=9, retrans_out=1, fackets_out=0。
No28:No28的ack number确认了未重传的数据包,因此触发FRTO拥塞撤销,更新cwnd=max(cwnd,2*ssthresh)=4,ssthresh = max(ssthresh,prior_ssthresh)=4,同时server端从Loss状态切换为Open状态,然后在Open状态下进入reno的拥塞避免过程,No28的ack number新确认了9个数据包,9/cwnd=2,因此更新cwnd=cwnd+2=6,cwnd_cnt=9-2*4=1,最终ssthresh=4, cwnd=6,cwnd_cnt=1, packets_out=0, sacked_out=0, lost_out=9, retrans_out=0, fackets_out=0