Linux两种方法来处理传入TCP数据段:快速路径(Fast Path)和慢速路径(Slow Path)
来源:互联网 发布:淘宝美工入门教程 编辑:程序博客网 时间:2024/06/15 16:33
在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来完成这一任务:
- 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 }
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系统调用即将结束时:
- 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;
- ...
- 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;
- ...
(1)当读过紧急数据时;紧急数据是由慢速路径处理,需要保持在慢速路径模式直到收完紧急数据,然后就可以开启快速路径模式了。
- 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 }
- ...
- 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);
- ...
- 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);
- ...
- 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报文是否与预测标志匹配:
- 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 };
- 122 #define TCP_HP_BITS (~(TCP_RESERVED_BITS|TCP_FLAG_PSH))
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放入接收队列中:
- 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_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检查失败
慢速路径处理的代码如下:
- 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;
- ...
- 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 }
- 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 }
- 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 }
- Linux两种方法来处理传入TCP数据段:快速路径(Fast Path)和慢速路径(Slow Path)
- 路径(path)题解
- 用直接路径(direct-path)insert提升性能的两种方法
- 用直接路径(direct-path)insert提升性能的两种方法
- Path Sum 路径和(注:同时包含得到各个路径的模板:两种不同表达形式的代码)
- PDF----path(路径)处理
- C#Path路径处理
- Ubuntu Linux更改PATH路径(转)
- Path Sum II --路径和(重)
- LeetCode | Path Sum(路径和)
- LeetCode 112. Path Sum(路径和)
- python根据路径导入模块的两种方法:sys.path.append和imp.load_source
- linux修改path路径
- 5.3 慢速路径处理
- 改变linux中的path路径的方法
- 修改linux的path路径的方法
- Path(路径)标记语法
- PATH路径
- 多密钥ssh-key生成与管理
- httpclient抓取https网页数据
- 归并排序
- webkit资源加载
- itop4412硬件全面测试
- Linux两种方法来处理传入TCP数据段:快速路径(Fast Path)和慢速路径(Slow Path)
- Spring注解详解
- java解压zip压缩包
- VScode&Python:如何在控制台进行输入,切换解释器版本
- 算法提高 2-1屏幕打印
- 安装完mariadb,root用户用密码or不用密码都能登录
- 单例模式的三种实现及区别
- 每日一坑 pyinstaller 将.py生成.exe 报错 “IndexError: tuple index out of range”
- SLAM知名教授