1. 程式人生 > 其它 >聊一聊tcp 擁塞控制 二

聊一聊tcp 擁塞控制 二

擁塞視窗的調整撤銷

  很多網路不支援ECN,所以追蹤丟失包時需要推測。重新排序(reordering)對於傳送方來說通常是一個問題,因為它不能分清缺失的ACK是由於丟失還是被延遲了,所以TCP可能會做出錯誤的判斷,不必要的調整了擁塞視窗。這時就需要一種對錯誤的擁塞調整做出修正的機制——擁塞視窗調整撤銷。

從Recovery狀態撤銷

怎麼來探測是否不必要重傳了資料包呢?

  • D-SACK   在最近一次恢復期間重傳的段都被D-SACK確認。這就說明了調整是不必要的。最近一次恢復期間重傳的資料包個數記為undo_retrans,如果收到一個D-SACK,則undo_retrans--,直到undo_retrans為0,說明全部的重傳都是沒必要的,則需要撤銷視窗調整。
  • Timestamp   使用該選項時,通過比較收到ACK的時間戳和重發資料包的時間戳,可以判斷視窗調整是否沒必要。   tcp_may_undo()中的!tp->undo_retrans和tcp_packet_delayed(tp)分別對應以上兩種方法。tcp_packet_delayed()中其實也包含兩種方法:Timestamp和F-RTO。!tp->retrans_stamp表示已經使用F-RTO進行處理。只有檢查出至少有一種成立時,才能進行擁塞視窗調整撤銷。
/* Nothing was retransmitted or returned timestamp is less
 * than timestamp of the first retransmission.
*/ static inline bool tcp_packet_delayed(const struct tcp_sock *tp) { return !tp->retrans_stamp || tcp_tsopt_ecr_before(tp, tp->retrans_stamp); }
/*
1、undo_marker表明套介面進入了擁塞狀態(TCP_CA_Recovery/TCP_CA_Loss),調整了擁塞視窗;否則就沒有必要調整
2、undo_retrans等於0. 報文重傳之後被D-SACK確認,表明這些重傳為不必要的,原始報文未丟失。
3、retrans_stamp等於0(重傳報文時間戳retrans_stamp等於零). 在進入擁塞狀態後還沒有進行過任何重傳,或者重傳報文都已送達
4、接收到的ACK確認報文中的回覆時間戳(rcv_tsecr)在重傳報文的時間戳之前,表明是對於原始報文的確認,而不是對重傳報文。
*/
static inline bool tcp_may_undo(const struct tcp_sock *tp)
{
    return tp->undo_marker && (!tp->undo_retrans || tcp_packet_delayed(tp));
}
/* People celebrate: "We love our President!" */
static bool tcp_try_undo_recovery(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);

    if (tcp_may_undo(tp)) { /*是否可以進行擁塞撤銷*/
        int mib_idx;

        /* Happy end! We did not retransmit anything
         * or our original transmission succeeded.
         */
        DBGUNDO(sk, inet_csk(sk)->icsk_ca_state == TCP_CA_Loss ? "loss" : "retrans");
        tcp_undo_cwnd_reduction(sk, false);
        if (inet_csk(sk)->icsk_ca_state == TCP_CA_Loss)
            mib_idx = LINUX_MIB_TCPLOSSUNDO;
        else
            mib_idx = LINUX_MIB_TCPFULLUNDO;

        NET_INC_STATS_BH(sock_net(sk), mib_idx);
    }
    if (tp->snd_una == tp->high_seq && tcp_is_reno(tp)) {
        /* Hold old state until something *above* high_seq
         * is ACKed. For Reno it is MUST to prevent false
         * fast retransmits (RFC2582). SACK TCP is safe   這個是什麼鬼   虛假快速重傳??. 
,如果當前視窗中還有重傳報文存在於網路中,保留retrans_stamp的值,避免這些重傳報文觸發dupack,再次引起錯誤的快速重傳,
此時需要保持擁塞狀態不撤銷,當再次接收到新的ACK報文(tcp_try_undo_recovery再次執行,但不會再執行以上的撤銷擁塞視窗部分)
*/ tcp_moderate_cwnd(tp); if (!tcp_any_retrans_done(sk)) tp->retrans_stamp = 0; return true; }
//如果SND.UNA大於high_seq,套介面直接恢復到TCP_CA_Open狀態
tcp_set_ca_state(sk, TCP_CA_Open);
return false;
}
static void tcp_undo_cwnd_reduction(struct sock *sk, bool unmark_loss)
{
    struct tcp_sock *tp = tcp_sk(sk);

    if (unmark_loss) {// 是否取消lost 報文丟失 標誌
        struct sk_buff *skb;

        tcp_for_write_queue(skb, sk) {
            if (skb == tcp_send_head(sk))
                break;
            TCP_SKB_CB(skb)->sacked &= ~TCPCB_LOST;
        }
        tp->lost_out = 0;
        tcp_clear_all_retrans_hints(tp);
    }

    if (tp->prior_ssthresh) {// 根據前一個 慢啟動閾值的舊值是否存在來判斷是否撤銷操作
        const struct inet_connection_sock *icsk = inet_csk(sk);

        if (icsk->icsk_ca_ops->undo_cwnd)// 如果當前擁塞演算法有 undo_cwnd 介面 則使用他
            tp->snd_cwnd = icsk->icsk_ca_ops->undo_cwnd(sk);
        else// 否則就是當前視窗 和 2倍滿啟動閾值的 最大值
            tp->snd_cwnd = max(tp->snd_cwnd, tp->snd_ssthresh << 1);

        if (tp->prior_ssthresh > tp->snd_ssthresh) {
            tp->snd_ssthresh = tp->prior_ssthresh; //恢復到出現 recovery 狀態時 的ssh_thresh
            tcp_ecn_withdraw_cwr(tp);// 取消 TCP_ECN_DEMAND_CWR 
        }
    } else {// 不存在滿啟動閾值舊值 則在當前擁塞視窗和啟動閾值間  選一個較大值作為當前擁塞視窗
        tp->snd_cwnd = max(tp->snd_cwnd, tp->snd_ssthresh);
    }
    tp->snd_cwnd_stamp = tcp_time_stamp; // 記錄最近一次檢測cwnd 的時間
    tp->undo_marker = 0;/*復位撤銷標誌*/
}

上述是在!before(tp->snd_una, tp->high_seq) 條件成立的前提下,也就是ack的序號已經大於擁塞開始時的 high_seq

  undo_marker表明套介面進入了擁塞狀態(TCP_CA_Recovery/TCP_CA_Loss),調整了擁塞視窗

撤銷TCP_CA_Recovery狀態二(undo_partial)

  對於TCP_CA_Recovery擁塞狀態,如果ACK報文沒有確認全部的進入擁塞時SND.NXT(high_seq)之前的資料,僅確認了一部分(FLAG_SND_UNA_ADVANCED),執行撤銷函式tcp_try_undo_partial

/* Undo during fast recovery after partial ACK.  
如果ack 確認了部分重傳的段,則會呼叫此函式 進行擁塞視窗撤銷。 其中引數acked 為此次確認的段 數目
重傳結束 退出recovery 狀態*/
static bool tcp_try_undo_partial(struct sock *sk, const int acked,
                 const int prior_unsacked, int flag)
{
    struct tcp_sock *tp = tcp_sk(sk);
    /*沒有使用tcp_may_undo進行撤銷擁塞視窗的判斷,而是使用了其中的一部分,即tcp_packet_delayed判斷報文是否僅是被延遲了,
            忽略undo_retrans值的判斷。其邏輯是在接收到部分確認ACK的情況下,只要tcp_packet_delayed成立,原始報文就沒有丟失而是被延時了,
            就應檢查當前的亂序級別設定是否需要更新(tcp_update_reordering),防止快速重傳被誤觸發。*/
    if (tp->undo_marker && tcp_packet_delayed(tp)) {
        /* Plain luck! Hole if filled with delayed
         * packet, rather than with a retransmit.
         */
        tcp_update_reordering(sk, tcp_fackets_out(tp) + acked, 1);

        /* We are getting evidence that the reordering degree is higher
         * than we realized. If there are no retransmits out then we
         * can undo. Otherwise we clock out new packets but do not
         * mark more packets lost or retransmit more.
         *///不進行undo_retrans值的判斷,但是這裡判斷變數retrans_out(重傳報文數量)是否有值
         //如果網路中還有發出的重傳報文,不進行擁塞視窗的撤銷操作,而是進行擁塞視窗調整  1 -----      函式結束處理,等待重傳報文被確認
        if (tp->retrans_out) {
            tcp_cwnd_reduction(sk, prior_unsacked, 0, flag);
            return true;
        }

        if (!tcp_any_retrans_done(sk))
            tp->retrans_stamp = 0;//變數retrans_stamp記錄了第一個重傳報文的時間戳,如果已經沒有了重傳報文,清零此時間戳

        DBGUNDO(sk, "partial recovery");
        // 網路中沒有重傳的報文or傳送skb快取中沒有報文是被重傳過  ------ 則 取消所喲報文lost 狀態以及調整擁塞視窗閾值等
        tcp_undo_cwnd_reduction(sk, true);
        NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPPARTIALUNDO);
        //嘗試進入TCP_CA_Open狀態,但是,如果套介面還有亂序報文或者丟失報文,將進入TCP_CA_Disorder擁塞狀態。
        tcp_try_keep_open(sk);
        return true;
    }
    return false;
}

上述情況存在嘗試進入TCP_CA_Open狀態,但是,如果套介面還有亂序報文或者丟失報文,將進入TCP_CA_Disorder擁塞狀態。

撤銷TCP_CA_Recovery狀態三(dsack)

對於處在TCP_CA_Recovery擁塞狀態的套介面,ACK報文並沒有推進SND.UNA序號,或者在partial-undo未執行的情況下,嘗試進行DSACK相關的撤銷操作,由函式tcp_try_undo_dsack完成。

/* Try to undo cwnd reduction, because D-SACKs acked all retransmitted data 
傳送方在探測到一個D-SACK塊時,可使undo_retrans減一。如果D-SACK塊最終確認了在最近視窗
中的每個不必要的重傳,重傳計數器因為D-SACK降為0,傳送方增大擁塞視窗,恢復最新一次對ssthresh的修改。
*/
static bool tcp_try_undo_dsack(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);
//如果undo_marker有值,並且undo_retrans為零,表明所有的重傳報文都被D-SACK所確認,即重傳是不必要的,執行擁塞視窗恢復操作
    if (tp->undo_marker && !tp->undo_retrans) {
        DBGUNDO(sk, "D-SACK");
        tcp_undo_cwnd_reduction(sk, false);
        NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPDSACKUNDO);
        return true;
    }
    return false;
}

撤銷TCP_CA_Loss狀態

/* Process an ACK in CA_Loss state. Move to CA_Open if lost data are
 * recovered or spurious. Otherwise retransmits more on partial ACKs.
 如果ACK報文推進了SND.UNA序號,嘗試進行TCP_CA_Loss狀態撤銷,由函式tcp_try_undo_loss完成。
 對於FRTO,如果S/ACK確認了並沒有重傳的報文(原始報文),同樣嘗試進入撤銷流程,
 因為此ACK報文表明RTO值設定的不夠長(並非擁塞導致報文丟失),過早進入了TCP_CA_Loss狀態。
 */
static void tcp_process_loss(struct sock *sk, int flag, bool is_dupack)
{
    struct tcp_sock *tp = tcp_sk(sk);
    bool recovered = !before(tp->snd_una, tp->high_seq); //SND.UNA不在high_seq之前,表明恢復流程已經結束

    if ((flag & FLAG_SND_UNA_ADVANCED) &&
        tcp_try_undo_loss(sk, false))//如果ACK報文推進了SND.UNA序號,嘗試使用tcp_try_undo_loss進行TCP_CA_Loss狀態撤銷
        return;

    if (tp->frto) { /* F-RTO RFC5682 sec 3.1 (sack enhanced version). */
        /* Step 3.b. A timeout is spurious if not all data are
         * lost, i.e., never-retransmitted data are (s)acked.
         如果S/ACK確認了並沒有重傳的報文(原始報文),同樣嘗試進入撤銷流程,因為此ACK報文表明RTO值設定的不夠長(並非擁塞導致報文丟失),
         過早進入了TCP_CA_Loss狀態。       */
        if ((flag & FLAG_ORIG_SACK_ACKED) &&
            tcp_try_undo_loss(sk, true))
            return;

        if (after(tp->snd_nxt, tp->high_seq)) {
            if (flag & FLAG_DATA_SACKED || is_dupack)
                tp->frto = 0; /* Step 3.a. loss was real  loss 狀態是真的 不是誤判*/
        } else if (flag & FLAG_SND_UNA_ADVANCED && !recovered) {
            tp->high_seq = tp->snd_nxt;
            __tcp_push_pending_frames(sk, tcp_current_mss(sk),
                          TCP_NAGLE_OFF);
            if (after(tp->snd_nxt, tp->high_seq))
                return; /* Step 2.b */
            tp->frto = 0;
        }
    }

    if (recovered) {//為此ACK報文表明RTO值設定的不夠長(並非擁塞導致報文丟失),過早進入了TCP_CA_Loss狀態。
        /* F-RTO RFC5682 sec 3.1 step 2.a and 1st part of step 3.a */
        tcp_try_undo_recovery(sk);
        return;
    }
    if (tcp_is_reno(tp)) {
        /* A Reno DUPACK means new data in F-RTO step 2.b above are
         * delivered. Lower inflight to clock out (re)tranmissions.
         */
        if (after(tp->snd_nxt, tp->high_seq) && is_dupack)
            tcp_add_reno_sack(sk);
        else if (flag & FLAG_SND_UNA_ADVANCED)
            tcp_reset_reno_sack(tp);
    }
    tcp_xmit_retransmit_queue(sk);
}
http代理伺服器(3-4-7層代理)-網路事件庫公共元件、核心kernel驅動 攝像頭驅動 tcpip網路協議棧、netfilter、bridge 好像看過!!!! 但行好事 莫問前程 --身高體重180的胖子