SACK Reneging

TCP發送端不能夠清除SACK序號塊確認的數據,因爲接收端很可能由於內存壓力等原因,刪除亂序隊列中SACK確認過的報文。發送端重傳隊列中的報文只有在接收到ACK報文的Acknowledge序號字段確認之後,才能移除隊列和釋放。

接收端丟棄OFO報文

如下在檢測的已用接收緩存大於套接口總的接收緩存sk_rcvbuf時,並且接收緩存已不能夠再擴大,最後的措施就是釋放亂序報文隊列,參見函數tcp_prune_ofo_queue。

static int tcp_try_rmem_schedule(struct sock *sk, struct sk_buff *skb, unsigned int size)
{
    if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
        !sk_rmem_schedule(sk, skb, size)) {

        if (tcp_prune_queue(sk) < 0)
            return -1;

        while (!sk_rmem_schedule(sk, skb, size)) {
            if (!tcp_prune_ofo_queue(sk))
                return -1;
        }

由於釋放了ofo報文,在函數tcp_prune_ofo_queue的最後,對於開啓了SACK功能的連接,調用函數tcp_sack_reset,清空DSACK和SACK信息。

static inline void tcp_sack_reset(struct tcp_options_received *rx_opt)
{       
    rx_opt->dsack = 0;
    rx_opt->num_sacks = 0;
} 
static bool tcp_prune_ofo_queue(struct sock *sk)
{
    ...
    /* Reset SACK state.  A conforming SACK implementation will
     * do the same at a timeout based retransmit.  When a connection
     * is in a sad state like this, we care only about integrity
     * of the connection not performance.
     */
    if (tp->rx_opt.sack_ok)
        tcp_sack_reset(&tp->rx_opt);

SACK Reneging發生

函數tcp_clean_rtx_queue清除被確認的報文,如果在此之後,發現未在for循環中確認的報文,之前被SACK序號塊確認過,表明接收端可能發生了SACK Reneging,設置標誌FLAG_SACK_RENEGING。否則,如果接收端沒有發生SACK Reneging,ACK報文的確認序號應當包含SACK確認的序號範圍。

static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack,
                   u32 prior_snd_una, struct tcp_sacktag_state *sack)
{
    u32 prior_sacked = tp->sacked_out;
    u32 reord = tp->snd_nxt; /* lowest acked un-retx un-sacked seq */


    first_ackt = 0;

    for (skb = skb_rb_first(&sk->tcp_rtx_queue); skb; skb = next) {

    }

    if (skb && (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED))
        flag |= FLAG_SACK_RENEGING;

在ACK報文處理函數tcp_ack中,對於可疑的ACK(其中包含重複ACK等),由函數tcp_fastretrans_alert進行處理。

static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
{
    /* See if we can take anything off of the retransmit queue. */
    flag |= tcp_clean_rtx_queue(sk, prior_fack, prior_snd_una, &sack_state);

    if (tcp_ack_is_dubious(sk, flag)) {
        if (!(flag & (FLAG_SND_UNA_ADVANCED | FLAG_NOT_DUP))) {
            num_dupack = 1;
            /* Consider if pure acks were aggregated in tcp_add_backlog() */
            if (!(flag & FLAG_DATA))
                num_dupack = max_t(u16, 1, skb_shinfo(skb)->gso_segs);
        }
        tcp_fastretrans_alert(sk, prior_snd_una, num_dupack, &flag, &rexmit);
    }

以下函數tcp_fastretrans_alert中,調用tcp_check_sack_reneging檢查SACK Reneging是否發生。如果爲真,退出處理。

static void tcp_fastretrans_alert(struct sock *sk, const u32 prior_snd_una,
                  int num_dupack, int *ack_flag, int *rexmit)
{
    /* B. In all the states check for reneging SACKs. */
    if (tcp_check_sack_reneging(sk, flag))
        return;

如果接收到的ACK報文攜帶的確認序號,指向了之前記錄的已經被SACK確認過的序號,意味着接收端很可能將SACK序號塊對應的數據刪除。但是,爲了避免不必要的重傳,我們等待一段很短的時間,等待接收端發送更多的ACK報文,期待後續ACK確認SACK中序號。

如下函數tcp_check_sack_reneging,定時器時長爲RTT的一半,但是不超過10ms。注意套接口中變量srtt_us中保存的爲RTT的8倍值。

static bool tcp_check_sack_reneging(struct sock *sk, int flag)
{
    if (flag & FLAG_SACK_RENEGING) {
        struct tcp_sock *tp = tcp_sk(sk);
        unsigned long delay = max(usecs_to_jiffies(tp->srtt_us >> 4),
                      msecs_to_jiffies(10));

        inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, delay, TCP_RTO_MAX);
        return true;
    }
    return false;
}

在超時處理函數tcp_retransmit_timer,將調用以下tcp_timeout_mark_lost函數處理重傳隊列的相關標記操作。如果隊列的首個報文skb被SACK確認過,即sacked具有標記TCPCB_SACKED_ACKED,表明接收端發生了SACKRENEGING,清空了亂序隊列。這裏需要清除接收到的SACK信息。

清空SACK確認數據計數sacked_out,並設置套接口的標誌is_sack_reneg。之後,遍歷重傳隊列,清除其中所有報文的TCPCB_SACKED_ACKED標誌位。

static void tcp_timeout_mark_lost(struct sock *sk)
{
    head = tcp_rtx_queue_head(sk);
    is_reneg = head && (TCP_SKB_CB(head)->sacked & TCPCB_SACKED_ACKED);
    if (is_reneg) {
        NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPSACKRENEGING);
        tp->sacked_out = 0;
        /* Mark SACK reneging until we recover from this loss event. */
        tp->is_sack_reneg = 1;
    } else if (tcp_is_reno(tp)) {
        tcp_reset_reno_sack(tp);
    }

    skb = head;
    skb_rbtree_walk_from(skb) {
        if (is_reneg)
            TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_ACKED;
        else if (tcp_is_rack(sk) && skb != head && tcp_rack_skb_timeout(tp, skb, 0) > 0)
            continue; /* Don't mark recently sent ones lost yet */
        tcp_mark_skb_lost(sk, skb);
    }

SACK Reneging標誌清除

還是位於tcp_fastretrans_alert函數中,SACK Reneging檢查通過。後續如果ACK確認的報文序號位於high_seq(丟包發生時的SND.NXT)序號之後(或相等),表明已經從丟包中恢復過來,撤銷擁塞狀態TCP_CA_Recovery。

static void tcp_fastretrans_alert(struct sock *sk, const u32 prior_snd_una,
                  int num_dupack, int *ack_flag, int *rexmit)
{
    /* B. In all the states check for reneging SACKs. */
    if (tcp_check_sack_reneging(sk, flag))
        return;

    /* D. Check state exit conditions. State can be terminated
     *    when high_seq is ACKed. */
    if (icsk->icsk_ca_state == TCP_CA_Open) {
        ...
    } else if (!before(tp->snd_una, tp->high_seq)) {
        switch (icsk->icsk_ca_state) {
        case TCP_CA_Recovery:
            if (tcp_is_reno(tp))
                tcp_reset_reno_sack(tp);
            if (tcp_try_undo_recovery(sk))
                return;
            tcp_end_cwnd_reduction(sk);
            break;

在撤銷TCP_CA_Recovery狀態函數中,擁塞狀態設置爲TCP_CA_Open,清空SACK Reneging標誌。

/* People celebrate: "We love our President!" */
static bool tcp_try_undo_recovery(struct sock *sk)
{
    ...
    tcp_set_ca_state(sk, TCP_CA_Open);
    tp->is_sack_reneg = 0;
    return false;

另外,除了以上的快速恢復,對於F-RTO和Partial Loss恢復算法,在成功恢復之後,由函數tcp_try_undo_loss恢復到TCP_CA_Open狀態,隨之清空SACK Reneging標誌。

/* Undo during loss recovery after partial ACK or using F-RTO. */
static bool tcp_try_undo_loss(struct sock *sk, bool frto_undo)
{
    struct tcp_sock *tp = tcp_sk(sk);

    if (frto_undo || tcp_may_undo(tp)) {
        tcp_undo_cwnd_reduction(sk, true);

        DBGUNDO(sk, "partial loss");

        inet_csk(sk)->icsk_retransmits = 0;
        if (frto_undo || tcp_is_sack(tp)) {
            tcp_set_ca_state(sk, TCP_CA_Open);
            tp->is_sack_reneg = 0;
        }

內核版本 5.0

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