zoukankan      html  css  js  c++  java
  • TCP的核心系列 — SACK和DSACK的实现(七)

    我们发送重传包时,重传包也可能丢失,如果没有检查重传包是否丢失的机制,那么只能依靠超时来恢复了。

    37版本把检查重传包是否丢失的部分独立出来,这就是tcp_mark_lost_retrans()。

    在处理SACK块的同时,会检测是否有出现乱序,如果有乱序,那么会计算乱序的长度并更新。

    本文主要内容:检查重传包是否丢失,以及乱序的检测和更新。

    Author:zhangskd @ csdn

    检查重传包是否丢失

    tcp_mark_lost_retrans()用于检查重传的包是否丢失,2.6.22内核在检查重传包是否丢失时是有Bug的,

    具体可见:http://projects.itri.aist.go.jp/gnet/sack-bug.html

    Q: 怎么检查重传包是否丢失呢?

    A: 我们知道,要发送数据时,是先发送重传包,之后才发送新包的。

        如果重传包顺利到达接收端,当新包到达时,服务器端会收到一个对新包的正常确认。

        如果重传包丢失了,当新包到达时,服务器端会收到一个对新包的选择性确认。

        基于这个事实:

        当重传一个包时,我们记录当时要发送的下一新包的序列号(当时的tp->snd_nxt)。

        当我们收到SACK时,就检查新包是被正常ACK,还是被SACK。如果新包被SACK,

        但是重传包还没有,就说明当时重传的包已经丢失了。 

    重传一个包时,会记录当时要发送的下一个新包的序号,即tp->snd_nxt。

    int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb)
    {
        ...
    
        /* 如果之前网络中没有重传包 */
        if (! tp->retrans_out) 
            tp->lost_retrans_low = tp->snd_nxt;
    
        TCP_SKB_CB(skb)->sacked |= TCPCB_RETRANS; /* 打上R标记 */
        tp->retrans_out += tcp_skb_pcount(skb); /* 更新retrans_out */
    
        /* Save stamp of the first retransmit. */
        if (! tp->retrans_stamp)
            tp->retrans_stamp = TCP_SKB_CB(skb)->when;
    
        tp->undo_retrans++;
    
        /* snd_nxt is stored to detect loss of retransmitted segment,
         * see tcp_input.c tcp_sacktag_write_queue().
         * 就是在这里!把这时的snd_nxt保存到重传包的ack_seq。
         */
        TCP_SKB_CB(skb)->ack_seq = tp->snd_nxt;
    
        ...
    }
    

    检查重传包是否丢失,如果丢失了,重新打L标志。

    /* Check for lost retransmit. */
    static void tcp_mark_lost_retrans(struct sock *sk)
    {
        const struct inet_connection_sock *icsk = inet_csk(sk);
        struct tcp_sock *tp = tcp_sk(sk);
        struct sk_buff *skb;
        int cnt = 0;
        u32 new_low_seq = tp->snd_nxt; /* 下一个要发送的新包序列号 */
        u32 received_upto = tcp_highest_sack_seq(tp); /* 被SACK过的最大序列号 */
    
        /* 使用这个方法的条件:
         * 使用FACK;有重传包;上次的最低snd_nxt被SACK;处于Recovery状态
         */
        if (! tcp_is_fack(tp) || ! tp->retrans_out || ! after(received_upto, tp->lost_retrans_low)
            || icsk->icsk_ca_state != TCP_CA_Recovery)
        return;
    
        tcp_for_write_queue(skb, sk) {
            /* 注意了:对于重传包来说,ack_seq其实是当时的snd_nxt */
            u32 ack_seq = TCP_SKB_CB(skb)->ack_seq;
    
            if (skb == tcp_send_head(sk)) /* 发送队列头了 */
                break;
    
            /* 我们关注的是重传的包,如果遍历完了,就退出 */
            if (cnt == tp->retrans_out)
                break;
     
            /* 不关心成功确认过的包 */
            if (! after(TCP_SKB_CB(skb)->end_seq, tp->snd_una))
                continue;
    
            /* 只关注重传包,必须有R标志才处理 */
            if (! (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_RETRANS))
                continue;
    
            /* 
             * 如果重传包记录的snd_nxt被SACK了,那说明重传包丢了;否则应该在新包之前被确认才对。
             */
            if (after(received_upto, ack_seq)) {
                TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_RETRANS; /* 取消R标志 */
                tp->retrans_out -= tcp_skb_pcount(skb); /* 更新网络中重传包数量 */
                tcp_skb_mark_lost_uncond_verify(tp, skb); /* 给重传包打上LOST标志,并更新相关变量 */
                NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPLOSTRETRANSMIT);
    
            } else { /* 如果重传包对应的snd_nxt在最高SACK序列号之后 */
                if (before(ack_seq, new_low_seq))
                    new_low_seq = ack_seq; /* 更新未检测的重传包对应的最小snd_nxt */
                cnt += tcp_skb_pcount(skb); /* 用于判断重传包是否检查完了 */
            }
        }
     
        /* 如果还有未检查完的重传包,那么更新未检测的重传包对应的最小snd_nxt */
        if (tp->retrans_out)
            tp->lost_retrans_low = new_low_seq;
    
    }
    

    给数据包打上LOST标志,更新相关变量。

    static void tcp_skb_mark_lost_uncond_verify(struct tcp_sock *tp, struct sk_buff *skb)
    {
        /* 更新重传过的包的最低、最高序号 */
        tcp_verfiy_retransmit_hint(tp, skb);
    
        /* 如果这个包还未打上L标志,且没有S标志 */
        if (! (TCP_SKB_CB(skb)->sacked & (TCP_LOST | TCPCB_SACKED_ACKED))) {
            tp->lost_out += tcp_skb_pcount(skb); /* 更新网络中丢失包数量 */
            TCP_SKB_CB(skb)->sacked |= TCPCB_LOST; /* 打上L标志 */
        }
    }
    
    /* This must be called before lost_out is incremented
     * 记录重传过的包的最低序号、最高序号。
     */
    static void tcp_verify_retransmit_hint(struct tcp_sock *tp, struct sk_buff *skb)
    {
        if ((tp->retransmit_skb_hint == NULL) || before(TCP_SKB_CB(skb)->seq,
            TCP_SKB_CB(tp->retransmit_skb_hint)->seq))
            tp->retransmit_skb_hint = skb;
    
        if (! tp->lost_out || after(TCP_SKB_CB(skb)->end_seq, tp->retransmit_high))
            tp->retransmit_high = TCP_SKB_CB(skb)->end_seq;
    }
    

     

    乱序处理

    说明

    Reordering metric is maximal distance, which a packet can be displaced in packet stream.

    With SACKs we can estimate it:

    1. SACK fills old hole and the corresponding segment was not ever retransmitted -> reordering.

        Alas, we cannot use it when segment was retransmitted.

    2. The last flaw it solved with D-SACK. D-SACK arrives for retransmitted and already SACKed segment

        -> reordering..

    Both of these heuristics are not used in Loss state, when we cannot account for retransmits accurately.

    对于乱序,我们主要关注如何检测乱序,以及计算乱序的长度。

    在tcp_sacktag_one()中有进行乱序的检测,那么在收到SACK或DSACK时怎么判断有乱序呢?

    (1)skb的记分牌为S|R,然后它被DSACK。

    我们想象一下,一个数据包乱序了,它滞留在网络的某个角落里。我们收到后续包的SACK,认为这个包丢失了,

    进行重传。之后原始包到达接收端了,这个数据包被SACK了。最后重传包也到达接收端了,这个包被DSACK了。

    (2)如果一个包落在highest_sack之前,它既没被SACK过,也不是重传的,那么它肯定是乱序了,到现在才被SACK。

    如果检测到了乱序,那么乱序队列的长队为:tp->fackets_out - state.reord。

    static void tcp_update_reordering(struct sock *sk, const int metric,
                                           const int ts)
    {
        struct tcp_sock *tp = tcp_sk(sk);
    
        if (metric > tp->reordering) {
            int mib_idx;
            /* 更新reordering的值,取其小者*/
            tp->reordering = min(TCP_MAX_REORDERING, metric);
            
            if (ts)
                mib_idx = LINUX_MIB_TCPTSREORDER;
            else if (tcp_is_reno(tp))
                mib_idx = LINUX_MIB_TCPRENOREORDER;
            else if (tcp_is_fack(tp))
                mib_idx = LINUX_MIB_TCPFACKREORDER;
            else 
                mib_idx = LINUX_MIB_TCPSACKREORDER;
    
            NET_INC_STATS_BH(sock_net(sk), mib_idx);
    #if FASTRETRANS_DEBUG > 1
            printk(KERN_DEBUG "Disorder%d %d %u f%u s%u rr%d
    ",
                       tp->rx_opt.sack_ok, inet_csk(sk)->icsk_ca_state,
                       tp->reordering, tp->fackets_out, tp->sacked_out,
                       tp->undo_marker ? tp->undo_retrans : 0);
    #endif
            tcp_disable_fack(tp); /* 出现了reorder,再用fack就太激进了*/
        }
    }
    /* Packet counting of FACK is based on in-order assumptions, therefore
     * TCP disables it when reordering is detected.
     */
    static void tcp_disable_fack(struct tcp_sock *tp)
    {
        /* RFC3517 uses different metric in lost marker => reset on change */
        if (tcp_is_fack(tp))
            tp->lost_skb_hint = NULL;
        tp->rx_opt.sack_ok &= ~2; /* 取消FACK选项*/
    }
    
  • 相关阅读:
    内存-程序运行的空间
    数据在内存中是这样存储的(二进制形式存储)
    从编写源代码到程序在内存中运行的全过程解析
    QT开发工具
    Linux中Too many open files 问题分析和解决
    TCP端口状态说明ESTABLISHED、TIME_WAIT
    HttpClient当HTTP连接的时候出现大量CLOSE_WAIT连接
    缓存穿透、击穿、雪崩
    Http长连接和Keep-Alive以及Tcp的Keepalive
    防止表单重复提交
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333333.html
Copyright © 2011-2022 走看看