Reno與RACK對丟失/重傳報文的標記

主要涉及到兩個變量,一是重傳報文計數retrans_out;二是丟失報文計數lost_out。

RACK丟失報文判斷

如下函數tcp_rack_detect_loss,如果報文具有丟失標誌(TCPCB_LOST),但是沒有重傳標誌(TCPCB_SACKED_RETRANS),表明丟失報文還未進行重傳,不進行重複處理。否則,在RACK確認報文已經丟失之後,由函數tcp_mark_skb_lost進行丟失標記。

static void tcp_rack_detect_loss(struct sock *sk, u32 *reo_timeout)
{
    *reo_timeout = 0;
    reo_wnd = tcp_rack_reo_wnd(sk);
    list_for_each_entry_safe(skb, n, &tp->tsorted_sent_queue, tcp_tsorted_anchor) {
        struct tcp_skb_cb *scb = TCP_SKB_CB(skb);

        /* Skip ones marked lost but not yet retransmitted */
        if ((scb->sacked & TCPCB_LOST) &&
            !(scb->sacked & TCPCB_SACKED_RETRANS))
            continue;

        if (!tcp_rack_sent_after(tp->rack.mstamp,
                     tcp_skb_timestamp_us(skb),
                     tp->rack.end_seq, scb->end_seq))
            break;

        /* A packet is lost if it has not been s/acked beyond
         * the recent RTT plus the reordering window.
         */
        remaining = tcp_rack_skb_timeout(tp, skb, reo_wnd);
        if (remaining <= 0) {
            tcp_mark_skb_lost(sk, skb);

在函數tcp_mark_skb_lost中,如果重傳報文(標誌TCPCB_SACKED_RETRANS)丟失,清除其重傳標誌TCPCB_SACKED_RETRANS,以便進行下一次重傳。在以上tcp_rack_detect_loss丟失檢測中,可見,對此種丟失未重傳報文不進行重複處理。

注意,這裏對重傳丟失的報文,減少重傳報文計數retrans_out。以下將會看到Reno在標記丟包時,不處理retrans_out計數。

void tcp_mark_skb_lost(struct sock *sk, struct sk_buff *skb)
{
    struct tcp_sock *tp = tcp_sk(sk);

    tcp_skb_mark_lost_uncond_verify(tp, skb);
    if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_RETRANS) {
        TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_RETRANS;
        tp->retrans_out -= tcp_skb_pcount(skb);

與Reno不同,RACK(或者SACK)可標記重傳隊列中多個報文爲丟失狀態(TCPCB_LOST),Reno僅能表示重傳隊列首個報文。因而,在重傳時,RACK(或SACK)也可重傳多個丟失的報文。參見以下重傳函數tcp_xmit_retransmit_queue中的判斷,retrans_out不能大於lost_out,不能重傳大於丟失數量的報文。

void tcp_xmit_retransmit_queue(struct sock *sk)
{
    rtx_head = tcp_rtx_queue_head(sk);
    skb = tp->retransmit_skb_hint ?: rtx_head;
    max_segs = tcp_tso_segs(sk, tcp_current_mss(sk));
    skb_rbtree_walk_from(skb) {
        ...
        segs = tp->snd_cwnd - tcp_packets_in_flight(tp);
        if (segs <= 0) return;
		
        sacked = TCP_SKB_CB(skb)->sacked;
        /* In case tcp_shift_skb_data() have aggregated large skbs,
         * we need to make sure not sending too bigs TSO packets
         */
        segs = min_t(int, segs, max_segs);
   
        if (tp->retrans_out >= tp->lost_out) {
            break; 
        ...
        if (tcp_retransmit_skb(sk, skb, segs)) return;

Reno丟包計數

以下爲Reno算法的丟包標記函數tcp_newreno_mark_lost,與RACK(或者SACK)不同,Reno沒有足夠的信息判斷多個報文的丟失情況,根據重複ACK(dupack)僅能判斷,SND.UNA序號開始的報文(重傳隊列首報文)丟失。所以,如果重傳隊列首個skb,包含多個報文,或者其長度大於MSS值,進行分片,TCPCB_LOST丟包標誌只能設置於第一個長度不大於MSS的報文。

這裏也就不需要像以上的RACK算法,或者SACK算法遍歷重傳隊列,Reno調用tcp_skb_mark_lost_uncond_verify函數標記重傳隊列首報文(可能是分片後報文)丟失即可。

另外Reno與RACK/SACK的不同在於,後者在tcp_mark_skb_lost函數中檢測重傳報文的丟失,並且減少重傳報文計數,而Reno不進行處理。Reno僅在接收到確認ACK(無論是確認原始還是重傳報文)時將retrans_out減一。

void tcp_newreno_mark_lost(struct sock *sk, bool snd_una_advanced)
{        
    const u8 state = inet_csk(sk)->icsk_ca_state;
    struct tcp_sock *tp = tcp_sk(sk);

    if ((state < TCP_CA_Recovery && tp->sacked_out >= tp->reordering) ||
        (state == TCP_CA_Recovery && snd_una_advanced)) {
        struct sk_buff *skb = tcp_rtx_queue_head(sk);
        u32 mss;

        if (TCP_SKB_CB(skb)->sacked & TCPCB_LOST)
            return;

        mss = tcp_skb_mss(skb);
        if (tcp_skb_pcount(skb) > 1 && skb->len > mss)
            tcp_fragment(sk, TCP_FRAG_IN_RTX_QUEUE, skb,
                     mss, mss, GFP_ATOMIC);

        tcp_skb_mark_lost_uncond_verify(tp, skb);

如下tcp_skb_mark_lost_uncond_verify,如果報文沒有被確認丟失並且也沒有被對端SACK確認接收,增加lost_out丟包計數,並且設置標誌TCPCB_LOST。對於Reno而言,不可能被SACK確認,並且在以上函數tcp_newreno_mark_lost中,也已經確認了報文還沒有設置TCPCB_LOST標誌,所以這裏的if判斷必定成立。

另外,由於Reno僅標記一個報文,lost_out值遞增1。而且在以上函數中判斷只要首報文被設置了TCPCB_LOST標誌,不繼續重複標記,因而,lost_out的值實際上總是1。

void tcp_skb_mark_lost_uncond_verify(struct tcp_sock *tp, struct sk_buff *skb)
{       
    tcp_verify_retransmit_hint(tp, skb);
        
    tcp_sum_lost(tp, skb); 
    if (!(TCP_SKB_CB(skb)->sacked & (TCPCB_LOST|TCPCB_SACKED_ACKED))) {
        tp->lost_out += tcp_skb_pcount(skb);
        TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;

接下來看一看Reno的丟失計數lost_out的減少。在接收到ACK確認報文後,清理重傳隊列函數tcp_clean_rtx_queue中,如果檢測到報文設置了TCPCB_LOST標誌,將丟包計數lost_out減去相應的報文數量。對於Reno而言,僅有可能在重傳隊列的首報文中設置TCPCB_LOST標誌。由於在設置TCPCB_LOST標誌時,確保了重傳隊列第一個skb僅包含一個報文,這裏lost_out相當於遞減1,其值變爲0。

注意,由於被對端確認接收,此報文將由重傳隊列中移除,這樣隊列中的下一個報文(成爲首報文)纔有可能被標記爲TCPCB_LOST,參見以上函數tcp_newreno_mark_lost。

static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack,
                   u32 prior_snd_una, struct tcp_sacktag_state *sack)
{

    for (skb = skb_rb_first(&sk->tcp_rtx_queue); skb; skb = next) {
        struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
        u8 sacked = scb->sacked;

        if (after(scb->end_seq, tp->snd_una)) {
            ...
        } else {
            acked_pcount = tcp_skb_pcount(skb);
        }

        if (sacked & TCPCB_LOST)
            tp->lost_out -= acked_pcount;

Reno重傳計數

在報文重傳函數tcp_retransmit_skb中,重傳成功之後,更新重傳計數,由於在上節介紹的tcp_newreno_mark_lost函數中,將重傳隊列首個skb分片爲單報文,此處retrans_out相當於遞增1。並且爲報文增加重傳標誌TCPCB_RETRANS。

int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb, int segs)
{
    struct tcp_sock *tp = tcp_sk(sk);
    int err = __tcp_retransmit_skb(sk, skb, segs);

    if (err == 0) {
#if FASTRETRANS_DEBUG > 0
        if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_RETRANS) {
            net_dbg_ratelimited("retrans_out leaked\n");
        }
#endif
        TCP_SKB_CB(skb)->sacked |= TCPCB_RETRANS;
        tp->retrans_out += tcp_skb_pcount(skb);

在以上函數tcp_retransmit_skb的調用函數tcp_xmit_retransmit_queue中,如果重傳的報文數量retrans_out大於等於丟失報文數量lost_out,停止遍歷重傳隊列。對於Reno,丟失報文計數lost_out爲1,僅允許重傳一個報文。此後,retrans_out的值也增加爲1。

void tcp_xmit_retransmit_queue(struct sock *sk)
{
    rtx_head = tcp_rtx_queue_head(sk);
    skb = tp->retransmit_skb_hint ?: rtx_head;
    max_segs = tcp_tso_segs(sk, tcp_current_mss(sk));
    skb_rbtree_walk_from(skb) {
        ...
        segs = tp->snd_cwnd - tcp_packets_in_flight(tp);
        if (segs <= 0) return;
		
        sacked = TCP_SKB_CB(skb)->sacked;
        /* In case tcp_shift_skb_data() have aggregated large skbs,
         * we need to make sure not sending too bigs TSO packets
         */
        segs = min_t(int, segs, max_segs);
   
        if (tp->retrans_out >= tp->lost_out) {
            break; 
        ...
        if (tcp_retransmit_skb(sk, skb, segs)) return;

在接收到ACK確認報文後,清理重傳隊列函數tcp_clean_rtx_queue中,如果報文設置了重傳標誌TCPCB_RETRANS,將retrans_out減少相應的報文數量,對於Reno而言,相當於遞減1。同時在此函數中,也將丟失報文數量lost_out減去1。

static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack,
                   u32 prior_snd_una, struct tcp_sacktag_state *sack)
{

    for (skb = skb_rb_first(&sk->tcp_rtx_queue); skb; skb = next) {
        struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
        u8 sacked = scb->sacked;

        if (after(scb->end_seq, tp->snd_una)) {
            ...
        } else {
            acked_pcount = tcp_skb_pcount(skb);
        }
		
        if (unlikely(sacked & TCPCB_RETRANS)) {
            if (sacked & TCPCB_SACKED_RETRANS)
                tp->retrans_out -= acked_pcount;
            flag |= FLAG_RETRANS_DATA_ACKED;
		...
        if (sacked & TCPCB_LOST)
            tp->lost_out -= acked_pcount;

內核版本 5.0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章