Linux网络编程: TCP协议之SACK与D-SACK详解

一、参考RFC

https://www.ietf.org/rfc/rfc2018
https://www.ietf.org/rfc/rfc2883.txt

二、SACK选项(RFC2018)

SACK实现的需要发送方和接收方协作。为此,TCP首部实际上定义了两种选项:SACK允许选项、SACK选项。

  • SACK允许选项,即Sack-Permitted选项,用于标识是否支持 SACK,是在 TCP 连接建立时发送。
  • SACK选项,则包含了具体的 SACK 信息。

当前所有操作系统都已经默认打开SACK支持了,但也有些系统需要协商。

2.1 Sack-Permitted 选项

也就是SACK允许选项,格式如下:

TCP Sack-Permitted Option:

Kind: 4

+---------+---------+
| Kind=4  | Length=2|
+---------+---------+

SACK_Permitted 选项,该选项只允许在 TCP 连接建立时,有SYN标志的包中设置,也即TCP握手的前两个包中,分别表示通信的两方各自是否支持SACK。

在Linux中可以查看/proc/sys/net/ipv4/tcp_sack文件是否默认打开SACK-Permitted。如果为1,默认打开,SYN包不会再发送这个选项来确认了。

$ cat /proc/sys/net/ipv4/tcp_sack 
1

wireshark抓包确认是在SYN包中存在
在这里插入图片描述

Linux 2.2之后默认打开Sack-Permitted,见
(default: enabled; since Linux 2.2) Enable RFC 2018 TCP Selective Acknowledgements
https://man7.org/linux/man-pages/man7/tcp.7.html

2.2 Sack选项

该选项参数告诉对方已经接收到并缓存的不连续的数据块,发送方可根据此信息检查究竟是哪些块丢失,从而发送相应的数据块。
由于整个TCP首部的选项部分不能超过40字节,所以一个ACK段中最多可以容纳4组SACK信息。

       TCP SACK Option:
       Kind: 5
       Length: Variable

                         +--------+--------+
                         | Kind=5 | Length |
       +--------+--------+--------+--------+
       |      Left Edge of 1st Block       |
       +--------+--------+--------+--------+
       |      Right Edge of 1st Block      |
       +--------+--------+--------+--------+
       |                                   |
       /            . . .                  /
       |                                   |
       +--------+--------+--------+--------+
       |      Left Edge of nth Block       |
       +--------+--------+--------+--------+
       |      Right Edge of nth Block      |
       +--------+--------+--------+--------+

Left Edge表示已收到的不连续块的第一个序号,Right Edge表示已收到的不连续块的最后一个序号+1,即左闭右开区间。通过ACK和SACK信息,发送方就可以确定接收方具体没有收到的数据就是从ACK到最大SACK信息之间的那些空洞的序号。

注意两个关键词:已收到不连续,SACK信息指明的区间数据已经收到了,不用再重传了。

注意:SACK会消费发送方的资源,试想,如果一个攻击者给数据发送方发一堆SACK的选项,这会导致发送方开始要重传甚至遍历已经发出的数据,这会消耗很多发送端的资源。

2.3 SACK使用示例

2.3.1 如果只有一个Block, 那么left edge应该比ack大

wireshark可以使用tcp.options.sack来过滤
在这里插入图片描述

2.3.2 如果有多个Block, 第一个block的范围不能被SACK的第二个block覆盖

wireshark可以使用tcp.options.sack.count > 1来过滤多个SACK Block的包
在这里插入图片描述

2.3.3 SACK在数据重传时候的应用

考虑有SACK包的一段TCP通信数据

在这里插入图片描述

要想接收5846这之前所有的数据,但是已经确认了4287之前的数据。还缺少[4287,5846) 这一段区间数据,但是这一段数据又已经有一部分发送成功了,还缺少[4287, 4330), [4910, 5185)这一部分数据。

在这里插入图片描述

如果双方都支持SACK选项,那么只需要发送[4287, 4330), [4910, 5185)这两段数据即可;如果不支持SACK选项,得必须重发[4287,5846)这一整段数据。

第一部分44字节,第二部分275字节。
在这里插入图片描述
在这里插入图片描述

44字节分了2个包发送。
在这里插入图片描述

又重传了[4910, 5185)这一部分数据。

对于区间重传的数据seq是可以直接填写的。

对于接收端来说,收到数据要有ACK
在这里插入图片描述

44字节接收完了,还缺275字节。
在这里插入图片描述

最终两部分缺失数据接收完毕。
在这里插入图片描述

结论:在重传数据的时候,可以根据SACK信息,只需要重传44+275字节,而不用重传5846-4287这一段数据了,节省流量。

三、D-SACK(RFC2883)

D-SACK,又叫duplicate-SACK。
RFC2883扩展了RFC2018对SACK选项的定义,来指示接收端收到了重复包(duplicate packet),并通知发送端。
RFC2883建议在收到重复报文的时候,SACK选项的第一个块(这个块也叫做D-SACK块)可以用来传递这个重复报文的信息。这样允许TCP发送端根据SACK选项来推测不必要的重传。进而利用这些信息在乱序传输的环境中执行更健壮的操作。

这个D-SACK扩展是与原有的SACK选项的实现相互兼容的。D-SACK的使用也不需要TCP连接的双方额外协商(只要之前协商了SACK选项即可)。当TCP的发送方不理解D-SACK扩展的时候会简单的丢弃D-SACK块并继续处理SACK选项中的其他块。

接收端每个重复包最多在一个D-SACK块中上报一次。如果接收端依次发送了两个带有相同D-SACK块信息的ACK报文,则表示接收端接收了两次重复包,因此带有D-SACK块信息的ACK确认包传输丢失的时候重复包信息也会丢失。

在linux中/proc/sys/net/ipv4/tcp_dsack控制发出的报文中是否携带DSACK信息,但是不管该参数设置为何值,对于接收的TCP报文,linux总是会执行D-SACK块的检测处理。当linux检测到D-SACK块信息的时候会尝试撤销拥塞控制对于拥塞窗口的作用。另外在TLP丢包探测中也可以用来做loss probe的丢包探测。

Linux 2.4开始默认支持D-SACK,见https://man7.org/linux/man-pages/man7/tcp.7.html

在这里插入图片描述

3.1 D-SACK的特点:

  1. 只能是第一个SACK block。即使SACK有多个block也只有第一个block能表示D-SACK,也就是说每个SACK选项中最多有一个D-SACK块。
  2. 如果SACK的第一个block的范围被ACK所覆盖,那么就是D-SACK
  3. 如果SACK的第一个block的范围被SACK的第二个block覆盖,那么就是D-SACK——相同也算是覆盖。

3.2 D-SACK的意义

可见,引入了D-SACK,有这么几个好处:

  • 可以让发送方知道,是发出去的包丢了,还是回来的ACK包丢了。
  • 是不是自己的timeout太小了,导致重传。
  • 网络上出现了先发的包后到的情况(又称reordering)
  • 网络上是不是把我的数据包给复制了。
  • 可以做拥塞控制。

3.3 抓包示例

接收端在接收到SACK报文的时候,应该把第一个SACK块与这个ACK报文的ack number比较(而不是和当前已经接收到的最大的ack number比较),如果小于等于ack number则说明是D-SACK块,如果大于ack number则应该与第二个SACK块比较。如果第二个SACK块包含第一个SACK块,则说明第一个SACK块为D-SACK块,如果上面两个条件都不满足说明第一个SACK块是普通的SACK块。

tcp.options.sack.dsack and tcp.options.sack.count > 1 过滤D-SACK组合情况

在这里插入图片描述

3.4 RFC示例

示例一:ACK丢包

下面的示例中,丢了两个ACK,所以,发送端重传了第一个数据包(3000-3499),于是接收端发现重复收到,于是回了一个SACK=3000-3500,因为ACK都到了4000意味着收到了4000之前的所有数据,所以这个SACK就是D-SACK——旨在告诉发送端我收到了重复的数据,而且我们的发送端还知道,数据包没有丢,丢的是ACK包。

Transmitted  Received    ACK Sent
Segment      Segment     (Including SACK Blocks)
 
3000-3499    3000-3499   3500 (ACK dropped)
3500-3999    3500-3999   4000 (ACK dropped)
3000-3499    3000-3499   4000, SACK=3000-3500
                                    ---------

示例二,网络延误

下面的示例中,网络包(1000-1499)被网络给延误了,导致发送方没有收到ACK,而后面到达的三个包触发了“Fast Retransmit算法”,所以重传,但重传时,被延误的包又到了,所以,回了一个SACK=1000-1500,因为ACK已到了3000,所以,这个SACK是D-SACK——标识收到了重复的包。

这个案例下,发送端知道之前因为“Fast Retransmit算法”触发的重传不是因为发出去的包丢了,也不是因为回应的ACK包丢了,而是因为网络延时了。

Transmitted    Received    ACK Sent
Segment        Segment     (Including SACK Blocks)
 
500-999        500-999     1000
1000-1499      (delayed)
1500-1999      1500-1999   1000, SACK=1500-2000
2000-2499      2000-2499   1000, SACK=1500-2500
2500-2999      2500-2999   1000, SACK=1500-3000
1000-1499      1000-1499   3000
               1000-1499   3000, SACK=1000-1500
                                      ---------

还有比较多的例子,详细的可以参考 RFC2883。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值