TCP报文段进入慢速路径处理的条件有:
(1)收到乱序数据或乱序队列非空
(2)收到紧急指针
(3)接收缓存耗尽
(4)收到0窗口通告
(5)收到的报文中含有除了PUSH和ACK之外的标记,如SYN、FIN、RST
(6)报文的时间戳选项解析失败
(7)报文的PAWS检查失败
慢速路径处理的代码如下:
5076 int tcp_rcv_established(struct sock *sk, struct sk_buff *skb, 5077 const struct tcphdr *th, unsigned int len) 5078 { 5079 struct tcp_sock *tp = tcp_sk(sk); ... 5251 slow_path: 5252 if (len < (th->doff << 2) || tcp_checksum_complete_user(sk, skb)) //报文长度非法或检验和错误 5253 goto csum_error; 5254 5255 if (!th->ack && !th->rst) 5256 goto discard; 5257 5258 /* 5259 * Standard slow path. 5260 */ 5261 5262 if (!tcp_validate_incoming(sk, skb, th, 1)) //其它合法性检查 5263 return 0; 5264 5265 step5: 5266 if (tcp_ack(sk, skb, FLAG_SLOWPATH | FLAG_UPDATE_TS_RECENT) < 0) //ACK非法,则丢弃之 5267 goto discard; 5268 5269 tcp_rcv_rtt_measure_ts(sk, skb); //更新RTT估计量 5270 5271 /* Process urgent data. */ 5272 tcp_urg(sk, skb, th); //紧急指针 5273 5274 /* step 7: process the segment text */ 5275 tcp_data_queue(sk, skb); //处理数据,包括乱序数据 5276 5277 tcp_data_snd_check(sk); //试图发送队列中的数据 5278 tcp_ack_snd_check(sk); //发送ACK 5279 return 0; ...tcp_validate_incoming函数用于检查时间戳、序列号等字段:
4985 static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb, 4986 const struct tcphdr *th, int syn_inerr) 4987 { 4988 struct tcp_sock *tp = tcp_sk(sk); 4989 4990 /* RFC1323: H1. Apply PAWS check first. */ 4991 if (tcp_fast_parse_options(skb, th, tp) && tp->rx_opt.saw_tstamp && //解析选项成功并且时间戳选项开启 4992 tcp_paws_discard(sk, skb)) { //检查序列号回绕 4993 if (!th->rst) { 4994 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSESTABREJECTED); 4995 tcp_send_dupack(sk, skb); //立即发送重复ACK 4996 goto discard; //丢弃之 4997 } 4998 /* Reset is accepted even if it did not pass PAWS. */ 4999 } 5000 5001 /* Step 1: check sequence number */ 5002 if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) { 5003 /* RFC793, page 37: "In all states except SYN-SENT, all reset 5004 * (RST) segments are validated by checking their SEQ-fields." 5005 * And page 69: "If an incoming segment is not acceptable, 5006 * an acknowledgment should be sent in reply (unless the RST 5007 * bit is set, if so drop the segment and return)". 5008 */ 5009 if (!th->rst) { 5010 if (th->syn) 5011 goto syn_challenge; 5012 tcp_send_dupack(sk, skb); 5013 } 5014 goto discard; 5015 } 5016 5017 /* Step 2: check RST bit */ 5018 if (th->rst) { 5019 /* RFC 5961 3.2 : 5020 * If sequence number exactly matches RCV.NXT, then 5021 * RESET the connection 5022 * else 5023 * Send a challenge ACK 5024 */ 5025 if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) 5026 tcp_reset(sk); //处理RST标记,设置TCP状态机,释放部分资源 5027 else 5028 tcp_send_challenge_ack(sk); //发送ACK挑战,以应对Blind In-Window Attack 5029 goto discard; 5030 } 5031 5032 /* step 3: check security and precedence [ignored] */ 5033 5034 /* step 4: Check for a SYN 5035 * RFC 5691 4.2 : Send a challenge ack 5036 */ 5037 if (th->syn) { 5038 syn_challenge: 5039 if (syn_inerr) 5040 TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS); 5041 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPSYNCHALLENGE); 5042 tcp_send_challenge_ack(sk); 5043 goto discard; 5044 } 5045 5046 return true; 5047 5048 discard: 5049 __kfree_skb(skb); 5050 return false; 5051 }
4991-4996:如果有时间戳选项的话检查PAWS,不通过则认为是旧包,丢弃之,并立即发送ACK;不过RST包即使PAWS检查不过也是可以接受的,并不丢弃。
5002-5014:tcp_sequence函数用来确保由数据的序列号落入接收窗口之内,否则丢弃之;但如果SYN包的序列号非法则需要发送ACK挑战(原因见RFC5961)。
5018-5046:按照RFC5961的要求处理SYN报文和RST报文,以防止Blind In-Window Attack。
Blind In-Window Attack简介:这个攻击的基本原理是攻击者通过发送伪造的RST包或SYN包导致TCP通信两端中的一个认为包非法而发送RST,从而异常结束连接。而这个攻击能够奏效的前提是所伪造的包的序列号必须在窗口范围内,要保证这一点攻击者必须发送许多攻击包进行猜测,因此得名。防御这种攻击的基本方法是,对于RST包,仔细检查其序列号,只有其序列号真正等于tp->rcv_nxt时才发送RST(攻击者能够伪造出符合这一特征的RST包的概率是很低的);而对于建立状态下SYN包,只是发送ACK,不发RST。
慢速处理路径中ACK的处理、拥塞控制信息的更新以及紧急指针在后续章节中详细讨论。
数据接收处理在tcp_data_queue函数中进行:
4300 static void tcp_data_queue(struct sock *sk, struct sk_buff *skb) 4301 { 4302 const struct tcphdr *th = tcp_hdr(skb); 4303 struct tcp_sock *tp = tcp_sk(sk); 4304 int eaten = -1; 4305 bool fragstolen = false; 4306 4307 if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq)//没有数据 4308 goto drop; 4309 4310 skb_dst_drop(skb); 4311 __skb_pull(skb, th->doff * 4); //skb->data指向数据段 4312 4313 TCP_ECN_accept_cwr(tp, skb); 4314 4315 tp->rx_opt.dsack = 0; 4316 4317 /* Queue data for delivery to the user. 4318 * Packets in sequence go to the receive queue. 4319 * Out of sequence packets to the out_of_order_queue. 4320 */ 4321 if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {//正序包 4322 if (tcp_receive_window(tp) == 0) //接收窗口无法容纳新数据 4323 goto out_of_window; 4324 4325 /* Ok. In sequence. In window. */ 4326 if (tp->ucopy.task == current && //处于进程上下文中;这时应该是进程调用release_sock函数然后进入了tcp_v4_do_rcv 4327 tp->copied_seq == tp->rcv_nxt && tp->ucopy.len &&//接收队列中没有数据且用户缓存长度不为0 4328 sock_owned_by_user(sk) && !tp->urg_data) {//进程正在系统调用中访问socket且没有收到紧急指针 4329 int chunk = min_t(unsigned int, skb->len, 4330 tp->ucopy.len); 4331 4332 __set_current_state(TASK_RUNNING); 4333 4334 local_bh_enable();//必须开启软中断,因为从内核向用户态缓存copy数据可能会睡眠,在禁用软中断的条件下是不允许的 4335 if (!skb_copy_datagram_iovec(skb, 0, tp->ucopy.iov, chunk)) {//将skb中的数据copy到用户缓存中 4336 tp->ucopy.len -= chunk; 4337 tp->copied_seq += chunk; 4338 eaten = (chunk == skb->len); 4339 tcp_rcv_space_adjust(sk); 4340 } 4341 local_bh_disable(); 4342 } 4343 4344 if (eaten <= 0) {//skb中的数据没有被全部copy完毕 4345 queue_and_out: 4346 if (eaten < 0 && 4347 tcp_try_rmem_schedule(sk, skb, skb->truesize))//检查接收缓存空间是否能容纳skb 4348 goto drop; 4349 4350 eaten = tcp_queue_rcv(sk, skb, 0, &fragstolen);//将skb放入接收队列中;如果是与队列尾部的skb合并则eaten为1 4351 } 4352 tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq; 4353 if (skb->len) 4354 tcp_event_data_recv(sk, skb); 4355 if (th->fin)//FIN包 4356 tcp_fin(sk);//处理FIN 4357 4358 if (!skb_queue_empty(&tp->out_of_order_queue)) {//乱序队列非空 4359 tcp_ofo_queue(sk);//整理乱序队列,查看是否能将乱序队列中的包放入接收队列 4360 4361 /* RFC2581. 4.2. SHOULD send immediate ACK, when 4362 * gap in queue is filled. 4363 */ 4364 if (skb_queue_empty(&tp->out_of_order_queue)) 4365 inet_csk(sk)->icsk_ack.pingpong = 0; 4366 } 4367 4368 if (tp->rx_opt.num_sacks) 4369 tcp_sack_remove(tp); 4370 4371 tcp_fast_path_check(sk); //检查是否可以开启快速处理路径 4372 4373 if (eaten > 0)//将skb与队列尾部的skb合并 4374 kfree_skb_partial(skb, fragstolen); 4375 if (!sock_flag(sk, SOCK_DEAD))//将整个skb放入接收队列中 4376 sk->sk_data_ready(sk, 0);//通知进程接收数据 4377 return; 4378 } 4379 //非正序包 4380 if (!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt)) {//旧包 4381 /* A retransmit, 2nd most common case. Force an immediate ack. */ 4382 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_DELAYEDACKLOST); 4383 tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq); 4384 4385 out_of_window: 4386 tcp_enter_quickack_mode(sk); 4387 inet_csk_schedule_ack(sk);//标识此socket正在等待发送ACK,如果以后有数据要发送的话会尽快发送,以便将携带的ACK尽快发送到对端 4388 drop: 4389 __kfree_skb(skb); 4390 return; 4391 } 4392 4393 /* Out of window. F.e. zero window probe. */ 4394 if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt + tcp_receive_window(tp))) 4395 goto out_of_window; 4396 4397 tcp_enter_quickack_mode(sk); 4398 4399 if (before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {//部分是旧数据,部分是新数据 4400 /* Partial packet, seq < rcv_next < end_seq */ 4401 SOCK_DEBUG(sk, "partial packet: rcv_next %X seq %X - %X\n", 4402 tp->rcv_nxt, TCP_SKB_CB(skb)->seq, 4403 TCP_SKB_CB(skb)->end_seq); 4404 4405 tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, tp->rcv_nxt); 4406 4407 /* If window is closed, drop tail of packet. But after 4408 * remembering D-SACK for its head made in previous line. 4409 */ 4410 if (!tcp_receive_window(tp)) 4411 goto out_of_window; 4412 goto queue_and_out;//将skb放入接收队列中 4413 } 4414 4415 tcp_data_queue_ofo(sk, skb);//处理乱序包 4416 }乱序数据的重组由 tcp_ofo_queue函数完成:
4023 static void tcp_ofo_queue(struct sock *sk) 4024 { 4025 struct tcp_sock *tp = tcp_sk(sk); 4026 __u32 dsack_high = tp->rcv_nxt; 4027 struct sk_buff *skb; 4028 4029 while ((skb = skb_peek(&tp->out_of_order_queue)) != NULL) {//取乱序队列首包 4030 if (after(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))//空洞仍然没有填满 4031 break; 4032 4033 if (before(TCP_SKB_CB(skb)->seq, dsack_high)) {//包中有数据被放入接收缓存了 4034 __u32 dsack = dsack_high; 4035 if (before(TCP_SKB_CB(skb)->end_seq, dsack_high))//包中的数据已经全部被放入接收缓存了 4036 dsack_high = TCP_SKB_CB(skb)->end_seq; 4037 tcp_dsack_extend(sk, TCP_SKB_CB(skb)->seq, dsack); 4038 } 4039 4040 if (!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt)) {//包中的数据已经全部被放入接收缓存了 4041 SOCK_DEBUG(sk, "ofo packet was already received\n"); 4042 __skb_unlink(skb, &tp->out_of_order_queue); 4043 __kfree_skb(skb); 4044 continue; 4045 } 4046 SOCK_DEBUG(sk, "ofo requeuing : rcv_next %X seq %X - %X\n", 4047 tp->rcv_nxt, TCP_SKB_CB(skb)->seq, 4048 TCP_SKB_CB(skb)->end_seq); 4049 //空洞被填满,包可以放入接收队列中 4050 __skb_unlink(skb, &tp->out_of_order_queue); 4051 __skb_queue_tail(&sk->sk_receive_queue, skb); 4052 tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq; 4053 if (tcp_hdr(skb)->fin) 4054 tcp_fin(sk); 4055 } 4056 }tcp_data_queue_ofo负责将乱序包按照seq顺序放入乱序队列中,并设置SACK选项信息:
4121 static void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb) 4122 { 4123 struct tcp_sock *tp = tcp_sk(sk); 4124 struct sk_buff *skb1; 4125 u32 seq, end_seq; 4126 4127 TCP_ECN_check_ce(tp, skb); 4128 4129 if (unlikely(tcp_try_rmem_schedule(sk, skb, skb->truesize))) {//检查接收缓存空间是否能容纳skb 4130 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPOFODROP); 4131 __kfree_skb(skb); 4132 return; 4133 } 4134 4135 /* Disable header prediction. */ 4136 tp->pred_flags = 0; 4137 inet_csk_schedule_ack(sk);//标识此socket正在等待发送ACK,如果以后有数据要发送的话会尽快发送,以便将携带的ACK尽快发送到对端 4138 4139 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPOFOQUEUE); 4140 SOCK_DEBUG(sk, "out of order segment: rcv_next %X seq %X - %X\n", 4141 tp->rcv_nxt, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq); 4142 4143 skb1 = skb_peek_tail(&tp->out_of_order_queue); 4144 if (!skb1) {//乱序队列中没有数据 4145 /* Initial out of order segment, build 1 SACK. */ 4146 if (tcp_is_sack(tp)) { 4147 tp->rx_opt.num_sacks = 1; 4148 tp->selective_acks[0].start_seq = TCP_SKB_CB(skb)->seq; 4149 tp->selective_acks[0].end_seq = 4150 TCP_SKB_CB(skb)->end_seq; 4151 } 4152 __skb_queue_head(&tp->out_of_order_queue, skb);//将skb放入乱序队列中 4153 goto end; 4154 } 4155 4156 seq = TCP_SKB_CB(skb)->seq; 4157 end_seq = TCP_SKB_CB(skb)->end_seq; 4158 4159 if (seq == TCP_SKB_CB(skb1)->end_seq) {//当前skb与队列中end_seq最大的数据无缝拼接 4160 bool fragstolen; 4161 4162 if (!tcp_try_coalesce(sk, skb1, skb, &fragstolen)) {//看能否将skb合并到skb1中 4163 __skb_queue_after(&tp->out_of_order_queue, skb1, skb);//如果不能则将skb房子skb1的后面 4164 } else { 4165 kfree_skb_partial(skb, fragstolen); 4166 skb = NULL; 4167 } 4168 4169 if (!tp->rx_opt.num_sacks || 4170 tp->selective_acks[0].end_seq != seq) 4171 goto add_sack; 4172 4173 /* Common case: data arrive in order after hole. */ 4174 tp->selective_acks[0].end_seq = end_seq; 4175 goto end; 4176 } 4177 4178 /* Find place to insert this segment. */ 4179 while (1) {//找到合适的地方插入skb 4180 if (!after(TCP_SKB_CB(skb1)->seq, seq))//seq1 <= seq 4181 break;//找到第一个序列号小于等于skb的包 4182 if (skb_queue_is_first(&tp->out_of_order_queue, skb1)) { 4183 skb1 = NULL; 4184 break;//skb的序列号最小 4185 } 4186 skb1 = skb_queue_prev(&tp->out_of_order_queue, skb1); 4187 } 4188 4189 /* Do skb overlap to previous one? */ 4190 if (skb1 && before(seq, TCP_SKB_CB(skb1)->end_seq)) {//seq >= seq1 && seq < end_seq1 4191 if (!after(end_seq, TCP_SKB_CB(skb1)->end_seq)) {//end_seq <= end_seq1 4192 /* All the bits are present. Drop. */ //skb的数据完全被包含在skb1中 4193 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPOFOMERGE); 4194 __kfree_skb(skb); 4195 skb = NULL; 4196 tcp_dsack_set(sk, seq, end_seq); 4197 goto add_sack; 4198 } 4199 if (after(seq, TCP_SKB_CB(skb1)->seq)) {//seq > seq1 && end_seq > end_seq1 4200 /* Partial overlap. */ 4201 tcp_dsack_set(sk, seq, 4202 TCP_SKB_CB(skb1)->end_seq); 4203 } else { //seq == seq1 && end_seq > end_seq1 4204 if (skb_queue_is_first(&tp->out_of_order_queue, 4205 skb1)) 4206 skb1 = NULL; 4207 else 4208 skb1 = skb_queue_prev( 4209 &tp->out_of_order_queue, 4210 skb1); 4211 } 4212 } 4213 if (!skb1) 4214 __skb_queue_head(&tp->out_of_order_queue, skb); 4215 else 4216 __skb_queue_after(&tp->out_of_order_queue, skb1, skb); 4217 4218 /* And clean segments covered by new one as whole. */ 4219 while (!skb_queue_is_last(&tp->out_of_order_queue, skb)) { 4220 skb1 = skb_queue_next(&tp->out_of_order_queue, skb); 4221 4222 if (!after(end_seq, TCP_SKB_CB(skb1)->seq))//end_seq <= seq1;skb与其后续skb没有重叠数据 4223 break; 4224 if (before(end_seq, TCP_SKB_CB(skb1)->end_seq)) {//end_seq > seq1 && end_seq < end_seq1;skb与其后续skb有部分重叠数据但并不完全覆盖后续skb的数据 4225 tcp_dsack_extend(sk, TCP_SKB_CB(skb1)->seq, 4226 end_seq); 4227 break; 4228 }//skb完全包含了其后续skb的数据,需要释放被完全重叠的skb 4229 __skb_unlink(skb1, &tp->out_of_order_queue); 4230 tcp_dsack_extend(sk, TCP_SKB_CB(skb1)->seq, 4231 TCP_SKB_CB(skb1)->end_seq); 4232 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPOFOMERGE); 4233 __kfree_skb(skb1); 4234 } 4235 4236 add_sack: 4237 if (tcp_is_sack(tp)) 4238 tcp_sack_new_ofo_skb(sk, seq, end_seq); 4239 end: 4240 if (skb) 4241 skb_set_owner_r(skb, sk); 4242
4129:如果TCP接收缓存的空间紧张,乱序队列中的数据是可以被删除的(反正也没确认,不删白不删):
4061 static int tcp_try_rmem_schedule(struct sock *sk, struct sk_buff *skb, 4062 unsigned int size) 4063 { 4064 if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || //接收缓存分配超量 4065 !sk_rmem_schedule(sk, skb, size)) { //全局缓存或接收缓存空间达到上限 4066 4067 if (tcp_prune_queue(sk) < 0) //释放一部分缓存,必要时会清空乱序队列 4068 return -1; 4069 4070 if (!sk_rmem_schedule(sk, skb, size)) { //经过释放后仍然超限 4071 if (!tcp_prune_ofo_queue(sk)) //清除乱序队列中所有的包 4072 return -1; 4073 4074 if (!sk_rmem_schedule(sk, skb, size)) 4075 return -1; 4076 } 4077 } 4078 return 0; 4079 }
数据包放入接收队列进程后,进程就可以使用系统调用将包中的数据copy到用户缓存中,最终完成TCP的数据接收。
在慢速处理路径的最后部分,调用tcp_data_snd_check函数发送数据并检查接收缓存空间:
4719 static void tcp_new_space(struct sock *sk) 4720 { 4721 struct tcp_sock *tp = tcp_sk(sk); 4722 4723 if (tcp_should_expand_sndbuf(sk)) { //应该扩展发送缓存空间 4724 int sndmem = SKB_TRUESIZE(max_t(u32, 4725 tp->rx_opt.mss_clamp, 4726 tp->mss_cache) + 4727 MAX_TCP_HEADER); 4728 int demanded = max_t(unsigned int, tp->snd_cwnd, 4729 tp->reordering + 1); 4730 sndmem *= 2 * demanded; 4731 if (sndmem > sk->sk_sndbuf) 4732 sk->sk_sndbuf = min(sndmem, sysctl_tcp_wmem[2]); 4733 tp->snd_cwnd_stamp = tcp_time_stamp; 4734 } 4735 4736 sk->sk_write_space(sk); //调用sock_def_write_space函数,如果有可用发送空间则通知(唤醒)进程 4737 } 4738 4739 static void tcp_check_space(struct sock *sk) 4740 { 4741 if (sock_flag(sk, SOCK_QUEUE_SHRUNK)) { //发送缓存空间曾经收缩过 4742 sock_reset_flag(sk, SOCK_QUEUE_SHRUNK); 4743 if (sk->sk_socket && 4744 test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) //发生过发送缓存耗尽事件,这意味着可能有进程在等待可用缓存空间 4745 tcp_new_space(sk); 4746 } 4747 } 4748 4749 static inline void tcp_data_snd_check(struct sock *sk) 4750 { 4751 tcp_push_pending_frames(sk); //调用tcp_write_xmit发送数据 4752 tcp_check_space(sk); //检查发送缓存空间 4753 }以上简要分析了TCP在慢速路径处理模式下的工作。下节我们着重关注一下TCP发送和接收ACK的细节。