Selective Acknowledgment 选项 浅析 2

来自:http://abcdxyzk.github.io/blog/2013/09/06/kernel-net-sack/

RFC文档rfc2883     rfc2018

tcp_ack中对sack的处理

 if (TCP_SKB_CB(skb)->sacked)//SACK相关处理
            flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una,
                            &sack_state);

tcp_ack()有个非常重要的参数flag,其贯穿整个ACK的处理过程,它记录了从输入段中能够获取到的任何信息(比如是否携带了数据、是否重复ACK、是否是SACK等),供后面的拥塞控制、RTT采样等操作参考。flag可能是如下值的组合。

//这个说明当前的输入帧包含有数据。
#define FLAG_DATA        0x01 /* Incoming frame contained data.        */
//这个说明当前的ack是一个窗口更新的ack
#define FLAG_WIN_UPDATE        0x02 /* Incoming ACK was a window update.    */
//这个ack确认了一些数据
#define FLAG_DATA_ACKED        0x04 /* This ACK acknowledged new data.        */
//这个表示ack确认了一些我们重传的段。
#define FLAG_RETRANS_DATA_ACKED    0x08 /* "" "" some of which was retransmitted.    */
#define FLAG_SYN_ACKED        0x10 /* This ACK acknowledged SYN.        */
//新的sack
#define FLAG_DATA_SACKED    0x20 /* New SACK.                */
#define FLAG_ECE        0x40 /* ECE in this ACK                */
//sack检测到了数据丢失。
#define FLAG_LOST_RETRANS    0x80 /* This ACK marks some retransmission lost */
//此ack由慢速路径处理
#define FLAG_SLOWPATH        0x100 /* Do not skip RFC checks for window update.*/
#define FLAG_ORIG_SACK_ACKED    0x200 /* Never retransmitted data are (s)acked    */
//ack 更新了snd_una 收到ack后窗口向右移动
#define FLAG_SND_UNA_ADVANCED    0x400 /* Snd_una was changed (!= FLAG_DATA_ACKED) */
//ack 中包含dsack
#define FLAG_DSACKING_ACK    0x800 /* SACK blocks contained D-SACK info */
//检测到之前的sack确认过的数据段被对端丢弃
#define FLAG_SACK_RENEGING    0x2000 /* snd_una advanced to a sacked seq */
#define FLAG_UPDATE_TS_RECENT    0x4000 /* tcp_replace_ts_recent() */
#define FLAG_NO_CHALLENGE_ACK    0x8000 /* do not call tcp_send_challenge_ack()    */

#define FLAG_ACKED        (FLAG_DATA_ACKED|FLAG_SYN_ACKED)
#define FLAG_NOT_DUP        (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED)
//收到sack则说明可能有的段丢失了。而ECE则是路由器提示我们有拥塞了。我们必须处理。
#define FLAG_CA_ALERT        (FLAG_DATA_SACKED|FLAG_ECE)
#define FLAG_FORWARD_PROGRESS    (FLAG_ACKED|FLAG_DATA_SACKED)

#define TCP_REMNANT (TCP_FLAG_FIN|TCP_FLAG_URG|TCP_FLAG_SYN|TCP_FLAG_PSH)
#define TCP_HP_BITS (~(TCP_RESERVED_BITS|TCP_FLAG_PSH))

 

 

 

 

static int
tcp_sacktag_write_queue(struct sock *sk, const struct sk_buff *ack_skb,
            u32 prior_snd_una, struct tcp_sacktag_state *state)
{
    struct tcp_sock *tp = tcp_sk(sk);
    //ptr指向的就是SACK选项的起始位置
    const unsigned char *ptr = (skb_transport_header(ack_skb) +
                    TCP_SKB_CB(ack_skb)->sacked);
    //sp_wire在ptr的基础上前移两个字节,跳过选项的kind和len,指向SACK信息块的开始
    struct tcp_sack_block_wire *sp_wire = (struct tcp_sack_block_wire *)(ptr+2);
    //用于保存最后解析出来的SACK信息块,因为一个skb中最多可以携带4个,所以数组长度为4
    struct tcp_sack_block sp[TCP_NUM_SACKS];
    struct tcp_sack_block *cache;
    struct sk_buff *skb;
    //每个SACK信息块为8字节,所以num_sacks记录的是该skb中携带的SACK信息块的个数
    int num_sacks = min(TCP_NUM_SACKS, (ptr[1] - TCPOLEN_SACK_BASE) >> 3);
    int used_sacks;
    bool found_dup_sack = false;
    int i, j;
    int first_sack_index;

    state->flag = 0;
    state->reord = tp->packets_out;
//如果之前还没有SACK确认过的段,那么复位highest_sack域为发送队列的头部,即当前发送队列中
    //最大的序号(该数据可能还尚未发送),
    if (!tp->sacked_out) {
        if (WARN_ON(tp->fackets_out))
            tp->fackets_out = 0;
        tcp_highest_sack_reset(sk);//因为当sacked_out为0,则说明没有通过sack确认的段,此时highest_sack自然就指向写队列的头。
        //--->tcp_sk(sk)->highest_sack = tcp_write_queue_head(sk);
}

    }

/*
D-SACK的判断是通过RFC2883中所描述的进行的。如果是下面两种情况,则说明收到了一个D-SACK。
1 如果SACK的第一个段所ack的区域被当前skb的ack所确认的段覆盖了一部分,则说明我们收到了一个d-sack,
而代码中也就是sack第一个段的起始序列号小于snd_una
2、如果sack的第二个段完全包含了第二个段,则说明我们收到了重复的sack
*/
    found_dup_sack = tcp_check_dsack(sk, ack_skb, sp_wire,
                     num_sacks, prior_snd_una);
    if (found_dup_sack)//检查是否有DSACK信息块,如果有,设置FLAG_DSACKING_ACK标记,表示输入段中有DSACK
        state->flag |= FLAG_DSACKING_ACK;

    /* Eliminate too old ACKs, but take into
     * account more or less fresh ones, they can
     * contain valid SACK info.为接收方通告过的最大接收窗口。
     * 如果SACK信息是很早以前的,直接丢弃
     */
    if (before(TCP_SKB_CB(ack_skb)->ack_seq, prior_snd_una - tp->max_window))
        return 0;
//当前根本就没有需要确认的段,所以也没有必要继续处理

    if (!tp->packets_out)
        goto out;
//后面会对sp[]数组按照SACK信息块中seq的大小做升序排列,first_sack_index
    //记录了排序后原本输入段携带的第一个SACK信息块在sp[]中的位置,如果第一个
    //SACK块无效,那么最终first_sack_index为-1
    used_sacks = 0;
    first_sack_index = 0;
    for (i = 0; i < num_sacks; i++) {
        //如果有DSACK,那么它一定在第一个位置。综合前面的判断设置dup_sack
        bool dup_sack = !i && found_dup_sack;

        sp[used_sacks].start_seq = get_unaligned_be32(&sp_wire[i].start_seq);
        sp[used_sacks].end_seq = get_unaligned_be32(&sp_wire[i].end_seq);
//如果SACK信息块无效,分别进行相关统计
        if (!tcp_is_sackblock_valid(tp, dup_sack,
                        sp[used_sacks].start_seq,
                        sp[used_sacks].end_seq)) {
            int mib_idx;

            if (dup_sack) {
                if (!tp->undo_marker)
                    mib_idx = LINUX_MIB_TCPDSACKIGNOREDNOUNDO;
                else
                    mib_idx = LINUX_MIB_TCPDSACKIGNOREDOLD;
            } else {
                /* Don't count olds caused by ACK reordering */
                if ((TCP_SKB_CB(ack_skb)->ack_seq != tp->snd_una) &&
                    !after(sp[used_sacks].end_seq, tp->snd_una))
                    continue;
                mib_idx = LINUX_MIB_TCPSACKDISCARD;
            }

            NET_INC_STATS(sock_net(sk), mib_idx);
            if (i == 0)//第一个SACK是无效的SACK,所以设置first_sack_index为-1
                first_sack_index = -1;
            continue;
        }

        /* Ignore very old stuff early */
        if (!after(sp[used_sacks].end_seq, prior_snd_una))
            continue;

        used_sacks++;
    }

    /* order SACK blocks to allow in order walk of the retrans queue 
    对实际使用的SACK块,按起始序列号,从小到大进行冒泡排序。*/
    for (i = used_sacks - 1; i > 0; i--) {
        for (j = 0; j < i; j++) {
            if (after(sp[j].start_seq, sp[j + 1].start_seq)) {
                swap(sp[j], sp[j + 1]);

                /* Track where the first SACK lock goes to */
                if (j == first_sack_index)///注意first_sack_index会跟踪原始的第一个SACK信息块所在位置
                    first_sack_index = j + 1;
            }
        }
    }

    skb = tcp_write_queue_head(sk);
    state->fack_count = 0;
    i = 0;
/*一旦收到对端的SACK信息,那么说明发生了丢包或者乱序,
而且这种状况可能往往无法    立即恢复,这意味着发送方会连续多次收到SACK信息,
而且这些SACK信息很可能是重复的。为了减少对发送队列的遍历次数,
这里发送方在用SACK信息块更新发送队列时采    用了cache机制。本质上也很简单,
就是将上一次的SACK信息块保存下来,在本次处理过程中,如果发现上一次已
经处理过了该范围的SACK,那么就可以跳过不处理。
cache    //信息块就保存在tp->recv_sack_cache[]中
*/处理重传队列中的skb

重传队列中的skb有三种类型,分别是SACKED(S), RETRANS® 和LOST(L),而每种类型所处理的数据包的个数分别保存在sacked_out, retrans_out 和lost_out中。

而处于重传队列的skb也就是会处于下面6中状态:

Valid combinations are: b
 * Tag  InFlight    Description
 * 0    1        - orig segment is in flight.inFlight也就是表示还在网络中的段的个数;没有ack的包
 * S    0        - nothing flies, orig reached receiver.
 * L    0        - nothing flies, orig lost by net.
 * R    2        - both orig and retransmit are in flight.
 * L|R    1        - orig is lost, retransmit is in flight.
 * S|R  1        - orig reached receiver, retrans is still in flight.
 * (L|S|R is logically valid, it could occur when L|R is sacked,
 *  but it is equivalent to plain S and code short-curcuits it to S.
 *  L|S is logically invalid, it would mean -1 packet in flight 8))
 *
重传队列中的skb的状态变迁是通过下面这几种事件来触发的:
These 6 states form finite state machine, controlled by the following events:
 * 1. New ACK (+SACK) arrives. (tcp_sacktag_write_queue())
 * 2. Retransmission. (tcp_retransmit_skb(), tcp_xmit_retransmit_queue())
 * 3. Loss detection event of two flavors:
 *    A. Scoreboard estimator decided the packet is lost.
 *       A'. Reno "three dupacks" marks head of queue lost.
 *       A''. Its FACK modification, head until snd.fack is lost.
 *    B. SACK arrives sacking SND.NXT at the moment, when the
 *       segment was retransmitted.
 * 4. D-SACK added new rule: D-SACK changes any tag to S.
 *
tcp socket的high_seq域,这个域是我们进入拥塞控制的时候最大的发送序列号,也就是snd_nxt.
if (!tp->sacked_out) {/* 如果之前没有SACK块 */ /* It's already past, so skip checking against it */ cache = tp->recv_sack_cache + ARRAY_SIZE(tp->recv_sack_cache); } else { cache = tp->recv_sack_cache; /* Skip empty blocks in at head of the cache 上次的SACK 信息块保存在了数组的末尾n 个位置, 所以这里跳过开头的无效SACK信息*/ while (tcp_sack_cache_ok(tp, cache) && !cache->start_seq && !cache->end_seq) cache++; } //下面的逻辑首先是检查cache和SACK信息块是否有交集,如果有,跳过当前SACK块,提高效率 while (i < used_sacks) { u32 start_seq = sp[i].start_seq; u32 end_seq = sp[i].end_seq; bool dup_sack = (found_dup_sack && (i == first_sack_index)); struct tcp_sack_block *next_dup = NULL; if (found_dup_sack && ((i + 1) == first_sack_index)) next_dup = &sp[i + 1]; /* Skip too early cached blocks blocks如果cache的区间为[100, 200), 而当前SACK信息块的区间为[300, 400), 直接跳过这些cache块 */ while (tcp_sack_cache_ok(tp, cache) && !before(start_seq, cache->end_seq)) cache++; /* Can skip some work by looking recv_sack_cache? cache和SACK块一定是有交集的*/ if (tcp_sack_cache_ok(tp, cache) && !dup_sack && after(end_seq, cache->start_seq)) { //1. cache[100, 300), SACK[40, 440) /* Head todo? */ if (before(start_seq, cache->start_seq)) { skb = tcp_sacktag_skip(skb, sk, state, start_seq);//前移发送队列,使得遍历指针知道seq为100的的地方 skb = tcp_sacktag_walk(skb, sk, next_dup, state,//用[40, 100)即[start_seq, cache->start_seq)标记发送队列 start_seq, cache->start_seq, dup_sack); } /* Rest of the block already fully processed? cache[100, 300), SACK[50, 200)SACK信息块的后半段已经全部被cache包含, 这部分已经标记过了,没必要重新标记,所以继续处理下一个SACK信息块 */ if (!after(end_seq, cache->end_seq)) goto advance_sp; /*next_dup不为NULL的唯一一种情况就是输入段的DSACK信息块表示的确认范围被 后面的SACK信息块完全包裹,比如DSACK为[100, 200), 后面SACK为[50, 300), 只有这种情况,排序后,DSACK块才能排在SACK之后,这样next_dup才不为NULL 有可能DSACK和cache也有一部分重合 *///果start_seq < next_dup->start_seq < cache->start_seq,那么next_dup落在 //* (start_seq, cache->start_seq) 内的部分已经被上面的处理过了:)现在处理的next_dup的剩余部分 skb = tcp_maybe_skipping_dsack(skb, sk, next_dup, state, cache->end_seq); /* ...tail remains todo... */ if (tcp_highest_sack_seq(tp) == cache->end_seq) { /* ...but better entrypoint exists! */ skb = tcp_highest_sack(sk); if (!skb)/* 如果已经到了snd_nxt了,那么直接退出SACK块的遍历 */ break; state->fack_count = tp->fackets_out; cache++; goto walk; } ///跳过发送队列中那些序号小于cache->end_seq的skb,它们已经被标记过了 skb = tcp_sacktag_skip(skb, sk, state, cache->end_seq); /* Check overlap against next cached too (past this one already) */ cache++; continue; } //这个SACK信息块和cache块没有重叠,并且其start_seq一定大于所有的cache块的end_seq if (!before(start_seq, tcp_highest_sack_seq(tp))) { skb = tcp_highest_sack(sk); if (!skb) break; state->fack_count = tp->fackets_out; } //跳过发送队列中那些序号小于start_seq的段 skb = tcp_sacktag_skip(skb, sk, state, start_seq); walk://更新序号位于[start_seq,end_seq)之间的skb的记分牌 skb = tcp_sacktag_walk(skb, sk, next_dup, state, start_seq, end_seq, dup_sack); advance_sp: i++; }
、、

用上次缓存的tp->recv_sack_cache块来避免重复工作,提高处理效率。

主要思想就是,处理sack块时,和cache块作比较,如果它们有交集,说明交集部分已经处理过了,

不用再重复处理。

//更新cache,cache的大小同样是4个,并且每次收到SACK,上一次的cache内容都会清除,
    //然后将本次接收的SACK块排序后的结果保存在cache中
    /* Clear the head of the cache sack blocks so we can skip it next time */
    for (i = 0; i < ARRAY_SIZE(tp->recv_sack_cache) - used_sacks; i++) {
        tp->recv_sack_cache[i].start_seq = 0;
        tp->recv_sack_cache[i].end_seq = 0;
    }
    for (j = 0; j < used_sacks; j++)
        tp->recv_sack_cache[i++] = sp[j];
//更新乱序信息
    if ((state->reord < tp->fackets_out) &&
        ((inet_csk(sk)->icsk_ca_state != TCP_CA_Loss) || tp->undo_marker))
        tcp_update_reordering(sk, tp->fackets_out - state->reord, 0);
//更新计数器
    tcp_verify_left_out(tp);
out:

#if FASTRETRANS_DEBUG > 0
    WARN_ON((int)tp->sacked_out < 0);
    WARN_ON((int)tp->lost_out < 0);
    WARN_ON((int)tp->retrans_out < 0);
    WARN_ON((int)tcp_packets_in_flight(tp) < 0);
#endif
    return state->flag;
}

 

tcp_sacktag_walk是遍历重传队列,找到对应需要设置的段,然后设置tcp_cb的sacked域为TCPCB_SACKED_ACKED,这里要注意,还有一种情况就是sack确认了多个skb,这个时候我们就需要合并这些skb,然后再处理

 

/*从参数skb指向的位置开始向后遍历发送队列,
更新序号位于[start_seq,end_seq)之间的skb的记分牌*/
static struct sk_buff *tcp_sacktag_walk(struct sk_buff *skb, struct sock *sk,
                    struct tcp_sack_block *next_dup,
                    struct tcp_sacktag_state *state,
                    u32 start_seq, u32 end_seq,
                    bool dup_sack_in)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct sk_buff *tmp;

    tcp_for_write_queue_from(skb, sk) {
        int in_sack = 0;
        bool dup_sack = dup_sack_in;
//已经遍历到了tp->sk_send_head,后面的skb还没有发送,结束处理
        if (skb == tcp_send_head(sk))
            break;
//因为发送队列中skb的序号是递增的,而当前skb的seq大于等于
        //SACK块的末尾序号,说明该skb和SACK信息块已经没有交集了,
        //而且发送队列后续skb也不会和该SACK信息块有交集了,结束处理
        /* queue is in-order => we can short-circuit the walk early */
        if (!before(TCP_SKB_CB(skb)->seq, end_seq))
            break;
/*//假设用序号dseq和dend表示DSACK信息块的序号,
DSACK有两种情形:        
1)dend<=snd_una,即DSACK是由已经ACK的数据段触发的;    
2)dend>sud_una并且DSACK信息块的序号被后面的SACK信息块包含;        
//情形1根本就无需更新记分牌,所以如果next_dup不为NULL,那么一定        
//是情形2。此时,由于DSACK被SACK包围,所以一定是先处理SACK        
//然后再处理DSACK,此时为了避免重复遍历发送队列,所以需要同时处理这    两个SACK信息块。
*/
/*下一个是DSACK块,需要先处理DSACK块。如果dend>=skb->seq,
        //说明DSACK块和当前skb可能有交集*/
        if (next_dup  &&
            before(TCP_SKB_CB(skb)->seq, next_dup->end_seq)) {
              //传入的序号范围是DSACK的序号范围
            in_sack = tcp_match_skb_to_sack(sk, skb,
                            next_dup->start_seq,
                            next_dup->end_seq);
              //1)in_sack==0:压根就没有DSACK;
        //2)DSACK无法确认skb;
        //3)in_sack<0:DSACK导致了skb分片,但是分片失败了
            if (in_sack > 0)
                dup_sack = true;
        }

        /* skb reference here is a bit tricky to get right, since
         * shifting can eat and free both this skb and the next,
         * so not even _safe variant of the loop is enough.
         */
        if (in_sack <= 0) {
            //对于情形1,没得说,这里用SACK检测。情形2和3也一样是因为SACK是DSACK的超集
            tmp = tcp_shift_skb_data(sk, skb, state,
                         start_seq, end_seq, dup_sack);
            if (tmp) {
                if (tmp != skb) {
                    skb = tmp;
                    continue;
                }

                in_sack = 0;
            } else {
                in_sack = tcp_match_skb_to_sack(sk, skb,
                                start_seq,
                                end_seq);
            }
        }
//结果小于0,说明skb分割失败,处理结束
        if (unlikely(in_sack < 0))
            break;
//如果最终检测到skb数据可以被确认,则标记该skb
        if (in_sack) {
            TCP_SKB_CB(skb)->sacked =
/* Mark the given newly-SACKed range as such, adjusting counters and hints.
用来设置对应的tag,这里所要设置的也就是tcp_cb的sacked域
值 为:
#define TCPCB_SACKED_ACKED      0x01 /* SKB ACK'd by a SACK block    
#define TCPCB_SACKED_RETRANS    0x02  /* SKB retransmitted       
#define TCPCB_LOST              0x04  /* SKB is lost         
#define TCPCB_TAGBITS           0x07  /* All tag bits         
#define TCPCB_EVER_RETRANS      0x80  /* Ever retransmitted frame
#define TCPCB_RETRANS     (TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS)
*/ tcp_sacktag_one(sk, state, TCP_SKB_CB(skb)->sacked, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq, dup_sack, tcp_skb_pcount(skb), &skb->skb_mstamp); if (!before(TCP_SKB_CB(skb)->seq, tcp_highest_sack_seq(tp))) tcp_advance_highest_sack(sk, skb); } state->fack_count += tcp_skb_pcount(skb); } return skb; } /* Avoid all extra work that is being done by sacktag while walking in * a normal way 参数skb指向发送队列中的某个skb,该函数从skb开始向后遍历发送队列, 直到遍历到sk->sk_send_head(即下一个要发送的skb, 可能为NULL)或者skb的末尾序号 (假设序号范围为[seq1, end1))end1大于等于参数skip_to_seq为止, 返回终止时的skb指针。 该函数的作用就是跳过那些序号小于SACK信息块确认范围的skb。 */ static struct sk_buff *tcp_sacktag_skip(struct sk_buff *skb, struct sock *sk, struct tcp_sacktag_state *state, u32 skip_to_seq) { tcp_for_write_queue_from(skb, sk) { if (skb == tcp_send_head(sk)) break; if (after(TCP_SKB_CB(skb)->end_seq, skip_to_seq)) break; state->fack_count += tcp_skb_pcount(skb); } return skb; }

 

note:SACK 选项的接收方在收到一个包后可能会被要求做大量处理。这种 N:1 的比例使 SACK 发送方可以打击非常强大的平台上的接收方。

然后攻击者发送一个充满 SACK 选项的包,目的是使另一方主机扫描整个队列以处理每个选项。一个指向队列末尾包的选项可能会迫使接收方的 TCP 协议栈遍历整个队列以判断此选项指向哪一个包。显然,攻击客户机可以根据自己的喜好发送任何 SACK 选项,但不太容易看出的一个问题是,它可以轻易控制被攻击方的重传队列的长度。SACK 选项的创建者可以控制接收者对接收到的每个选项执行的工作量,因为它也可以控制队列的大小。

 

在DSACK判断函数tcp_check_dsack中,如果SACK序号块被认定为DSACK,并且undo_retrans大于零(进行过重传操作),并且,DSACK序号块的终止序号满足如下条件:

 prior_SND.UNA >= end_seq_0 > undo_marker     表明对端接收到了原始报文和拥塞之后发送的重传报文,将undo_retrans递减一 dup-seg

 参考之前文章Selective Acknowledgment 选项 浅析 

 tcp_check_dsack用于判断收到的第一个SACK块是否是DSACK,参数sp指向输入段携带的SACK选项信息,num_sacks表示输入段携带了几个SACK块。

参考RFC 2883.,就是接收端只有在收到重复段的情况下才会发送DSACK,而重复段有两种情况:

  1. 该段已经被确认过了;
  2. 该段是个乱序段,但是之前也已经接收过该乱序段了;

所以,对应的DSACK块有两种情况:

  1. DSACK块的起始序号小于ACK序号;
  2. DSACK块的序号范围一定在后一个SACK块的序号范围之内。
static bool tcp_check_dsack(struct sock *sk, const struct sk_buff *ack_skb,
			    struct tcp_sack_block_wire *sp, int num_sacks,
			    u32 prior_snd_una, struct tcp_sacktag_state *state)
{
	struct tcp_sock *tp = tcp_sk(sk);
    //DSACK只能出现在sp[0],这里提取sp[0]的起始序号
	u32 start_seq_0 = get_unaligned_be32(&sp[0].start_seq);
	u32 end_seq_0 = get_unaligned_be32(&sp[0].end_seq);
	u32 dup_segs;
    //如果SACK块的起始序号小于输入段携带的确认号,那么说明对端一定是收到了
	//一个已经确认过的重复段,才会触发这样的SACK,所以认为这是一个DSACK
	if (before(start_seq_0, TCP_SKB_CB(ack_skb)->ack_seq)) {
		NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPDSACKRECV);
	} else if (num_sacks > 1) {
        //如果有多个SACK块,那么提取第二个SACK块的起始序号
		u32 end_seq_1 = get_unaligned_be32(&sp[1].end_seq);
		u32 start_seq_1 = get_unaligned_be32(&sp[1].start_seq);
        //如果第二个SACK块将第一个SACK块完全包含,那么说明对端一定是收到了
		//一个乱序的重复段,所以也认为这是一个DSACK
		if (after(end_seq_0, end_seq_1) || before(start_seq_0, start_seq_1))
			return false;
		NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPDSACKOFORECV);
	} else {
		return false;
	}

	dup_segs = tcp_dsack_seen(tp, start_seq_0, end_seq_0, state);
	if (!dup_segs) {	/* Skip dubious DSACK */
		NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPDSACKIGNOREDDUBIOUS);
		return false;
	}

	NET_ADD_STATS(sock_net(sk), LINUX_MIB_TCPDSACKRECVSEGS, dup_segs);

	/* D-SACK for already forgotten data... Do dumb counting. */
	if (tp->undo_marker && tp->undo_retrans > 0 &&
	    !after(end_seq_0, prior_snd_una) &&
	    after(end_seq_0, tp->undo_marker))
        //undo_retrans记录重传数据包的个数,如果undo_retrans降到0, 就说明之前的重传都是不必要的,进行拥塞调整撤销。
       // 表明对端接收到了原始报文和拥塞之后发送的重传报文,将undo_retrans---dupseg
        //undo_marker 作为一个标记,记录下进入恢复阶段时的 snd_una 值
		tp->undo_retrans = max_t(int, 0, tp->undo_retrans - dup_segs);

	return true;
}
/* Take a notice that peer is sending D-SACKs. Skip update of data delivery
 * and spurious retransmission information if this DSACK is unlikely caused by
 * sender's action:
 * - DSACKed sequence range is larger than maximum receiver's window.
 * - Total no. of DSACKed segments exceed the total no. of retransmitted segs.
 */
static u32 tcp_dsack_seen(struct tcp_sock *tp, u32 start_seq,
			  u32 end_seq, struct tcp_sacktag_state *state)
{
	u32 seq_len, dup_segs = 1;

	if (!before(start_seq, end_seq))
		return 0;

	seq_len = end_seq - start_seq;
	/* Dubious DSACK: DSACKed range greater than maximum advertised rwnd */
	if (seq_len > tp->max_window)
		return 0;
	if (seq_len > tp->mss_cache)
		dup_segs = DIV_ROUND_UP(seq_len, tp->mss_cache);

	tp->dsack_dups += dup_segs;
	/* Skip the DSACK if dup segs weren't retransmitted by sender */
	if (tp->dsack_dups > tp->total_retrans)
		return 0;
    //设置sack_ok的bit3,表示检测到了DSACK
	tp->rx_opt.sack_ok |= TCP_DSACK_SEEN;
	tp->rack.dsack_seen = 1;

	state->flag |= FLAG_DSACKING_ACK;
	/* A spurious retransmission is delivered */
	state->sack_delivered += dup_segs;

	return dup_segs;
}

检测SACK块是否有效

@is_dsack: 要检测的SACK块是否是一个DSACK块
static int tcp_is_sackblock_valid(struct tcp_sock *tp, int is_dsack, u32 start_seq, u32 end_seq)
{
	/* Too far in future, or reversed (interpretation is ambiguous) */
	//cond1:SACK的确认范围包含了还没有发送的数据;
	//cond2: SACK块的第一个序号大于等于第二个序号,序号范围非法
	if (after(end_seq, tp->snd_nxt) || !before(start_seq, end_seq))
		return 0;

	/* Nasty start_seq wrap-around check (see comments above) */
	//和上面的cond1类似,确认范围不合理
	if (!before(start_seq, tp->snd_nxt))
		return 0;

	/* In outstanding window? ...This is valid exit for D-SACKs too.
	 * start_seq == snd_una is non-sensical (see comments above)
	 */
	//满足该条件,说明SACK确认范围确实是在[snd_una, snd_nxt)之间,合法SACK
	if (after(start_seq, tp->snd_una))
		return 1;
	//到了这里,说明SACK确认范围和[snd_una, snd_nxt)是这样的:
	//snd_una=200, snd_nxt=1000,start_seq=100, end_seq = 500
	//即SACK的前半部分是已经被ACK过的,后半部分是没有ACK的(虚假SACK)

	if (!is_dsack || !tp->undo_marker)
		return 0;

	/* ...Then it's D-SACK, and must reside below snd_una completely */
	if (!after(end_seq, tp->snd_una))
		return 0;

	if (!before(start_seq, tp->undo_marker))
		return 1;

	/* Too old */
	if (!after(end_seq, tp->undo_marker))
		return 0;

	/* Undo_marker boundary crossing (overestimates a lot). Known already:
	 *   start_seq < undo_marker and end_seq >= undo_marker.
	 */
	return !before(start_seq, end_seq - tp->max_window);
}

 

posted @ 2020-05-17 17:08  codestacklinuxer  阅读(245)  评论(0编辑  收藏  举报