写前一篇文章TCP的乱序和丢包判断(附Reordering更新算法)-理论的时候,我觉得我在一边拉一边吃,玩的都是排泄物,言之无味,不知所云,我想把一些能看得见摸得着的东西独立出来,就成了本文,如果有一天我忘掉了TCP的细节,我想我直接把本文的例子跑一遍,应该就能拾起个大概了。

声明

本文完全旨在解释上一篇文章里那些枯燥的理论,我实在是觉得自己文字功底差,一直以来都倾向于用例子来给出解释。花了点时间整理了几个用例,希望能把问题解释清楚。在用实例解释问题的时候,最忌讳的是把很多因素杂糅了一起,因为本来就是通过特例解释,并没有完备性支撑,所以我尽量把问题孤立化,加之以前也写了不少文章解释其它的方面,所以本文的所有用例均采用以下的配置:

net.ipv4.tcp_fack = 0
net.ipv4.tcp_sack = 1
net.ipv4.tcp_congestion_control = cubic

关于fack开启的情形,请自行分析。另外本文的主要目的是解释reordering相关的,不是拥塞算法,所以默认采用了cubic算法,至于其它的比如bbr算法(以及任何携带cong_control回调的拥塞控制算法)下是什么表现,本文不涉及。

  另外,本文的所有实例均是packetdrill脚本,如果你不会的话就请自行谷歌百度packetdrill的使用方法并下载安装,本文并不负责介绍关于packetdrill的任何东西,给出一个链接算是比较厚道了:Packetdrill 简明使用手册

case 1:认识乱序空洞左边缘确定

这个用例可以让你清晰看懂如何来确定乱序空洞的边缘在哪里以及如何确定。为此我准备了两个packetdrill脚本来进行对比性解释。首先看第一个,packetdrill代码如下:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4+0  write(4, ..., 1000) = 1000
// 确保已经确认到了1001
+.1 < . 1:1(0) ack 1001 win 32792
// 连续发送10个段
+0  write(4, ..., 10000) = 10000+.0 %{ print tcpi_reordering }%
// 重复确认1001,同时携带一个6001-7001的SACK
+.1 < . 1:1(0) ack 1001 win 257 <sack 6001:7001,nop,nop>
// 随即重复确认1001,同时再携带两个SACK
+.0 < . 1:1(0) ack 1001 win 257 <sack 2001:3001 9001:10001,nop,nop>
+.0 %{ print tcpi_reordering }%// 最终完全确认10个数据包
+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

以下是脚本的输出:

3
8
8

下面我给出一个解析,解释发生了什么。我把整个事情发生的过程整理成下图:

可见,虽然输出的是3,8,8,但事实上,由于reordering值的更新是单调递增的,所以reordering值经过的第二次8到7的更新是失败的,真实的reordering更新尝试应该是3,8,7.

  最终我们来看一下以上脚本过程的抓包:

第一个脚本到此结束了。

  接下来我们看下case 1的第二个例子,该例子说明了乱序空洞的右边缘如何确定。首先还是先给出packetdrill脚本的代码:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0 +0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0  +0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4  +0  write(4, ..., 1000) = 1000
+.1 < . 1:1(0) ack 1001 win 32792
+0  write(4, ..., 10000) = 10000  +.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 2001:3001 9001:10001,nop,nop>
+.0 < . 1:1(0) ack 1001 win 257 <sack 6001:7001,nop,nop>
+.0 %{ print tcpi_reordering }%+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

它的输出是:

3
4
7

以下是关于该脚本运行时的图解:

依然有抓包来确认:

也许,到底为止,事情应该结束了,你也应该完全明白了乱序空洞左右边缘的确定方法以及reordering值的更新机制,但是且慢,还有一个更好的例子呢。该case的最后一个例子,旨在解释在收到携带SACK的ACK包时,遍历传输队列以求取乱序空洞左右边缘的过程。该例子的packetdrill代码如下:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4+0  write(4, ..., 1000) = 1000
+.1 < . 1:1(0) ack 1001 win 32792
+0  write(4, ..., 10000) = 10000+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 3001:4001,nop,nop>
+.0 < . 1:1(0) ack 1001 win 257 <sack 4001:5001 9001:10001,nop,nop>
+.0 %{ print tcpi_reordering }%+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

它的输出是:

3
3
3

为什么reordering值没有变化?这是一个问题。按照左边缘和右边缘的确定方法,右边缘显然是9001开始的数据包,而左边缘是4001开始的数据包,这样一来reordering的值应该更新为6才对啊!但事实证明,reordering值并没有变化,依然是3,这是为什么?因为后一个ACK包携带的第一个SACK包在之前的最右边的SACK包3001之后,这明显是一个按序的确认,何来乱序呢?

  从常理上分析更容易理解,第一个SACK确认了3001开始的一个数据包,第二个SACK确认了从4001开始以及从9001开始的两个数据包,先发送的先被确认,这并不能表明出现了乱序啊,事实证明确实没有判定为乱序。确认了这一点之后,我们再来看收到SACK之后遍历TCP传输队列以确定乱序空洞左右边缘的算法:

for each skb in write-queue  if thisSACK contains skb && skb.SACKed == FALSE && skb.RETRANS == FALSE && skb < HrHl = skbif thisSACK contains skb  Hr = skbskb.SACKed = TRUEif Hr > Hl && (Hr - Hl + 1 > reordering)reordering = Hr - Hl + 1

请注意skb < Hr这个判断条件,想要让一个最新被SACK的数据包成为左边缘,不仅仅要求它此前没有被SACK以及此前没有被重传,还要求它不超过此前的右边缘。有了这个判定,对于事情的理解就简单多了。有了这个理解,我们来猜一下下面脚本的结果:

...
+0  write(4, ..., 10000) = 10000
+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 3001:4001 8001:9001,nop,nop>
+.0 < . 1:1(0) ack 1001 win 257 <sack 4001:5001 9001:10001,nop,nop>
+.0 %{ print tcpi_reordering }%
+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

显然,答案是:

3
6
8

好的,现在我们进入下一个更加简单些的用例。


case 2 认识RACK机制与reordering值的关系

依旧先看一个脚本:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4+0  write(4, ..., 1000) = 1000+.1 < . 1:1(0) ack 1001 win 32792+0  write(4, ..., 10000) = 10000+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 2001:3001 9001:10001,nop,nop>
// 间隔一些时间再SACK新的数据包
+.1 < . 1:1(0) ack 1001 win 257 <sack 5001:6001,nop,nop>
+.0 %{ print tcpi_reordering }%+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

输出是:

3
5
6

以下给出一个图解,解释一下为什么:

下面是抓包的分析确认:

如果我们连续发送两个SACK,不给RACK定时器超时的机会会怎样?下面我们就试一下,请运行下面的packetdrill脚本:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4+0  write(4, ..., 1000) = 1000+.1 < . 1:1(0) ack 1001 win 32792+0  write(4, ..., 10000) = 10000+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 2001:3001 9001:10001,nop,nop>
// 立即SACK新的数据包
+.0 < . 1:1(0) ack 1001 win 257 <sack 5001:6001,nop,nop>
+.0 %{ print tcpi_reordering }%+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

输出是:

3
5
7

下面是一个图解:

依然给出抓包分析:

看来,归根结底这并不是RACK的影响,而是RACK定时器的触发影响了TCP的拥塞窗口,导致两种情况下重传的数据包数量不同,进而造成了两种情况下乱序空洞的左边缘不同(左边缘不能被重传过),最终左右边缘的间距不同。

  接下来,我们看个更简单的用例。


case 3 认识reordering更新与快速重传的先后顺序

假设当前的reordering值是默认值3,开启SACK的情况下,有人会说,为什么已经有3个数据包被SACK,依然没有触发快速重传呢?比如下面的脚本所示:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4+0  write(4, ..., 1000) = 1000+.1 < . 1:1(0) ack 1001 win 32792+0  write(4, ..., 10000) = 10000+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 5001:6001 9001:10001,nop,nop>
// 如果下面的这行不是随即确认,而是间隔一段时间(把+.0改成+.1),待RACK超时发生重传后,情况将会有大不同,请自行分析!
+.0 < . 1:1(0) ack 1001 win 257 <sack 2001:3001,nop,nop>
+.0 %{ print tcpi_reordering }%+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

输出如下:

3
8
8

从输出上看,在收到第二个包含两个SACK段的包后,虽然被SACK的包量达到了3个,但是此时的reordering值已经更新,可见reordering值的更新在判断快速重传条件之前!

  下面是一个图解:

下面给出抓包分析:

注意,虽然数据包被重传了,但不是快速重传所触发,而是RACK超时所触发!作为一个反过来的例子,下面的脚本可以印证上面的说法:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4+0  write(4, ..., 1000) = 1000+.1 < . 1:1(0) ack 1001 win 32792+0  write(4, ..., 10000) = 10000+.0 %{ print tcpi_reordering }%
+.1 < . 1:1(0) ack 1001 win 257 <sack 5001:6001 9001:10001,nop,nop>
+.0 < . 1:1(0) ack 1001 win 257 <sack 7001:8001,nop,nop>
+.0 %{ print tcpi_reordering }%+5 < . 1:1(0) ack 10001 win 257
+.0 %{ print tcpi_reordering }%

输出如下:

3
3
6

该脚本几乎和上面的脚本没有什么区别,只是把第3个SACK从2001开始的包改成了从7001开始的包,按照reordering值的更新算法,这次将不会引发reordering值的更新,进而在收到3个SACK后触发快速重传,从下面的抓包分析中能看出来确实触发了快速重传:

以下是针对上述脚本的图解:

我们来看最后一个用例。


case 4 理解TCP记分板Mark lost算法的自适应性

这块内容比较独立,理论上的东西请参考我此前的文章:关于TCP快速重传的细节-重传优先级与重传触发条件。本文给出两个实际的例子来解释枯燥的理论。首先看第一个例子,该例子中,被SACK的包聚集于后面:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4+0  write(4, ..., 1000) = 1000+.1 < . 1:1(0) ack 1001 win 32792+0  write(4, ..., 10000) = 10000
// 被SACK的包量大于3,触发快速重传,快速重传前进行数据包的LOST标记
+.1 < . 1:1(0) ack 1001 win 257 <sack 5001:10001,nop,nop>                     +5 < . 1:1(0) ack 10001 win 257

很显然,按照Mark LOST算法,在最后保留reordering个被SACK的数据包,本例中,前面所有的数据包均将被标记为LOST,事实也正是如此!在我的probe输出中,一共有4个包被标记为LOST:

下面是此例的抓包分析:

再看一个例子,该例子中,被SACK的数据包聚集于前面:

0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 32792
+0  accept(3, ..., ...) = 4+0  write(4, ..., 1000) = 1000+.1 < . 1:1(0) ack 1001 win 32792+0  write(4, ..., 10000) = 10000+.1 < . 1:1(0) ack 1001 win 257 <sack 2001:6001 9001:10001,nop,nop>
// 以下的打印不一定准确,因为存在一个异步的RACK定时器超时的过程,该过程同样会进行LOST标记,且与tcp_get_info的调用顺序不确定。
// 因此需要采用tcp_probe的方式,HOOK住tcp_xmit_recovery函数,然后打印tp->lost_out的值最为准确和实时。
+.0 %{ print tcpi_lost }%+5 < . 1:1(0) ack 10001 win 257

依然用probe输出,被标记为LOST的包量仅为1!如下图所示:

我们再看下抓包以确认上述分析:

我们发现不止1个数据包被重传,然而仔细观察前面的时间戳后,发现从第二个重传包开始,便不再是快速重传所起的作用,而是RACK超时重传了,当RACK超时被触发后,有4个包被标记为LOST!在早期的内核中,RACK尚未文档化的时候,内核当然并没有引入RACK机制,没有RACK,就只能timeout了。引入了RACK,反而更加令人迷惑,然而正如抓包看到的那样,虽然没有触发快速重传,但不那么慢的间隔内,重传确实发生了,这就是RACK带来的东西。

遗留的问题

本文根本没有详述关于RACK的细节,但这并不意味着它不重要,RACK是作为时间序丢包判断最近才出现的,以下三类丢包判断是并列的:

  • 空间序-dupthresh: 3 OOO packets delivered (packet count)
  • 序列假定-FACK: sequence delta to highest sacked sequence (sequence space)
  • 时间序-RACK: sent time delta to the latest delivered packet (time domain)

这些问题并没有在本文中进行论述,因为这个话题有点大,所以说就先略过了。

TCP的乱序和丢包判断(附Reordering更新算法)-实例case相关推荐

  1. TCP的乱序和丢包判断(附Reordering更新算法)-理论

    又到了周末,生物钟准时在午夜让我恍惊起而长嗟,一想到TCP,恍如昨日,也不知怎么就千里迢迢之后心依旧茫然,算是拾起来的东西吧,就坐下来再写点关于TCP的东西.由于最近在追<龙珠超>,也是很 ...

  2. TCP协议(二) 重传 乱序和丢包

    TCP重传机制 接收端给发送端的Ack确认只会确认最后一个连续的包,比如,发送端发了1,2,3,4,5一共五份数据,接收端收到了1,2,于是回ack 3,然后收到了4和5. 1)超时重传 一种是不回a ...

  3. 被面试官问懵:TCP 四次挥手收到乱序的 FIN 包会如何处理?

    摘要:收到个读者的问题,他在面试的时候,被搞懵了,因为面试官问了他这么一个网络问题. 本文分享自华为云社区<TCP 四次挥手收到乱序的 FIN 包会如何处理?>,作者:小林coding . ...

  4. TCP的带宽估计和丢包恢复

    TCP的带宽估计和丢包恢复 一.带宽估计 TCP的带宽估计主要通过拥塞控制算法实现,用到两个变量: 1.cwnd     TCP对当前链路可用带宽的估计 2.ssthreash   拥塞控制算法&qu ...

  5. TCPIP协议栈的心跳、丢包重传、连接超时机制实例详解

    目录 1.问题概述 2.TCPIP协议栈的心跳机制 2.1.TCP中的ACK机制 2.2.TCPIP协议栈的心跳机制说明 2.3.修改TCPIP协议栈的默认心跳参数 3.libwebsockets开源 ...

  6. TCP 四次挥手收到乱序的 FIN 包会如何处理?

    大家好,我是小林. 收到个读者的问题,他在面试鹅厂的时候,被搞懵了,因为面试官问了他这么一个网络问题: 不得不说,鹅厂真的很喜欢问网络问题,而且爱问异常情况下的网络问题,之前也有篇另外一个读者面试鹅厂 ...

  7. Linux下网络丢包故障定位

    Linux下网络丢包故障定位 | syxdevcode博客转载: 云网络丢包故障定位全景指南 硬件网卡丢包Ring Buffer溢出 如图所示,物理介质上的数据帧到达后首先由NIC(网络适配器)读取, ...

  8. java 丢包_Java数据报之失序和丢包

    习惯了TCP编程,认为UDP可以包办这些问题是错误的.一个UDP应用程序要承担可靠性方面的全部工作,包括报文的丢失.重复.时延.乱序以及连接失效等问题. 通常我们在可靠性好,传输时延小的局域网上开发测 ...

  9. java从电脑接收数据丢失_网络编程:Java数据报之失序和丢包Java -电脑资料

    习惯了TCP编程,认为UDP可以包办这些问题是错误的, 习惯了TCP编程,认为UDP可以包办这些问题是错误的.一个UDP应用程序要承担可靠性方面的全部工作,包括报文的丢失.重复.时延.乱序以及连接失效 ...

  10. java udp丢包重发_UDPDataPacker_Java数据报之失序和丢包

    Java数据报之失序和丢包 习惯了TCP编程,认为UDP可以包办这些问题是错误的.一个UDP应用程序要承担可靠性方面的全部工作,包括报文的丢失.重复.时延.乱序以及连接失效等问题. 通常我们在可靠性好 ...

最新文章

  1. 2018.5.29 Oracle连接到空闲例程
  2. R语言ggplot2可视化抑制可视化网格中的竖线输出、抑制可视化网格中的横线线输出、抑制背景网格输出实战
  3. js解析url query_js如何解析url
  4. 元宇宙“众声喧哗”,三季度财报超预期的欢聚能否分一杯羹?
  5. 20154319 《网络对抗技术》后门原理与实践
  6. c linux time微秒_Linux基础知识(Linux系统、Linux中的链表)
  7. 用JS中的cookie实现商品的浏览记录
  8. 【软测试】(两)计算机组成原理-cpu
  9. eWebEditor浏览器兼容 ie8 ie7
  10. python学习手册笔记——29.运算符重载
  11. netty权威指南 微云_《Netty权威指南》(二)NIO 入门
  12. 送书 | 聊聊阳光问政
  13. 在线JSON格式化美化
  14. 自定义配置文件 /etc/httpd/conf.d
  15. Leetcode str
  16. 爱她就送ta一场樱花雨
  17. 【学习笔记】《基于φ-OTDR的分布式扰动传感系统定位算法研究-北交-通信与信息系统-吴》重点笔记
  18. win7计算机任务栏颜色怎么,如何修改win7电脑下方任务栏的颜色?
  19. 中国电子学会2022年06月份青少年软件编程Python等级考试试卷一级真题(含答案)
  20. 阅读Learning towards Minimum Hyperspherical Energy笔记

热门文章

  1. 华硕的主板装的服务器系统改win7,华硕主板win10改win7系统怎么操作
  2. CVPR 2022 | 基于密度与深度分解的自增强非成对图像去雾
  3. 盛语小智教育机器人是骗人的_盛语小智机器人骗局揭露【是不是真的有效】多久可以见...
  4. iOS开发之Moya网络请求使用与封装
  5. c语言中int类型的范围,C语言int的取值范围?
  6. 表白墙微信小程序源码
  7. 解决win10 phptoshop #fff纯白不是这样的白 显示器高级的问题
  8. 文档型漏洞攻击研究报告
  9. Flutter-------写一个app启动页
  10. android 渠道配置manifest,Android studio 配置多渠道打包配置