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的细节。
[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的细节。
相关文章推荐
- Linux设置环境变量PATH路径的两种方法
- 详解Linux系统下修改环境变量PATH路径的方法
- Linux添加库路径的两种方法
- 用直接路径(direct-path)insert提升性能的两种方法
- linux 定时任务访问 Ci 框架下的方法路径处理过程
- C#编程技巧之常用文件名、路径处理方法--Path类
- linux下安装JDK以及修改path路径的方法
- python根据路径导入模块的两种方法:sys.path.append和imp.load_source
- 在Linux里设置环境变量的方法(export PATH)--路径
- 详解Linux系统修改环境变量PATH路径的方法
- Linux 快速定位web路径方法
- Linux大文件快速处理小方法
- 详解Linux系统修改环境变量PATH路径的方法
- 用直接路径(direct-path)insert提升性能的两种方法
- Linux查找处理文件名后包含空格的文件(两种方法)
- Linux系统下修改环境变量PATH路径的三种方法
- java学习笔记:getPath取路径文件夹中有空格的处理方法
- Linux系统下修改环境变量PATH路径的方法
- Linux 快速定位web路径方法
- 改变linux中的path路径的方法