時間排序的SACK未確認報文鏈表

內核實現的時間排序的未確認報文鏈表(time-sorted sent but un-SACKed skbs),用於加速RACK算法的處理。

tsorted鏈表初始化

首先是位於套接口的初始化函數tcp_init_sock中,初始化此鏈表tsorted_sent_queue。

void tcp_init_sock(struct sock *sk)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct tcp_sock *tp = tcp_sk(sk);

    INIT_LIST_HEAD(&tp->tsorted_sent_queue);

其次,是位於子套接口的創建函數tcp_create_openreq_child中,初始化子套接口的tsorted鏈表。

struct sock *tcp_create_openreq_child(const struct sock *sk,
                      struct request_sock *req, struct sk_buff *skb)
{
    INIT_LIST_HEAD(&newtp->tsorted_sent_queue);

最後,在斷開連接、復位連接、銷燬套接口、或者因套接口異常需要關閉時,將在函數tcp_write_queue_purge中清理tsorted鏈表,並且進行重新初始化。在初始化之前,先使用函數tcp_skb_tsorted_anchor_cleanup進行了相應清理操作,對此稍後進行介紹。

void tcp_write_queue_purge(struct sock *sk)
{
    struct sk_buff *skb;

    tcp_chrono_stop(sk, TCP_CHRONO_BUSY);
    while ((skb = __skb_dequeue(&sk->sk_write_queue)) != NULL) {
        tcp_skb_tsorted_anchor_cleanup(skb);
        sk_wmem_free_skb(sk, skb);
    }
    tcp_rtx_queue_purge(sk);
    INIT_LIST_HEAD(&tcp_sk(sk)->tsorted_sent_queue);

sk_buff中的tsorted鏈表掛點

在sk_buff結構中,tsorted鏈表成員tcp_tsorted_anchor與_skb_refdst和destructor定義在一個聯合體union中,其中tcp_tsorted_anchor和_skb_refdst共用內存空間,意味着兩者不能同時使用。

struct sk_buff {
    ...
    union {
        struct {
            unsigned long   _skb_refdst;
            void        (*destructor)(struct sk_buff *skb);
        };
        struct list_head    tcp_tsorted_anchor;
    };  

在操作tsorted鏈表時,需要使用以下兩個宏tcp_skb_tsorted_save和tcp_skb_tsorted_restore,前者初始化sk_buff結構中的tsorted鏈表掛點,保存_skb_refdst中的原有數據。後者在tsorted鏈表操作完成之後,還原_skb_refdst的值。

#define tcp_skb_tsorted_save(skb) {     \
    unsigned long _save = skb->_skb_refdst; \
    skb->_skb_refdst = 0UL;
        
#define tcp_skb_tsorted_restore(skb)        \
    skb->_skb_refdst = _save;       \
}

tsorted鏈表添加

tsorted鏈表的添加操作發生在數據發送和數據重傳之後,如下在數據發送函數__tcp_transmit_skb中,對於不包含任何數據的Pure ACK報文不需要進行clone克隆處理,其它情況下,首先對報文進行克隆。在克隆之前,調用以上函數tcp_skb_tsorted_save保存_skb_refdst值,並將原值清零。在克隆完成之後,還原結構體中_skb_refdst的值。

static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
                  int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{
    BUG_ON(!skb || !tcp_skb_pcount(skb));
    tp = tcp_sk(sk);

    if (clone_it) {
        TCP_SKB_CB(skb)->tx.in_flight = TCP_SKB_CB(skb)->end_seq - tp->snd_una;
        oskb = skb;

        tcp_skb_tsorted_save(oskb) {
            if (unlikely(skb_cloned(oskb)))
                skb = pskb_copy(oskb, gfp_mask);
            else
                skb = skb_clone(oskb, gfp_mask);
        } tcp_skb_tsorted_restore(oskb);

        if (unlikely(!skb))
            return -ENOBUFS;
    }

克隆之後的報文將用於執行發送操作,在IP層的發送函數ip_queue_xmit中,可以由套接口中緩存的路由項重新設置skb中的_skb_refdst值,或者通過路由查找進行設置。如果報文發送成功,調用tcp_update_skb_after_send函數,其中將處理tsorted鏈表的添加操作,這裏使用的是克隆之前的報文結構oskb。

    ...
    err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);

    if (unlikely(err > 0)) {
        tcp_enter_cwr(sk);
        err = net_xmit_eval(err);
    }
    if (!err && oskb) {
        tcp_update_skb_after_send(sk, oskb, prior_wstamp);
        tcp_rate_skb_sent(sk, oskb);
    }

在看一下在重傳函數__tcp_retransmit_skb中,如果要重傳的報文skb結構的數據指針非4字節對齊(最後兩位不爲零),或者skb數據段的頭部空間超過或等於U16_MAX長度,將導致校驗和計算的起始位置變量csum_start(16位)溢出,具體可參見內核函數skb_partial_csum_set中的類似判斷。以上的兩種情況出現的機率都不大,但是如果出現,如下將拷貝報文結構,消除以上兩種問題,這樣,在TCP傳輸函數tcp_transmit_skb中就不需再對報文進行克隆處理。

報文重傳完成之後,調用tcp_update_skb_after_send處理tsorted鏈表(在tcp_transmit_skb中略過了這一步)。

int __tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb, int segs)
{
    /* make sure skb->data is aligned on arches that require it
     * and check if ack-trimming & collapsing extended the headroom
     * beyond what csum_start can cover.
     */
    if (unlikely((NET_IP_ALIGN && ((unsigned long)skb->data & 3)) ||
             skb_headroom(skb) >= 0xFFFF)) {
        struct sk_buff *nskb;

        tcp_skb_tsorted_save(skb) {
            nskb = __pskb_copy(skb, MAX_TCP_HEADER, GFP_ATOMIC);
            err = nskb ? tcp_transmit_skb(sk, nskb, 0, GFP_ATOMIC) : -ENOBUFS;
        } tcp_skb_tsorted_restore(skb);

        if (!err) {
            tcp_update_skb_after_send(sk, skb, tp->tcp_wstamp_ns);
            tcp_rate_skb_sent(sk, skb);
        }
    } else {

函數tcp_update_skb_after_send如下,將報文skb鏈接到tsorted鏈表的末尾。

static void tcp_update_skb_after_send(struct sock *sk, struct sk_buff *skb, u64 prior_wstamp)
{   
    struct tcp_sock *tp = tcp_sk(sk);
    
    skb->skb_mstamp_ns = tp->tcp_wstamp_ns;
    if (sk->sk_pacing_status != SK_PACING_NONE) {
        u...
    }
    list_move_tail(&skb->tcp_tsorted_anchor, &tp->tsorted_sent_queue);
}

tsorted鏈表與重傳隊列

對於新發送的報文(非重傳),函數tcp_write_xmit將報文發送之後,調用tcp_event_new_data_sent函數。

static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, int push_one, gfp_t gfp)
{
    ...
    while ((skb = tcp_send_head(sk))) {
        ...
        if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
            break;

repair:
        /* Advance the send_head.  This one is sent out.
         * This call will increment packets_out.
         */
        tcp_event_new_data_sent(sk, skb);

函數tcp_event_new_data_sent將報文由發送隊列中移除,並添加到重傳隊列中,

static void tcp_event_new_data_sent(struct sock *sk, struct sk_buff *skb)
{   
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct tcp_sock *tp = tcp_sk(sk);
    unsigned int prior_packets = tp->packets_out;
    
    tp->snd_nxt = TCP_SKB_CB(skb)->end_seq;
    
    __skb_unlink(skb, &sk->sk_write_queue);
    tcp_rbtree_insert(&sk->tcp_rtx_queue, skb);

在重傳函數__tcp_retransmit_skb中,如果報文長度大於允許的重傳長度,將報文進行分片,僅發送允許長度的報文。否則,如果skb長度小於允許的長度,並且小於當前的MSS值,嘗試合併重傳隊列中的後續報文組成長度爲MSS的報文。

int __tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb, int segs)
{
    ...
    len = cur_mss * segs;
    if (skb->len > len) {
        if (tcp_fragment(sk, TCP_FRAG_IN_RTX_QUEUE, skb, len,
                 cur_mss, GFP_ATOMIC))
            return -ENOMEM; /* We'll try again later. */
    } else {
        if (skb_unclone(skb, GFP_ATOMIC))
            return -ENOMEM;

        diff = tcp_skb_pcount(skb);
        tcp_set_skb_tso_segs(skb, cur_mss);
        diff -= tcp_skb_pcount(skb);
        if (diff)
            tcp_adjust_pcount(sk, skb, diff);
        if (skb->len < cur_mss)
            tcp_retrans_try_collapse(sk, skb, cur_mss);
    }

如下分片函數tcp_fragment,其將skb分爲兩個報文結構:skb和buff,其中skb的數據長度爲參數中指定的len;而buff中爲剩餘的數據。在函數最後,將buff鏈接到重傳隊列,以及將其鏈接在原skb在tsorted鏈表中的後部。

int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue,
         struct sk_buff *skb, u32 len, unsigned int mss_now, gfp_t gfp)
{
    struct sk_buff *buff;
    ...
    tcp_insert_write_queue_after(skb, buff, sk, tcp_queue);
    if (tcp_queue == TCP_FRAG_IN_RTX_QUEUE)
        list_add(&buff->tcp_tsorted_anchor, &skb->tcp_tsorted_anchor);

如下函數tcp_retrans_try_collapse遍歷重傳隊列中skb之後的報文,如果有合適的報文,由函數tcp_collapse_retrans完成合並。

static void tcp_retrans_try_collapse(struct sock *sk, struct sk_buff *to, int space)
{   
    skb_rbtree_walk_from_safe(skb, tmp) {
        ...
        if (!tcp_collapse_retrans(sk, to))
            break;
    }
}

如下tcp_collapse_retrans函數,如果下一個報文的長度小於當前skb的末尾可用空間,將其數據拷貝到skb數據空間內。否則,由函數skb_shift將數據移動到skb的共享區。函數的最後,調用tcp_rtx_queue_unlink_and_free將被合併的報文由重傳隊列和tsorted鏈表中移除。

static bool tcp_collapse_retrans(struct sock *sk, struct sk_buff *skb)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct sk_buff *next_skb = skb_rb_next(skb);

    next_skb_size = next_skb->len;

    BUG_ON(tcp_skb_pcount(skb) != 1 || tcp_skb_pcount(next_skb) != 1);

    if (next_skb_size) {
        if (next_skb_size <= skb_availroom(skb))
            skb_copy_bits(next_skb, 0, skb_put(skb, next_skb_size),
                      next_skb_size);
        else if (!skb_shift(skb, next_skb, next_skb_size))
            return false;
    }
    ...
    tcp_rtx_queue_unlink_and_free(next_skb, sk);

tsorted與報文確認

在接收到ACK確認報文,將使用函數tcp_clean_rtx_queue清除重傳隊列中序號在ACK確認序號(SND.UNA)之前的數據,這些數據已經被對端接收。調用函數tcp_rtx_queue_unlink_and_free將確認的報文skb,由重傳隊列和tsorted鏈表中移除,並釋放。注意,對於僅確認了部分數據的skb(fully_acked==0),暫不清除。

static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack,
                   u32 prior_snd_una, struct tcp_sacktag_state *sack)
{
    bool fully_acked = true;

    for (skb = skb_rb_first(&sk->tcp_rtx_queue); skb; skb = next) {
        struct tcp_skb_cb *scb = TCP_SKB_CB(skb);

        if (after(scb->end_seq, tp->snd_una)) {
            if (tcp_skb_pcount(skb) == 1 || !after(tp->snd_una, scb->seq))
                break
            acked_pcount = tcp_tso_acked(sk, skb);
            if (!acked_pcount) break;
            fully_acked = false;
        } else {
            acked_pcount = tcp_skb_pcount(skb);
        }
        ...

        if (!fully_acked) break;
        ...
        tcp_rtx_queue_unlink_and_free(skb, sk);
    }

對於SACK確認的報文,其序號塊可能僅確認了skb中的部分數據,函數tcp_shifted_skb嘗試將此部分數據與前面已被SACK確認的skb進行合併。在合併到前一個skb(prev)之後,如果skb中不再包含數據,由重傳隊列和tsorted鏈表中移除,並釋放。

static bool tcp_shifted_skb(struct sock *sk, struct sk_buff *prev,
                struct sk_buff *skb, struct tcp_sacktag_state *state,
                unsigned int pcount, int shifted, int mss, bool dup_sack)
{
    struct tcp_sock *tp = tcp_sk(sk);
    u32 start_seq = TCP_SKB_CB(skb)->seq;   /* start of newly-SACKed */
    u32 end_seq = start_seq + shifted;  /* end of newly-SACKed */

    ... 
    TCP_SKB_CB(prev)->end_seq += shifted;
    TCP_SKB_CB(skb)->seq += shifted;
	
    if (skb->len > 0) {
        BUG_ON(!tcp_skb_pcount(skb));
        NET_INC_STATS(sock_net(sk), LINUX_MIB_SACKSHIFTED);
        return false;
    }
    ...
    tcp_rtx_queue_unlink_and_free(skb, sk);

    NET_INC_STATS(sock_net(sk), LINUX_MIB_SACKMERGED);

如下tcp_sacktag_walk函數,對於SACK確認的報文,將其由tsorted鏈表中移除。

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)
{
    skb_rbtree_walk_from(skb) {
        int in_sack = 0;
        bool dup_sack = dup_sack_in;

        ...
        if (unlikely(in_sack < 0)) break;

        if (in_sack) {
            TCP_SKB_CB(skb)->sacked =
                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),
                        tcp_skb_timestamp_us(skb));
            tcp_rate_skb_delivered(sk, skb, state->rate);
            if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED)
                list_del_init(&skb->tcp_tsorted_anchor);

銷燬tsorted鏈表

當套接口初始化、關閉、出錯、銷燬或者接收到對端復位報文時,將使用函數tcp_write_queue_purge清空套接口的發送隊列和重傳隊列。函數tcp_skb_tsorted_anchor_cleanup將清空鏈接在套接口tsorted鏈表上的skb結構指針tcp_tsorted_anchor。最後,重新初始化tsorted鏈表tsorted_sent_queue。

void tcp_write_queue_purge(struct sock *sk)
{
    struct sk_buff *skb;

    tcp_chrono_stop(sk, TCP_CHRONO_BUSY);
    while ((skb = __skb_dequeue(&sk->sk_write_queue)) != NULL) {
        tcp_skb_tsorted_anchor_cleanup(skb);
        sk_wmem_free_skb(sk, skb);
    }
    tcp_rtx_queue_purge(sk);
    INIT_LIST_HEAD(&tcp_sk(sk)->tsorted_sent_queue);

如下tcp_rtx_queue_purge函數清空重傳隊列,清空報文結構skb的tcp_tsorted_anchor鏈表連接點。由於這裏是清空整個tsorted鏈表,沒有必要調用刪除鏈表元素的函數list_del。

static inline void tcp_skb_tsorted_anchor_cleanup(struct sk_buff *skb)
{   
    skb->destructor = NULL;
    skb->_skb_refdst = 0UL;
}
static inline void tcp_rtx_queue_unlink(struct sk_buff *skb, struct sock *sk)
{
    tcp_skb_tsorted_anchor_cleanup(skb);
    rb_erase(&skb->rbnode, &sk->tcp_rtx_queue);
}
static void tcp_rtx_queue_purge(struct sock *sk)
{
    struct rb_node *p = rb_first(&sk->tcp_rtx_queue);

    while (p) {
        struct sk_buff *skb = rb_to_skb(p);

        p = rb_next(p);
        /* Since we are deleting whole queue, no need to list_del(&skb->tcp_tsorted_anchor)
         */
        tcp_rtx_queue_unlink(skb, sk);
        sk_wmem_free_skb(sk, skb);

RACK之tsorted鏈表處理

函數tcp_rack_detect_loss負責依據RACK算法標記丟失報文,即如果發送時間靠後的報文已經被確認(ACK或者SACK),那麼之前的未確認報文認爲已經丟失。爲抵禦亂序的情況,RACK在確認報文和丟失報文之間設置了一定的時間差值。

如下遍歷tsorted時間排序的報文鏈表,從最早發送的報文開始,如果其已經被標記爲丟失,但是還沒有重傳,不進行處理。

static void tcp_rack_detect_loss(struct sock *sk, u32 *reo_timeout)
{   
    struct tcp_sock *tp = tcp_sk(sk);
    
    *reo_timeout = 0;
    reo_wnd = tcp_rack_reo_wnd(sk);
    list_for_each_entry_safe(skb, n, &tp->tsorted_sent_queue,
                 tcp_tsorted_anchor) {
        struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
        s32 remaining;
        
        /* Skip ones marked lost but not yet retransmitted */
        if ((scb->sacked & TCPCB_LOST) &&
            !(scb->sacked & TCPCB_SACKED_RETRANS))
            continue;

如果遇到報文,其發送時間戳不在RACK記錄的時間戳(最近確認報文的發送時間戳)之前,或者時間戳相等,但是其其結束序號在RACK記錄的序號的後邊,表明此報文在RACK記錄的報文之後發送,結束遍歷。

        if (!tcp_rack_sent_after(tp->rack.mstamp,
                     tcp_skb_timestamp_us(skb),
                     tp->rack.end_seq, scb->end_seq))
            break;

如果一個報文經過了當前RTT(非SRTT)加上亂序窗口時長之後還沒被ACK確認或者SACK確認,即認爲此報文已經丟失,並將其由tsorted鏈表中移除。隨後,在重傳之後,還會將此報文添加到tsorted鏈表中,由於那時其發送時間戳已經變化,將位於tsorted鏈表尾部。

        /* A packet is lost if it has not been s/acked beyond
         * the recent RTT plus the reordering window.
         */
        remaining = tcp_rack_skb_timeout(tp, skb, reo_wnd);
        if (remaining <= 0) {
            tcp_mark_skb_lost(sk, skb);
            list_del_init(&skb->tcp_tsorted_anchor);
        } else {
            /* Record maximum wait time */
            *reo_timeout = max_t(u32, *reo_timeout, remaining);

tsorted與ACK時間戳

在處理重傳隊列函數tcp_clean_rtx_queue中,函數tcp_ack_tstamp記錄對端發送的ACK報文時間戳。

static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack,
                   u32 prior_snd_una, struct tcp_sacktag_state *sack)
{
    for (skb = skb_rb_first(&sk->tcp_rtx_queue); skb; skb = next) {

        tcp_ack_tstamp(sk, skb, prior_snd_una);

由於在函數調用__skb_tstamp_tx中要操作路由緩存_skb_refdst,這裏先行將tsorted鏈表指針tcp_tsorted_anchor保存,之後進行還原。

static void tcp_ack_tstamp(struct sock *sk, struct sk_buff *skb, u32 prior_snd_una)
{
    /* Avoid cache line misses to get skb_shinfo() and shinfo->tx_flags */
    if (likely(!TCP_SKB_CB(skb)->txstamp_ack))
        return;

    shinfo = skb_shinfo(skb);
    if (!before(shinfo->tskey, prior_snd_una) &&
        before(shinfo->tskey, tcp_sk(sk)->snd_una)) {
        tcp_skb_tsorted_save(skb) {
            __skb_tstamp_tx(skb, NULL, sk, SCM_TSTAMP_ACK);
        } tcp_skb_tsorted_restore(skb);

內核版本 5.0

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