5.3 慢速路径处理

  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的细节。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值