SACK Reneging
TCP发送端不能够清除SACK序号块确认的数据,因为接收端很可能由于内存压力等原因,删除乱序队列中SACK确认过的报文。
发送端重传队列中的报文只有在接收到ACK报文的Acknowledge序号字段确认之后,才能移除队列和释放。
接收端丢弃OFO报文
如下在检测的已用接收缓存大于套接口总的接收缓存sk_rcvbuf时,并且接收缓存已不能够再扩大,最后的措施就是释放乱序报文队列,参见函数tcp_prune_ofo_queue。
由于释放了ofo报文,在函数tcp_prune_ofo_queue的最后,对于开启了SACK功能的连接,调用函数tcp_sack_reset,清空DSACK和SACK信息。
SACK Reneging发生
函数tcp_clean_rtx_queue清除被确认的报文,如果在此之后,发现未在for循环中确认的报文,之前被SACK序号块确认过,表明接收端可能发生了SACK Reneging,设置标志FLAG_SACK_RENEGING。
否则,如果接收端没有发生SACK Reneging,ACK报文的确认序号应当包含SACK确认的序号范围。
static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack,
			       u32 prior_snd_una,
			       struct tcp_sacktag_state *sack, bool ece_ack)
{
	const struct inet_connection_sock *icsk = inet_csk(sk);
	u64 first_ackt, last_ackt;
	struct tcp_sock *tp = tcp_sk(sk);
	u32 prior_sacked = tp->sacked_out;
	u32 reord = tp->snd_nxt; /* lowest acked un-retx un-sacked seq */
	if (skb) {
		tcp_ack_tstamp(sk, skb, prior_snd_una);
		if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED)
			flag |= FLAG_SACK_RENEGING;
	}
	
	return flag;
}在ACK报文处理函数tcp_ack中,对于可疑的ACK(其中包含重复ACK等),由函数tcp_fastretrans_alert进行快速重传处理
函数tcp_fastretrans_alert中,调用tcp_check_sack_reneging检查SACK Reneging是否发生。如果为真,退出处理。
static void tcp_fastretrans_alert(struct sock *sk, const u32 prior_snd_una,
                  int num_dupack, int *ack_flag, int *rexmit)
{
    /* B. In all the states check for reneging SACKs. */
    if (tcp_check_sack_reneging(sk, flag))
        return;如果接收到的ACK报文携带的确认序号,指向了之前记录的已经被SACK确认过的序号,意味着接收端很可能将SACK序号块对应的数据删除。但是,为了避免不必要的重传,我们等待一段很短的时间,等待接收端发送更多的ACK报文,期待后续ACK确认SACK中序号。
/* If ACK arrived pointing to a remembered SACK, it means that our
 * remembered SACKs do not reflect real state of receiver i.e.
 * receiver _host_ is heavily congested (or buggy).
 *
 * To avoid big spurious retransmission bursts due to transient SACK
 * scoreboard oddities that look like reneging, we give the receiver a
 * little time (max(RTT/2, 10ms)) to send us some more ACKs that will
 * restore sanity to the SACK scoreboard. If the apparent reneging
 * persists until this RTO then we'll clear the SACK scoreboard.
 */
static bool tcp_check_sack_reneging(struct sock *sk, int flag)
{
	if (flag & FLAG_SACK_RENEGING) {
		struct tcp_sock *tp = tcp_sk(sk);
		unsigned long delay = max(usecs_to_jiffies(tp->srtt_us >> 4),
					  msecs_to_jiffies(10));
		inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
					  delay, TCP_RTO_MAX);
		return true;
	}
	return false;
}定时器时长为RTT的一半,但是不超过10ms。注意套接口中变量srtt_us中保存的为RTT的8倍值
在超时处理函数tcp_retransmit_timer,将调用以下tcp_timeout_mark_lost函数处理重传队列的相关标记操作。如果队列的首个报文skb被SACK确认过,即sacked具有标记TCPCB_SACKED_ACKED,表明接收端发生了SACKRENEGING,清空了乱序队列。这里需要清除接收到的SACK信息。
清空SACK确认数据计数sacked_out,并设置套接口的标志is_sack_reneg。之后,遍历重传队列,清除其中所有报文的TCPCB_SACKED_ACKED标志位。
static void tcp_timeout_mark_lost(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb, *head;
	bool is_reneg;			/* is receiver reneging on SACKs? */
	head = tcp_rtx_queue_head(sk);
	//如果SACK确认了重传队列首部的报文(本该由ACK.SEQ确认),表明对端丢弃了其OFO队列
	is_reneg = head && (TCP_SKB_CB(head)->sacked & TCPCB_SACKED_ACKED);
	if (is_reneg) {
		NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPSACKRENEGING);
		tp->sacked_out = 0;
		/* Mark SACK reneging until we recover from this loss event. */
		tp->is_sack_reneg = 1;
	} else if (tcp_is_reno(tp)) {
		tcp_reset_reno_sack(tp);
	}
	skb = head;
	skb_rbtree_walk_from(skb) {
		if (is_reneg)
			TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_ACKED;
		else if (tcp_is_rack(sk) && skb != head &&
			 tcp_rack_skb_timeout(tp, skb, 0) > 0) //RACK算法,报文并没有超时,不标记
			continue; /* Don't mark recently sent ones lost yet */
		//否则,调用函数tcp_mark_skb_lost对重传队列中的报文进行丢失标记
		tcp_mark_skb_lost(sk, skb);
	}
	tcp_verify_left_out(tp);
	tcp_clear_all_retrans_hints(tp);
}
SACK Reneging标志清除
  位于tcp_fastretrans_alert函数中,SACK Reneging检查通过。后续如果ACK确认的报文序号位于high_seq(丢包发生时的SND.NXT)序号之后(或相等),表明已经从丢包中恢复过来,撤销拥塞状态TCP_CA_Recovery。
static void tcp_fastretrans_alert(struct sock *sk, const u32 prior_snd_una,
                  int num_dupack, int *ack_flag, int *rexmit)
{
    /* B. In all the states check for reneging SACKs. */
    if (tcp_check_sack_reneging(sk, flag))
        return;
    /* D. Check state exit conditions. State can be terminated
     *    when high_seq is ACKed. */
    if (icsk->icsk_ca_state == TCP_CA_Open) {
        ...
    } else if (!before(tp->snd_una, tp->high_seq)) {
        switch (icsk->icsk_ca_state) {
        case TCP_CA_Recovery:
            if (tcp_is_reno(tp))
                tcp_reset_reno_sack(tp);
            if (tcp_try_undo_recovery(sk))
                return;
            tcp_end_cwnd_reduction(sk);
            break;在撤销TCP_CA_Recovery状态函数中,拥塞状态设置为TCP_CA_Open,清空SACK Reneging标志。
/* People celebrate: "We love our President!" */
static bool tcp_try_undo_recovery(struct sock *sk)
{
    ...
    tcp_set_ca_state(sk, TCP_CA_Open);
    tp->is_sack_reneg = 0;
    return false;另外,除了以上的快速恢复,对于F-RTO和Partial Loss恢复算法,在成功恢复之后,由函数tcp_try_undo_loss恢复到TCP_CA_Open状态,随之清空SACK Reneging标志。
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号