SACK选项及生成

SACK功能由两个TCP选项组成,分别为SACK_PERMITTED和SACK选项。前者用于协商SACK能力,仅可出现在设置了SYN标志的报文中,如下为其格式。

    TCP Sack-Permitted Option:
    Kind: 4
	
    +---------+---------+
    | Kind=4  | Length=2|
    +---------+---------+

后者用于在连接建立之后传输实际的SACK数据,其格式如下。当TCP接收到不连续的数据块并且加入队列之后,将发送SACK响应,通知发送端。此选项包含不定长度的块列表,每个块描述接收到的数据的首个序号(left)和结尾序号加一(right)。对于n个块的话,占用长度为: 1 kind + 1 length + n*8,由于TCP选项的总长度为40字节,所以最多可容纳4个SACK块。通常情况下,为了进行RTTM的测量,TCP报文会携带Timestamps选项,其长度为10个字节,所以SACK选项一般最大为3个块。

    TCP SACK Option:
    Kind: 5

    Length: Variable
    +--------+--------+
    | Kind=5 | Length |
    +--------+--------+--------+--------+
    | Left Edge of 1st Block |
    +--------+--------+--------+--------+
    | Right Edge of 1st Block |
    +--------+--------+--------+--------+
    | |
    / . . . /
    | |
    +--------+--------+--------+--------+
    | Left Edge of nth Block |
    +--------+--------+--------+--------+
    | Right Edge of nth Block |
    +--------+--------+--------+--------+

SACK_PERM协商

可通过PROC文件/proc/sys/net/ipv4/tcp_sack控制对SACK功能的支持,默认情况下tcp_sack为一。这样在SYN报文中将携带SACK_PERM选项。

static unsigned int tcp_syn_options(struct sock *sk, struct sk_buff *skb,
                struct tcp_out_options *opts, struct tcp_md5sig_key **md5)
{
    struct tcp_sock *tp = tcp_sk(sk);

    if (likely(sock_net(sk)->ipv4.sysctl_tcp_sack)) {
        opts->options |= OPTION_SACK_ADVERTISE;
        if (unlikely(!(OPTION_TS & opts->options)))
            remaining -= TCPOLEN_SACKPERM_ALIGNED;
    } 

由于SACK_PERM选项占用2个字节,通常将其与timestamps选项的kind和长度字段(2字节)合并为一个32bit,但是,如果不支持timestamps选项,将在SACK_PERM选项前添加两个NOP选项。

static void tcp_options_write(__be32 *ptr, struct tcp_sock *tp, struct tcp_out_options *opts)
{

    if (unlikely(OPTION_SACK_ADVERTISE & options)) {
        *ptr++ = htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) |
                   (TCPOPT_SACK_PERM << 8) | TCPOLEN_SACK_PERM);
    }

    if (unlikely(opts->num_sack_blocks)) {
        ...
    }

当TCP服务端解析报文的TCP选项字段时,如果当前为连接建立阶段的带有SYN标志的报文,并且本端开启了SACK的支持,设置sack_ok标志,表示SACK_PERM协商完成。使用函数tcp_sack_reset复位SACK相关记录。对于SACK选项,如果其长度值合法,并且SACK_PERM协商成功,在TCP控制块结构成员sacked中保存sack选项首地址相较于TCP头部的偏移值,将在函数tcp_sacktag_write_queue中依据此偏移值访问SACK块数据。

void tcp_parse_options(const struct net *net, const struct sk_buff *skb,
               struct tcp_options_received *opt_rx, int estab, struct tcp_fastopen_cookie *foc)
{
            switch (opcode) {
            case TCPOPT_SACK_PERM:
                if (opsize == TCPOLEN_SACK_PERM && th->syn &&
                    !estab && net->ipv4.sysctl_tcp_sack) {
                    opt_rx->sack_ok = TCP_SACK_SEEN;
                    tcp_sack_reset(opt_rx);
                }
                break;
            case TCPOPT_SACK:
                if ((opsize >= (TCPOLEN_SACK_BASE + TCPOLEN_SACK_PERBLOCK)) &&
                   !((opsize - TCPOLEN_SACK_BASE) % TCPOLEN_SACK_PERBLOCK) &&
                   opt_rx->sack_ok) {
                    TCP_SKB_CB(skb)->sacked = (ptr - 2) - (unsigned char *)th;
                }
                break;

同样,TCP服务端将回复给客户端的SYNACK报文中添加SACK_PERM字段。客户端在函数tcp_rcv_synsent_state_process中调用以上介绍的选项解析函数tcp_parse_options,解析SACK_PERM选项,记录下协商结果(sack_ok)。

static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct tcp_sock *tp = tcp_sk(sk);

    tcp_parse_options(sock_net(sk), skb, &tp->rx_opt, 0, &foc);

SACK块的基本操作

对于SACK块,内核中分为两个类型:SACK块和重复SACK块(DSACK)。以下函数tcp_sack_extend,tcp_sack_maybe_coalesce和tcp_sack_remove操作SACK块;而函数tcp_dsack_set和tcp_dsack_extend负责操作DSACK块。

函数tcp_sack_extend如下,其尝试扩展SACK块的左右边界。如果当前报文的开始序号在sp块中记录的结束序号之前,并且sp块中记录的起始序号在报文的结束序号之前,表明两者的左或者右边界一定存在交叉部分,或者两者为包含关系,进行相应的边界扩展。

static inline bool tcp_sack_extend(struct tcp_sack_block *sp, u32 seq, u32 end_seq)
{
    if (!after(seq, sp->end_seq) && !after(sp->start_seq, end_seq)) {
        if (before(seq, sp->start_seq))
            sp->start_seq = seq;
        if (after(end_seq, sp->end_seq))
            sp->end_seq = end_seq;
        return true;
    }
    return false;
}

函数tcp_sack_maybe_coalesce检查SACK块是否可进行合并。如以上函数tcp_sack_extend所述,返回值,表明将后一个SACK合并到了sp中,将SACK块数量减一,并将之后的SACK块向前移动。

static void tcp_sack_maybe_coalesce(struct tcp_sock *tp)
{
    int this_sack;
    struct tcp_sack_block *sp = &tp->selective_acks[0];
    struct tcp_sack_block *swalk = sp + 1;
    
    for (this_sack = 1; this_sack < tp->rx_opt.num_sacks;) {
        if (tcp_sack_extend(sp, swalk->start_seq, swalk->end_seq)) {
            int i;

            /* Zap SWALK, by moving every further SACK up by one slot. Decrease num_sacks. 
             */
            tp->rx_opt.num_sacks--;
            for (i = this_sack; i < tp->rx_opt.num_sacks; i++)
                sp[i] = sp[i + 1]; 
            continue;
        }
        this_sack++, swalk++;
    }
}

函数tcp_sack_remove移除SACK块,遍历selective_acks数组,找到在RCV.NXT之前的SACK块,如果SACK块仅有一部分在RCV.NXT之前,发出警告。如果SACK块的起止序号都在RCV.NXT之前,表明已经接收到了此块数据,随将数组中之后的SACK块向前移动,覆盖此SACK块,达到删除的效果。

/* RCV.NXT advances, some SACKs should be eaten. */
static void tcp_sack_remove(struct tcp_sock *tp)
{
    struct tcp_sack_block *sp = &tp->selective_acks[0];
    int num_sacks = tp->rx_opt.num_sacks;

    for (this_sack = 0; this_sack < num_sacks;) {
        /* Check if the start of the sack is covered by RCV.NXT. */
        if (!before(tp->rcv_nxt, sp->start_seq)) {

            WARN_ON(before(tp->rcv_nxt, sp->end_seq)); /* RCV.NXT must cover all the block! */

            /* Zap this SACK, by moving forward any other SACKS. */
            for (i = this_sack+1; i < num_sacks; i++)
                tp->selective_acks[i-1] = tp->selective_acks[i];
            num_sacks--;
            continue;
        }
        this_sack++;
        sp++;
    }
    tp->rx_opt.num_sacks = num_sacks;

如下为DSACK的操作函数tcp_dsack_set,其前提是连接协商完成了对SACK_PERM的支持,并且系统开启了对重复DSACK的支持,将重复的起止序号保存在套接口的duplicate_sack结构中。这里duplicate_sack数组的最大长度为一。

static void tcp_dsack_set(struct sock *sk, u32 seq, u32 end_seq)
{
    struct tcp_sock *tp = tcp_sk(sk);

    if (tcp_is_sack(tp) && sock_net(sk)->ipv4.sysctl_tcp_dsack) {
        int mib_idx;

        if (before(seq, tp->rcv_nxt))
            mib_idx = LINUX_MIB_TCPDSACKOLDSENT;
        else
            mib_idx = LINUX_MIB_TCPDSACKOFOSENT;

        NET_INC_STATS(sock_net(sk), mib_idx);

        tp->rx_opt.dsack = 1;
        tp->duplicate_sack[0].start_seq = seq;
        tp->duplicate_sack[0].end_seq = end_seq;
    }
}

如下DSACK块的扩展函数tcp_dsack_extend,如果dsack为0,调用以上介绍的函数tcp_dsack_set添加dsack块。否则,借用SACK的函数tcp_sack_extend扩展dsack块。

static void tcp_dsack_extend(struct sock *sk, u32 seq, u32 end_seq)
{
    struct tcp_sock *tp = tcp_sk(sk);

    if (!tp->rx_opt.dsack)
        tcp_dsack_set(sk, seq, end_seq);
    else
        tcp_sack_extend(tp->duplicate_sack, seq, end_seq);
}

报文验证与DSACK

如下接收验证函数tcp_validate_incoming中,如果PAWS验证没有通过,并且此报文没有设置RST标志位,调用函数tcp_send_dupack发送DSACK。或者接收报文的序号不可接受,并且未设置SYN标志(SYNACK报文),调用函数tcp_send_dupack发送DSACK。

static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, int syn_inerr)
{
    struct tcp_sock *tp = tcp_sk(sk);
    bool rst_seq_match = false;

    /* RFC1323: H1. Apply PAWS check first. */
    if (tcp_fast_parse_options(sock_net(sk), skb, th, tp) && tp->rx_opt.saw_tstamp &&
        tcp_paws_discard(sk, skb)) {
        if (!th->rst) {
            if (!tcp_oow_rate_limited(sock_net(sk), skb,
                          LINUX_MIB_TCPACKSKIPPEDPAWS, &tp->last_oow_ack_time))
                tcp_send_dupack(sk, skb);
            goto discard;
    }

    /* Step 1: check sequence number */
    if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
        if (!th->rst) {
            if (th->syn)
                goto syn_challenge;
            if (!tcp_oow_rate_limited(sock_net(sk), skb,
                          LINUX_MIB_TCPACKSKIPPEDSEQ, &tp->last_oow_ack_time))
                tcp_send_dupack(sk, skb);

以下验证接收到的RST报文,如果RST的序号不等于RCV.NXT,但是等于SACK块中记录的最大序号(max_sack),那么也认为此RST报文有效,复位连接。

    if (th->rst) {
        if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt || tcp_reset_check(sk, skb)) {
            rst_seq_match = true;
        } else if (tcp_is_sack(tp) && tp->rx_opt.num_sacks > 0) {
            struct tcp_sack_block *sp = &tp->selective_acks[0];
            int max_sack = sp[0].end_seq;

            for (this_sack = 1; this_sack < tp->rx_opt.num_sacks; ++this_sack) {
                max_sack = after(sp[this_sack].end_seq, max_sack) ?
                    sp[this_sack].end_seq : max_sack;
            }
            if (TCP_SKB_CB(skb)->seq == max_sack) rst_seq_match = true;
        }

        if (rst_seq_match) tcp_reset(sk);

如上所示,内核使用tcp_send_dupack函数发送DSACK,其首先确认是否接收到重复数据,之后计算重复数据的起止序号,设置DSACK块,调用tcp_send_ack执行发送操作。

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

    if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
        before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {

        if (tcp_is_sack(tp) && sock_net(sk)->ipv4.sysctl_tcp_dsack) {
            u32 end_seq = TCP_SKB_CB(skb)->end_seq;

            tcp_rcv_spurious_retrans(sk, skb);
            if (after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt))
                end_seq = tp->rcv_nxt;
            tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, end_seq);
        }
    }

    tcp_send_ack(sk);
}

数据报文接收与SACK/DSACK发送

如下数据接收函数tcp_data_queue,如果报文的起始序号seq等于当前套接口的下一个等待接收序号rcv_nxt,表明接收到一个保序的报文。在接收完成之后,如果存在SACK块,调用tcp_sack_remove尝试删除已经接收到其序号范围的SACK块。

static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
{
    if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {
        eaten = tcp_queue_rcv(sk, skb, &fragstolen);
        if (skb->len)
            tcp_event_data_recv(sk, skb);
        if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
            tcp_fin(sk);

        if (!RB_EMPTY_ROOT(&tp->out_of_order_queue)) {
            tcp_ofo_queue(sk);

            /* RFC5681. 4.2. SHOULD send immediate ACK, when gap in queue is filled.
             */
            if (RB_EMPTY_ROOT(&tp->out_of_order_queue))
                inet_csk(sk)->icsk_ack.pending |= ICSK_ACK_NOW;
        }

        if (tp->rx_opt.num_sacks)
            tcp_sack_remove(tp);
        return;
    }

如果报文的结束序号end_seq在当前套接口的下一个等待接收序号rcv_nxt之前,表明接收到一个重复的报文。使用函数tcp_dsack_set将其起止序号添加到DSACK块中。

    if (!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt)) {
        tcp_rcv_spurious_retrans(sk, skb);
        /* A retransmit, 2nd most common case.  Force an immediate ack. */
        NET_INC_STATS(sock_net(sk), LINUX_MIB_DELAYEDACKLOST);
        tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq);

out_of_window:
        tcp_enter_quickack_mode(sk, TCP_MAX_QUICKACKS);
        inet_csk_schedule_ack(sk);
drop:
        tcp_drop(sk, skb);
        return;
    }

另外,如果以上不成立,结束序号在rcv_nxt之后,但是报文的开始序号seq在rcv_nxt之前,表明接收到的报文的前段有重复的数据,长度为rcv_nxt - seq,将相应重复的序号设置到DSACK块中。

    /* Out of window. F.e. zero window probe. */
    if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt + tcp_receive_window(tp)))
        goto out_of_window;

    if (before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
        /* Partial packet, seq < rcv_next < end_seq */
        SOCK_DEBUG(sk, "partial packet: rcv_next %X seq %X - %X\n",
               tp->rcv_nxt, TCP_SKB_CB(skb)->seq,
               TCP_SKB_CB(skb)->end_seq);

        tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, tp->rcv_nxt);
		
        goto queue_and_out;
    }

    tcp_data_queue_ofo(sk, skb);

最后,在函数tcp_data_queue中,如果接收到的为保序的报文,将其放入接收队列,并且更新rcv_nxt序号。如果套接口乱序队列不为空,需检查是否可将其中的数据移至接收队列。如下函数tcp_ofo_queue,遍历乱序队列,如果其中的报文起始序号在rcv_nxt之前,表明此报文与刚刚接收的报文有重复数据,计算重复数据的起止序号,添加到DSACK块中(tcp_dsack_extend)。

static void tcp_ofo_queue(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);
    __u32 dsack_high = tp->rcv_nxt;

    p = rb_first(&tp->out_of_order_queue);
    while (p) {
        skb = rb_to_skb(p);
        if (after(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))
            break;

        if (before(TCP_SKB_CB(skb)->seq, dsack_high)) {
            __u32 dsack = dsack_high;
            if (before(TCP_SKB_CB(skb)->end_seq, dsack_high))
                dsack_high = TCP_SKB_CB(skb)->end_seq;
            tcp_dsack_extend(sk, TCP_SKB_CB(skb)->seq, dsack);
        }
        p = rb_next(p);
        rb_erase(&skb->rbnode, &tp->out_of_order_queue);

如果乱序队列中包含rcv_nxt序号之后的连续的数据,将报文数据添加到接收队列(sk_receive_queue),并且更新rcv_nxt,继续以上的过程。

        if (unlikely(!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt))) {
            SOCK_DEBUG(sk, "ofo packet was already received\n");
            tcp_drop(sk, skb);
            continue;
        }
        SOCK_DEBUG(sk, "ofo requeuing : rcv_next %X seq %X - %X\n",
               tp->rcv_nxt, TCP_SKB_CB(skb)->seq,
               TCP_SKB_CB(skb)->end_seq);

        tail = skb_peek_tail(&sk->sk_receive_queue);
        eaten = tail && tcp_try_coalesce(sk, tail, skb, &fragstolen);
        tcp_rcv_nxt_update(tp, TCP_SKB_CB(skb)->end_seq);
        fin = TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN;
        if (!eaten)
            __skb_queue_tail(&sk->sk_receive_queue, skb);
        else
            kfree_skb_partial(skb, fragstolen);

乱序报文与SACK/DSACK

以下函数tcp_data_queue_ofo处理乱序的TCP报文,如果套接口的乱序队列为空,并且SACK_PERM协商成功,添加SACK块到selective_acks数组中(index为0)。

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

    if (unlikely(tcp_try_rmem_schedule(sk, skb, skb->truesize))) {
        NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPOFODROP);
        tcp_drop(sk, skb);
        return;
    }

    p = &tp->out_of_order_queue.rb_node;
    if (RB_EMPTY_ROOT(&tp->out_of_order_queue)) { /* Initial out of order segment, build 1 SACK. */
        if (tcp_is_sack(tp)) {
            tp->rx_opt.num_sacks = 1;
            tp->selective_acks[0].start_seq = seq;
            tp->selective_acks[0].end_seq = end_seq;
        }
        rb_link_node(&skb->rbnode, NULL, p);
        rb_insert_color(&skb->rbnode, &tp->out_of_order_queue);
        tp->ooo_last_skb = skb;
        goto end;
    }

如果当前报文skb正好可与乱序队列的最后一个报文序号连着一起,将其合并到最后一个报文,跳到增加sack块处执行。

    /* In the typical case, we are adding an skb to the end of the list.
     * Use of ooo_last_skb avoids the O(Log(N)) rbtree lookup.
     */
    if (tcp_ooo_try_coalesce(sk, tp->ooo_last_skb, skb, &fragstolen)) {
coalesce_done:
        tcp_grow_window(sk, skb);
        kfree_skb_partial(skb, fragstolen);
        skb = NULL;
        goto add_sack;
    }

如果当前报文的起始序号seq在乱序队列的最后一个报文的结束序号之后,直接指向插入操作。

    /* Can avoid an rbtree lookup if we are adding skb after ooo_last_skb */
    if (!before(seq, TCP_SKB_CB(tp->ooo_last_skb)->end_seq)) {
        parent = &tp->ooo_last_skb->rbnode;
        p = &parent->rb_right;
        goto insert;
    }

以下情况表明当前报文位于乱序队列的中部,如果乱序队列中的报文skb1完全的包含当前报文的数据,丢弃当前报文,设置重复DSACK,参见函数tcp_dsack_set。

    /* Find place to insert this segment. Handle overlaps on the way. */
    parent = NULL;
    while (*p) {
        parent = *p;
        skb1 = rb_to_skb(parent);
        if (before(seq, TCP_SKB_CB(skb1)->seq)) {
            p = &parent->rb_left;
            continue;
        }
        if (before(seq, TCP_SKB_CB(skb1)->end_seq)) {
            if (!after(end_seq, TCP_SKB_CB(skb1)->end_seq)) {
                /* All the bits are present. Drop. */
                NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPOFOMERGE);
                tcp_drop(sk, skb);
                skb = NULL;
                tcp_dsack_set(sk, seq, end_seq);
                goto add_sack;
            }

如果当前报文与乱序队列中的报文skb1部分重叠,同样需要设置DSACK。否则,当前报文包含乱序队列中的报文skb1,替换乱序队列中的报文skb1,设置扩展DSACK,参见函数tcp_dsack_extend。

            if (after(seq, TCP_SKB_CB(skb1)->seq)) {
                /* Partial overlap. */
                tcp_dsack_set(sk, seq, TCP_SKB_CB(skb1)->end_seq);
            } else {
                /* skb's seq == skb1's seq and skb covers skb1. Replace skb1 with skb.
                 */
                rb_replace_node(&skb1->rbnode, &skb->rbnode, &tp->out_of_order_queue);
                tcp_dsack_extend(sk,
                         TCP_SKB_CB(skb1)->seq, TCP_SKB_CB(skb1)->end_seq);
                NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPOFOMERGE);
                tcp_drop(sk, skb1);
                goto merge_right;
            }
        }

由于以上在将当前报文插入乱序队列的过程中,可能有重叠情况的发生,在插入之后,清除重复的报文,并且更新扩展DSACK块中的序号范围。

    /* Insert segment into RB tree. */
    rb_link_node(&skb->rbnode, parent, p);
    rb_insert_color(&skb->rbnode, &tp->out_of_order_queue);

merge_right:
    /* Remove other segments covered by skb. */
    while ((skb1 = skb_rb_next(skb)) != NULL) {
        if (!after(end_seq, TCP_SKB_CB(skb1)->seq))
            break;
        if (before(end_seq, TCP_SKB_CB(skb1)->end_seq)) {
            tcp_dsack_extend(sk, TCP_SKB_CB(skb1)->seq, end_seq);
            break;
        }
        rb_erase(&skb1->rbnode, &tp->out_of_order_queue);
        tcp_dsack_extend(sk, TCP_SKB_CB(skb1)->seq, TCP_SKB_CB(skb1)->end_seq);
        NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPOFOMERGE);
        tcp_drop(sk, skb1);
    }

具体的SACK块添加操作由函数tcp_sack_new_ofo_skb完成。

add_sack:
    if (tcp_is_sack(tp))
        tcp_sack_new_ofo_skb(sk, seq, end_seq);

如果SACK块列表中存在与当前报文的起止序号交叉的块,扩展列表中的响应SACK块,参见以上介绍的tcp_sack_extend函数,之后,将扩展的SACK块移动到列表的首位。如果列表中SACK块数量大于一,调用以上介绍的函数tcp_sack_maybe_coalesce尝试进行合并。

static void tcp_sack_new_ofo_skb(struct sock *sk, u32 seq, u32 end_seq)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct tcp_sack_block *sp = &tp->selective_acks[0];
    int cur_sacks = tp->rx_opt.num_sacks;

    if (!cur_sacks) goto new_sack;

    for (this_sack = 0; this_sack < cur_sacks; this_sack++, sp++) {
        if (tcp_sack_extend(sp, seq, end_seq)) {
            /* Rotate this_sack to the first one. */
            for (; this_sack > 0; this_sack--, sp--)
                swap(*sp, *(sp - 1));
            if (cur_sacks > 1)
                tcp_sack_maybe_coalesce(tp);
            return;
        }
    }

否则,如果没有在列表中找到相邻的SACK块,创建一个新的SACK块,当SACK块数量大于等于TCP_NUM_SACKS(4)的时候,丢弃列表末端的SACK块。并将之前的SACK块全部向后移动,将新SACK块添加在列表前端。

    if (this_sack >= TCP_NUM_SACKS) {
        if (tp->compressed_ack > TCP_FASTRETRANS_THRESH)
            tcp_send_ack(sk);
        this_sack--;
        tp->rx_opt.num_sacks--;
        sp--;
    }
    for (; this_sack > 0; this_sack--, sp--)
        *sp = *(sp - 1);

new_sack:
    /* Build the new head SACK, and we're done. */
    sp->start_seq = seq;
    sp->end_seq = end_seq;
    tp->rx_opt.num_sacks++;
}

在函数tcp_data_queue_ofo的开始部分调用tcp_try_rmem_schedule函数,处理TCP协议rmem短缺的问题,可能需要释放掉乱序队列中的报文,以满足空间需求,如下函数tcp_prune_ofo_queue,在释放乱序队列(部分或者全部)之后,清空全部的SACK块。

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);
    return true;

SACK选项发送

如下函数tcp_established_options所示,SACK的数量由SACK和DSACK的数量组成。但是,总的SACK占用的空间不超出TCP选项字段剩余的空间。

static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb,
                    struct tcp_out_options *opts, struct tcp_md5sig_key **md5)
{
    eff_sacks = tp->rx_opt.num_sacks + tp->rx_opt.dsack;
    if (unlikely(eff_sacks)) {
        const unsigned int remaining = MAX_TCP_OPTION_SPACE - size;
        opts->num_sack_blocks = min_t(unsigned int, eff_sacks,
                  (remaining - TCPOLEN_SACK_BASE_ALIGNED) / TCPOLEN_SACK_PERBLOCK);
        size += TCPOLEN_SACK_BASE_ALIGNED +
            opts->num_sack_blocks * TCPOLEN_SACK_PERBLOCK;
    }

如下所示SACK选项赋值时,优先发送DSACK块的内容,之后是SACK列表的块内容。在内存中,duplicate_sack数组和selective_acks数组连在一起。最后,因为DSACK只有一个块,发送之后将其数量清空。

static void tcp_options_write(__be32 *ptr, struct tcp_sock *tp, struct tcp_out_options *opts)
{
    if (unlikely(opts->num_sack_blocks)) {
        struct tcp_sack_block *sp = tp->rx_opt.dsack ?
            tp->duplicate_sack : tp->selective_acks;
        int this_sack;

        *ptr++ = htonl((TCPOPT_NOP  << 24) | (TCPOPT_NOP  << 16) |
                   (TCPOPT_SACK <<  8) |
                   (TCPOLEN_SACK_BASE + (opts->num_sack_blocks *
                             TCPOLEN_SACK_PERBLOCK)));

        for (this_sack = 0; this_sack < opts->num_sack_blocks;
             ++this_sack) {
            *ptr++ = htonl(sp[this_sack].start_seq);
            *ptr++ = htonl(sp[this_sack].end_seq);
        }

        tp->rx_opt.dsack = 0;
    }

内核版本 5.0

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页