kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

TCP拥塞状态机 tcp_fastretrans_alert

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/* 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);                              // 重传丢失包
}

下面看一下里面的函数:

先看:tcp_mark_head_lost:通过给丢失的数据包标志TCPCB_LOST,就可以表明哪些数据包需要重传。

注意参数:packets = fackets_out - reordering,其实就是sacked_out + lost_out。被标志为LOST的段数不能超过packets。

那么packets 就是标记丢失的包们数量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/* Mark head of queue up as lost. With RFC3517 SACK, the packets is
 * is against sacked "cnt", otherwise it's against facked "cnt"
 */
static void tcp_mark_head_lost(struct sock *sk, int packets)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb;
	int cnt, oldcnt;
	int err;
	unsigned int mss;

	BUG_TRAP(packets <= tp->packets_out);       // 丢失的包不可能比所有发出去的包的数量
	if (tp->lost_skb_hint) {                    // 如果已经有标识为丢失的段了
		skb = tp->lost_skb_hint;                // 下一个需要标记的数据段
		cnt = tp->lost_cnt_hint;                // 已经标记了多少段
	} else {
		skb = tcp_write_queue_head(sk);         // 获得链表的第一个结构元素
		cnt = 0;                                // 初始化标记了0个数据
	}
	// 下面开始遍历
	tcp_for_write_queue_from(skb, sk) {
		if (skb == tcp_send_head(sk))           // return sk->sk_send_head; 即snd_nxt,那么还没有发送不需要处理,break;
			break;
		/* TODO: do this better */
		/* this is not the most efficient way to do this... */
		tp->lost_skb_hint = skb;                // 更新丢失队列信息
		tp->lost_cnt_hint = cnt;

		if (after(TCP_SKB_CB(skb)->end_seq, tp->high_seq))   // high_seq是最大的标记为LOST的号,不可以超过这个
			break;                              // 若这个skb超过,退出

		oldcnt = cnt;                           // 保存cnt
		if (tcp_is_fack(tp) || tcp_is_reno(tp) ||
			(TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED))
			cnt += tcp_skb_pcount(skb);         // 表示这个段已经被标记

		if (cnt > packets) {
			if (tcp_is_sack(tp) || (oldcnt >= packets))  // 已经超过了丢失包数量,break
				break;

			mss = skb_shinfo(skb)->gso_size;             // 得到MSS
			err = tcp_fragment(sk, skb, (packets - oldcnt) * mss, mss);   // 下面分配,前面说过了
			if (err < 0)
				break;
			cnt = packets;
		}
		// 下面这一段就是做标记动作
		if (!(TCP_SKB_CB(skb)->sacked & (TCPCB_SACKED_ACKED|TCPCB_LOST))) {
			TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;   // 标识
			tp->lost_out += tcp_skb_pcount(skb);     // 丢失包+=
			tcp_verify_retransmit_hint(tp, skb);     // 其实就是标记这个丢失,加入重传标记队列
		   }
	}
	tcp_verify_left_out(tp);
}

看一下tcp_verify_retransmit_hint函数:

1
2
3
4
5
6
7
8
9
10
11
static void tcp_verify_retransmit_hint(struct tcp_sock *tp, struct sk_buff *skb)
{
	if ((tp->retransmit_skb_hint == NULL) ||
		before(TCP_SKB_CB(skb)->seq,
		   TCP_SKB_CB(tp->retransmit_skb_hint)->seq))
		tp->retransmit_skb_hint = skb;                         // 加入这个队列

	if (!tp->lost_out ||
		after(TCP_SKB_CB(skb)->end_seq, tp->retransmit_high))  // 如果最后一个数据标号比high大,明显更新high
		tp->retransmit_high = TCP_SKB_CB(skb)->end_seq;
}

OK,再看一下这个函数tcp_try_undo_recovery:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static int tcp_try_undo_recovery(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);

	if (tcp_may_undo(tp)) {                               // 如果可以undo
		/* Happy end! We did not retransmit anything
		 * or our original transmission succeeded.
		 */
		DBGUNDO(sk, inet_csk(sk)->icsk_ca_state == TCP_CA_Loss ? "loss" : "retrans");
		tcp_undo_cwr(sk, 1);                              // 具体处理
		if (inet_csk(sk)->icsk_ca_state == TCP_CA_Loss)
			NET_INC_STATS_BH(LINUX_MIB_TCPLOSSUNDO);
		else
			NET_INC_STATS_BH(LINUX_MIB_TCPFULLUNDO);
		tp->undo_marker = 0;
	}
	if (tp->snd_una == tp->high_seq && tcp_is_reno(tp)) {
		/* Hold old state until something *above* high_seq
		 * is ACKed. For Reno it is MUST to prevent false
		 * fast retransmits (RFC2582). SACK TCP is safe. */
		tcp_moderate_cwnd(tp);                            // 更新窗口大小
		return 1;
	}
	tcp_set_ca_state(sk, TCP_CA_Open);
	return 0;
}

OK看一下tcp_may_undo函数:检测能否撤销

1
2
3
4
static inline bool tcp_may_undo(const struct tcp_sock *tp)
{
	return tp->undo_marker && (!tp->undo_retrans || tcp_packet_delayed(tp));
}

首先得有undo_marker标识才OK!然后undo_retrans的意思是最近的Recovery时间内重传的数据包个数,如果收到一个DSACK那么undo_retrans减一,如果最后等于0,那么说明都被确认了,没有必要重传,所以没有必要调整窗口。或tcp_packet_delayed(tp)条件。如下:

1
2
3
4
5
6
static inline int tcp_packet_delayed(struct tcp_sock *tp)
{
	return !tp->retrans_stamp ||
		(tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
		 (__s32)(tp->rx_opt.rcv_tsecr - tp->retrans_stamp) < 0);     // 接收ACK时间在重传数据之前
}

下面 看一下这个函数tcp_complete_cwr:

1
2
3
4
5
6
7
static inline void tcp_complete_cwr(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_ssthresh);              // 调整窗口
	tp->snd_cwnd_stamp = tcp_time_stamp;
	tcp_ca_event(sk, CA_EVENT_COMPLETE_CWR);                         // 出发事件
}
1
2
3
4
5
6
7
8
9
/* CWND moderation, preventing bursts due to too big ACKs
 * in dubious situations.
 */
static inline void tcp_moderate_cwnd(struct tcp_sock *tp)            // 修改窗口值
{
	tp->snd_cwnd = min(tp->snd_cwnd,
			   tcp_packets_in_flight(tp) + tcp_max_burst(tp));       // 防止怀疑的ACK情况,所以取min值
	tp->snd_cwnd_stamp = tcp_time_stamp;
}

再看看这个函数tcp_try_undo_dsack:当DSACK确认所有的重传数据,那么undo_retrans=0,那么需要回复窗口原来的情况

1
2
3
4
5
6
7
8
9
10
11
12
/* Try to undo cwnd reduction, because D-SACKs acked all retransmitted data */
static void tcp_try_undo_dsack(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);

	if (tp->undo_marker && !tp->undo_retrans) {          // 所有的段都被确认了
		DBGUNDO(sk, "D-SACK");
		tcp_undo_cwr(sk, 1);                             // 撤销(1)
		tp->undo_marker = 0;
		NET_INC_STATS_BH(LINUX_MIB_TCPDSACKUNDO);
	}
}

撤销函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static void tcp_undo_cwr(struct sock *sk, const int undo)
{
	struct tcp_sock *tp = tcp_sk(sk);

	if (tp->prior_ssthresh) {                            // 如果保存了旧的门限值
		const struct inet_connection_sock *icsk = inet_csk(sk);

		if (icsk->icsk_ca_ops->undo_cwnd)
			tp->snd_cwnd = icsk->icsk_ca_ops->undo_cwnd(sk);           // 这个函数可以自己添加
		else
			tp->snd_cwnd = max(tp->snd_cwnd, tp->snd_ssthresh << 1);   // 如果没有定义那个函数,那么做简单的处理

		if (undo && tp->prior_ssthresh > tp->snd_ssthresh) {
			tp->snd_ssthresh = tp->prior_ssthresh;
			TCP_ECN_withdraw_cwr(tp);
		}
	} else {                                            // 没有保存旧的阈值
		tp->snd_cwnd = max(tp->snd_cwnd, tp->snd_ssthresh);
	}
	tcp_moderate_cwnd(tp);                              // 上面已经说了
	tp->snd_cwnd_stamp = tcp_time_stamp;

	/* There is something screwy going on with the retrans hints after
	   an undo */
	tcp_clear_all_retrans_hints(tp);                    // 清空所有的重传信息
}

接收到重复的ACK,那么需要对sacked_out处理,看函数tcp_add_reno_sack:

1
2
3
4
5
6
7
8
9
/* Emulate SACKs for SACKless connection: account for a new dupack. */

static void tcp_add_reno_sack(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	tp->sacked_out++;                           // 收到重复的ACK,那么这个值++
	tcp_check_reno_reordering(sk, 0);           // 检查是否有reordering(1)
	tcp_verify_left_out(tp);
}

看看这个检查reordering函数:

1
2
3
4
5
6
7
8
9
10
/* If we receive more dupacks than we expected counting segments
 * in assumption of absent reordering, interpret this as reordering.
 * The only another reason could be bug in receiver TCP.
 */
static void tcp_check_reno_reordering(struct sock *sk, const int addend)
{
	struct tcp_sock *tp = tcp_sk(sk);
	if (tcp_limit_reno_sacked(tp))       // 检查sack的数量是否超过限度
		tcp_update_reordering(sk, tp->packets_out + addend, 0); // 如果是reordering则更新reordering
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Limits sacked_out so that sum with lost_out isn't ever larger than
 * packets_out. Returns zero if sacked_out adjustement wasn't necessary.
 */
int tcp_limit_reno_sacked(struct tcp_sock *tp)          // 限制sacked_out目的是使得sacked_out + lost_out <= packeted_out
{
	u32 holes;

	holes = max(tp->lost_out, 1U);                      // 获得hole
	holes = min(holes, tp->packets_out);

	if ((tp->sacked_out + holes) > tp->packets_out) {   // 如果大于发出的包,那么reordering就需要了
		tp->sacked_out = tp->packets_out - holes;       // 因为此处的dup-ack是reorder造成的
		return 1;
	}
	return 0;
}

下面看看更新reordering函数tcp_update_reordering:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static void tcp_update_reordering(struct sock *sk, const int metric,
				  const int ts)
{
	struct tcp_sock *tp = tcp_sk(sk);
	if (metric > tp->reordering) {                          // 如果现在的数量 > 之前的reorder
		tp->reordering = min(TCP_MAX_REORDERING, metric);   // 获得ordering值(注意不能超过最大设置值)

		/* This exciting event is worth to be remembered. 8) */
		if (ts)
			NET_INC_STATS_BH(LINUX_MIB_TCPTSREORDER);       // 统计信息
		else if (tcp_is_reno(tp))
			NET_INC_STATS_BH(LINUX_MIB_TCPRENOREORDER);
		else if (tcp_is_fack(tp))
			NET_INC_STATS_BH(LINUX_MIB_TCPFACKREORDER);
		else
			NET_INC_STATS_BH(LINUX_MIB_TCPSACKREORDER);
#if FASTRETRANS_DEBUG > 1
		printk(KERN_DEBUG "Disorder%d %d %u f%u s%u rr%d\n",
			   tp->rx_opt.sack_ok, inet_csk(sk)->icsk_ca_state,
			   tp->reordering,
			   tp->fackets_out,
			   tp->sacked_out,
			   tp->undo_marker ? tp->undo_retrans : 0);
#endif
		tcp_disable_fack(tp);       // 禁用fack(fack是基于有序的,因为已经使用order了,所以禁用fack)
	}
}

下面再看一下这个tcp_try_undo_partial函数:在恢复状态,收到部分ACK确认,使用这个函数撤销拥塞调整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* Undo during fast recovery after partial ACK. */

static int tcp_try_undo_partial(struct sock *sk, int acked)
{
	struct tcp_sock *tp = tcp_sk(sk);
	/* Partial ACK arrived. Force Hoe's retransmit. */     // 收到部分ACK,对于SACK来说不需要重传,对于RENO需要
	int failed = tcp_is_reno(tp) || (tcp_fackets_out(tp) > tp->reordering);  // 或者facked_out数量比reordering要大

	if (tcp_may_undo(tp)) {                                // 是否可以调整(上面已说)
		/* Plain luck! Hole if filled with delayed
		 * packet, rather than with a retransmit.
		 */
		if (tp->retrans_out == 0)                          // 重传包=0
			tp->retrans_stamp = 0;                         // 重置重传时间

		tcp_update_reordering(sk, tcp_fackets_out(tp) + acked, 1);   // 需要更新reordering( 上面 )

		DBGUNDO(sk, "Hoe");
		tcp_undo_cwr(sk, 0);                               // 撤销操作( 上面 )
		NET_INC_STATS_BH(LINUX_MIB_TCPPARTIALUNDO);

		/* So... Do not make Hoe's retransmit yet.
		 * If the first packet was delayed, the rest
		 * ones are most probably delayed as well.
		 */
		failed = 0;                // 表示不用重传了,可以发送新的数据
	}
	return failed;                 // 返回是否需要重传
}

下面继续看tcp_try_undo_loss函数:收到部分确认之后,从loss状态撤销窗口调整

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* Undo during loss recovery after partial ACK. */
static int tcp_try_undo_loss(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);

	if (tcp_may_undo(tp)) {                            // 如果可以undo
		struct sk_buff *skb;
		tcp_for_write_queue(skb, sk) {                 // 遍历整个发送queue
			if (skb == tcp_send_head(sk))              // 直到还没有发送的数据头之前(前面的都已经发送)
				break;
			TCP_SKB_CB(skb)->sacked &= ~TCPCB_LOST;    // 清除LOST标记
		}

		tcp_clear_all_retrans_hints(tp);               // 清除所有的重传信息

		DBGUNDO(sk, "partial loss");
		tp->lost_out = 0;                              // 重置
		tcp_undo_cwr(sk, 1);                           // 撤销窗口调整
		NET_INC_STATS_BH(LINUX_MIB_TCPLOSSUNDO);
		inet_csk(sk)->icsk_retransmits = 0;
		tp->undo_marker = 0;
		if (tcp_is_sack(tp))
			tcp_set_ca_state(sk, TCP_CA_Open);         // 设置状态OPEN
		return 1;
	}
	return 0;
}

下面看一下tcp_update_scoreboard函数:其实就是更新lost包数量,这个涉及到不同的算法不一样的结果,没有SACK(reno),有SACK,有FACK情况

1) 没有SACK:每次收到重复的ACK或部分ack时,标志一个包为丢失。

2) 有SACK:sacked_out - reordering > 0 时候,标记为这么多丢失,若小于0,标记为1个丢失(前提是有重传标识)

3) 有FACK:fackets_out - reordering >0 时候,标记为这么多丢失,若小于0,标记为1个丢失

( 注意:小于0的情况是因为考虑到reordering情况 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/* Account newly detected lost packet(s) */

static void tcp_update_scoreboard(struct sock *sk, int fast_rexmit)
{
	struct tcp_sock *tp = tcp_sk(sk);

	if (tcp_is_reno(tp)) {                            // 最普通的,没有SACK情况
		tcp_mark_head_lost(sk, 1);                    // 标记为一个丢失
	} else if (tcp_is_fack(tp)) {                     // 如果是fack
		int lost = tp->fackets_out - tp->reordering;  // 判断这个值大小
		if (lost <= 0)
			lost = 1;                                 // 小于0指标记一个
		tcp_mark_head_lost(sk, lost);                 // 否则标记所有的
	} else {   // 仅仅有SACK情况
		int sacked_upto = tp->sacked_out - tp->reordering;
		if (sacked_upto < fast_rexmit)
			sacked_upto = fast_rexmit;
		tcp_mark_head_lost(sk, sacked_upto);          // 同上
	}

	/* New heuristics: it is possible only after we switched
	 * to restart timer each time when something is ACKed.
	 * Hence, we can detect timed out packets during fast
	 * retransmit without falling to slow start.
	 */
	if (tcp_is_fack(tp) && tcp_head_timedout(sk)) {   // 下面检查超时包( 先检查第一个数据包是否超时 )
		struct sk_buff *skb;

		skb = tp->scoreboard_skb_hint ? tp->scoreboard_skb_hint
			: tcp_write_queue_head(sk);

		tcp_for_write_queue_from(skb, sk) {
			if (skb == tcp_send_head(sk))
				break;
			if (!tcp_skb_timedout(sk, skb))           // 检查所有的超时包,没有超时的就break
				break;

			if (!(TCP_SKB_CB(skb)->sacked & (TCPCB_SACKED_ACKED|TCPCB_LOST))) {
				TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;   // 标记为lost
				tp->lost_out += tcp_skb_pcount(skb);     // 增加lost数量
				tcp_verify_retransmit_hint(tp, skb);
			}
		}

		tp->scoreboard_skb_hint = skb;

		   tcp_verify_left_out(tp);
	}
}

下面继续看这个减小窗口函数:tcp_cwnd_down,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* Decrease cwnd each second ack. */ // 每收到2个确认将拥塞窗口减1,直到拥塞窗口等于慢启动阈值。
static void tcp_cwnd_down(struct sock *sk, int flag)
{
	struct tcp_sock *tp = tcp_sk(sk);
	int decr = tp->snd_cwnd_cnt + 1; // 计数器

	if ((flag & (FLAG_ANY_PROGRESS | FLAG_DSACKING_ACK)) ||
		(tcp_is_reno(tp) && !(flag & FLAG_NOT_DUP))) {
		tp->snd_cwnd_cnt = decr & 1;                    // 因为此处只可能是0,1三个值,这样的操作其实就是切换值,
		                                                // 例如现在是第一个ACK,即之前的snd_cwnd_cnt=0,decr=1,那么1&1=1,
		                                                // 将snd_cwnd_cnt赋值为1;第二个ACK到来,decr=2,则2&1=0,
		                                                // 相当于又将snd_cwnd_cnt初始化为0,因为两个ACK就需要处理一次。
		decr >>= 1;   // 除以2,是判断是第一个ACK,还是第二个;第一个的话值=0,下面不会执行,是2的话=1,下面一句会执行

		if (decr && tp->snd_cwnd > tcp_cwnd_min(sk))    // 如果是第二个ACK && 比最小的门限值还大一点,那么还需要减小cwnd
			tp->snd_cwnd -= decr;                       // 减小一个

		tp->snd_cwnd = min(tp->snd_cwnd, tcp_packets_in_flight(tp) + 1);  // 用于微调,和外面的数据包数量比较
		tp->snd_cwnd_stamp = tcp_time_stamp;            // 改变时间戳
	}
}

tcp重传数据包 tcp_xmit_retransmit_skb

http://blog.csdn.net/shanshanpt/article/details/22202743

当知道需要重传数据结的时候执行这个函数:
对于函数tcp_xmit_retransmit_queue:需要重传哪些包呢到底?
首先是lost、标记的包;
然后还需要处理:之前发送过的但是尚未收到确认的包(向前重传),或者新数据,在这两者之间有一个选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/* This gets called after a retransmit timeout, and the initially
 * retransmitted data is acknowledged.  It tries to continue
 * resending the rest of the retransmit queue, until either
 * we've sent it all or the congestion window limit is reached.
 * If doing SACK, the first ACK which comes back for a timeout
 * based retransmit packet might feed us FACK information again.
 * If so, we use it to avoid unnecessarily retransmissions.
 */
void tcp_xmit_retransmit_queue(struct sock *sk)
{
	const struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb;
	int packet_cnt;

	if (tp->retransmit_skb_hint) {                      // 如果有重传信息
		skb = tp->retransmit_skb_hint;
		packet_cnt = tp->retransmit_cnt_hint;           // 保存cnt值
	} else {
		skb = tcp_write_queue_head(sk);                 // 发送队列
		packet_cnt = 0;
	}
	// 第一步,如果有丢失的包,那么需要重传
	/* First pass: retransmit lost packets. */
	if (tp->lost_out) {  // lost_out > 0
		tcp_for_write_queue_from(skb, sk) {             // 遍历
			__u8 sacked = TCP_SKB_CB(skb)->sacked;      // 获得sacked标识

			if (skb == tcp_send_head(sk))
				   break;
			/* we could do better than to assign each time */
			tp->retransmit_skb_hint = skb;              // 更新两个值
			tp->retransmit_cnt_hint = packet_cnt;

			/* Assume this retransmit will generate
			 * only one packet for congestion window
			 * calculation purposes.  This works because
			 * tcp_retransmit_skb() will chop up the
			 * packet to be MSS sized and all the
			 * packet counting works out.
			 */
			if (tcp_packets_in_flight(tp) >= tp->snd_cwnd)  // 如果传输中的报文数量 > 窗口数量,那么没有必要再发送数据
				return;

			if (sacked & TCPCB_LOST) {                      // 如果是LOST标识
				if (!(sacked & (TCPCB_SACKED_ACKED|TCPCB_SACKED_RETRANS))) {  // 如果丢失了 && 没有被选择确认或者重传
					if (tcp_retransmit_skb(sk, skb)) {      // 重传该数据函数!!!最后再看(1)
						tp->retransmit_skb_hint = NULL;     // 重传之后重置这个值
						return;                             // 返回
					}
					if (icsk->icsk_ca_state != TCP_CA_Loss)
						NET_INC_STATS_BH(LINUX_MIB_TCPFASTRETRANS);
					else
						NET_INC_STATS_BH(LINUX_MIB_TCPSLOWSTARTRETRANS);

					if (skb == tcp_write_queue_head(sk))    // 如果是第一个重传数据,那么重置重传计数器!!!
						inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
									  inet_csk(sk)->icsk_rto,
									  TCP_RTO_MAX);
				}

				packet_cnt += tcp_skb_pcount(skb);          // 重传数量
				if (packet_cnt >= tp->lost_out)             // 大于lost的数量,那么break;下面就不是lost数据问题了
					break;
			}
		}
	}

	/* OK, demanded retransmission is finished. */
	// 上面的是必须要重传的,下面的在前向重传和发送新数据之间进行选择
	/* Forward retransmissions are possible only during Recovery. */
	if (icsk->icsk_ca_state != TCP_CA_Recovery)  // 只有在恢复状态才可以这样做,在丢失状态不可以;
		return;                                  // 原因:在丢失状态希望通过可控制的方式进行重传?这一块不是很懂

	/* No forward retransmissions in Reno are possible. */
	if (tcp_is_reno(tp))                         // 前向选择重传只能是SACK下,reno下是不可能的~
		return;

	/* Yeah, we have to make difficult choice between forward transmission
	 * and retransmission... Both ways have their merits...
	 *
	 * For now we do not retransmit anything, while we have some new
	 * segments to send. In the other cases, follow rule 3 for
	 * NextSeg() specified in RFC3517.
	 */ // 下面还是需要选择考虑传输新数据还是前向重传,优先考虑新数据

	if (tcp_may_send_now(sk))                    // 检查是否有新的数据在等待传输(1)
		return;                                  // 以及这些新数据是否可以发送,可以的话返回,不需要做下面事

	/* If nothing is SACKed, highest_sack in the loop won't be valid */
	if (!tp->sacked_out)
		return;
	// 下面开始就是“前向重传”处理
	if (tp->forward_skb_hint)                    // 是否已经缓存这个队列
		skb = tp->forward_skb_hint;
	else
		skb = tcp_write_queue_head(sk);          // 没有

	tcp_for_write_queue_from(skb, sk) {          // 需要遍历
		if (skb == tcp_send_head(sk))            // 到头了
			break;
		tp->forward_skb_hint = skb;

		if (!before(TCP_SKB_CB(skb)->seq, tcp_highest_sack_seq(tp)))   // 不可以超过最大的即highest_sack_seq
			break;

		if (tcp_packets_in_flight(tp) >= tp->snd_cwnd)   // 如果传输中的包数量 > 窗口大小
			break;                                       // 不能再发了

		if (sacked & (TCPCB_SACKED_ACKED|TCPCB_SACKED_RETRANS))     // 已经被sack了或者在sack时已经被重传了
			continue;

		/* Ok, retransmit it. */
		if (tcp_retransmit_skb(sk, skb)) {               // 下面就是传输这个包
			tp->forward_skb_hint = NULL;
			break;
		}

		if (skb == tcp_write_queue_head(sk))             // 如果是第一个重传的包,那么启动设置定时器
			inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
						  inet_csk(sk)->icsk_rto,
						  TCP_RTO_MAX);

		NET_INC_STATS_BH(LINUX_MIB_TCPFORWARDRETRANS);
	}
}

看一下检查是否有新的数据需要传输的函数:tcp_may_send_now

因为此处涉及到Nagle算法,所以先简介一下:

Nagle算法:如果发送端欲多次发送包含少量字符的数据包(一般情况下,后面统一称长度小于MSS的数据包为小包,称长度等于MSS的数据包为大包),则发送端会先将第一个小包发送出去,而将后面到达的少量字符数据都缓存起来而不立即发送,直到收到接收端对前一个数据包报文段的ACK确认、或当前字符属于紧急数据,或者积攒到了一定数量的数据(比如缓存的字符数据已经达到数据包报文段的最大长度)等多种情况才将其组成一个较大的数据包发送出去。

1
2
3
4
5
6
7
8
9
10
int tcp_may_send_now(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb = tcp_send_head(sk);                 // 获得需要发送的数据头部

	return (skb &&                                           // 尚有新数据需要传输
		tcp_snd_test(sk, skb, tcp_current_mss(sk, 1),        // 看下面这个函数:检查是否这些新的数据需要尽快发送出去
				 (tcp_skb_is_last(sk, skb) ?     // 是否是最后一个包
				  tp->nonagle : TCP_NAGLE_PUSH)));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* This checks if the data bearing packet SKB (usually tcp_send_head(sk))
 * should be put on the wire right now.  If so, it returns the number of
 * packets allowed by the congestion window.
 */
static unsigned int tcp_snd_test(struct sock *sk, struct sk_buff *skb,
				 unsigned int cur_mss, int nonagle)
{
	struct tcp_sock *tp = tcp_sk(sk);
	unsigned int cwnd_quota;

	tcp_init_tso_segs(sk, skb, cur_mss);                     // 看看这个包的tso信息,便于后期和其他包一起处理

	if (!tcp_nagle_test(tp, skb, cur_mss, nonagle))          // 使用Nagle测试是不是数据现在就允许被发送,看下面函数(1)
		return 0;                                            // 如果不可以就返回了

	cwnd_quota = tcp_cwnd_test(tp, skb);                     // 返回还可以发送几个窗口的数据
	if (cwnd_quota && !tcp_snd_wnd_test(tp, skb, cur_mss))   // 如果有窗口数据可以发送 &&
		cwnd_quota = 0;                                      // 不可发送,设置=0

	return cwnd_quota;
}

看Nagle测试函数tcp_nagle_test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* Return non-zero if the Nagle test allows this packet to be
 * sent now.
 */
static inline int tcp_nagle_test(struct tcp_sock *tp, struct sk_buff *skb,
				 unsigned int cur_mss, int nonagle)           // 注意:测试返回1就是说明那个数据包现在允许直接发送出去
{                                     // 而Nagle对于小包是缓存一起发送的,除了第一个包、最后一个包
	/* Nagle rule does not apply to frames, which sit in the middle of the
	 * write_queue (they have no chances to get new data).
	 *
	 * This is implemented in the callers, where they modify the 'nonagle'
	 * argument based upon the location of SKB in the send queue.
	 */
	if (nonagle & TCP_NAGLE_PUSH)                // 设置了这个标识是因为说明可能是第一个包或者第二个包,或者其他一些允许的原因呢
		return 1;                                // Nagle允许直接发送包出去

	/* Don't use the nagle rule for urgent data (or for the final FIN).
	 * Nagle can be ignored during F-RTO too (see RFC4138).
	 */
	if (tp->urg_mode || (tp->frto_counter == 2) ||          // 注意对于紧急数据来说不可以使用Nagle规则!上面说过Nagle是缓存处理数据,紧急数据不可以!
		(TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN))          // 注意结束包(FIN)和F-RTO标识包都需要立马发送出去
		return 1;

	if (!tcp_nagle_check(tp, skb, cur_mss, nonagle))        // 在Nagle算法下,是否允许发送这个包?返回0则允许立刻发送
		return 1;

	return 0;
}

tcp_nagle_check函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Return 0, if packet can be sent now without violation Nagle's rules:   Nagle算法允许下面条件的包可以正常发送
 * 1. It is full sized.                                          // 大小等于MSS,即缓存满,或者是大包
 * 2. Or it contains FIN. (already checked by caller)            // 是结束包FIN
 * 3. Or TCP_NODELAY was set.                                    // 不允许延迟的包
 * 4. Or TCP_CORK is not set, and all sent packets are ACKed.    // TCP_CORK没有设置
 *    With Minshall's modification: all sent small packets are ACKed.
 */
static inline int tcp_nagle_check(const struct tcp_sock *tp,
				 const struct sk_buff *skb,
				  unsigned mss_now, int nonagle)
{
	return (skb->len < mss_now &&                           // 检查在Nagle算法情况下,是不是可以发送这个包
		((nonagle & TCP_NAGLE_CORK) ||                      // 满足上面四个条件就OK
		 (!nonagle && tp->packets_out && tcp_minshall_check(tp))));
}

tcp_cwnd_test函数用于测试在当前的拥塞窗口情况下,最多还可以发送几个新数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* Can at least one segment of SKB be sent right now, according to the
 * congestion window rules?  If so, return how many segments are allowed.
 */
static inline unsigned int tcp_cwnd_test(struct tcp_sock *tp,   // 根据当前的拥塞窗口,返回当前还可以发送几个segs
					 struct sk_buff *skb)
{
	u32 in_flight, cwnd;

	/* Don't be strict about the congestion window for the final FIN.  */
	if ((TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN) &&       // 如果是最后的FIN包
		tcp_skb_pcount(skb) == 1)
		return 1;                                       // 返回一个OK

	in_flight = tcp_packets_in_flight(tp);              // 获得还在传输中的包
	cwnd = tp->snd_cwnd;                                // 获得当前窗口大小
	if (in_flight < cwnd)
		return (cwnd - in_flight);                      // 剩下的部分都是可以发送的

	return 0;
}

主要是用于测试最后一个数据是不是在窗口内,在则可以发送,不在则不可以发送

1
2
3
4
5
6
7
8
9
10
11
/* Does at least the first segment of SKB fit into the send window? */
static inline int tcp_snd_wnd_test(struct tcp_sock *tp, struct sk_buff *skb,
				   unsigned int cur_mss)
{
	u32 end_seq = TCP_SKB_CB(skb)->end_seq;

	if (skb->len > cur_mss)   // skb数据长度比MSS长
		end_seq = TCP_SKB_CB(skb)->seq + cur_mss;       // 最后一个seq

	return !after(end_seq, tcp_wnd_end(tp));            // 最后一个seq是不是在窗口内,不在则不可以发送
}

tcp重传数据包 tcp_retransmit_skb 函数

http://blog.csdn.net/shanshanpt/article/details/22202999

基于CentOS6.5 2.6.32-504.16.2.el6.x86_64

tcp_retransmit_skb 重传数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/* This retransmits one SKB.  Policy decisions and retransmit queue
 * state updates are done by the caller.  Returns non-zero if an
 * error occurred which prevented the send.
 */ // 如果消耗很多的内存做其他事,那么就没有多余的来做队列的处理了
int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);
	unsigned int cur_mss;
	int err;

	/* Inconslusive MTU probe */
	if (icsk->icsk_mtup.probe_size) {
		icsk->icsk_mtup.probe_size = 0;
	}

	/* Do not sent more than we queued. 1/4 is reserved for possible
	 * copying overhead: fragmentation, tunneling, mangling etc.
	 */
	if (atomic_read(&sk->sk_wmem_alloc) >                                    // sk_wmem_alloc:传输队列大小
		min(sk->sk_wmem_queued + (sk->sk_wmem_queued >> 2), sk->sk_sndbuf))  // sk_wmem_queud:固定的队列大小
		return -EAGAIN;

	if (before(TCP_SKB_CB(skb)->seq, tp->snd_una)) {         // 若这样,说明是有一部分数据才需要重传,形如:seq---snd_una---end_seq,前面一半已收到ACK
		if (before(TCP_SKB_CB(skb)->end_seq, tp->snd_una))   // 若这样,说明全部ACK,无需重传,BUG
			BUG();
		if (tcp_trim_head(sk, skb, tp->snd_una - TCP_SKB_CB(skb)->seq))      // 将无须重传的部分去掉
			return -ENOMEM;
	}

	if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
		return -EHOSTUNREACH; /* Routing failure or similar. */

	cur_mss = tcp_current_mss(sk);

	/* If receiver has shrunk his window, and skb is out of
	 * new window, do not retransmit it. The exception is the
	 * case, when window is shrunk to zero. In this case
	 * our retransmit serves as a zero window probe.
	 */
	if (!before(TCP_SKB_CB(skb)->seq, tcp_wnd_end(tp))       // 如果数据在窗口后面,不会发送
		&& TCP_SKB_CB(skb)->seq != tp->snd_una)
		return -EAGAIN;
	if (skb->len > cur_mss) {                                // 如果skb长度 > MSS
		if (tcp_fragment(sk, skb, cur_mss, cur_mss))         // 先分片,并调整packet_out等统计值。再传送
			return -ENOMEM; /* We'll try again later. */
	} else {
		int oldpcount = tcp_skb_pcount(skb);

		if (unlikely(oldpcount > 1)) {
			tcp_init_tso_segs(sk, skb, cur_mss);             // 按当前mss重置skb->gso_XXX
			tcp_adjust_pcount(sk, skb, oldpcount - tcp_skb_pcount(skb)); // 调整packet_out等统计值
		}
	}

	tcp_retrans_try_collapse(sk, skb, cur_mss);              // 尝试和后几个包合并后一起重传出去,加快速度

	/* Some Solaris stacks overoptimize and ignore the FIN on a
	 * retransmit when old data is attached.  So strip it off
	 * since it is cheap to do so and saves bytes on the network.
	 */ //Solaris系统的协议栈有时候会忽略重传SKB上带有的FIN标志的payload,将payload全部剥离掉,节省网络流量
	if (skb->len > 0 &&
		(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) &&
		tp->snd_una == (TCP_SKB_CB(skb)->end_seq - 1)) {
		if (!pskb_trim(skb, 0)) {
			/* Reuse, even though it does some unnecessary work */
			tcp_init_nondata_skb(skb, TCP_SKB_CB(skb)->end_seq - 1,
						 TCP_SKB_CB(skb)->tcp_flags);
			skb->ip_summed = CHECKSUM_NONE;
		}
	}

	/* Make a copy, if the first transmission SKB clone we made
	 * is still in somebody's hands, else make a clone.
	 */
	TCP_SKB_CB(skb)->when = tcp_time_stamp;

	/* make sure skb->data is aligned on arches that require it
	 * and check if ack-trimming & collapsing extended the headroom
	 * beyond what csum_start can cover.
	 */
	if (unlikely((NET_IP_ALIGN && ((unsigned long)skb->data & 3)) ||
			 skb_headroom(skb) >= 0xFFFF)) {
		struct sk_buff *nskb = __pskb_copy(skb, MAX_TCP_HEADER,
						   GFP_ATOMIC);
		err = nskb ? tcp_transmit_skb(sk, nskb, 0, GFP_ATOMIC) :
				 -ENOBUFS;
	} else {
		err = tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);     // 这个才是正在的传输函数
	}

	if (err == 0) {                                         // 发送成功,那么就需要更新TCP统计信息
		/* Update global TCP statistics. */
		TCP_INC_STATS(sock_net(sk), TCP_MIB_RETRANSSEGS);

		tp->total_retrans++;                                // 整体重传数量++

#if FASTRETRANS_DEBUG > 0
		if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_RETRANS) {
			if (net_ratelimit())
				printk(KERN_DEBUG "retrans_out leaked.\n");
		}
#endif
		if (!tp->retrans_out)
			tp->lost_retrans_low = tp->snd_nxt;
		TCP_SKB_CB(skb)->sacked |= TCPCB_RETRANS;
		tp->retrans_out += tcp_skb_pcount(skb);             // 重传出去的数量+=。。。

		/* Save stamp of the first retransmit. */
		if (!tp->retrans_stamp)
			tp->retrans_stamp = TCP_SKB_CB(skb)->when;      // 第一次重传时间戳

		tp->undo_retrans += tcp_skb_pcount(skb);

		/* snd_nxt is stored to detect loss of retransmitted segment,
		 * see tcp_input.c tcp_sacktag_write_queue().
		 */
		TCP_SKB_CB(skb)->ack_seq = tp->snd_nxt;
	}
	return err;
}

tcp_retrans_try_collapse 重传时尝试和后几个包合并后传出去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 只做简单合并,所以条件设置严格
/* Check if coalescing SKBs is legal. */
static int tcp_can_collapse(struct sock *sk, struct sk_buff *skb)
{
	if (tcp_skb_pcount(skb) > 1)         // skb只包含一个数据包,没有TSO分包
		return 0;
	/* TODO: SACK collapsing could be used to remove this condition */
	if (skb_shinfo(skb)->nr_frags != 0)  // 数据都在线性空间,非线性空间中没有数据
		return 0;
	if (skb_cloned(skb))                 // 不是clone
		return 0;
	if (skb == tcp_send_head(sk))
		return 0;
	/* Some heurestics for collapsing over SACK'd could be invented */
	if (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED)  // 已经被sack的当然不用重传
		return 0;

	return 1;
}

/* Collapse packets in the retransmit queue to make to create
 * less packets on the wire. This is only done on retransmission.
 */
static void tcp_retrans_try_collapse(struct sock *sk, struct sk_buff *to,
					 int space)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb = to, *tmp;
	int first = 1;

	if (!sysctl_tcp_retrans_collapse)
		return;
	if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_SYN)  // SYN包不合并
		return;

	tcp_for_write_queue_from_safe(skb, tmp, sk) {
		if (!tcp_can_collapse(sk, skb))           // 要和并的包判断是否符合条件
			break;

		space -= skb->len;

		if (first) {
			first = 0;
			continue;
		}

		if (space < 0)
			break;
		/* Punt if not enough space exists in the first SKB for
		 * the data in the second
		 */
		if (skb->len > skb_tailroom(to))          // 第一个包的tailroom空间足够容下该包
			break;

		if (after(TCP_SKB_CB(skb)->end_seq, tcp_wnd_end(tp))) // 大于窗口不合并
			break;

		tcp_collapse_retrans(sk, to);             // 进行两个包的合并
	}
}

tcp_collapse_retrans 重传合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/* Collapses two adjacent SKB's during retransmission. */
static void tcp_collapse_retrans(struct sock *sk, struct sk_buff *skb)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *next_skb = tcp_write_queue_next(sk, skb);
	int skb_size, next_skb_size;

	skb_size = skb->len;
	next_skb_size = next_skb->len;

	BUG_ON(tcp_skb_pcount(skb) != 1 || tcp_skb_pcount(next_skb) != 1);

	tcp_highest_sack_combine(sk, next_skb, skb);

	tcp_unlink_write_queue(next_skb, sk);    // 将要合并的包从队列中删掉

	skb_copy_from_linear_data(next_skb, skb_put(skb, next_skb_size),
				  next_skb_size);            // 将数据copy到前一个包上,调整前一个的len,tail

	if (next_skb->ip_summed == CHECKSUM_PARTIAL)
		skb->ip_summed = CHECKSUM_PARTIAL;

	if (skb->ip_summed != CHECKSUM_PARTIAL)
		skb->csum = csum_block_add(skb->csum, next_skb->csum, skb_size);

	/* Update sequence range on original skb. */
	TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(next_skb)->end_seq;  // end_seq 等于后一个包的end_seq,所以如果skb->end_seq > next_skb->seq,就会合并出一个len>end_seq-seq的异常数据(内核保证了sk_write_queue不会出现这情况)

	/* Merge over control information. This moves PSH/FIN etc. over */
	TCP_SKB_CB(skb)->tcp_flags |= TCP_SKB_CB(next_skb)->tcp_flags;

	/* All done, get rid of second SKB and account for it so
	 * packet counting does not break.
	 */
	TCP_SKB_CB(skb)->sacked |= TCP_SKB_CB(next_skb)->sacked & TCPCB_EVER_RETRANS;

	/* changed transmit queue under us so clear hints */
	tcp_clear_retrans_hints_partial(tp);
	if (next_skb == tp->retransmit_skb_hint)
		tp->retransmit_skb_hint = skb;

	tcp_adjust_pcount(sk, next_skb, tcp_skb_pcount(next_skb)); // 调整pcount

	sk_wmem_free_skb(sk, next_skb);        // 合并到了前一个包上,所以释放这个包
}