主要目的是为了检测由RTO偏小引起的伪RTO。主要算法是:
当重传定时器超时后,记这时候的snd.nxt为highmark。然后看收到的两个确认是否符合这个要求:ack > snd.una && ack < highmark,如果都符合,那么就宣布这是个伪RTO。
FRTO先重传第一个未被确认的包,并且在收到第一个确认后做如下判断:a. 如果这个ack确认了新的数据但是序列号小于highmark,那么再发2个新的数据包,等收到第二个确认再来判断。b.这个ack是个重复确认或者大于等于highmark,说明原来的包是真丢了,那么说明不是一个伪FRTO,还是进入到传统的RTO恢复当中去。对于收到的第二个确认,也是做和第一个确认相同的判断,只不过对符合要求的情况不需要在发包了,直接宣布是伪RTO。
F-RTO is implemented (mainly) in fourfunctions:
* -tcp_use_frto() is used to determine if TCP is can use F-RTO
* -tcp_enter_frto() prepares TCP state on RTO if F-RTO is used, it is
* called when tcp_use_frto() showed green light
* -tcp_process_frto() handles incoming ACKs during F-RTO algorithm
* -tcp_enter_frto_loss() is called if there is not enough evidence to prove thatthe * RTO is indeed spurious. Ittransfers the control from F-RTO to the conventional RTO recovery
*/
以下代码来自linux-2.6内核版本
1. tcp_enter_frto()进入FRTO状态
void tcp_enter_frto(struct sock *sk)
{
const struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
tp->frto_counter = 1; //表示刚刚进入FRTO阶段
if (icsk->icsk_ca_state <= TCP_CA_Disorder ||
tp->snd_una == tp->high_seq ||
(icsk->icsk_ca_state == TCP_CA_Loss && !icsk->icsk_retransmits)) {
/*进入FRTO的处理,如果当前网络状况还可以,比如ca_state 是OPEN或者Disorder;snd_una == high_seq, 只差一个包没有被确认; 虽然是LOSS状态,但是之前没有发生过超时重传, 那么就记录下现在的ssthresh,并告知拥塞控制算法出现了EVENT_FRTO事件*/
tp->prior_ssthresh = tcp_current_ssthresh(sk);
tp->snd_ssthresh = icsk->icsk_ca_ops->ssthresh(sk);
tcp_ca_event(sk, CA_EVENT_FRTO);
}
/* Have to clear retransmission markers here to keep the bookkeeping
* in shape, even though we are not yet in Loss state.
* If something was really lost, it is eventually caught up
* in tcp_enter_frto_loss.
*/
/* 为进入LOSS状态做点准备工作*/
tp->retrans_out = 0;
tp->undo_marker = tp->snd_una;
tp->undo_retrans = 0;
/* 把发送队列write_queue上所有已经发送过的包都置为未重传*/
sk_stream_for_retrans_queue(skb, sk) {
TCP_SKB_CB(skb)->sacked &= ~TCPCB_RETRANS;
}
tcp_sync_left_out(tp);
/* FRTO 状态实际上还是OPEN状态啦 */
tcp_set_ca_state(sk, TCP_CA_Open);
/* 记录下进入FRTO时的snd_nxt */
tp->frto_highmark = tp->snd_nxt;
}
2. 处理FRTO状态
当接收方收到ACK后,在tcp_ack()中会检测当前是否处于FRTO状态, 如果是的话(tp->frto_counter != 0),就会调用tcp_process_frto来处理。
static void tcp_process_frto(struct sock *sk, u32 prior_snd_una)
{
struct tcp_sock *tp = tcp_sk(sk);
tcp_sync_left_out(tp);
/* 如果收到当前的确认后,snd_una并没有前进,说明又是一个DACK;或者snd_una >= 进入FRTO时的snd_nxt,所有发出去的包都收到了,那么很有可能是重传的包填了hole,也就是说第一次重传的数据是真丢了。这两种情况都可以判断出之前的包是真丢了,所以还是进入LOSS状态吧 */
if (tp->snd_una == prior_snd_una ||
!before(tp->snd_una, tp->frto_highmark)) {
/* RTO was caused by loss, start retransmitting in
* go-back-N slow start
*/
tcp_enter_frto_loss(sk);
return;
}
/* 如果收到的确认是大于snd.una且小于frto_highmark的,那就符合spurious RTO的预期了*/
if (tp->frto_counter == 1) {
/* 这是进入FRTO后收到的第一个ACK */
/* First ACK after RTO advances the window: allow two new
* segments out.
*/
tp->snd_cwnd = tcp_packets_in_flight(tp) + 2;
} else {
/* 这是进入FRTO后收到的第2个ACK */
/* Also the second ACK after RTO advances the window.
* The RTO was likely spurious. Reduce cwnd and continue
* in congestion avoidance
*/
tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_ssthresh);
tcp_moderate_cwnd(tp);
}
/* F-RTO affects on two new ACKs following RTO.
* At latest on third ACK the TCP behavior is back to normal.
*/
/* frto_counter 的取值为0,1,2,1表示刚刚进入FRTO,还没有收到ACK; 2表示已经收到了一个ACK, 这是第二个ACK; 0表示已经收到了2个ACK, FRTO阶段已经结束了,在tcp_ack里也不会再调用tcp_process_ftro了。*/
tp->frto_counter = (tp->frto_counter + 1) % 3;
}
3. 没有足够的证据表明是个伪RTO, 那么就进入到Loss状态去吧
static void tcp_enter_frto_loss(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
int cnt = 0;
tp->sacked_out = 0;
tp->lost_out = 0;
tp->fackets_out = 0;
sk_stream_for_retrans_queue(skb, sk) {//遍历write_queue里面所有已经发了的包
cnt += tcp_skb_pcount(skb);
TCP_SKB_CB(skb)->sacked &= ~TCPCB_LOST; //TCPCB_LOST位置为0
if (!(TCP_SKB_CB(skb)->sacked&TCPCB_SACKED_ACKED)) {
//如果之前没有被SACK确认并且序列号小于frto_highmark的包,都标记为丢了
/* Do not mark those segments lost that were
* forward transmitted after RTO
*/
if (!after(TCP_SKB_CB(skb)->end_seq,
tp->frto_highmark))
TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;
tp->lost_out += tcp_skb_pcount(skb);
}
} else { //被SACK确认过
tp->sacked_out += tcp_skb_pcount(skb);
tp->fackets_out = cnt;
}
}
tcp_sync_left_out(tp);
/* 看看之前在FRTO期间发了几个包,如果frto_counter = 1的话,表示收到了一个确认,但是没有发新包;如果frto_counter = 2的话,收到了2个确认,没有新包; 如果frto_counter = 0的话,表示收到了2个确认,发了2个新包。所以 + frto_counter会使得in_flight的数目保持不变。*/
tp->snd_cwnd = tp->frto_counter + tcp_packets_in_flight(tp)+1;
tp->snd_cwnd_cnt = 0;
tp->snd_cwnd_stamp = tcp_time_stamp;
tp->undo_marker = 0; /* 不能撤销窗口调整*/
tp->frto_counter = 0;
tp->reordering = min_t(unsigned int, tp->reordering,
sysctl_tcp_reordering);
tcp_set_ca_state(sk, TCP_CA_Loss);
tp->high_seq = tp->frto_highmark; //进入FRTO是的snd_nxt
TCP_ECN_queue_cwr(tp);
clear_all_retrans_hints(tp); //清空所有的重传提示,重传的时候从write_queue的起点开始遍历
}