TCP快速重传触发条件的一个细节

版权声明:本文为博主原创,无版权,未经博主允许可以随意转载,无需注明出处,随意修改或保持可作为原创! https://blog.csdn.net/dog250/article/details/86747551

倒数第二个工作日,拒绝午饭!撰文以记之。

浙江温州皮鞋湿,下雨进水不会胖!


前几日和前同事聊天聊到一个Linux内核协议栈实现中关于TCP快速重传触发条件的一个细节,觉得比较有意思。

这个细节是这样的。

且看tcp_ack中,如果我们发现该ACK所携带的信息是 可疑的, 那么逻辑就会进入到进一步的筛选判断中,以最终抉择 是不是要让该TCP连接的拥塞状态机从Open切换到Disorder或者Recovery状态

我们看一卡这个 可疑的 其判断标准是什么。

#define FLAG_NOT_DUP        (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED)
#define FLAG_CA_ALERT       (FLAG_DATA_SACKED|FLAG_ECE)
...
static inline bool tcp_ack_is_dubious(const struct sock *sk, const int flag)
{
    return !(flag & FLAG_NOT_DUP) || (flag & FLAG_CA_ALERT) ||
        inet_csk(sk)->icsk_ca_state != TCP_CA_Open;
}

FLAG_CA_ALERT这个倒是很明确,只要携带了SACK,或者显式的拥塞标志,那肯定是可疑的,需要进一步判断。

这里比较有意思的是:

为什么只要携带FLAG_DATA标志可以代表NOT_DUP?

比较明显的疑点就是,在关闭SACK支持的情况下,如果一个双向传输的TCP连接,对端每次都发送点数据过来,那么本端岂不是永远都不会被触发快速重传吗?

简单点说是这样的,确实是只要对端发送数据过来,就不会进入tcp_ack_is_dubious分支,进而不会触发快速重传。

编写下面的packetdrill脚本以验证事实:

0   socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [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 <...>

+0 < . 1:1(0) ack 1 win 257
+0  accept(3, ..., ...) = 4

// Send 1 data segment and get an ACK, so cwnd is now 4.
+0  write(4, ..., 1000) = 1000
+0  > P. 1:1001(1000) ack 1

+0 < . 1:1(0) ack 1001 win 257

// Write 4 data segments.
+0  write(4, ..., 4000) = 4000
+0  > P. 1001:5001(4000) ack 1

// Get 3 SACKs.
// 
+0 < . 1:11(10) ack 1001 win 257 <sack 2001:3001,nop,nop>
+0  < . 11:21(10) ack 1001 win 257 <sack 2001:4001,nop,nop>
+0  < . 21:31(10) ack 1001 win 257 <sack 2001:5001,nop,nop>
// net.ipv4.tcp_sack=0时,这里没有快速重传,这里超时
// net.ipv4.tcp_sack=1时,这里触发了快速重传,因为FLAG_CA_ALERT置位。

// Receiver ACKs all data.
+10 < . 1:1(0) ack 6001 win 257

这是一个问题吗?如果是问题为什么多年以来Linux的TCP实现一直这么写。


仔细思考了一下这个问题,最后发现其实这不是问题。

TCP是全双工通信协议,它原本应该全双工两个方向独立进行传输/确认,为了提高资源利用率,实施了 捎带确认 的逻辑,也就是说,捎带确认只是一个优化,而不是与生俱来的。

在开启SACK支持时,捎带确认中埋藏SACK告诉反方向的一端说可能有拥塞丢包,那这只是一种友情协助,并不是人家的义务。而关闭SACK支持时,无论如何捎带确认也无法明确告知反方向数据包可能丢包或者乱序了…

换句话说, 只有3次重复ACK明确是由本端发送的数据包所触发的时候,才会去进一步判断是否要快速重传,否则在可疑判断时将会直接放行!

显然,携带数据的包是对端主动发送的,而其携带的ACK属于捎带确认,不属于本端触发的确认,因此不会被认为是重复ACK。

如果我们不区分 捎带确认数据接收触发的确认 会怎样呢?下图示之:
在这里插入图片描述
为了在 可疑异常 情况下可以尽快让 数据接收触发的确认 反馈到发送端,RFC规定, 可疑异常情况下的数据接收触发的确认,不允许捎带!必须立刻发送!


从小没咋放过鞭炮,也没咋吃过一家人一起的除夕团圆饭,所以不知年味儿,虽然如此,我还是祝福每一个人,每一个家庭都能团团圆圆,幸运美满,明年获得更多的幸福收获!

浙江温州皮鞋湿,下雨进水不会胖!

猜你喜欢

转载自blog.csdn.net/dog250/article/details/86747551