• TCP的核心系列 — SACK和DSACK的实现(四)


    和18版本不同,37版本把DSACK的检测部分独立出来,可读性更好。

    37版本在DSACK的处理中也做了一些优化,对DSACK的两种情况分别进行处理。

     

    本文主要内容:DSACK的检测、DSACK的处理。

    Author:zhangskd @ csdn

    dsack检测

    根据RFC 2883,DSACK的处理流程如下:

    1)look at the first SACK block :

    —If the first SACK block is covered by the Cumulative Acknowledgement field, then it is a D-SACK

    block, and is reporting duplicate data.

    —Else, if the first SACK block is covered by the second SACK block, then the first SACK block is a

    D-SACK block, and is reporting duplicate data.

    2)otherwise, interpret the SACK blocks using the normal SACK procedures.

    简单来说,符合以下任一情况的,就是DSACK:

    1)第一个SACK块的起始序号小于它的确认序号,说明此SACK块包含了确认过的数据。

    2)第一个SACK块包含在第二个SACK块中,说明第一个SACK块是重复的。

    static int tcp_check_dsack(struct sock *sk, struct sk_buff *ack_skb, 
            struct tcp_sack_block_wire *sp, int num_sacks, u32 prior_snd_una)
    {
        struct tcp_sock *tp = tcp_sk(sk);
        u32 start_seq_0 = get_unaligned_be32(&sp[0].start_seq); /* 第一个SACK块的起始 */
        u32 end_seq_0 = get_unaligned_be32(&sp[0].end_seq); /* 第一个SACK块的结束 */
        int dup_sack = 0; /* 是否有DSACK */
    
        /* 如果第一个SACK块的起始序号小于它的确认序号,说明此SACK块包含了确认过的数据,
         * 所以第一个SACK块是DSACK。
         */
        if (before(start_seq_0, TCP_SKB_CB(ack_skb)->ack_seq)) {
            dup_sack = 1;
            tcp_dsack_seen(tp);
            NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPDSACKRECV);
    
        } else if (num_sacks > 1) {
            u32 end_seq_1 = get_unaligned_be32(&sp[1].end_seq); /* 第二个块的结束序号 */
            u32 start_seq_1 = get_unaligned_be32(&sp[1].start_seq); /* 第二个块的起始序号 */
    
            /* 如果第一个SACK块包含在第二个SACK块中,说明第一个SACK块是重复的,即为DSACK */
            if (! after(end_seq_0, end_seq_1) && ! before(start_seq_0, start_seq_1)) {
                dup_sack = 1;
                tcp_dsack_seen(tp);
                NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPDSACKOFORECV);
            }
        }
    
        /* D-SACK for already forgotten data... Do dumb counting.
         * undo_retrans记录重传数据包的个数,如果undo_retrans降到0,
         * 就说明之前的重传都是不必要的,进行拥塞调整撤销。
         */
        if (dup_sack && ! after(end_seq_0, prior_snd_una) &&
            after(end_seq_0, tp->undo_marker))
            tp->undo_retrans--;
    
        return dup_sack;
    }
    /* Take a notice that peer is sending D-SACKs */
    static void tcp_dsack_seen(struct tcp_sock *tp)
    {
        tp->rx_opt.sack_ok |= 4;
    }

    在以上函数中,undo_marker为进入Recovery或FRTO状态时记录的snd_una,prior_snd_una为根据该ACK

    更新窗口前的snd_una。如果回复的DSACK在这块中间,说明是超时重传或FRTO后进行的重传,因此需要减

    少undo_retrans。当undo_retrans减小到0,说明之前的重传都是不必要的,网络并没有拥塞,因此要进行拥

    塞调整撤销。

    dsack处理

    当处理一个块时,会检查下一个块是不是DSACK块,如果是则用next_dup指向该DSACK块。

    为什么在处理当前SACK块的时候,还要考虑到下个DSACK块呢?

    我们知道DSACK有两种情况,一种是DSACK块小于snd_una,另一种情况是DSACK块大于snd_una且包含在

    第一个块中,我们来分别分析下。

    (1)DSACK块大于snd_una且包含在第一个SACK块中

    排序前块的顺序:start_seq2、start_seq1、start_seq3,start_seq2为DSACK块起始序号,是第一个块。

    排序后块的顺序:start_seq1、start_seq2、start_seq3,DSACK变为第二个块了。

    从上图可以看出,start_seq2表示的DSACK块,是包含在start_seq1表示的SACK块中的,因此两个块需要

    同时处理。不然等start_seq1表示的SACK块处理完后,再处理DSACK块,就需要做一些重复的工作。

    当DSACK包含在第一个SACK块中,那么处理DSACK块在cache中的部分。

    static struct sk_buff *tcp_maybe_skipping_dsack(struct sk_buff *skb, struct sock *sk,
                                                    struct tcp_sack_block *next_dup,
                                                    struct tcp_sacktag_state *state,
                                                    u32 skip_to_seq)
    {
        /* 如果下个SACK块不是DSACK块,那么不用进行dsack处理 */
        if (next_dup == NULL)
            return skb;
    
        /* 如果在(cache->start_seq, cache->end_seq)中包含dsack */
        if (before(next_dup->start_seq, skip_to_seq)) {
    
            /* 找到next_dup->start_seq之后的skb */
            skb = tcp_sacktag_skip(skb, sk, state, next_dup->start_seq);
    
            /* 处理next_dup->start_seq之后的skb */
            skb = tcp_sacktag_walk(skb, sk, NULL, state, next_dup->start_seq, next_dup->end_seq, 1);
        }
    }
    

    (2)DSACK块小于snd_una

    这时候DSACK排序后也是第一个块,会被直接处理,next_dup在这里就没有意义了。

    DSACK的两种情况都在tcp_sacktag_walk()中处理,第一种时next_dup不为空、dup_sack_in为0;

    第二种时next_dup为空,dup_sack_in为1。

    Reference

    RFC 2883

  • 相关阅读:
    petshop4.0(转)
    分层依据(转)
    如何让一个函数返回多个值
    谁访问过我的电脑
    博客第一帖!
    开发辅助工具大收集
    VC 通过IHTMLINTEFACE 接口实现网页执行自定义js代码
    vi命令大全
    #include <sys/types.h>
    #include <arpa/inet.h>
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333336.html
Copyright © 2020-2023  润新知