9.4 ER(Early Retransmit)定时器

9.4.1 Why

        TCP发送的数据如果丢失则快速重传算法会立即重传数据而不用等到重传定时器超时,从而快速地恢复数据。如果发送端接收不到足够数量(一般来说是3个)的ACK,则快重传算法无法起作用,这个时候就只能等待RTO超时。ER算法主要是为了解决这个问题。在下面的条件下,就会导致收不到足够的ACK:

(1)拥塞窗口比较小

(2)窗口中一个很大数量的段丢失或者在传输的结尾处发生了丢包

        如果满足了上面的两个条件,那么就会发生发送端由于接收不到足够数量的ACK导致快重传算法无法生效。比如拥塞窗口是3,然后第一个段丢失了,那么理论上最多发送段只可能收到2个重复的ACK,此时由于快重传要求3个重复的ack,那么发送端将会等待RTO超时,然后重传第一个段。

        在上面的第二个条件中,有两种可能性,其中ER算法是为了解决第一种可能性(也就是当连续的很多段丢失)。而第二种情况则需要TLP(Tail Loss Probe)来解决。

        接下来来描述一下ER的算法。ER可以基于两种模式,一种是基于字节的,一种是基于段(segment-based)的,Linux中的ER是基于段的。ER算法会在小窗口下(flight count 小于4)减小触发快重传的重复ACK的阈值,比如减小到1或者2。而在Linux的实现中为了防止假超时会加上一个延迟再重传数据,这个功能就靠ER定时器实现。

9.4.2 When

        TCP在收到ACK时会调用tcp_fastretrans_alert函数判断是否需要快速重传:

2745 static void tcp_fastretrans_alert(struct sock *sk, int pkts_acked,
2746                   int prior_sacked, int prior_packets,
2747                   bool is_dupack, int flag)
2748 {
...
2828         if (!tcp_time_to_recover(sk, flag)) {
2829             tcp_try_to_open(sk, flag, newly_acked_sacked);
2830             return;
2831         }
...
 
        tcp_time_to_recover函数 决定什么时候进入Recovery状态
2076 static bool tcp_time_to_recover(struct sock *sk, int flag)
2077 {
2078     struct tcp_sock *tp = tcp_sk(sk);
2079     __u32 packets_out;
2080 
2081     /* Trick#1: The loss is proven. */
2082     if (tp->lost_out)
2083         return true;
2084 
2085     /* Not-A-Trick#2 : Classic rule... */
2086     if (tcp_dupack_heuristics(tp) > tp->reordering)
2087         return true;
2088 
2089     /* Trick#3 : when we use RFC2988 timer restart, fast
2090      * retransmit can be triggered by timeout of queue head.
2091      */
2092     if (tcp_is_fack(tp) && tcp_head_timedout(sk))
2093         return true;
2094 
2095     /* Trick#4: It is still not OK... But will it be useful to delay
2096      * recovery more?
2097      */
2098     packets_out = tp->packets_out;
2099     if (packets_out <= tp->reordering &&
2100         tp->sacked_out >= max_t(__u32, packets_out/2, sysctl_tcp_reordering) &&
2101         !tcp_may_send_now(sk)) {  //检查Nagle算法等
2102         /* We have nothing to send. This connection is limited
2103          * either by receiver window or by application.
2104          */
2105         return true;
2106     }
2107 
2108     /* If a thin stream is detected, retransmit after first
2109      * received dupack. Employ only if SACK is supported in order
2110      * to avoid possible corner-case series of spurious retransmissions
2111      * Use only if there are no unsent data.
2112      */
2113     if ((tp->thin_dupack || sysctl_tcp_thin_dupack) &&
2114         tcp_stream_is_thin(tp) && tcp_dupack_heuristics(tp) > 1 &&
2115         tcp_is_sack(tp) && !tcp_send_head(sk))
2116         return true;
2117 
2118     /* Trick#6: TCP early retransmit, per RFC5827.  To avoid spurious
2119      * retransmissions due to small network reorderings, we implement
2120      * Mitigation A.3 in the RFC and delay the retransmission for a short
2121      * interval if appropriate.
2122      */
2123     if (tp->do_early_retrans && !tp->retrans_out && tp->sacked_out &&
2124         (tp->packets_out >= (tp->sacked_out + 1) && tp->packets_out < 4) &&
2125         !tcp_may_send_now(sk))
2126         return !tcp_pause_early_retransmit(sk, flag);
2127 
2128     return false;
2129 } 
        tcp_pause_early_retransmit函数是唯一能够设置ER定时器的函数:
1947 static bool tcp_pause_early_retransmit(struct sock *sk, int flag)
1948 {
1949     struct tcp_sock *tp = tcp_sk(sk);
1950     unsigned long delay;
1951 
1952     /* Delay early retransmit and entering fast recovery for
1953      * max(RTT/4, 2msec) unless ack has ECE mark, no RTT samples
1954      * available, or RTO is scheduled to fire first.
1955      */
1956     if (sysctl_tcp_early_retrans < 2 || sysctl_tcp_early_retrans > 3 ||
1957         (flag & FLAG_ECE) || !tp->srtt)
1958         return false;
1959 
1960     delay = max_t(unsigned long, (tp->srtt >> 5), msecs_to_jiffies(2));
1961     if (!time_after(inet_csk(sk)->icsk_timeout, (jiffies + delay))) <span><span class="comment">/* 如果超时重传定时器更早超时*/</span></span>
1962         return false;
1963 
1964     inet_csk_reset_xmit_timer(sk, ICSK_TIME_EARLY_RETRANS, delay,
1965                   TCP_RTO_MAX);
1966     return true;
1967 }
        由代码可知,Linux为设置ER定时器设置了重重障碍,现在来仔细数数:

(1)有丢包事件发生

(2)重复的ACK小于等于乱序的阈值

(3)未开启FACK,或没有未收到确认的包,或队列首包已发送但未超时

(4)已发送的数据 > 乱序的阈值,或被SACK段的数量小于阈值,或允许发送skb

(5)thin_dupack功能未开启,或当前链接并不是"thin"的,或重复ACK的数量大于1,或SACK未开启,或有要发送的数据

(6)do_early_retrans开启

(7)没有重传完毕但没有确认的报文

(8)有被SACK的报文

(9)在网络中的报文数量比被SACK的报文数量多至少1个

(10)在网络中的报文数量少于4

(11)现在不允许发送数据

(12)sysctl_tcp_early_retrans的值是2或3

(13)ACK中没有ECE标记

(14)tp->srtt(smoothed round trip time)的值大于0

(15)icsk->icsk_retransmit_timer超时时间在延迟时间之后

        以上条件全部满足后,ER定时器会被设置,其超时时间是一个比重传定时器更小的值。

        安装丢失探测定时器、重传定时器、坚持定时器时ER定时器就会被清除。

9.4.3 What

        ER定时器的超时函数是tcp_resume_early_retransmit:

2961 void tcp_resume_early_retransmit(struct sock *sk)
2962 {
2963     struct tcp_sock *tp = tcp_sk(sk);
2964 
2965     tcp_rearm_rto(sk);  //更新RTO,设置重传定时器
2966 
2967     /* Stop if ER is disabled after the delayed ER timer is scheduled */
2968     if (!tp->do_early_retrans)  //ER功能被禁用
2969         return;
2970 
2971     tcp_enter_recovery(sk, false);    //进入恢复状态
2972     tcp_update_scoreboard(sk, 1);    //更新记分板,标记skb丢失
2973     tcp_xmit_retransmit_queue(sk);   //重传数据
2974 }
        tcp_xmit_retransmit_queue函数执行重传数据的功能:

2447 void tcp_xmit_retransmit_queue(struct sock *sk)
2448 {
...
2457     if (!tp->packets_out)    //没有在网络中的数据
2458         return;
...
2463     if (tp->retransmit_skb_hint) {
2464         skb = tp->retransmit_skb_hint;
2465         last_lost = TCP_SKB_CB(skb)->end_seq;
2466         if (after(last_lost, tp->retransmit_high))
2467             last_lost = tp->retransmit_high;
2468     } else {
2469         skb = tcp_write_queue_head(sk);
2470         last_lost = tp->snd_una;
2471     }
2472
2473     tcp_for_write_queue_from(skb, sk) { //从skb开始遍历整个发送队列
2474         __u8 sacked = TCP_SKB_CB(skb)->sacked;
2475 
2476         if (skb == tcp_send_head(sk))
2477             break;
...
2489         if (tcp_packets_in_flight(tp) >= tp->snd_cwnd)  //网络中的数据超过拥塞窗口
2490             return;
...
2523         if (sacked & (TCPCB_SACKED_ACKED|TCPCB_SACKED_RETRANS)) //skb被SACK过或被重传过
2524             continue;
2525 
2526         if (tcp_retransmit_skb(sk, skb)) { //重传skb
2527             NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPRETRANSFAIL);
2528             return;
2529         }
...
2535         if (skb == tcp_write_queue_head(sk))
2536             inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
2537                           inet_csk(sk)->icsk_rto,
2538                           TCP_RTO_MAX);
2539     }
...
        值得注意的是在快速重传时不会重传已经被SACK过或被重传过的skb,这些skb也许能够被顺利收到,在这里不重传会减小网络拥塞。

你可能感兴趣的:(tcp,网络,linux内核)