TCP-聊一聊重传次数

本文转载自:聊一聊重传次数

RTO的计算方法中,介绍了RFC6298对于RTO的计算和RTO timer的管理算法。
但有一个重要的问题RFC没有提到,那就是如果出现了超时重传,那重传多少次可以放弃呢?
当然这是一个实现相关的细节,不同的操作系统可能有不同的实现策略。
在这篇wiki中,就来介绍一下Linux中是怎么限制超时重传次数的。

1 听说Linux有两个参数限制超时重传次数

没错,Linux中确实定义了两个参数来限定超时重传的次数的,以下是源码中Documentation/networking/ip-sysctl.txt文档中的描述

tcp_retries1 - INTEGER
    This value influences the time, after which TCP decides, that
    something is wrong due to unacknowledged RTO retransmissions,
    and reports this suspicion to the network layer.
    See tcp_retries2 for more details.

    RFC 1122 recommends at least 3 retransmissions, which is the
    default.

tcp_retries2 - INTEGER
    This value influences the timeout of an alive TCP connection,
    when RTO retransmissions remain unacknowledged.
    Given a value of N, a hypothetical TCP connection following
    exponential backoff with an initial RTO of TCP_RTO_MIN would
    retransmit N times before killing the connection at the (N+1)th RTO.

    The default value of 15 yields a hypothetical timeout of 924.6
    seconds and is a lower bound for the effective timeout.
    TCP will effectively time out at the first RTO which exceeds the
    hypothetical timeout.

    RFC 1122 recommends at least 100 seconds for the timeout,
    which corresponds to a value of at least 8.

就是这样一段话,可能由于过于概括,会令人产生很多疑问,甚至产生一些误解。
比如常见的问题有:

  1. 超过tcp_retries1这个阈值后,到底是report了怎样一种suspicion呢?
  2. tcp_retries1和tcp_retries2的数字是表示RTO重传的次数上限,对吗?
  3. 文档中提到,924.6s is a lower bound for the effective timeout。
    这里的effective timeout是指什么?为什么是lower bound,tcp_retries2不应该是限制重传次数的upper bound吗?

下面就结合Linux 3.10的源码来逐个解释一下以上几个问题。并在最后给出一个总结。

2 重传超过tcp_retries1会怎样

文档中说的suspicion到底是什么呢?来看一下tcp_retries1相关的代码部分

// RTO timer的处理函数是tcp_retransmit_timer(),与tcp_retries1相关的代码调用关系如下  
tcp_retransmit_timer()
    => tcp_write_timeout()  // 判断是否重传了足够的久
        => retransmit_timed_out(sk, sysctl_tcp_retries1, 0, 0)  // 判断是否超过了阈值

// tcp_write_timeout()的具体相关内容  
...
if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
    // 如果超时发生在三次握手期间,此时有专门的tcp_syn_retries来负责限定重传次数
    ...
} else {    // 如果超时发生在数据发送期间
    // 这个函数负责判断重传是否超过阈值,返回真表示超过。后续会详细分析这个函数  
    if (retransmits_timed_out(sk, sysctl_tcp_retries1, 0, 0)) { 
        /* Black hole detection */
        tcp_mtu_probing(icsk, sk);  // 如果开启tcp_mtu_probing(默认关闭)了,则执行PMTU

        dst_negative_advice(sk);    // 更新路由缓存
    }
    ...
}

从以上的代码可以看到,一旦重传超过阈值tcp_retries1,主要的动作就是更新路由缓存。
用以避免由于路由选路变化带来的问题。

3 重传超过tcp_retries2会怎样

会直接放弃重传,关闭TCP流

// 依然还是在tcp_write_timeout()中,retry_until一般是tcp_retries2
...
if (retransmits_timed_out(sk, retry_until, syn_set ? 0 : icsk->icsk_user_timeout, syn_set)) {
    /* Has it gone just too far? */
    tcp_write_err(sk);      // 调用tcp_done关闭TCP流
    return 1;
}


4 retries限制的重传次数吗

咋一看文档,很容易想到retries的数字就是限定的重传的次数,甚至源码中对于retries常量注释中都写着”This is how many retries it does…”

#define TCP_RETR1       3   /*
                             * This is how many retries it does before it
                             * tries to figure out if the gateway is
                             * down. Minimal RFC value is 3; it corresponds
                             * to ~3sec-8min depending on RTO.
                             */

#define TCP_RETR2       15  /*
                             * This should take at least
                             * 90 minutes to time out.
                             * RFC1122 says that the limit is 100 sec.
                             * 15 is ~13-30min depending on RTO.
                             */

那就就来看看retransmits_timed_out的具体实现,看看到底是不是限制的重传次数

/* This function calculates a "timeout" which is equivalent to the timeout of a
 * TCP connection after "boundary" unsuccessful, exponentially backed-off
 * retransmissions with an initial RTO of TCP_RTO_MIN or TCP_TIMEOUT_INIT if
 * syn_set flag is set.
 */
static bool retransmits_timed_out(struct sock *sk,
                              unsigned int boundary,
                              unsigned int timeout,
                              bool syn_set)
{
    unsigned int linear_backoff_thresh, start_ts;
    // 如果是在三次握手阶段,syn_set为真
    unsigned int rto_base = syn_set ? TCP_TIMEOUT_INIT : TCP_RTO_MIN;

    if (!inet_csk(sk)->icsk_retransmits)
            return false;

    // retrans_stamp记录的是数据包第一次发送的时间,在tcp_retransmit_skb()中设置
    if (unlikely(!tcp_sk(sk)->retrans_stamp))
            start_ts = TCP_SKB_CB(tcp_write_queue_head(sk))->when;
    else
            start_ts = tcp_sk(sk)->retrans_stamp;

    // 如果用户态未指定timeout,则算一个出来
    if (likely(timeout == 0)) {
            /* 下面的计算过程,其实就是算一下如果以rto_base为第一次重传间隔,
             * 重传boundary次需要多长时间
             */
            linear_backoff_thresh = ilog2(TCP_RTO_MAX/rto_base);

            if (boundary <= linear_backoff_thresh)
                    timeout = ((2 << boundary) - 1) * rto_base;
            else
                    timeout = ((2 << linear_backoff_thresh) - 1) * rto_base +
                            (boundary - linear_backoff_thresh) * TCP_RTO_MAX;
    }
    // 如果数据包第一次发送的时间距离现在的时间间隔,超过了timeout值,则认为重传超于阈值了
    return (tcp_time_stamp - start_ts) >= timeout;
}

从以上的代码分析可以看到,真正起到限制重传次数的并不是真正的重传次数。
而是以tcp_retries1或tcp_retries2为boundary,以rto_base(如TCP_RTO_MIN 200ms)为初始RTO,计算得到一个timeout值出来。如果重传间隔超过这个timeout,则认为超过了阈值。
上面这段话太绕了,下面举两个个例子来说明

以判断是否放弃TCP流为例,如果tcp_retries2=15,那么计算得到的timeout=924600ms。

1. 如果RTT比较小,那么RTO初始值就约等于下限200ms
   由于timeout总时长是924600ms,表现出来的现象刚好就是重传了15次,超过了timeout值,从而放弃TCP流

2. 如果RTT较大,比如RTO初始值计算得到的是1000ms
   那么根本不需要重传15次,重传总间隔就会超过924600ms。
   比如我测试的一个RTT=400ms的情况,当tcp_retries2=10时,仅重传了3次就放弃了TCP流


5 另外几个小问题

理解了Linux决定重传次数的真实机制,就不难回答一下几个问题了

>> effective timeout指的是什么?  
<< 就是retransmits_timed_out计算得到的timeout值

>> 924.6s是怎么算出来的?
<< 924.6s = (( 2 << 9) -1) * 200ms + (15 - 9) * 120s

>> 为什么924.6s是lower bound?
<< 重传总间隔必须大于timeout值,即 (tcp_time_stamp - start_ts) >= timeout

>> 那RTO超时的间隔到底是不是源码注释的"15 is ~13-30min depending on RTO."呢?  
<< 显然不是! 虽然924.6s(15min)是一个lower bound,但是它同时也是一个upper bound!
   怎么理解?举例说明  
        1. 如果某个RTO值导致,在已经重传了14次后,总重传间隔开销是924s
        那么它还需要重传第15次,即使离924.6s只差0.6s。这就是发挥了lower bound的作用
        2. 如果某个RTO值导致,在重传了10次后,总重传间隔开销是924s
        重传第11次后,第12次超时触发时计算得到的总间隔变为1044s,超过924.6s
        那么此时会放弃第12次重传,这就是924.6s发挥了upper bound的作用
   总的来说,在Linux3.10中,如果tcp_retres2设置为15。总重传超时周期应该在如下范围内
        [924.6s, 1044.6s)

所以综合上述,Linux并不是直接拿tcp_retries1和tcp_retries2来限制重传次数的,而是用计算得到
的一个timeout值来判断是否要放弃重传的。真正的重传次数同时与RTT相关。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容