聊一聊tcp 拥塞控制五 d-sack reordering lost-pkt
查看之前的blog:浅析sack
dsack检测
根据RFC 2883,DSACK的处理流程如下:
1)look at the first SACK block :
—If the first SACK block is covered by the Cumulative Acknowledgement field, then it is a D-SACK
block, and is reporting duplicate data.
—Else, if the first SACK block is covered by the second SACK block, then the first SACK block is a
D-SACK block, and is reporting duplicate data.
2)otherwise, interpret the SACK blocks using the normal SACK procedures.
简单来说,符合以下任一情况的,就是DSACK:
1)第一个SACK块的起始序号小于它的确认序号,说明此SACK块包含了确认过的数据。
2)第一个SACK块包含在第二个SACK块中,说明第一个SACK块是重复的
static bool tcp_check_dsack(struct sock *sk, const struct sk_buff *ack_skb, struct tcp_sack_block_wire *sp, int num_sacks, u32 prior_snd_una) { struct tcp_sock *tp = tcp_sk(sk); u32 start_seq_0 = get_unaligned_be32(&sp[0].start_seq);/* 第一个SACK块的起始 */ u32 end_seq_0 = get_unaligned_be32(&sp[0].end_seq);/* 第一个SACK块的结束 */ bool dup_sack = false; /* 如果第一个SACK块的起始序号小于它的确认序号,说明此SACK块包含了确认过的数据, * 所以第一个SACK块是DSACK。 */ if (before(start_seq_0, TCP_SKB_CB(ack_skb)->ack_seq)) { dup_sack = true; tcp_dsack_seen(tp); NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPDSACKRECV); } else if (num_sacks > 1) { u32 end_seq_1 = get_unaligned_be32(&sp[1].end_seq);/* 第二个块的结束序号 */ u32 start_seq_1 = get_unaligned_be32(&sp[1].start_seq);/* 第二个块的起始序号 */ if (!after(end_seq_0, end_seq_1) && !before(start_seq_0, start_seq_1)) {/* 如果第一个SACK块包含在第二个SACK块中,说明第一个SACK块是重复的,即为DSACK */ dup_sack = true; tcp_dsack_seen(tp); NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPDSACKOFORECV); } } /* D-SACK for already forgotten data... Do dumb counting. undo_retrans记录重传数据包的个数,如果undo_retrans降到0, * 就说明之前的重传都是不必要的,进行拥塞调整撤销 */ /* undo_marker为进入Recovery或FRTO状态时记录的snd_una,prior_snd_una为根据该ACK 更新窗口前的snd_una。如果回复的DSACK在这块中间,说明是超时重传或FRTO后进行的重传,因此需要减 少undo_retrans。当undo_retrans减小到0,说明之前的重传都是不必要的,网络并没有拥塞,因此要进行拥 塞调整撤销。*/ if (dup_sack && tp->undo_marker && tp->undo_retrans > 0 && !after(end_seq_0, prior_snd_una) && after(end_seq_0, tp->undo_marker)) tp->undo_retrans--; return dup_sack; }
乱序检测的目的时探测网络是否发生重排导致的丢包,并以此来更新 dupthresh 值。只要能收到一个 ACK 或者 SACK,其序列号位于当前已经被 ACK 或 SACK 的数据包最大的序列号之前,就说明网络发生了重排造成了乱序,此时如果涉及的数据包大小大于当前能容忍的网络乱序值,即 dupthresh 值,就说明网络乱序加重了,此时应该更新 dupthresh 值。之所以保持 dupthresh 的值递增,是考虑其初始值 3 只是一个经验值,既然真实检测到乱序,如果其值比 3 小,并不能说明网络的乱序度估计偏大,同时 TCP 保守地递增乱序度,也是为了让快速重传的进入保持保守的姿态,从而增加友好性。
转载:
一、DSACK介绍
RFC2883通过指定使用SACK来指示接收端的重复包(duplicate packet)扩展了RFC2018对SACK选项的定义(SACK选项的介绍和示例参考前面内容)。RFC2883建议在收到重复报文的时候,SACK选项的第一个块(这个块也叫做DSACK块)可以用来传递触发这个ACK确认包的系列号,这个就是DSACK(duplicate-SACK)功能。这样允许TCP发送端根据SACK选项来推测不必要的重传。进而利用这些信息在乱序传输的环境中执行更健壮的操作。这个DSACK扩展是与原有的SACK选项的实现相互兼容的。DSACK的使用也不需要TCP连接的双方额外协商(只要之前协商了SACK选项即可)。当TCP的发送方不理解DSACK扩展的时候会简单的丢弃DSACK块并继续处理SACK选项中的其他块。
当DSACK使能的时候,总结起来如下
-
一个DSACK块只用来传递一个接收端最近接收到的重复报文的系列号,每个SACK选项中最多有一个DSACK块
-
接收端每个重复包最多在一个DSACK块中上报一次。如果接收端依次发送了两个带有相同DSACK块信息的ACK报文,则表示接收端接收了两次重复包,因此带有DSACK块信息的ACK确认包传输丢失的时候重复包信息也会丢失。
-
和普通的SACK块一样,DSACK块左边指定重复包的第一个字节的系列号,右边指定重复包最后一个字节的下一个系列号
-
如果收到重复报文,第一个SACK块应该应该指定触发这个ACK确认包的系列号(这个SACK块也叫做DSACK块)。如果这个重复报文是一个大的不连续块的一部分,那么接下来的这个SACK块应该指定这个大的不连续块,额外的SACK块应该按照RFC2018指定的顺序排列。
另外还有一种部分重复段(partial duplicate segment)的上报场景,我们会在示例中展示说明。注意按照上面的描述,DSACK块有可能处于ack number之前。接收端在接收到SACK报文的时候,应该把第一个SACK块与这个ACK报文的ack number比较(而不是和当前已经接收到的最大的ack number比较),如果小于等于ack number则说明是DSACK块,如果大于ack number则应该与第二个SACK块比较,如果第二个SACK块包含第一个SACK块,则说明第一个SACK块为DSACK块,如果上面两个条件都不满足说明第一个SACK块是普通的SACK块。
在linux中/proc/sys/net/ipv4/tcp_dsack控制发出的报文中是否携带DSACK信息,但是不管该参数设置为何值,对于接收的TCP报文,linux总是会执行DSACK块的检测处理。当linux检测到DSACK块信息的时候会尝试撤销拥塞控制对于拥塞窗口的作用。另外在TLP丢包探测中也可以用来做loss probe的丢包探测。
二、wireshark示例
1、tcp_dsack=1,接收到ack number之前的数据包
如下图所示,client和server三次握手后,依次发送No4(1-6)、No6(7-13)、No8(7-13)、No10(14-20)四个数据包,server端接收到No8时候发现是一个重复包,因此回复一个带有DSACK的ACK确认包(No9),其中DSACK块信息为(7-14),表示收到了一个系列号为(7-13)的重复包。
2、tcp_dsack=1,接收到ack number之后的数据包
-
client端分别发送No4(1-6)和No6(7-13),server端正常回复不带有SACK选项的ACK确认包
-
接着client发送No8(21-27),模拟系列号(14-20)的报文在由client向server传输的过程中丢包
-
server端回复一个带有SACK选项的ACK确认包(No9),其中包含一个SACK块(21-28)
-
client端继续发送No10(24-34),server端回复No11的ACK确认包,包含一个SACK块(21-35)
-
接着client发送No12(42-48),模拟系列号为(35-41)的报文传输过程中发生丢失
-
server端回复No13的ACK报文,带有两个SACK块,依次为(42-49)和(21-35)
-
client端发送No14(28-34),这个数据包与No10数据包系列号相同
-
server端收到No14的重复包之后,回复No15确认包,带有一个DSACK块(28-35)和两个额外的SACK块,这两个SACK块依次为(21-35)(42-49)
-
接着client发送No16(14-20),server端回复No17确认包,带有一个SACK块(42-49)。
-
client发送No18(35-41),server端回复No19确认包,不带有SACK信息,整个传输过程结束
3、tcp_dsack=1,接收到ack number之后的部分重传段
此处不在罗嗦叙述过程,此处仅把SACK的信息文字补充描述一下,其他信息请自行下载对应的wireshark文件
其中No9、No11、No13、No15包含SACK信息,No9包含一个SACK块(28-35),No11包含一个SACK块(28-42),No13包含两个SACK块信息(49-56)、(28-42),No15包含一个DSACK块(28-42)和一个额外的SACK块(21-56),注意此处No14(21-55)与之前的两个SACK块(49-56)、(28-42)都是重复包,着一个TCP包与前面的三个包内容重复还发送了新的数据,这种同时带有新数据和重复数据的tcp报文就叫做部分重传段,当部分重传段中同时带有多个重复段的时候,协议规定DSACK块值反馈第一个重复端的系列号范围。可以看到此时linux的处理是符合协议的。
4、tcp_dsack=1,接收到ack number之前的部分重传段
此处同样仅用文字描述一下SACK信息,No9带有一个SACK块(21-28),No11带有一个SACK块(21-35),No13带有两个SACK块(42-49)和(21-35),No15数据包带有一个DSACK块(21-56)。注意这里与上面同样是带有两个重复段大的部分重传段,但是linux反馈的DSACK信息却把两个重复段都包含了,而且还扩展到了最新发送的55系列号(DSACK块中的56表示55系列号的后一个系列号)。RFC2883协议中的对这种场景的描述是:
When the SACK option is used for reporting partial duplicate segments, the first D-SACK block reports the first duplicate sub-segment. If the data packet being acknowledged contains multiple partial duplicate sub-segments, then only the first such duplicate sub-segment is reported in the SACK option.
而linux的处理则是,如果当前接收到的数据包的系列号与待接收的连续系列号相同,那么回复的DSACK块的起始系列号则为乱序队列中的最小系列号,终止系列号则为新收到的数据包的最大系列号加1。显然linux的处理与协议要求不符合,而且与DSACK扩展功能的预期不相符,具体原因我也没查到(不过并不是所有不符合协议的实现都是bug,有些实现可能会在协议的基础上做一些扩展)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
2020-11-19 XDP/AF_XDP ? eBPF
2020-11-19 ipvs--eBPF转载
2019-11-19 主动关闭 tcp_timewait_state_process 处理
2019-11-19 主动关闭 time wait结构体