码迷,mamicode.com
首页 > 系统相关 > 详细

Linux TCP拥塞控制中undo操作

时间:2016-05-02 23:07:14      阅读:656      评论:0      收藏:0      [点我收藏+]

标签:

Linux的TCP实现复杂且繁琐,建议不要直接去看代码,而是花点时间把TCP规范先撸一遍。本文主要描述一下TCP实现中undo操作,然后顺便再吐一下槽(千万不要觉得吐槽有什么不好,很多好东西都是从吐槽开始的,从造纸,蒸汽机,到法兰西第一共和国,再到Linux...)。

1.TCP对网络拥塞是基于预测的

TCP属于网络的一部分,这无可厚非,但当一个人说自己精通网络的时候,他更可能的意思是自己精通网络节点的行为而不是端到端的行为。比如,一个Cisco的工程师精通各种路由协议,可以设计出复杂的OSPF网络,BGP网络,优化各个节点的pps,在某处启用spanning tree,各种算法的灵活运用,不管在研发的标准还是在运维的标准,都足以让你烟花缭乱,崇拜至极,好像网络中的一切尽掌握在其手中,玩转地不亦乐乎,我曾经接触过这样的人,也认识不少这样的朋友,曾经站在研发的角度与其对峙,也曾站在他们的立场上置码农的伤痛于不顾...这些人保证,只要数据包经过他们掌管的设备,在效率的意义上,他就可以保证其尽可能线速通过,在安全的意义上,任何邪恶的蛛丝马迹都无法逃避出他们的法眼...

然而,一旦涉及TCP,他们就无能了。我一直都觉得,一个TCP连接的行为是“与全世界相关的”,没有任何一个管理员可以控制全世界所有的计算机的行为,就算是神仙也不行。TCP只定义了端到端的行为,就好像假设两个通信的进程是相邻的一样,TCP首先就是这样假设的,这就是“端到端滑动窗口”的初衷,即发送多少数据仅仅和接收端可以接收多少有关。当发现端到端之间并不是直接相邻而是一个变换莫测具有蝴蝶效应的网络时,拥塞控制算法是被加进去的。

TCP在设计之初,并没有任何的显式拥塞提醒机制,也没有否认应答机制,因此对于发送端而言,预测网络情况的唯二工具就是ACK和RTT,并且,RTT是测量出来的,这就意味着它会受到接收端延迟发送ACK的影响,虽然TCP规范要求避免stretch ACK,但是如果ACK丢了,什么也不好说...因此对于TCP网络而言,拥塞是预测出来的,下面是现实中的类似预测的例子:
同居的室友去买菜,然后走了10个小时都没有回来,你会怎么想,你可能早就会打电话确认,可是如果什么都没有,你只能假设室友出事了,于是报警;
同居的室友和四个互不相识的邻居前后一起去同一个市场买菜,四人回来很久了,先走的室友还没有回来,此时你会怎么想。
不管上面任何一种情况,都可能仅仅意味着室友玩了一个恶作剧而已...

2.当预测错误就是可以undo的

如果我们预测了网络的拥塞,可是后来有迹象表明事实并不是我们想象的那样,此时就可以undo那些为了应对网络拥塞而做出的动作,问题是哪些可以undo,哪些不可以undo。

3.哪些可以undo

一旦我们预测了网络的拥塞,我们需要做的有两件事:
a).减少拥塞窗口
b).重发可能丢失的数据包(人去买菜没有再回来,如果仅仅是为了菜的话,你可以再派一个人去买菜)。
减少拥塞窗口是为了不为既已拥塞的网络添堵,这无疑是一种君子行为,不适合中国式司机,当然也不适合中国的TCP研发者们,虽然也会减窗,但初衷不是不添堵,而是不减窗发了数据也没用,净增加流量...当发现预测错误,网络根本就没有拥塞的时候,TCP最好的行为就是补偿自己了,在一个“人人为我,我为人人”的世界,没人会因为你的君子行为补偿你,只有自己可以。因此可以将拥塞窗口升高至降窗之前的值,这也算是一种自我补偿了。

4.哪些不可以undo

在预判断为拥塞的时候,会重发可能丢失的数据包,这部分是不可以undo的,没有任何机制可以将发出去的数据再收回来!唯一能做的,就是下次预测的时候再准确些吧。

5.什么时候可以undo

我们知道,TCP可以通过reordering个重复ACK以及超时来预判断数据包已经丢失,但是并不意味着发生了重复ACK或者超时一定是数据包丢失了,也可能是数据包发生了重排序,延迟乱序到达了接收端,因此重复ACK或者超时仅仅是数据包丢失的必要非充分条件。

有一种情况下,我们可以下结论说我们的预判断是错误的,那就是我们重发的数据包均被对端证实收到了两遍,这就是说一遍是原始的发送,一遍是重发。通过DSACK或者时间戳机制可以可以很好的发现这一点。一旦发现所有的重发包均收到了DSACK,我们就可以undo了,把可以undo的全都undo掉。    

6.undo到哪里

如果已经因为错误的预判断进入了recovery状态,那么undo到哪里呢?

如果在收到重发数据包的所有DSACK时,发送窗口已经足够干净了,那么就可以直接到Open状态了,但是如果在UNA之后依然有数据被标记为SACK或者LOST,这就表明依然有乱序的可能,那么就进入Disorder状态好了。我们知道,在recovery状态,窗口是下降的,然而在Disorder状态,窗口并不一定是僵住(僵住是为了在确认有足够的重复ACK前,无法预判断是乱序还是丢包,所以要先僵住窗口)的,如果我们在undo的时候,reordering的值已经变大,那么就说明网络更有可能是乱序了,此时可以继续增加拥塞窗口的大小。

下图是一个总体的图,大意所在:
技术分享

7.Linux内核协议栈的持续优化

如果你看2.6.8的内核和4.4的内核,会发现在TCP拥塞处理上有很大的不同,把2.6.32作为标准版的话,你会发现3.10版本增加了prr降窗算法,然后在紧随的3.11,引入了“reordering大于默认值在Disorder状态下可不必僵住而增加窗口”的算法,然后再看4.3/4.4,你会发现在拥塞状态机中,Mid Switch这个东西事实上已经完全打破了NewReno的假设,这就是NewReno提前退出。并且看似Linux在持续优化的过程中,已经变得不再君子,突发不再被限制,抢道插队时有发生...

8.一个刷重传队列的细节

这一节与本文大意无关,只是不想另外写一篇了。
在RFC规范中,拥塞状态下,应该首先发LOST数据包,然后发新数据包,最后“前向发送”cover(即High seq)之前既没有标记LOST,也没有SACK的数据包,RFC这么规定是有根据的,背后的数学推理根本就不会在RFC中给出,RFC只给结论!请不要质疑这种方案的合理性,请不要!
但是如果你看tcp_xmit_retransmit_queue的时候,很难发现这个逻辑:
    tcp_for_write_queue_from(skb, sk) {
        __u8 sacked = TCP_SKB_CB(skb)->sacked;

        if (skb == tcp_send_head(sk))
            break;
        if (hole == NULL)
            tp->retransmit_skb_hint = skb;
        // 窗口限制检查
        if (tcp_packets_in_flight(tp) >= tp->snd_cwnd)
            return;

        if (fwd_rexmitting) {
begin_fwd:
            // 最高前向发送到high sacked
            if (!before(TCP_SKB_CB(skb)->seq, tcp_highest_sack_seq(tp)))
                break;

        } else if (!before(TCP_SKB_CB(skb)->seq, tp->retransmit_high)) {
            tp->retransmit_high = last_lost;
            // 关键在这里!tcp_can_forward_retransmit会判断,如果有新数据就绪,就会break!
            // 进入tcp_can_forward_retransmit之后,会有tcp_may_send_now判断,其判断是否有
            // 新数据就绪。Linux并没有在一个路径按RFC规范的发送优先级进行发送,在有新数据
            // 就绪的时候,只是简单的退出,让发送路径自行处理!
            if (!tcp_can_forward_retransmit(sk))
                break;
            if (hole != NULL) {
                skb = hole;
                hole = NULL;
            }
            // 只有在tcp_can_forward_retransmit不会break的时候,才会“前向发送”
            fwd_rexmitting = 1;
            goto begin_fwd;

        } else if (!(sacked & TCPCB_LOST)) {
            if (hole == NULL && !(sacked & (TCPCB_SACKED_RETRANS|TCPCB_SACKED_ACKED)))
                hole = skb;
            continue;

        } else {
            // 首先发送LOST数据
            last_lost = TCP_SKB_CB(skb)->end_seq;
        }
        // 不会发送已经被SACK,或者已经被重传的数据包!
        if (sacked & (TCPCB_SACKED_ACKED|TCPCB_SACKED_RETRANS))
            continue;

        tcp_retransmit_skb(sk, skb);
    }

9.再次针对OpenSSL/OpenVPN/Linux TCP的吐槽

虽然目前不再从事OpenSSL/OpenVPN的工作,但是我依然在持续关注,依然无法容忍这种让人看不懂,即便看懂了也不爽的代码,如果有人能教我看懂了OpenSSL的ASN.1处理的代码,我愿意发 100块钱的红包!OpenVPN也一样!对于TCP的代码,请看下面的片段:
/* People celebrate: "We love our President!" */
static int tcp_try_undo_recovery(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);
....
哪位大神可以解释一下“People celebrate: "We love our President!"”的意义!我敢说,如果我在自己的代码中写这样的注释,一定会被认为是抿了两口二锅头后的杰作,然后被批,被人认为是不正常。曾经我在设计conntrack cache的时候,在代码中写了一句“旋转升降座椅一定会爆炸”,然后我删了,我觉得这不符合我们这个国家中规中矩的文化,于是我删了。在我们的文化里,不会有“talk is cheap show me the code”,因此也就不会有哈格里夫斯,不会有瓦特,不会有乔布斯,不会有Linus...只会有谁呢?反正就是一堆人说了算,抑扬顿挫,从小被灌输,然后长大灌输别人。爆炸!

Linux TCP拥塞控制中undo操作

标签:

原文地址:http://blog.csdn.net/dog250/article/details/51295481

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!