9.4 ER(Early Retransmit)タイマ


9.4.1 Why
TCPが送信するデータが失われると、高速再送アルゴリズムは、再送タイマがタイムアウトするまで待たずに、直ちにデータを再送し、データを迅速に復元する.送信側が十分な数(一般的には3個)のACKを受信できない場合、高速再送アルゴリズムは機能せず、このときRTOタイムアウトを待つしかない.ERアルゴリズムは主にこの問題を解決するためである.次の条件では、十分なACKが受信されません.
(1)混雑窓が比較的小さい
(2)ウィンドウ内の大きな数のセグメントが失われたり,伝送の最後にパケットが失われたりする.
上記の2つの条件が満たされると、送信側は、十分な数のACKを受信できないため、高速再送アルゴリズムが有効にならないことが発生する.例えば、混雑したウィンドウが3であり、その後、最初のセグメントが失われると、理論的には、最大送信セグメントは2つの重複したACKしか受信できない.この場合、高速再送によって3つの重複したackが要求されるため、送信側はRTOタイムアウトを待ってから、最初のセグメントを再送する.
上の第2の条件では,ERアルゴリズムが第1の可能性(すなわち,連続する多数のセグメントが失われる場合)を解決するための2つの可能性がある.2つ目のケースはTLP(Tail Loss Probe)で解決する必要がある.
次にERのアルゴリズムについて述べる.ERは、バイトベース、セグメントベース、LinuxのERはセグメントベースの2つのモードに基づいてもよい.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は順調に受信できるかもしれないが、ここで再送しないとネットワークの混雑を低減する.