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号