Reno与RACK对丢失/重传报文的标记

主要涉及到两个变量,一是重传报文计数retrans_out;二是丢失报文计数lost_out。

RACK丢失报文判断

如下函数tcp_rack_detect_loss,如果报文具有丢失标志(TCPCB_LOST),但是没有重传标志(TCPCB_SACKED_RETRANS),表明丢失报文还未进行重传,不进行重复处理。否则,在RACK确认报文已经丢失之后,由函数tcp_mark_skb_lost进行丢失标记。

static void tcp_rack_detect_loss(struct sock *sk, u32 *reo_timeout)
{
    *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);

        /* Skip ones marked lost but not yet retransmitted */
        if ((scb->sacked & TCPCB_LOST) &&
            !(scb->sacked & TCPCB_SACKED_RETRANS))
            continue;

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

        /* 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);

在函数tcp_mark_skb_lost中,如果重传报文(标志TCPCB_SACKED_RETRANS)丢失,清除其重传标志TCPCB_SACKED_RETRANS,以便进行下一次重传。在以上tcp_rack_detect_loss丢失检测中,可见,对此种丢失未重传报文不进行重复处理。

注意,这里对重传丢失的报文,减少重传报文计数retrans_out。以下将会看到Reno在标记丢包时,不处理retrans_out计数。

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

    tcp_skb_mark_lost_uncond_verify(tp, skb);
    if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_RETRANS) {
        TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_RETRANS;
        tp->retrans_out -= tcp_skb_pcount(skb);

与Reno不同,RACK(或者SACK)可标记重传队列中多个报文为丢失状态(TCPCB_LOST),Reno仅能表示重传队列首个报文。因而,在重传时,RACK(或SACK)也可重传多个丢失的报文。参见以下重传函数tcp_xmit_retransmit_queue中的判断,retrans_out不能大于lost_out,不能重传大于丢失数量的报文。

void tcp_xmit_retransmit_queue(struct sock *sk)
{
    rtx_head = tcp_rtx_queue_head(sk);
    skb = tp->retransmit_skb_hint ?: rtx_head;
    max_segs = tcp_tso_segs(sk, tcp_current_mss(sk));
    skb_rbtree_walk_from(skb) {
        ...
        segs = tp->snd_cwnd - tcp_packets_in_flight(tp);
        if (segs <= 0) return;
		
        sacked = TCP_SKB_CB(skb)->sacked;
        /* In case tcp_shift_skb_data() have aggregated large skbs,
         * we need to make sure not sending too bigs TSO packets
         */
        segs = min_t(int, segs, max_segs);
   
        if (tp->retrans_out >= tp->lost_out) {
            break; 
        ...
        if (tcp_retransmit_skb(sk, skb, segs)) return;

Reno丢包计数

以下为Reno算法的丢包标记函数tcp_newreno_mark_lost,与RACK(或者SACK)不同,Reno没有足够的信息判断多个报文的丢失情况,根据重复ACK(dupack)仅能判断,SND.UNA序号开始的报文(重传队列首报文)丢失。所以,如果重传队列首个skb,包含多个报文,或者其长度大于MSS值,进行分片,TCPCB_LOST丢包标志只能设置于第一个长度不大于MSS的报文。

这里也就不需要像以上的RACK算法,或者SACK算法遍历重传队列,Reno调用tcp_skb_mark_lost_uncond_verify函数标记重传队列首报文(可能是分片后报文)丢失即可。

另外Reno与RACK/SACK的不同在于,后者在tcp_mark_skb_lost函数中检测重传报文的丢失,并且减少重传报文计数,而Reno不进行处理。Reno仅在接收到确认ACK(无论是确认原始还是重传报文)时将retrans_out减一。

void tcp_newreno_mark_lost(struct sock *sk, bool snd_una_advanced)
{        
    const u8 state = inet_csk(sk)->icsk_ca_state;
    struct tcp_sock *tp = tcp_sk(sk);

    if ((state < TCP_CA_Recovery && tp->sacked_out >= tp->reordering) ||
        (state == TCP_CA_Recovery && snd_una_advanced)) {
        struct sk_buff *skb = tcp_rtx_queue_head(sk);
        u32 mss;

        if (TCP_SKB_CB(skb)->sacked & TCPCB_LOST)
            return;

        mss = tcp_skb_mss(skb);
        if (tcp_skb_pcount(skb) > 1 && skb->len > mss)
            tcp_fragment(sk, TCP_FRAG_IN_RTX_QUEUE, skb,
                     mss, mss, GFP_ATOMIC);

        tcp_skb_mark_lost_uncond_verify(tp, skb);

如下tcp_skb_mark_lost_uncond_verify,如果报文没有被确认丢失并且也没有被对端SACK确认接收,增加lost_out丢包计数,并且设置标志TCPCB_LOST。对于Reno而言,不可能被SACK确认,并且在以上函数tcp_newreno_mark_lost中,也已经确认了报文还没有设置TCPCB_LOST标志,所以这里的if判断必定成立。

另外,由于Reno仅标记一个报文,lost_out值递增1。而且在以上函数中判断只要首报文被设置了TCPCB_LOST标志,不继续重复标记,因而,lost_out的值实际上总是1。

void tcp_skb_mark_lost_uncond_verify(struct tcp_sock *tp, struct sk_buff *skb)
{       
    tcp_verify_retransmit_hint(tp, skb);
        
    tcp_sum_lost(tp, skb); 
    if (!(TCP_SKB_CB(skb)->sacked & (TCPCB_LOST|TCPCB_SACKED_ACKED))) {
        tp->lost_out += tcp_skb_pcount(skb);
        TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;

接下来看一看Reno的丢失计数lost_out的减少。在接收到ACK确认报文后,清理重传队列函数tcp_clean_rtx_queue中,如果检测到报文设置了TCPCB_LOST标志,将丢包计数lost_out减去相应的报文数量。对于Reno而言,仅有可能在重传队列的首报文中设置TCPCB_LOST标志。由于在设置TCPCB_LOST标志时,确保了重传队列第一个skb仅包含一个报文,这里lost_out相当于递减1,其值变为0。

注意,由于被对端确认接收,此报文将由重传队列中移除,这样队列中的下一个报文(成为首报文)才有可能被标记为TCPCB_LOST,参见以上函数tcp_newreno_mark_lost。

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) {
        struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
        u8 sacked = scb->sacked;

        if (after(scb->end_seq, tp->snd_una)) {
            ...
        } else {
            acked_pcount = tcp_skb_pcount(skb);
        }

        if (sacked & TCPCB_LOST)
            tp->lost_out -= acked_pcount;

Reno重传计数

在报文重传函数tcp_retransmit_skb中,重传成功之后,更新重传计数,由于在上节介绍的tcp_newreno_mark_lost函数中,将重传队列首个skb分片为单报文,此处retrans_out相当于递增1。并且为报文增加重传标志TCPCB_RETRANS。

int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb, int segs)
{
    struct tcp_sock *tp = tcp_sk(sk);
    int err = __tcp_retransmit_skb(sk, skb, segs);

    if (err == 0) {
#if FASTRETRANS_DEBUG > 0
        if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_RETRANS) {
            net_dbg_ratelimited("retrans_out leaked\n");
        }
#endif
        TCP_SKB_CB(skb)->sacked |= TCPCB_RETRANS;
        tp->retrans_out += tcp_skb_pcount(skb);

在以上函数tcp_retransmit_skb的调用函数tcp_xmit_retransmit_queue中,如果重传的报文数量retrans_out大于等于丢失报文数量lost_out,停止遍历重传队列。对于Reno,丢失报文计数lost_out为1,仅允许重传一个报文。此后,retrans_out的值也增加为1。

void tcp_xmit_retransmit_queue(struct sock *sk)
{
    rtx_head = tcp_rtx_queue_head(sk);
    skb = tp->retransmit_skb_hint ?: rtx_head;
    max_segs = tcp_tso_segs(sk, tcp_current_mss(sk));
    skb_rbtree_walk_from(skb) {
        ...
        segs = tp->snd_cwnd - tcp_packets_in_flight(tp);
        if (segs <= 0) return;
		
        sacked = TCP_SKB_CB(skb)->sacked;
        /* In case tcp_shift_skb_data() have aggregated large skbs,
         * we need to make sure not sending too bigs TSO packets
         */
        segs = min_t(int, segs, max_segs);
   
        if (tp->retrans_out >= tp->lost_out) {
            break; 
        ...
        if (tcp_retransmit_skb(sk, skb, segs)) return;

在接收到ACK确认报文后,清理重传队列函数tcp_clean_rtx_queue中,如果报文设置了重传标志TCPCB_RETRANS,将retrans_out减少相应的报文数量,对于Reno而言,相当于递减1。同时在此函数中,也将丢失报文数量lost_out减去1。

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) {
        struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
        u8 sacked = scb->sacked;

        if (after(scb->end_seq, tp->snd_una)) {
            ...
        } else {
            acked_pcount = tcp_skb_pcount(skb);
        }
		
        if (unlikely(sacked & TCPCB_RETRANS)) {
            if (sacked & TCPCB_SACKED_RETRANS)
                tp->retrans_out -= acked_pcount;
            flag |= FLAG_RETRANS_DATA_ACKED;
		...
        if (sacked & TCPCB_LOST)
            tp->lost_out -= acked_pcount;

内核版本 5.0

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