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也許能夠被順利收到,在這裏不重傳會減小網絡擁塞。