tcp_rcv_established函数的工作原理是把数据包的处理分为2类:fast path和slow path,其含义显而易见。这样分类的目的当然是加快数据包的处理,因为在正常情况下,数据包是按顺序到达的,网络状况也是稳定的,这时可以按照fast path直接把数据包存放到receive queue了。而在其他的情况下则需要走slow path流程了。

在协议栈中,是用头部预测来实现的,每个tcp sock有个pred_flags成员,它就是判别的依据

fast path处理,其揭示的含义如下:

Either the data transaction is taking place in only one direction (which means that we are the receiver and not transmitting any data) or in the case where we are sending out data also, the window advertised from the other end is constant. The latter means that we have not transmitted any data from our side for quite some time but are receiving data from the other end. The receive window advertised by the other end is constant.

    1. Other than PSH|ACK flags in the TCP header, no other flag is set (ACK is set for each TCP segment).
      This means that if any other flag is set such as URG, FIN, SYN, ECN, RST, and CWR, we know that something important is there to be attended and we need to move into the SLOW path.

    2. The header length has unchanged. If the TCP header length remains unchanged, we have not added/reduced any TCP option and we can safely assume that there is nothing important to be attended, if the above two conditions are TRUE.

从fast path进入slow path的触发条件(进入slow path 后pred_flags清除为0):

1 在tcp_data_queue中接收到乱序数据包
2 在tcp_prune_queue中用完缓存并且开始丢弃数据包
3 在tcp_urgent_check中遇到紧急指针
4 在tcp_select_window中发送的通告窗口下降到0.

从slow_path进入fast_path的触发条件:

1 When we have read past an urgent byte in tcp_recvmsg() . Wehave gotten an urgent byte and we remain in the slow path mode until we receive the urgent byte because it is handled in the slow path in tcp_rcv_established().
2 当在tcp_data_queue中乱序队列由于gap被填充而处理完毕时,运行tcp_fast_path_check。
3 tcp_ack_update_window()中更新了通告窗口。

/*
* TCP receive function for the ESTABLISHED state.
*
* It is split into a fast path and a slow path. The fast path is
* disabled when:
* - A zero window was announced from us - zero window probing
* is only handled properly in the slow path.
* - Out of order segments arrived.
* - Urgent data is expected.
* - There is no buffer space left
* - Unexpected TCP flags/window values/header lengths are received
* (detected by checking the TCP header against pred_flags)
* - Data is sent in both directions. Fast path only supports pure senders
* or pure receivers (this means either the sequence number or the ack
* value must stay constant)
* - Unexpected TCP option.
*
* When these conditions are not satisfied it drops into a standard
* receive procedure patterned after RFC793 to handle all cases.
* The first three cases are guaranteed by proper pred_flags setting,
* the rest is checked inline. Fast processing is turned on in
* tcp_data_queue when everything is OK.
*/
void tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th, unsigned int len)
{
struct tcp_sock *tp = tcp_sk(sk); skb_mstamp_get(&tp->tcp_mstamp);
if (unlikely(!sk->sk_rx_dst)) /* 路由为空,则重新设置路由 */
inet_csk(sk)->icsk_af_ops->sk_rx_dst_set(sk, skb);
/*
* Header prediction.
* The code loosely follows the one in the famous
* "30 instruction TCP receive" Van Jacobson mail.
*
* Van's trick is to deposit buffers into socket queue
* on a device interrupt, to call tcp_recv function
* on the receive process context and checksum and copy
* the buffer to user space. smart...
*
* Our current scheme is not silly either but we take the
* extra cost of the net_bh soft interrupt processing...
* We do checksum and copy also but from device to kernel.
*/ tp->rx_opt.saw_tstamp = 0; /* pred_flags is 0xS?10 << 16 + snd_wnd
* if header_prediction is to be made
* 'S' will always be tp->tcp_header_len >> 2
* '?' will be 0 for the fast path, otherwise pred_flags is 0 to
* turn it off (when there are holes in the receive
* space for instance)
* PSH flag is ignored.
*/
/* 快路检查&& 序号正确 && ack序号正确
TCP_HP_BITS的作用就是排除flag中的PSH标志位。只有在头部预测满足并且数据包以正确的顺序(该数据包的第一个序号就是下个要接收的序号)到达时才进入fast path

*/ if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&
TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&
!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) {
int tcp_header_len = tp->tcp_header_len; /* tcp头部长度 */ /* Timestamp header prediction: tcp_header_len
* is automatically equal to th->doff*4 due to pred_flags
* match.
*/ /* Check timestamp */ /* 有时间戳选项 */
if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {
/* No? Slow path! /* 解析时间戳选项失败,执行慢路 */
if (!tcp_parse_aligned_timestamp(tp, th))
goto slow_path; /* If PAWS failed, check it more carefully in slow path
*/
/* 序号回转,执行慢路 如果获取到的时间戳值 小于 下一个发送的tcp 段的时间搓
回显值 ?? paws检测 接收到的tcp段序号 是预期的但是时间戳值早过 发生了序号回卷??????
新的接收的数据段的时间戳)比ts_recent(对端发送过来的数据(也就是上一次)的最新的一个时间戳)小,则我们要进入slow path 处理paws。
*/
if ((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0)
goto slow_path; /* DO NOT update ts_recent here, if checksum fails
* and timestamp was corrupted part, it will result
* in a hung connection since we will drop all
* future packets due to the PAWS test.
*/
} if (len <= tcp_header_len) { /* 无数据该代码段是依据时戳选项来检查PAWS(Protect Against Wrapped Sequence numbers)。
如果发送来的仅是一个TCP头的话(没有捎带数据或者接收端检测到有乱序数据这些情况时都会发送一个纯粹的ACK包) */
/* Bulk data transfer: sender
主要的工作如下
1 保存对方的最近时戳 tcp_store_ts_recent。通过前面的if判断可以看出tcp总是回显2次时戳回显直接最先到达的数据包的时戳,
rcv_wup只在发送数据(这时回显时戳)时重置为rcv_nxt,所以接收到前一次回显后第一个数据包后,rcv_nxt增加了,但是
rcv_wup没有更新,所以后面的数据包处理时不会调用该函数来保存时戳。
2 ACK处理。这个函数非常复杂,包含了拥塞控制机制,确认处理等等。
3 检查是否有数据待发送 tcp_data_snd_check。
*/
if (len == tcp_header_len) {
/* Predicted packet is in window by definition.
* seq == rcv_nxt and rcv_wup <= rcv_nxt.
* Hence, check seq<=rcv_wup reduces to:
*//*
有时间戳选项
&& 所有接收的数据段均确认完毕
保存时间戳
/*static void tcp_store_ts_recent(struct tcp_sock *tp)
{
tp->rx_opt.ts_recent = tp->rx_opt.rcv_tsval;
tp->rx_opt.ts_recent_stamp = get_seconds();
}
*/
if (tcp_header_len ==
(sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)
tcp_store_ts_recent(tp); /* We know that such packets are checksummed
* on entry.
*/ /* 输入/快速路径ack处理 */
tcp_ack(sk, skb, 0);
__kfree_skb(skb);
/* 检查是否有数据要发送,并检查发送缓冲区大小
收到ack了,给数据包一次发送机会,tcp_push_pending_frames*/
tcp_data_snd_check(sk);
return;
} else { /* Header too small */
/* 数据多小,比头部都小,错包 */
TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS);
goto discard;
}
} else { /* 有数据 且通过了首部预测检测 说明收到的段符合预期 开始处理数据*/
int eaten = 0;
bool fragstolen = false;
/* 进程上下文 */
if (tp->ucopy.task == current &&
/* 期待读取的和期待接收的序号一致也就是
正在接收的段序号 和尚未从内核空间复制到用户空间的段最前的序号相等*/
tp->copied_seq == tp->rcv_nxt &&
len - tcp_header_len <= tp->ucopy.len && /* 数据<= 待读取长度(小于用户空间缓存) */
/* 控制块被用户空间锁定 */
sock_owned_by_user(sk)) {
__set_current_state(TASK_RUNNING); /* 设置状态为running??? */
/* 拷贝数据到msghdr */
if (!tcp_copy_to_iovec(sk, skb, tcp_header_len)) {
/* Predicted packet is in window by definition.
* seq == rcv_nxt and rcv_wup <= rcv_nxt.
* Hence, check seq<=rcv_wup reduces to:
*/ /* 有时间戳选项&& 收到的数据段均已确认,更新时间戳 */
if (tcp_header_len ==
(sizeof(struct tcphdr) +
TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)
tcp_store_ts_recent(tp); tcp_rcv_rtt_measure_ts(sk, skb); /* 接收端RTT估算 */ __skb_pull(skb, tcp_header_len);
/* 更新期望接收的序号 */
tcp_rcv_nxt_update(tp, TCP_SKB_CB(skb)->end_seq);
NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPHPHITSTOUSER);
eaten = 1;
}
}
/* 未拷贝数据到用户空间,或者拷贝失败----没有把数据放到ucopy中 */
if (!eaten) {
if (tcp_checksum_complete(skb))
goto csum_error;
/* skb长度> 预分配长度 */
if ((int)skb->truesize > sk->sk_forward_alloc)
goto step5; /* Predicted packet is in window by definition.
* seq == rcv_nxt and rcv_wup <= rcv_nxt.
* Hence, check seq<=rcv_wup reduces to:
*/ /* 有时间戳选项,且数据均已确认完毕,则更新时间戳 */
if (tcp_header_len ==
(sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)//在收到这个数据包之前,没有发送包也没有收到其他数据包,并且这个包不是乱序包
tcp_store_ts_recent(tp); tcp_rcv_rtt_measure_ts(sk, skb); NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPHPHITS); /* Bulk data transfer: receiver */ /* 数据加入接收队列 添加数据到sk_receive_queue中
会删除 tcp 首部 将数据包添加到队列中缓存起来 等待进程读取 同时设置改skb的宿主
释放回调函数 更新传输控制块已使用接收缓存总量 同时update tp->rcv_nxt, also update tp->bytes_received*/
eaten = tcp_queue_rcv(sk, skb, tcp_header_len,
&fragstolen);
} tcp_event_data_recv(sk, skb);//inet_csk_schedule_ack, 更新rtt
/* 确认序号确认了数据 */
if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {
/* Well, only one small jumplet in fast path... */
tcp_ack(sk, skb, FLAG_DATA);/* 处理ack */
tcp_data_snd_check(sk); /* 检查是否有数据要发送,需要则发送 */
if (!inet_csk_ack_scheduled(sk)) /* 没有ack要发送 在tcp_event_data_recv标记过,但可能ack已经发出了,就不用检测是否要发送了*/
goto no_ack;
}
/* 检查是否有ack要发送,需要则发送 */
__tcp_ack_snd_check(sk, 0);
no_ack:
if (eaten)
kfree_skb_partial(skb, fragstolen);
sk->sk_data_ready(sk);
return;
}
} slow_path:
/* 长度错误|| 校验和错误 */
if (len < (th->doff << 2) || tcp_checksum_complete(skb))
goto csum_error;
/* 无ack,无rst,无syn */
if (!th->ack && !th->rst && !th->syn)
goto discard; /*
* Standard slow path.
/* 种种校验
*/ if (!tcp_validate_incoming(sk, skb, th, 1))
return; step5:
/* 处理ack */
if (tcp_ack(sk, skb, FLAG_SLOWPATH | FLAG_UPDATE_TS_RECENT) < 0)
goto discard;
/* 计算rtt */
tcp_rcv_rtt_measure_ts(sk, skb); /* Process urgent data. */
/* 处理紧急数据 */
tcp_urg(sk, skb, th); /* step 7: process the segment text数据段处理 */
tcp_data_queue(sk, skb); tcp_data_snd_check(sk);/* 发送数据检查,有则发送 */
tcp_ack_snd_check(sk);/* 发送ack检查,有则发送 */
return; csum_error:
TCP_INC_STATS(sock_net(sk), TCP_MIB_CSUMERRORS);
TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS); discard:
tcp_drop(sk, skb);
}
/* There is something which you must keep in mind when you analyze the
* behavior of the tp->ato delayed ack timeout interval. When a
* connection starts up, we want to ack as quickly as possible. The
* problem is that "good" TCP's do slow start at the beginning of data
* transmission. The means that until we send the first few ACK's the
* sender will sit on his end and only queue most of his data, because
* he can only send snd_cwnd unacked packets at any given time. For
* each ACK we send, he increments snd_cwnd and transmits more of his
* queue. -DaveM
*/
static void tcp_event_data_recv(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
u32 now; inet_csk_schedule_ack(sk);/* 接收到了数据,设置ACK需调度标志*/ ---------------------------------
icsk->icsk_ack.lrcvtime = now; tcp_ecn_check_ce(tp, skb); if (skb->len >= 128)
tcp_grow_window(sk, skb);
}
/*
rcv_ssthresh是当前的接收窗口大小的一个阀值,其初始值就置为rcv_wnd。它跟rcv_wnd配合工作,
当本地socket收到数据报,并满足一定条件时,增长rcv_ssthresh的值,在下一次发送数据报组建TCP首部时,
需要通告对方当前的接收窗口大小,这时需要更新rcv_wnd,此时rcv_wnd的取值不能超过rcv_ssthresh的值。
两者配合,达到一个滑动窗口大小缓慢增长的效果。 */
static void tcp_grow_window(struct sock *sk, const struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk); /* Check #1 */
if (tp->rcv_ssthresh < tp->window_clamp &&
(int)tp->rcv_ssthresh < tcp_space(sk) &&
!tcp_under_memory_pressure(sk)) {
int incr; /* Check #2. Increase window, if skb with such overhead
* will fit to rcvbuf in future.
*/
if (tcp_win_from_space(skb->truesize) <= skb->len)
incr = 2 * tp->advmss;
else
incr = __tcp_grow_window(sk, skb); if (incr) {
incr = max_t(int, incr, 2 * skb->len);
tp->rcv_ssthresh = min(tp->rcv_ssthresh + incr,
tp->window_clamp);
inet_csk(sk)->icsk_ack.quick |= 1;
}
}
}
/* Does PAWS and seqno based validation of an incoming segment, flags will
* play significant role here.
*/
static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th, int syn_inerr)
{
struct tcp_sock *tp = tcp_sk(sk);
bool rst_seq_match = false; /* RFC1323: H1. Apply PAWS check first.
PAWS丢弃数据包要满足以下条件 1 The difference between the timestamp value obtained in the current segmentand last seen timestamp on the incoming TCP segment
should be more than TCP_PAWS_WINDOW (= 1), which means that if the segment that was transmitted 1 clock tick before the segment
that reached here earlier TCP seq should be acceptable. It may be because of reordering of the segments that the latter reached earlier.
2 the 24 days have not elapsed since last time timestamp was stored,
3 tcp_disordered_ack返回0.
static inline bool tcp_paws_discard(const struct sock *sk,
const struct sk_buff *skb)
{
const struct tcp_sock *tp = tcp_sk(sk); return !tcp_paws_check(&tp->rx_opt, TCP_PAWS_WINDOW) &&
!tcp_disordered_ack(sk, skb);
}
>tcp_paws_discard
|
|-->tcp_disordered_ack
其中关键是local方通过tcp_disordered_ack函数对一个刚收到的数据分段进行判断,下面我们对该函数的判断逻辑进行下总结:
大前提:该收到分段的TS值表明有回绕现象发生
a)若该分段不是一个纯ACK,则丢弃。因为显然这个分段所携带的数据是一个老数据了,不是local方目前希望接收的(参见PAWS的处理依据一节)
b)若该分段不是local所希望接收的,则丢弃。这个原因很显然
c)若该分段是一个纯ACK,但该ACK并不是一个重复ACK(由local方后续数据正确到达所引发的),则丢弃。因为显然该ACK是一个老的ACK,并不是由于为了加快local方重发而在每收到一个丢失分段后的分段而发出的ACK。
d)若该分段是一个ACK,且为重复ACK,并且该ACK的TS值超过了local方那个丢失分段后的重发rto,则丢弃。因为显然此时local方已经重发了那个导致此重复ACK产生的分段,因此再收到此重复ACK就可以直接丢弃。
e)若该分段是一个ACK,且为重复ACK,但是没有超过一个rto的时间,则不能丢弃,因为这正代表peer方收到了local方发出的丢失分段后的分段,local方要对此ACK进行处理(例如立刻重传) 这里有一个重要概念需要理解,即在出现TS问题后,纯ACK和带ACK的数据分段二者是显著不同的,对于后者,可以立刻丢弃掉,因为从一个窗口的某个seq到下一个窗口的同一个seq过程中,
一定有窗口变化曾经发生过,从而TS记录值ts_recent也一定更新过,此时一定可以通过PAWS进行丢弃处理。但是对于前者,一个纯ACK,就不能简单丢弃了,因为有这样一个现象是合理的,
即假定local方的接收缓存很大,并且peer方在发送时很快就回绕了,于是在local方的某个分段丢失后,peer方需要在每收到的后续分段时发送重复ACK,而此时该重发ACK的ack_seq就是这个丢失分段的序号,
而该重发ACK的seq已经是回绕后的重复序号了,尽管此时到底是回绕后的那个重复ACK还是之前的那个同样序号seq的重复ACK,对于local方来都需要处理(立刻启动重发动作),而不能简单丢弃掉。
来自 http://abcdxyzk.github.io/blog/2015/04/01/kernel-net-estab/ */
if (tcp_fast_parse_options(skb, th, tp) && tp->rx_opt.saw_tstamp &&
tcp_paws_discard(sk, skb)) {
if (!th->rst) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_PAWSESTABREJECTED);
if (!tcp_oow_rate_limited(sock_net(sk), skb,
LINUX_MIB_TCPACKSKIPPEDPAWS,
&tp->last_oow_ack_time))
tcp_send_dupack(sk, skb);
goto discard;
}
/* Reset is accepted even if it did not pass PAWS. */
} /* Step 1: check sequence number
检查数据包的序号是否正确,该判断失败后调用tcp_send_dupack发送一个duplicate acknowledge(未设置RST标志位时)。
由rcv_wup的更新时机(发送ACK时的tcp_select_window)可知位于序号rcv_wup前面的数据都已确认,
所以待检查数据包的结束序号至少要大于该值;同时开始序号要落在接收窗口内 */
if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
/* RFC793, page 37: "In all states except SYN-SENT, all reset
* (RST) segments are validated by checking their SEQ-fields."
* And page 69: "If an incoming segment is not acceptable,
* an acknowledgment should be sent in reply (unless the RST
* bit is set, if so drop the segment and return)".
*/
if (!th->rst) {
if (th->syn)
goto syn_challenge;
if (!tcp_oow_rate_limited(sock_net(sk), skb,
LINUX_MIB_TCPACKSKIPPEDSEQ,
&tp->last_oow_ack_time))
tcp_send_dupack(sk, skb);
} else if (tcp_reset_check(sk, skb)) {
tcp_reset(sk);
}
goto discard;
} /* Step 2: check RST bit 如果设置了RST,则调用tcp_reset处理*/
if (th->rst) {
/* RFC 5961 3.2 (extend to match against (RCV.NXT - 1) after a
* FIN and SACK too if available):
* If seq num matches RCV.NXT or (RCV.NXT - 1) after a FIN, or
* the right-most SACK block,
* then
* RESET the connection
* else
* Send a challenge ACK
*/
if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt ||
tcp_reset_check(sk, skb)) {
rst_seq_match = true;
} else if (tcp_is_sack(tp) && tp->rx_opt.num_sacks > 0) {
struct tcp_sack_block *sp = &tp->selective_acks[0];
int max_sack = sp[0].end_seq;
int this_sack; for (this_sack = 1; this_sack < tp->rx_opt.num_sacks;
++this_sack) {
max_sack = after(sp[this_sack].end_seq,
max_sack) ?
sp[this_sack].end_seq : max_sack;
} if (TCP_SKB_CB(skb)->seq == max_sack)
rst_seq_match = true;
} if (rst_seq_match)
tcp_reset(sk);
else {
/* Disable TFO if RST is out-of-order
* and no data has been received
* for current active TFO socket
*/
if (tp->syn_fastopen && !tp->data_segs_in &&
sk->sk_state == TCP_ESTABLISHED)
tcp_fastopen_active_disable(sk);
tcp_send_challenge_ack(sk, skb);
}
goto discard;
} /* step 3: check security and precedence [ignored] */ /* step 4: Check for a SYN
* RFC 5961 4.2 : Send a challenge ack
检查SYN,因为重发的SYN和原来的SYN之间不会发送数据,所以这2个SYN的序号是相同的
*/
if (th->syn) {
syn_challenge:
if (syn_inerr)
TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS);
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPSYNCHALLENGE);
tcp_send_challenge_ack(sk, skb);
goto discard;
} return true; discard:
tcp_drop(sk, skb);
return false;
}
/* Does PAWS and seqno based validation of an incoming segment, flags will
* play significant role here.
*/
static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th, int syn_inerr)
{
struct tcp_sock *tp = tcp_sk(sk);
bool rst_seq_match = false; /* RFC1323: H1. Apply PAWS check first.
PAWS丢弃数据包要满足以下条件 1 The difference between the timestamp value obtained in the current segmentand last seen timestamp on the incoming TCP segment
should be more than TCP_PAWS_WINDOW (= 1), which means that if the segment that was transmitted 1 clock tick before the segment
that reached here earlier TCP seq should be acceptable. It may be because of reordering of the segments that the latter reached earlier.
2 the 24 days have not elapsed since last time timestamp was stored,
3 tcp_disordered_ack返回0.
static inline bool tcp_paws_discard(const struct sock *sk,
const struct sk_buff *skb)
{
const struct tcp_sock *tp = tcp_sk(sk); return !tcp_paws_check(&tp->rx_opt, TCP_PAWS_WINDOW) &&
!tcp_disordered_ack(sk, skb);
}
>tcp_paws_discard
|
|-->tcp_disordered_ack
其中关键是local方通过tcp_disordered_ack函数对一个刚收到的数据分段进行判断,下面我们对该函数的判断逻辑进行下总结:
大前提:该收到分段的TS值表明有回绕现象发生
a)若该分段不是一个纯ACK,则丢弃。因为显然这个分段所携带的数据是一个老数据了,不是local方目前希望接收的(参见PAWS的处理依据一节)
b)若该分段不是local所希望接收的,则丢弃。这个原因很显然
c)若该分段是一个纯ACK,但该ACK并不是一个重复ACK(由local方后续数据正确到达所引发的),则丢弃。因为显然该ACK是一个老的ACK,并不是由于为了加快local方重发而在每收到一个丢失分段后的分段而发出的ACK。
d)若该分段是一个ACK,且为重复ACK,并且该ACK的TS值超过了local方那个丢失分段后的重发rto,则丢弃。因为显然此时local方已经重发了那个导致此重复ACK产生的分段,因此再收到此重复ACK就可以直接丢弃。
e)若该分段是一个ACK,且为重复ACK,但是没有超过一个rto的时间,则不能丢弃,因为这正代表peer方收到了local方发出的丢失分段后的分段,local方要对此ACK进行处理(例如立刻重传) 这里有一个重要概念需要理解,即在出现TS问题后,纯ACK和带ACK的数据分段二者是显著不同的,对于后者,可以立刻丢弃掉,因为从一个窗口的某个seq到下一个窗口的同一个seq过程中,
一定有窗口变化曾经发生过,从而TS记录值ts_recent也一定更新过,此时一定可以通过PAWS进行丢弃处理。但是对于前者,一个纯ACK,就不能简单丢弃了,因为有这样一个现象是合理的,
即假定local方的接收缓存很大,并且peer方在发送时很快就回绕了,于是在local方的某个分段丢失后,peer方需要在每收到的后续分段时发送重复ACK,而此时该重发ACK的ack_seq就是这个丢失分段的序号,
而该重发ACK的seq已经是回绕后的重复序号了,尽管此时到底是回绕后的那个重复ACK还是之前的那个同样序号seq的重复ACK,对于local方来都需要处理(立刻启动重发动作),而不能简单丢弃掉。
来自 http://abcdxyzk.github.io/blog/2015/04/01/kernel-net-estab/ */
if (tcp_fast_parse_options(skb, th, tp) && tp->rx_opt.saw_tstamp &&
tcp_paws_discard(sk, skb)) {
if (!th->rst) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_PAWSESTABREJECTED);
if (!tcp_oow_rate_limited(sock_net(sk), skb,
LINUX_MIB_TCPACKSKIPPEDPAWS,
&tp->last_oow_ack_time))
tcp_send_dupack(sk, skb);
goto discard;
}
/* Reset is accepted even if it did not pass PAWS. */
} /* Step 1: check sequence number
检查数据包的序号是否正确,该判断失败后调用tcp_send_dupack发送一个duplicate acknowledge(未设置RST标志位时)。
由rcv_wup的更新时机(发送ACK时的tcp_select_window)可知位于序号rcv_wup前面的数据都已确认,
所以待检查数据包的结束序号至少要大于该值;同时开始序号要落在接收窗口内 */
if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
/* RFC793, page 37: "In all states except SYN-SENT, all reset
* (RST) segments are validated by checking their SEQ-fields."
* And page 69: "If an incoming segment is not acceptable,
* an acknowledgment should be sent in reply (unless the RST
* bit is set, if so drop the segment and return)".
*/
if (!th->rst) {
if (th->syn)
goto syn_challenge;
if (!tcp_oow_rate_limited(sock_net(sk), skb,
LINUX_MIB_TCPACKSKIPPEDSEQ,
&tp->last_oow_ack_time))
tcp_send_dupack(sk, skb);
} else if (tcp_reset_check(sk, skb)) {
tcp_reset(sk);
}
goto discard;
} /* Step 2: check RST bit 如果设置了RST,则调用tcp_reset处理*/
if (th->rst) {
/* RFC 5961 3.2 (extend to match against (RCV.NXT - 1) after a
* FIN and SACK too if available):
* If seq num matches RCV.NXT or (RCV.NXT - 1) after a FIN, or
* the right-most SACK block,
* then
* RESET the connection
* else
* Send a challenge ACK
*/
if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt ||
tcp_reset_check(sk, skb)) {
rst_seq_match = true;
} else if (tcp_is_sack(tp) && tp->rx_opt.num_sacks > 0) {
struct tcp_sack_block *sp = &tp->selective_acks[0];
int max_sack = sp[0].end_seq;
int this_sack; for (this_sack = 1; this_sack < tp->rx_opt.num_sacks;
++this_sack) {
max_sack = after(sp[this_sack].end_seq,
max_sack) ?
sp[this_sack].end_seq : max_sack;
} if (TCP_SKB_CB(skb)->seq == max_sack)
rst_seq_match = true;
} if (rst_seq_match)
tcp_reset(sk);
else {
/* Disable TFO if RST is out-of-order
* and no data has been received
* for current active TFO socket
*/
if (tp->syn_fastopen && !tp->data_segs_in &&
sk->sk_state == TCP_ESTABLISHED)
tcp_fastopen_active_disable(sk);
tcp_send_challenge_ack(sk, skb);
}
goto discard;
} /* step 3: check security and precedence [ignored] */ /* step 4: Check for a SYN
* RFC 5961 4.2 : Send a challenge ack
检查SYN,因为重发的SYN和原来的SYN之间不会发送数据,所以这2个SYN的序号是相同的
*/
if (th->syn) {
syn_challenge:
if (syn_inerr)
TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS);
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPSYNCHALLENGE);
tcp_send_challenge_ack(sk, skb);
goto discard;
} return true; discard:
tcp_drop(sk, skb);
return false;
}

从上述分析过程中可知:

 /* 进程上下文 */
if (tp->ucopy.task == current &&
/* 期待读取的和期待接收的序号一致也就是
正在接收的段序号 和尚未从内核空间复制到用户空间的段最前的序号相等*/
tp->copied_seq == tp->rcv_nxt &&
len - tcp_header_len <= tp->ucopy.len && /* 数据<= 待读取长度(小于用户空间缓存) */
/* 控制块被用户空间锁定 */
sock_owned_by_user(sk)) {//此时用户进程正在recv 从内核获取数据 (用户进程正在休眠)

除了 recvmsg系统调用接收数据外,还有主动将数据从内核空间copy 到用户空间,注意:复制时 不应该将tcp 首部复制到用户空间

tcp ESTABLISHED 接收数据的更多相关文章

  1. 网络编程.TCP分块接收数据(AIO)(IOCP)

    1.前提:每次投递的接收缓冲区 在它返回后 就不再用它进行2次投递了 于是 接收缓冲区 在返回的时候,数据都是 从接收缓冲区的偏移[0]处开始填充的,且 接收缓冲区 可能被填满 也可能未被填满. 1. ...

  2. Java基础知识强化之网络编程笔记06:TCP之TCP协议发送数据 和 接收数据

    1. TCP协议发送数据 和 接收数据 TCP协议接收数据:• 创建接收端的Socket对象• 监听客户端连接.返回一个对应的Socket对象• 获取输入流,读取数据显示在控制台• 释放资源 TCP协 ...

  3. 关于原子哥ENC28J60网络通信模块接收数据代码的一点疑惑

    ---恢复内容开始--- 这几天做STM32的ENC28J60网络通信模块,自己在原子哥的代码上进行修改测试,,发现一个问题,电脑和板子进行通信的时候总隔一段时间板子就死机了. 使用自己的就不会死机, ...

  4. java-TCP协议发送和接收数据

    TCP协议接收数据的步骤: A:创建接收数据的Socket对象 创建对象的时候要指定端口 B:监听客户端连接 等待客户端连接 C:获取Socket对象的输入流(字节流) D:读数据,并显示在控制台 E ...

  5. 真的懂了:TCP协议中的三次握手和四次挥手(关闭连接时, 当收到对方的FIN报文时, 仅仅表示对方不在发送数据了, 但是还能接收数据, 己方也未必全部数据都发送对方了。相当于一开始还没接上话不要紧,后来接上话以后得让人把话讲完)

    一.TCP报文格式 下面是TCP报文格式图: (1) 序号, Seq(Sequence number), 占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记. (2) 确 ...

  6. 网络编程--使用TCP协议发送接收数据

    package com.zhangxueliang.tcp; import java.io.IOException; import java.io.OutputStream; import java. ...

  7. Indy10 Tcp接收数据问题

    在做Delphi开发时,使用Indy组件来做网络通讯是一种比较快捷的方式.今天要说一下indy10中tcp接收数据的问题. 我们在测试时经常使用Wrinteln来发送数据,用Readln来接收数据.用 ...

  8. tcp输入数据 慢速路径处理 && oob数据 接收 && 数据接收 tcp_data_queue

    大致的处理过程 TCP的接收流程:在tcp_v4_do_rcv中的相关处理(网卡收到报文触发)中,会首先通过tcp_check_urg设置tcp_sock的urg_data为TCP_URG_NOTYE ...

  9. TCP程序中发送和接收数据

    这里我们来探讨一下在网络编程过程中,有关read/write 或者send/recv的使用细节.这里有关常用的阻塞/非阻塞的解释在网上有很多很好的例子,这里就不说了,还有errno ==EAGAIN ...

随机推荐

  1. Python数据类型---数值类型

    一.整数(Integer)简称Int,又称整型,由正整数.负整数.0构成,不包括小数,分数. a,b=1,2 #一种赋值方法,此时a=1,b=2 print(a+b) #加法 3 print(a-b) ...

  2. lumen-ioc容器测试 (3)

    lumen-ioc容器测试 (1) lumen-ioc容器测试 (2) lumen-ioc容器测试 (3) lumen-ioc容器测试 (4) lumen-ioc容器测试 (5) lumen-ioc容 ...

  3. centos平台scp通过密钥远程复制文件(免密登录)

    一,说明:两台机器的平台和ip 1,a服务器: centos8:ip:121.122.123.47 版本 [root@yjweb ~]# cat /etc/redhat-release CentOS ...

  4. Java9系列第7篇:Java.util.Optional优化与增强

    我计划在后续的一段时间内,写一系列关于java 9的文章,虽然java 9 不像Java 8或者Java 11那样的核心java版本,但是还是有很多的特性值得关注.期待您能关注我,我将把java 9 ...

  5. CentOS 8 关闭防火墙

    SELINUX=disabled vim /etc/selinux/config systemctl disable firewalld.service

  6. C#Socket通讯(2)

    前言 前面已经把游戏的服务端UI搭起来来了,现在需要实现的就是编写服务端控制器与客户端的代码,实现服务端与客户端的数据传输,并将传输情况显示在服务端的UI上 服务端控制器完整代码 private st ...

  7. django—模型相关

    配置数据库信息 1.settings配置 DATABASES = {     "default": {         "ENGINE": "djan ...

  8. python识别视频黑屏或者低清晰度

    第一步:获取视频第一帧图片 https://www.cnblogs.com/pythonywy/p/13749735.html 第二步:进行识别 import os import numpy as n ...

  9. mysql 架构简介

    mysql的逻辑架构 第一层:进行连接处理.权限认证.安全校验等. 当客户端(应用)连接到mysql服务器时,服务器会创建使用一个线程进行处理连接(少量的线程服务大量的连接),随后服务器需要对该连接进 ...

  10. thinkphp数组给js赋值 tp模板把数组赋值给js变量

    var arr=transArr({$array|json_encode=true}); function transArr(obj) { var tem=[]; $.each(obj, functi ...