5

TCP拥塞状态机 tcp_fastretrans_alert

 3 years ago
source link: http://abcdxyzk.github.io/blog/2015/03/04/kernel-net-tcp_fastretrans_alert/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
TCP拥塞状态机 tcp_fastretrans_alert

这里主要说的是TCP拥塞情况下的状态状态处理

/* Process an event, which can update packets-in-flight not trivially.
 * Main goal of this function is to calculate new estimate for left_out,
 * taking into account both packets sitting in receiver's buffer and
 * packets lost by network.
 *
 * Besides that it does CWND reduction, when packet loss is detected
 * and changes state of machine.
 *
 * It does _not_ decide what to send, it is made in function
 * tcp_xmit_retransmit_queue().
 */
static void tcp_fastretrans_alert(struct sock *sk, int pkts_acked, int flag)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	int is_dupack = !(flag & (FLAG_SND_UNA_ADVANCED | FLAG_NOT_DUP));         // 判断是不是重复的ACK
	int do_lost = is_dupack || ((flag & FLAG_DATA_SACKED) &&                  // 判断是不是丢包:若是重复ACK 或者 SACK而且提前确认中没有到的包数量>重拍指标
					(tcp_fackets_out(tp) > tp->reordering));  // 后面会单独说说SACK和FACK内容,觉得总是理解不好
	int fast_rexmit = 0;

	if (WARN_ON(!tp->packets_out && tp->sacked_out))                          // 如果packet_out为0,那么不可能有sacked_out
		tp->sacked_out = 0;
	if (WARN_ON(!tp->sacked_out && tp->fackets_out))
		tp->fackets_out = 0;

	/* Now state machine starts.                               // 下面开始状态处理
	 * A. ECE, hence prohibit cwnd undoing, the reduction is required. */
	if (flag & FLAG_ECE)                                     // 如果是ECE
		tp->prior_ssthresh = 0;                                // 禁止拥塞窗口撤销,并开始减小拥塞窗口

	/* B. In all the states check for reneging SACKs. */
	if (tcp_check_sack_reneging(sk, flag))                     // 检查ACK是不是确认了已经被SACK选择确认的包了
		return;

	/* C. Process data loss notification, provided it is valid. */
	if (tcp_is_fack(tp) && (flag & FLAG_DATA_LOST) &&          // 提前确认、数据丢失
		before(tp->snd_una, tp->high_seq) &&               // 我们需要注意high_seq 可以标志为LOST的段序号的最大值
		icsk->icsk_ca_state != TCP_CA_Open &&              // 状态不是OPEN
		tp->fackets_out > tp->reordering) {                // 同上面说的
		tcp_mark_head_lost(sk, tp->fackets_out - tp->reordering);   // 发现丢包,需要标志出丢失的包。 (1) 这个函数后面看
		NET_INC_STATS_BH(LINUX_MIB_TCPLOSS);
	}

	/* D. Check consistency of the current state. */
	tcp_verify_left_out(tp);                                   // #define tcp_verify_left_out(tp) WARN_ON(tcp_left_out(tp) > tp->packets_out)
				                                   // 检查丢失的包应该比发送出去的包小,即确定确定left_out < packets_out
	/* E. Check state exit conditions. State can be terminated
	 *    when high_seq is ACKed. */                           // 下面检测状态退出条件!当high_seq 被确认的时候,这个状态就可以终止了
	if (icsk->icsk_ca_state == TCP_CA_Open) {                  // 如果是open状态
		BUG_TRAP(tp->retrans_out == 0);                        // 重传数量应该=0才是合理的
		tp->retrans_stamp = 0;                                 // 将重传发送时间置0
	} else if (!before(tp->snd_una, tp->high_seq)) {           // 如果high_seq已经被确认
		switch (icsk->icsk_ca_state) {
		case TCP_CA_Loss:
			icsk->icsk_retransmits = 0;                // 超时重传次数归零
			if (tcp_try_undo_recovery(sk))             // 尝试将前面的拥塞窗口的调整撤销,在这种情况下弄不清楚包的情况(2)
				return;                                // 如果使用了SACK,那么不管undo成功与否,都会返回Open态
			break;

		case TCP_CA_CWR:   // 发生某些道路拥塞,需要减慢发送速度
			/* CWR is to be held something *above* high_seq
			 * is ACKed for CWR bit to reach receiver. */
			if (tp->snd_una != tp->high_seq) {
				tcp_complete_cwr(sk);                 // 完成道路拥塞情况处理,就是减小cwnd(3)
				tcp_set_ca_state(sk, TCP_CA_Open);    // 将状态设置成OPEN
			}
			break;

		case TCP_CA_Disorder:
			tcp_try_undo_dsack(sk);                          // 尝试撤销cwnd的减少,因为DSACK确认了所有的重传数据(4)
			if (!tp->undo_marker ||                          // 跟踪了重传数据包?
				/* For SACK case do not Open to allow to undo
				 * catching for all duplicate ACKs. */
				tcp_is_reno(tp) || tp->snd_una != tp->high_seq) {   // 没有SACK || 两者不同步
				tp->undo_marker = 0;
				tcp_set_ca_state(sk, TCP_CA_Open);           // 将状态转换成OPEN
			}
			break;

		case TCP_CA_Recovery:
			if (tcp_is_reno(tp))                             // 没有SACK
				tcp_reset_reno_sack(tp);                     // sacked_out=0
			if (tcp_try_undo_recovery(sk))                   // 尝试撤销
				return;
			tcp_complete_cwr(sk);                            // 完成处理
			break;
		}
	}

	/* F. Process state. */
	switch (icsk->icsk_ca_state) {
	case TCP_CA_Recovery:
		if (!(flag & FLAG_SND_UNA_ADVANCED)) {               // snd_una没有改变
			if (tcp_is_reno(tp) && is_dupack)                // 不是SACK,而且是重复的ACK
				tcp_add_reno_sack(sk);                       // 接收到重复的ACK,tp->sacked_out++; 并且检查新的reorder问题(5)
		} else
			do_lost = tcp_try_undo_partial(sk, pkts_acked);  // 部分ACK接收并撤销窗口操作(6)注意返回的是是否需要重传表示
		break;                                               // 1代表重传,0代表不需要重传
	case TCP_CA_Loss:
		if (flag & FLAG_DATA_ACKED)                          // 如果是数据确认
			icsk->icsk_retransmits = 0;                      // 超时重传置次数0
		if (tcp_is_reno(tp) && flag & FLAG_SND_UNA_ADVANCED) // 没有ACK,&& snd_una改变了
			tcp_reset_reno_sack(tp);                         // 重置sacked=0
		if (!tcp_try_undo_loss(sk)) {                        // 尝试撤销拥塞调整,然后进入OPEN状态(7)
			tcp_moderate_cwnd(tp);                           // 调整窗口(8)
			tcp_xmit_retransmit_queue(sk);                   // 重传丢失的包(9)
			return;
		}
		if (icsk->icsk_ca_state != TCP_CA_Open)
			return;
		/* Loss is undone; fall through to processing in Open state. */
	default:
		if (tcp_is_reno(tp)) {                        // 么有SACK,那么就是RENO算法处理:收到三个dup-ACK(即sacked_out==3),就开始重传
			if (flag & FLAG_SND_UNA_ADVANCED)         // 如果收到少于 3 个 dupack 后又收到累计确认,则会重置之前的 sacked_out 计数
				tcp_reset_reno_sack(tp);              // 重新置0
			if (is_dupack)                            // 如果收到一个dup-ack,将sacked_out++
				tcp_add_reno_sack(sk);
		}

		if (icsk->icsk_ca_state == TCP_CA_Disorder)
			tcp_try_undo_dsack(sk);                   // DSACK确认了所有重传数据

		if (!tcp_time_to_recover(sk)) {               // 判断是否进入恢复状态
			tcp_try_to_open(sk, flag);                // 如果不可以,那么会判断是否进入Open、Disorder、CWR等状态
			return;                                   // 只有收到三个dup-ack时候,才进入快速回复,否则都返回
		}

		/* MTU probe failure: don't reduce cwnd */
		if (icsk->icsk_ca_state < TCP_CA_CWR &&
			icsk->icsk_mtup.probe_size &&
			tp->snd_una == tp->mtu_probe.probe_seq_start) {
			tcp_mtup_probe_failed(sk);            // MTU探测失败
			/* Restores the reduction we did in tcp_mtup_probe() */
			tp->snd_cwnd++;
			tcp_simple_retransmit(sk);            // 做一个简单的转发,而不使用回退机制。用于路径MTU发现。 
			return;
		}
		// 说明已经收到第 3 个连续 dupack,此时 sacked_out = 3,进入恢复态
		/* Otherwise enter Recovery state */
		// 进入恢复状态
		if (tcp_is_reno(tp))
			NET_INC_STATS_BH(LINUX_MIB_TCPRENORECOVERY);
		else
			NET_INC_STATS_BH(LINUX_MIB_TCPSACKRECOVERY);

		tp->high_seq = tp->snd_nxt;
		tp->prior_ssthresh = 0;
		tp->undo_marker = tp->snd_una;
		tp->undo_retrans = tp->retrans_out;

		if (icsk->icsk_ca_state < TCP_CA_CWR) {
			if (!(flag & FLAG_ECE))
				tp->prior_ssthresh = tcp_current_ssthresh(sk);  // 根据状态获取当前门限值
			tp->snd_ssthresh = icsk->icsk_ca_ops->ssthresh(sk); // 更新
			TCP_ECN_queue_cwr(tp);
		}

		tp->bytes_acked = 0;
		tp->snd_cwnd_cnt = 0;
		tcp_set_ca_state(sk, TCP_CA_Recovery);                  // 键入恢复状态
		fast_rexmit = 1;                                        // 快速重传
	}

	if (do_lost || (tcp_is_fack(tp) && tcp_head_timedout(sk)))  // 如果丢失需要重传 || 超时重传
		tcp_update_scoreboard(sk, fast_rexmit);                 // 标志丢失和超时的数据包,增加lost_out(10)
	tcp_cwnd_down(sk, flag);                                    // 减小cwnd窗口(11)
	tcp_xmit_retransmit_queue(sk);                              // 重传丢失包
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK