美文网首页TCP
TCP-聊一聊重传次数

TCP-聊一聊重传次数

作者: YDDMAX_Y | 来源:发表于2018-04-19 19:49 被阅读0次

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

    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相关。

    相关文章

      网友评论

        本文标题:TCP-聊一聊重传次数

        本文链接:https://www.haomeiwen.com/subject/wplgkftx.html