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

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