您的位置:首页 > 理论基础 > 计算机网络

Linux两种方法来处理传入TCP数据段:快速路径(Fast Path)和慢速路径(Slow Path)

2017-04-07 18:14 1621 查看
        在Linux中,有两种方法来处理传入TCP数据段:快速路径(Fast Path)和慢速路径(Slow Path)。使用快速路径只进行最少的处理,如处理数据段、发生ACK、存储时间戳等。使用慢速路径可以处理乱序数据段、PAWS、socket内存管理和紧急数据等。Linux通过预测标志来区分这两种处理模式,预测标志存储在tp->pred_flags,生成这个标志的函数是__tcp_fast_path_on和tcp_fast_path_on,TCP会直接使用这两个函数来生成预测标记,也可以调用tcp_fast_path_check来完成这一任务:

[cpp]
view plain
copy

613 static inline void __tcp_fast_path_on(struct tcp_sock *tp, u32 snd_wnd)  
614 {     
615     tp->pred_flags = htonl((tp->tcp_header_len << 26) |  
616                    ntohl(TCP_FLAG_ACK) |  
617                    snd_wnd);  
618 }     
619           
620 static inline void tcp_fast_path_on(struct tcp_sock *tp)  
621 {     
622     __tcp_fast_path_on(tp, tp->snd_wnd >> tp->rx_opt.snd_wscale);  
623 }     
624           
625 static inline void tcp_fast_path_check(struct sock *sk)  
626 {     
627     struct tcp_sock *tp = tcp_sk(sk);  
628           
629     if (skb_queue_empty(&tp->out_of_order_queue) &&  
630         tp->rcv_wnd &&  
631         atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf &&  
632         !tp->urg_data)  
633         tcp_fast_path_on(tp);  
634 }     

        613-617:__tcp_fast_path_on函数做的工作实际上是在构建TCP首部的第4个字节,即首部长度、标记位、窗口(格式见1.2节)。其中tp->tcp_header_len是首部长度的字节数,TCP首部中记录首部长度的数值位于第4个字节的高4bit,即第28-31bit,而且这个数值乘以4才是首部长度的字节数。故tp->tcp_header_len需要左移28位,再右移2位(除以4),即左移26位。

        620-622:tcp_fast_path_on封装了__tcp_fast_path_on函数,它在设置预测标记时会考虑窗口扩大因子的影响
        625-633:tcp_fast_path_check会先检查条件是否满足,如果满足再设置预测标记。条件是:

(1)没有乱序数据(629)

(2)接收窗口不为0(630)

(3)接收缓存未耗尽(631)

(4)没有紧急数据(632)

        TCP直接调用__tcp_fast_path_on的时机是connect系统调用即将结束时:

[cpp]
view plain
copy

5291 void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)  
5292 {  
...  
5320     if (!tp->rx_opt.snd_wscale)  
5321         __tcp_fast_path_on(tp, tp->snd_wnd);  
5322     else             
5323         tp->pred_flags = 0;  
...  

        TCP直接调用tcp_fast_path_on的时机是收到三次握手中的ACK报文时:

[cpp]
view plain
copy

5600 int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,  
5601               const struct tcphdr *th, unsigned int len)  
5602 {  
...  
5678         int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH |  
5679                           FLAG_UPDATE_TS_RECENT) > 0;  
5680   
5681         switch (sk->sk_state) {  
5682         case TCP_SYN_RECV:  
5683             if (acceptable) {  
...  
5745                 tcp_fast_path_on(tp);  
5746             } else {  
5747                 return 1;  
5748             }  
5749             break;  
...  

        TCP直接调用tcp_fast_path_check的时机有3处:
(1)当读过紧急数据时;紧急数据是由慢速路径处理,需要保持在慢速路径模式直到收完紧急数据,然后就可以开启快速路径模式了。

[cpp]
view plain
copy

1545 int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,  
1546         size_t len, int nonblock, int flags, int *addr_len)  
1547 {  
...  
1874         if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {  
1875             tp->urg_data = 0;  
1876             tcp_fast_path_check(sk);  
1877         }  
...  

(2)当发生方收到ACK并调用tcp_ack_update_window更新窗口时;通告窗口发生了变化,则必须更新预测标记,以免后续的输入报文因为窗口不符而进入慢速路径。

[cpp]
view plain
copy

3218 static int tcp_ack_update_window(struct sock *sk, const struct sk_buff *skb, u32 ack,  
3219                  u32 ack_seq)  
3220 {  
3221     struct tcp_sock *tp = tcp_sk(sk);  
3222     int flag = 0;  
3223     u32 nwin = ntohs(tcp_hdr(skb)->window);  
3224   
3225     if (likely(!tcp_hdr(skb)->syn))  
3226         nwin <<= tp->rx_opt.snd_wscale;  
3227   
3228     if (tcp_may_update_window(tp, ack, ack_seq, nwin)) {  
3229         flag |= FLAG_WIN_UPDATE;  
3230         tcp_update_wl(tp, ack_seq);  
3231   
3232         if (tp->snd_wnd != nwin) {  //窗口有变化  
3233             tp->snd_wnd = nwin;  
3234   
3235             /* Note, it is the only place, where 
3236              * fast path is recovered for sending TCP. 
3237              */  
3238             tp->pred_flags = 0;  
3239             tcp_fast_path_check(sk);  
...  

(3)当tcp_data_queue将数据放入接收队列时;这时可用的接收缓存大小发生变化,tcp_fast_path_check会检查这个缓存的变化是否允许开启快速路径模式。

[cpp]
view plain
copy

4300 static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)  
4301 {  
...  
4321     if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {  
4322         if (tcp_receive_window(tp) == 0)  
4323             goto out_of_window;  
4324   
...  
4371         tcp_fast_path_check(sk);  
...  

        设置了预测标记后,使用它是在处理TCP数据段的唯一入口函数——tcp_rcv_established:

[cpp]
view plain
copy

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);  
...  
5109     if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&  //预测标记匹配  
5110         TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&  //包的序列号恰好是本端期望接收的  
5111         !after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) {  //确认号没有超出本端最新发送的数据的序列号  
5112         int tcp_header_len = tp->tcp_header_len;  
5113 //进入快速处理路径  
5114         /* Timestamp header prediction: tcp_header_len 
5115          * is automatically equal to th->doff*4 due to pred_flags 
5116          * match. 
5117          */  
5118  
5119         /* Check timestamp */  
5120         if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {//TCP头可能有时间戳选项  
5121             /* No? Slow path! */  
5122             if (!tcp_parse_aligned_timestamp(tp, th))//解析时间戳选项失败  
5123                 goto slow_path;  
5124  
5125             /* If PAWS failed, check it more carefully in slow path */  
5126             if ((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0)//此包发送的时间比上个包早,可能是旧包,需仔细检查  
5127                 goto slow_path;  
5128  
5129             /* DO NOT update ts_recent here, if checksum fails 
5130              * and timestamp was corrupted part, it will result 
5131              * in a hung connection since we will drop all 
5132              * future packets due to the PAWS test. 
5133              */  
5134         }  
5135  
5136         if (len <= tcp_header_len) {  
5137             /* Bulk data transfer: sender */  
5138             if (len == tcp_header_len) {//包中无数据  
5139                 /* Predicted packet is in window by definition. 
5140                  * seq == rcv_nxt and rcv_wup <= rcv_nxt. 
5141                  * Hence, check seq<=rcv_wup reduces to: 
5142                  */  
5143                 if (tcp_header_len ==  
5144                     (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&//有时间戳选项  
5145                     tp->rcv_nxt == tp->rcv_wup)//期望接收的序列号与最后一次发送数据但时一致  
5146                     tcp_store_ts_recent(tp);//更新时间戳  
5147  
5148                 /* We know that such packets are checksummed 
5149                  * on entry. 
5150                  */  
5151                 tcp_ack(sk, skb, 0);//处理ACK标记  
5152                 __kfree_skb(skb);  
5153                 tcp_data_snd_check(sk);//发送发送缓存中尚未发送的包  
5154                 return 0;  
5155             } else { /* Header too small */  
5156                 TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);  
5157                 goto discard;  
5158             }  
5159         } else {//包中有数据  
5160             int eaten = 0;  
5161             int copied_early = 0;  
5162             bool fragstolen = false;  
5163  
5164             if (tp->copied_seq == tp->rcv_nxt &&//接收缓存中没有数据  
5165                 len - tcp_header_len <= tp->ucopy.len) {//当前包中的数据能够放入用户缓存中;tp->ucopy.len > 0意味着有进程等待收包  
5166 #ifdef CONFIG_NET_DMA  
5167                 if (tp->ucopy.task == current &&  
5168                     sock_owned_by_user(sk) &&  
5169                     tcp_dma_try_early_copy(sk, skb, tcp_header_len)) {  
5170                     copied_early = 1;  
5171                     eaten = 1;  
5172                 }  
5173 #endif  
5174                 if (tp->ucopy.task == current &&//当前是在进程上下文中运行  
5175                     sock_owned_by_user(sk) && !copied_early) {  
5176                     __set_current_state(TASK_RUNNING);  
5177  
5178                     if (!tcp_copy_to_iovec(sk, skb, tcp_header_len))//copy数据到用户缓存中,不必交付接收缓存  
5179                         eaten = 1;  
5180                 }  
5181                 if (eaten) {//copy成功  
5182                     /* Predicted packet is in window by definition. 
5183                      * seq == rcv_nxt and rcv_wup <= rcv_nxt. 
5184                      * Hence, check seq<=rcv_wup reduces to: 
5185                      */  
5186                     if (tcp_header_len ==  
5187                         (sizeof(struct tcphdr) +  
5188                          TCPOLEN_TSTAMP_ALIGNED) &&  
5189                         tp->rcv_nxt == tp->rcv_wup)  
5190                         tcp_store_ts_recent(tp);  
5191  
5192                     tcp_rcv_rtt_measure_ts(sk, skb);  
5193  
5194                     __skb_pull(skb, tcp_header_len);  
5195                     tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;  
5196                     NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITSTOUSER);  
5197                 }  
5198                 if (copied_early)  
5199                     tcp_cleanup_rbuf(sk, skb->len);  
5200             }  
5201             if (!eaten) {//没有被提前copy进用户缓存  
5202                 if (tcp_checksum_complete_user(sk, skb)) //检验和校验失败  
5203                     goto csum_error;  
5204  
5205                 if ((int)skb->truesize > sk->sk_forward_alloc) //skb的大小超过了发送队列中可以直接使用的空间大小(即已经分配了但尚未使用的空间)  
5206                     goto step5;  
5207  
5208                 /* Predicted packet is in window by definition. 
5209                  * seq == rcv_nxt and rcv_wup <= rcv_nxt. 
5210                  * Hence, check seq<=rcv_wup reduces to: 
5211                  */  
5212                 if (tcp_header_len ==  
5213                     (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&  
5214                     tp->rcv_nxt == tp->rcv_wup)  
5215                     tcp_store_ts_recent(tp);  
5216  
5217                 tcp_rcv_rtt_measure_ts(sk, skb);  
5218  
5219                 NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITS);  
5220  
5221                 /* Bulk data transfer: receiver */  
5222                 eaten = tcp_queue_rcv(sk, skb, tcp_header_len,  
5223                               &fragstolen);//将当前包放入接收队列中;此包并非乱序包,可以直接放入接收队列  
5224             }  
5225  
5226             tcp_event_data_recv(sk, skb);  
5227  
5228             if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {  
5229                 /* Well, only one small jumplet in fast path... */  
5230                 tcp_ack(sk, skb, FLAG_DATA);  
5231                 tcp_data_snd_check(sk);  
5232                 if (!inet_csk_ack_scheduled(sk))  
5233                     goto no_ack;  
5234             }  
5235  
5236             if (!copied_early || tp->rcv_nxt != tp->rcv_wup)  
5237                 __tcp_ack_snd_check(sk, 0);  
5238 no_ack:  
5239 #ifdef CONFIG_NET_DMA  
5240             if (copied_early)  
5241                 __skb_queue_tail(&sk->sk_async_wait_queue, skb);  
5242             else  
5243 #endif  
5244             if (eaten)  
5245                 kfree_skb_partial(skb, fragstolen);  
5246             sk->sk_data_ready(sk, 0);//调用sock_def_readable函数通知进程有数据可读  
5247             return 0;  
5248         }  
5249     }  
5250  
5251 slow_path://慢速路径  
...  

        5109:这样是检查输入的TCP报文是否与预测标志匹配:

[cpp]
view plain
copy

 64 union tcp_word_hdr {   
 65     struct tcphdr hdr;  
 66     __be32        words[5];  
 67 };  
 68   
 69 #define tcp_flag_word(tp) ( ((union tcp_word_hdr *)(tp))->words [3])  
 70   
 71 enum {   
 72     TCP_FLAG_CWR = __constant_cpu_to_be32(0x00800000),  
 73     TCP_FLAG_ECE = __constant_cpu_to_be32(0x00400000),  
 74     TCP_FLAG_URG = __constant_cpu_to_be32(0x00200000),  
 75     TCP_FLAG_ACK = __constant_cpu_to_be32(0x00100000),  
 76     TCP_FLAG_PSH = __constant_cpu_to_be32(0x00080000),  
 77     TCP_FLAG_RST = __constant_cpu_to_be32(0x00040000),  
 78     TCP_FLAG_SYN = __constant_cpu_to_be32(0x00020000),  
 79     TCP_FLAG_FIN = __constant_cpu_to_be32(0x00010000),  
 80     TCP_RESERVED_BITS = __constant_cpu_to_be32(0x0F000000),  
 81     TCP_DATA_OFFSET = __constant_cpu_to_be32(0xF0000000)  
 82 };  

        而TCP_HP_BITS的定义为:

[cpp]
view plain
copy

122 #define TCP_HP_BITS (~(TCP_RESERVED_BITS|TCP_FLAG_PSH))  

        可以看出,5109行的匹配方法是将TCP首部的第4个字节去掉TCP_FLAG_PSH标记后再与预测标记对比,只有首部长度和窗口大小与预测标记一致且标记位仅有TCP_FLAG_ACK的包才能通过。除了预测标记匹配通过外,当前包序列号和确认号还需要满足5110和5111行的要求才能进入快速处理路径。
  5120-5127:如果报文中携带时间戳选项,则需要解析时间戳然后检查是否有序列号回绕(PAWS)的问题。如果时间戳选项解析失败或PAWS检查失败,则需要进入慢速路径仔细检查

  5136-5157是对没有数据部分的报文的处理。

  5143-5144:TCP首部中有时间戳选项

  5145:这个条件满足意味着当前包是对最新一次发送的数据包的回应

  5146:这时需要更新时间戳信息

  5151-5152:调用tcp_ack处理完ACK标记后,这个没有数据的包就已经完成了使命,可以释放了

  5153:收到ACK后可能确认了一部分旧的数据,也可能更新了通告窗口,这时需要调用tcp_data_snd_check函数试着发送一下发送队列中的数据

  至此,快速处理路径中对无数据的包的处理完毕,下面是对有数据的包的处理。

  5164-5199:如果进程使用prequeue收包,则需要试图将数据直接copy到用户缓存;如果copy成功,则需要更新时间戳、设置tp->rcv_nxt等

  5201-5223:如果没有数据被直接copy到用户空间,则在进行了检验和校验、接收空间检查、时间戳保存等工作后,调用tcp_queue_rcv函数将skb放入接收队列中:

[cpp]
view plain
copy

4244 static int __must_check tcp_queue_rcv(struct sock *sk, struct sk_buff *skb, int hdrlen,  
4245           bool *fragstolen)  
4246 {  
4247     int eaten;  
4248     struct sk_buff *tail = skb_peek_tail(&sk->sk_receive_queue); //获取接收队列最尾部的skb  
4249   
4250     __skb_pull(skb, hdrlen);  
4251     eaten = (tail &&     //接收队列不为空  
4252          tcp_try_coalesce(sk, tail, skb, fragstolen)) ? 1 : 0; //试着将当前skb整合到从队列尾取得的skb中  
4253     tcp_sk(sk)->rcv_nxt = TCP_SKB_CB(skb)->end_seq;  
4254     if (!eaten) {        //整合不成功  
4255         __skb_queue_tail(&sk->sk_receive_queue, skb);  
4256         skb_set_owner_r(skb, sk);        
4257     }  
4258     return eaten;          
4259 }  

  可见无论skb是否整合成功,tcp_queue_rcv函数最终会将skb的数据部分放入到sk->sk_receive_queue中。

  处理完skb的数据部分后,tcp_rcv_established函数会调用tcp_event_data_recv函数更新拥塞控制信息(包括拥塞窗口),然后处理ACK标记位:
  5228:满足这个判断条件意味着要么ACK会确认新的数据(TCP_SKB_CB(skb)->ack_seq > tp->snd_una),要么是一个旧的ACK(TCP_SKB_CB(skb)->ack_seq < tp->snd_una),这两种情况都需要调用tcp_ack进行处理,然后再试图发送一下发送队列中的数据。

  5236-5237:如果没有数据通过DMA直接copy给进程,或接收了新的数据,则发送ACK(立即发送或使用延迟ACK机制)。换个角度考虑,不发送ACK的条件是:有数据通过DMA直接copy给进程,且没有接收新的数据。也就是说,skb中的数据长度为0。但这样的话skb是走不到5159这个分支中的啊。我实在想不出使这个条件为假的情况。

  5240-5240:如果有数据通过DMA传输,则将skb放入sk_async_wait_queue中,以防DMA传输失败。

  5246:最后,调用sk->sk_data_ready指向的函数通知进程有数据可以接收。

  至此,快速路径处理完成,不满足快速处理条件的skb会被送入慢速处理路径。在那里,skb会接受更多更严格的检查与处理。

TCP报文段进入慢速路径处理的条件有:

(1)收到乱序数据或乱序队列非空

(2)收到紧急指针

(3)接收缓存耗尽

(4)收到0窗口通告

(5)收到的报文中含有除了PUSH和ACK之外的标记,如SYN、FIN、RST

(6)报文的时间戳选项解析失败

(7)报文的PAWS检查失败

  慢速路径处理的代码如下:

[cpp]
view plain
copy

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函数用于检查时间戳、序列号等字段:

[cpp]
view plain
copy

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函数中进行:

[cpp]
view plain
copy

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函数完成:

[cpp]
view plain
copy

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选项信息:

[cpp]
view plain
copy

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接收缓存的空间紧张,乱序队列中的数据是可以被删除的(反正也没确认,不删白不删):

[cpp]
view plain
copy

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函数发送数据并检查接收缓存空间:

[cpp]
view plain
copy

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的细节。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: