kk Blog —— 通用基础

date [-d @int|str] [+%s|"+%F %T"]

CentOS各种设置

1
2
lsattr /etc/passwd /etc/group /etc/shadow /etc/gshadow
chattr -i /etc/passwd /etc/group /etc/shadow /etc/gshadow

英文:E437: terminal capability “cm” required
中文:e437 终端需要能力 cm

这个错误一般是环境变量TERM没有配置或者配置错误所致, 用 export TERM=linux 或者 export TERM=xterm


上传是用rz -be,并且去掉弹出的对话框中“Upload files as ASCII”前的勾选。 -a, –ascii -b, –binary 用binary的方式上传下载,不解释字符为ascii -e, –escape 强制escape 所有控制字符,比如Ctrl+x,DEL等


nginx

http://nginx.org/packages/rhel/7/x86_64/RPMS/


binkernel.spec

1
2
3
4
5
6
7
8
9
10
11
12
%pre
mkdir -p /usr/local/kernel/etc/
echo "version=%{version}-%{release}" > /usr/local/kernel/etc/install.conf

%post
/sbin/new-kernel-pkg --package kernel --mkinitrd --depmod --install 2.6.32-358.6.1.ws5.b.5.1.11t25

%preun
rm -rf /usr/local/kernel/

%postun
/sbin/new-kernel-pkg  --remove 2.6.32-358.6.1.ws5.b.5.1.11t25

更改 bash_history 默认历史记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
vim ~/.bashrc

# 忽略[连续]重复命令
HISTCONTROL=ignoredups

# 清除重复命令
# HISTCONTROL=erasedups

# 忽略特定命令
HISTIGNORE="[   ]*:ls:ll:cd:vi:pwd:sync:exit:history*"

# 命令历史文件大小10M
HISTFILESIZE=1000000000

# 保存历史命令条数10W
HISTSIZE=1000000

以上配置可以通过 set | grep HIST 查看可选项.


多终端追加
当打开多个终端,关闭其中一个终端时会覆盖其他终端的命令历史,这里我们采用追加的方式避免命令历史文件.bash_history 文件被覆盖。

shopt -s histappend

更多 shopt 可选项可以通过 echo $SHELLOPTS 命令查看。

关闭CentOS6启动进度条,显示详细自检信息。vim /boot/grub/grub.conf,将"rhgb"和 “quiet"去掉,保存即可


vmware虚拟机mkinitrd提示no module ehci-hcd 错误的话,加:

1
--builtin=ehci-hcd --builtin=ohci-hcd --builtin=uhci-hcd

CentOS6.0 下默认开selinux时出现httpd 报“SELinux policy enabled; httpd running as context unconfined_u:system”的解决方案

1
2
3
4
5
yum install policycoreutils-python

# To allow httpd to use nfs dirs in CentOS-6
setsebool -P httpd_use_nfs 1
setsebool -P httpd_enable_homedirs 1

CentOS 关闭防火墙

1) 永久性生效,重启后不会复原
开启:

1
2
chkconfig iptables on
chkconfig ip6tables on

关闭:

1
2
chkconfig iptables off
chkconfig ip6tables off

2) 即时生效,重启后复原
开启:

1
2
service iptables start
service ip6tables start

关闭:

1
2
service iptables stop
service ip6tables stop

CentOS安装软件:/lib/ld-linux.so.2: bad ELF interpreter 解决

是因为64位系统中安装了32位程序, 解决方法:

1
yum install glibc.i686

其他包

1
yum install libstdc++.i686

gcc, c++

1
2
3
4
yum install glibc
yum install glibc-devel
yum install gcc-c++
yum install libstdc++

tc模拟丢包率时延

tc 的最最基本的使用

1
2
3
4
tc qdisc show    # 显示
tc qdisc add dev eth0 root ...... # 加入
tc qdisc change dev eth0 root ...... # 修改存在的 qdisc ,记的,加入同一条后只能用 change 来修改
tc qdisc del dev eth0 root  # 删除

Linux 中延时模拟

设置延时 3s :

1
tc qdisc add dev eth0 root netem delay 3000ms

可以在 3000ms 后面在加上一个延时,比如 3000ms 200ms 表示 3000ms ± 200ms ,延时范围 2800 – 3200 之间.

Linux 中丢包模拟

设置丢包 50% ,iptables 也可以模拟这个,但一下不记的命令了,下次放上来:

1
tc qdisc change dev eth0 root netem loss 50%

上面的设丢包,如果给后面的 50% 的丢包比率修改成 50% 80% 时,这时和上面的延时不一样,这是指丢包比率为 50-80% 之间。

Linux TCP发送数据tcp_write_xmit

http://blog.csdn.net/youxin2012/article/details/27175253

__tcp_push_pending_frames 该函数将所有pending的数据,全部发送出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss,
			 int nonagle)
{
	/* If we are closed, the bytes will have to remain here.
	 * In time closedown will finish, we empty the write queue and
	 * all will be happy.
	 */
	/* 该socket已经关闭,那么直接返回 */
	if (unlikely(sk->sk_state == TCP_CLOSE))
		return;

	/* 发送数据 */
	if (tcp_write_xmit(sk, cur_mss, nonagle, 0, GFP_ATOMIC))
		tcp_check_probe_timer(sk); //发送数据失败,使用probe timer进行检查。
}

发送端 tcp_write_xmit 函数

版本:2.6.33.4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/* This routine writes packets to the network.  It advances the
 * send_head.  This happens as incoming acks open up the remote
 * window for us.
 *
 * LARGESEND note: !tcp_urg_mode is overkill, only frames between
 * snd_up-64k-mss .. snd_up cannot be large. However, taking into
 * account rare use of URG, this is not a big flaw.
 *
 * Returns 1, if no segments are in flight and we have queued segments, but
 * cannot send anything now because of SWS or another problem.
 */
static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
			  int push_one, gfp_t gfp)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb;
	unsigned int tso_segs, sent_pkts;
	int cwnd_quota;
	int result;

	/* sent_pkts用来统计函数中已发送报文总数。*/
	sent_pkts = 0;

	/* 检查是不是只发送一个skb buffer,即push one */
	if (!push_one) {
		/* 如果要发送多个skb,则需要检测MTU。
		 * 这时会检测MTU,希望MTU可以比之前的大,提高发送效率。
		 */
		/* Do MTU probing. */
		result = tcp_mtu_probe(sk);
		if (!result) {
			return 0;
		} else if (result > 0) {
			sent_pkts = 1;
		}
	}

	while ((skb = tcp_send_head(sk))) {
		unsigned int limit;

		/* 设置有关TSO的信息,包括GSO类型,GSO分段的大小等等。
		 * 这些信息是准备给软件TSO分段使用的。
		 * 如果网络设备不支持TSO,但又使用了TSO功能,
		 * 则报文在提交给网络设备之前,需进行软分段,即由代码实现TSO分段。
		 */
		tso_segs = tcp_init_tso_segs(sk, skb, mss_now);
		BUG_ON(!tso_segs);

		/* 检查congestion windows, 可以发送几个segment */
		/* 检测拥塞窗口的大小,如果为0,则说明拥塞窗口已满,目前不能发送。
		 * 拿拥塞窗口和正在网络上传输的包数目相比,如果拥塞窗口还大,
		 * 则返回拥塞窗口减掉正在网络上传输的包数目剩下的大小。
		 * 该函数目的是判断正在网络上传输的包数目是否超过拥塞窗口,
		 * 如果超过了,则不发送。
		 */
		cwnd_quota = tcp_cwnd_test(tp, skb);
		if (!cwnd_quota)
			break;

		/* 检测当前报文是否完全处于发送窗口内,如果是则可以发送,否则不能发送 */
		if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)))
			break;

		/* tso_segs=1表示无需tso分段 */
		if (tso_segs == 1) {
			/* 根据nagle算法,计算是否需要发送数据 */
			if (unlikely(!tcp_nagle_test(tp, skb, mss_now,
							 (tcp_skb_is_last(sk, skb) ?
							  nonagle : TCP_NAGLE_PUSH))))
				break;
		} else {
			/* 当不止一个skb时,通过TSO计算是否需要延时发送 */
			/* 如果需要TSO分段,则检测该报文是否应该延时发送。
		   * tcp_tso_should_defer()用来检测GSO段是否需要延时发送。
			 * 在段中有FIN标志,或者不处于open拥塞状态,或者TSO段延时超过2个时钟滴答,
			 * 或者拥塞窗口和发送窗口的最小值大于64K或三倍的当前有效MSS,在这些情况下会立即发送,
			 * 而其他情况下会延时发送,这样主要是为了减少软GSO分段的次数,以提高性能。
			 */
			if (!push_one && tcp_tso_should_defer(sk, skb))
				break;
		}

		limit = mss_now;
		/* 在TSO分片大于1的情况下,且TCP不是URG模式。通过MSS计算发送数据的limit
		 * 以发送窗口和拥塞窗口的最小值作为分段段长*/
		 */
		if (tso_segs > 1 && !tcp_urg_mode(tp))
			limit = tcp_mss_split_point(sk, skb, mss_now,
							cwnd_quota);
		/* 当skb的长度大于限制时,需要调用tso_fragment分片,如果分段失败则暂不发送 */
		if (skb->len > limit &&
			unlikely(tso_fragment(sk, skb, limit, mss_now)))
			break;

		/* 以上6行:根据条件,可能需要对SKB中的报文进行分段处理,分段的报文包括两种:
		 * 一种是普通的用MSS分段的报文,另一种则是TSO分段的报文。
		 * 能否发送报文主要取决于两个条件:一是报文需完全在发送窗口中,而是拥塞窗口未满。
		 * 第一种报文,应该不会再分段了,因为在tcp_sendmsg()中创建报文的SKB时已经根据MSS处理了,
		 * 而第二种报文,则一般情况下都会大于MSS,因为通过TSO分段的段有可能大于拥塞窗口的剩余空间,
		 * 如果是这样,就需要以发送窗口和拥塞窗口的最小值作为段长对报文再次分段。
		 */

		/* 更新tcp的时间戳,记录此报文发送的时间 */
		TCP_SKB_CB(skb)->when = tcp_time_stamp;

		if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
			break;

		/* Advance the send_head.  This one is sent out.
		 * This call will increment packets_out.
		 */
		/* 更新统计,并启动重传计时器 */
		/* 调用tcp_event_new_data_sent()-->tcp_advance_send_head()更新sk_send_head,
		 * 即取发送队列中的下一个SKB。同时更新snd_nxt,即等待发送的下一个TCP段的序号,
		 * 然后统计发出但未得到确认的数据报个数。最后如果发送该报文前没有需要确认的报文,
		 * 则复位重传定时器,对本次发送的报文做重传超时计时。
		 */
		tcp_event_new_data_sent(sk, skb);

		/* 更新struct tcp_sock中的snd_sml字段。snd_sml表示最近发送的小包(小于MSS的段)的最后一个字节序号,
		 * 在发送成功后,如果报文小于MSS,即更新该字段,主要用来判断是否启动nagle算法
		 */
		tcp_minshall_update(tp, mss_now, skb);
		sent_pkts++;

		if (push_one)
			break;
	}
	/* 如果本次有数据发送,则对TCP拥塞窗口进行检查确认。*/
	if (likely(sent_pkts)) {
		tcp_cwnd_validate(sk);
		return 0;
	}
	/*
	 * 如果本次没有数据发送,则根据已发送但未确认的报文数packets_out和sk_send_head返回,
	 * packets_out不为零或sk_send_head为空都视为有数据发出,因此返回成功。
	 */
	return !tp->packets_out && tcp_send_head(sk);
}

Linux TCP数据包接收处理tcp_data_queue

http://www.cppblog.com/fwxjj/archive/2013/02/18/197906.aspx

tcp_data_queue函数

这里就是对数据包的处理了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
{
	struct tcphdr *th = tcp_hdr(skb);
	struct tcp_sock *tp = tcp_sk(sk);
	int eaten = -1;
	/* 没有数据处理*/
	if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq)
		goto drop;
	/* 跳过tcp头部*/
	__skb_pull(skb, th->doff * 4);
	/* 如果收到对方发来的CWR,则本地TCP发送时不在设置ECE*/
	TCP_ECN_accept_cwr(tp, skb);
	/* 初始化Duplicate SACK*/
	if (tp->rx_opt.dsack) {
		tp->rx_opt.dsack = 0;
		tp->rx_opt.eff_sacks = tp->rx_opt.num_sacks;
	}

如果该数据包刚好是下一个要接收的数据,则可以直接copy到用户空间(如果存在且可用),否则排队到receive queue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/*  Queue data for delivery to the user.
 *  Packets in sequence go to the receive queue.
 *  Out of sequence packets to the out_of_order_queue.
 */
if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {
	if (tcp_receive_window(tp) == 0)
		goto out_of_window;

	/* Ok. In sequence. In window. */
	if (tp->ucopy.task == current &&
		tp->copied_seq == tp->rcv_nxt && tp->ucopy.len &&
		sock_owned_by_user(sk) && !tp->urg_data) {
		int chunk = min_t(unsigned int, skb->len,
				  tp->ucopy.len);

		// tcp_v4_rcv_do是有可能在tcp_recvmsg的进程上下文中调用的,tcp_recvmsg会先local_bh_disable,在调用
		// 如过到这里,说明就是这种情况,那么复制数据到用户空间,这里可以先开启软中断,保证系统能及时相应一些其他中断
		__set_current_state(TASK_RUNNING);
		local_bh_enable();
		if (!skb_copy_datagram_iovec(skb, 0, tp->ucopy.iov, chunk)) {
			tp->ucopy.len -= chunk;
			tp->copied_seq += chunk;
			eaten = (chunk == skb->len && !th->fin);
			tcp_rcv_space_adjust(sk);
		}
		local_bh_disable();
	}

	if (eaten <= 0) {
ueue_and_out:
		if (eaten < 0 &&
			/* 该函数用于判断是否有接收缓存,在tcp内存管理中将分析*/
			tcp_try_rmem_schedule(sk, skb->truesize))
			goto drop;

		skb_set_owner_r(skb, sk);
		__skb_queue_tail(&sk->sk_receive_queue, skb);
	}
	tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
	if (skb->len)
		tcp_event_data_recv(sk, skb);
	if (th->fin)
		tcp_fin(skb, sk, th);
	/* 到达的数据包哟可能填充了乱序队列中的hole */
	if (!skb_queue_empty(&tp->out_of_order_queue)) {
		tcp_ofo_queue(sk);

		/* RFC2581. 4.2. SHOULD send immediate ACK, when
		 * gap in queue is filled.
		 */
		/*关闭乒乓模式,在quick计数没消耗完时则可立即发送ACK,见tcp_in_quickack_mode*/
		if (skb_queue_empty(&tp->out_of_order_queue))
			inet_csk(sk)->icsk_ack.pingpong = 0;
	}
	/* 新数据到达导致返回给对方的SACK Block 调整*/
	if (tp->rx_opt.num_sacks)
		tcp_sack_remove(tp);
	/* 在当前slow path,检测是否可以进入fast path*/
	tcp_fast_path_check(sk);

	if (eaten > 0)
		__kfree_skb(skb);
	else if (!sock_flag(sk, SOCK_DEAD))
		sk->sk_data_ready(sk, 0);
	return;
}

下面看看函数tcp_ofo_queue,也即out-of-order queue的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/* This one checks to see if we can put data from the
 * out_of_order queue into the receive_queue.
 */
static void tcp_ofo_queue(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	__u32 dsack_high = tp->rcv_nxt;
	struct sk_buff *skb;

	while ((skb = skb_peek(&tp->out_of_order_queue)) != NULL) {
		/* 当前hole未覆盖,则处理结束*/
		if (after(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))
			break;
		/* DSACK处理*/
		if (before(TCP_SKB_CB(skb)->seq, dsack_high)) {
			__u32 dsack = dsack_high;
			if (before(TCP_SKB_CB(skb)->end_seq, dsack_high))
				dsack_high = TCP_SKB_CB(skb)->end_seq;
			tcp_dsack_extend(sk, TCP_SKB_CB(skb)->seq, dsack);
		}
		/* 该乱序数据包完全被到达的数据包覆盖,则从乱序队列中删除之,并释放该数据包*/
		if (!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt)) {
			SOCK_DEBUG(sk, "ofo packet was already received /n");
			__skb_unlink(skb, &tp->out_of_order_queue);
			__kfree_skb(skb);
			continue;
		}
		SOCK_DEBUG(sk, "ofo requeuing : rcv_next %X seq %X - %X/n",
			   tp->rcv_nxt, TCP_SKB_CB(skb)->seq,
			   TCP_SKB_CB(skb)->end_seq);
		/* hole被填充,取出该乱序数据包到receive queue中排队,并更新rcv_nxt */
		__skb_unlink(skb, &tp->out_of_order_queue);
		__skb_queue_tail(&sk->sk_receive_queue, skb);
		tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
		if (tcp_hdr(skb)->fin)
			tcp_fin(skb, sk, tcp_hdr(skb));
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* 该数据包的数据已经完全存在,则发送DSACK,并进入快速ACK模式,调度ACK发送*/
if (!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt)) {
		/* A retransmit, 2nd most common case.  Force an immediate ack. */
		NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_DELAYEDACKLOST);
		tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq);

out_of_window:
		tcp_enter_quickack_mode(sk);
		inet_csk_schedule_ack(sk);
drop:
		__kfree_skb(skb);
		return;
	}

	/* Out of window. F.e. zero window probe. */
	if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt + tcp_receive_window(tp)))
		goto out_of_window;

	tcp_enter_quickack_mode(sk);
	/* 部分数据已存在,则设置正确的DSACK,然后排队到receive queue*/
	if (before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
		/* Partial packet, seq < rcv_next < end_seq */
		SOCK_DEBUG(sk, "partial packet: rcv_next %X seq %X - %X/n",
			   tp->rcv_nxt, TCP_SKB_CB(skb)->seq,
			   TCP_SKB_CB(skb)->end_seq);

		tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, tp->rcv_nxt);

		/* If window is closed, drop tail of packet. But after
		 * remembering D-SACK for its head made in previous line.
		 */
		if (!tcp_receive_window(tp))
			goto out_of_window;
		goto queue_and_out;
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
	TCP_ECN_check_ce(tp, skb); /* 检查ECE是否设置 */
	/* 以下则把数据包排队到乱序队列 */
	/* 同样先判断内存是否满足 */
	if (tcp_try_rmem_schedule(sk, skb->truesize))
		goto drop;

	/* Disable header prediction. */
	tp->pred_flags = 0;
	inet_csk_schedule_ack(sk);

	SOCK_DEBUG(sk, "out of order segment: rcv_next %X seq %X - %X/n",
		   tp->rcv_nxt, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq);

	skb_set_owner_r(skb, sk);
	/* 该数据包是乱序队列的第一个数据包*/
	if (!skb_peek(&tp->out_of_order_queue)) {
		/* Initial out of order segment, build 1 SACK. */
		if (tcp_is_sack(tp)) {
			tp->rx_opt.num_sacks = 1;
			tp->rx_opt.dsack     = 0;
			tp->rx_opt.eff_sacks = 1;
			tp->selective_acks[0].start_seq = TCP_SKB_CB(skb)->seq;
			tp->selective_acks[0].end_seq =
						TCP_SKB_CB(skb)->end_seq;
		}
		__skb_queue_head(&tp->out_of_order_queue, skb);
	} else {
		struct sk_buff *skb1 = tp->out_of_order_queue.prev;
		u32 seq = TCP_SKB_CB(skb)->seq;
		u32 end_seq = TCP_SKB_CB(skb)->end_seq;
		/*刚好与乱序队列最后一个数据包数据衔接*/
		if (seq == TCP_SKB_CB(skb1)->end_seq) {
			__skb_queue_after(&tp->out_of_order_queue, skb1, skb);
			/*如果没有sack block或者当前数据包开始序号不等于第一个block右边界*/
			if (!tp->rx_opt.num_sacks ||
				tp->selective_acks[0].end_seq != seq)
				goto add_sack;
			/*该数据包在某个hole后是按序到达的,所以可以直接扩展第一个sack*/
			/* Common case: data arrive in order after hole. */
			tp->selective_acks[0].end_seq = end_seq;
			return;
		}

		/* Find place to insert this segment. */
		/* 该循环找到一个开始序号不大于该数据包开始序号的乱序队列中的数据包*/
		do {
			if (!after(TCP_SKB_CB(skb1)->seq, seq))
				break;
		} while ((skb1 = skb1->prev) !=
			 (struct sk_buff *)&tp->out_of_order_queue);

		/* Do skb overlap to previous one? 检查与前个数据包是否有重叠*/
		if (skb1 != (struct sk_buff *)&tp->out_of_order_queue &&
			before(seq, TCP_SKB_CB(skb1)->end_seq)) {
			if (!after(end_seq, TCP_SKB_CB(skb1)->end_seq)) {
				/* All the bits are present. Drop. */
				__kfree_skb(skb);
				tcp_dsack_set(sk, seq, end_seq);
				goto add_sack;
			}
			if (after(seq, TCP_SKB_CB(skb1)->seq)) {
				/* Partial overlap. */
				tcp_dsack_set(sk, seq,
						  TCP_SKB_CB(skb1)->end_seq);
			} else {
				skb1 = skb1->prev;
			}
		}
		/* 排队到乱序队列*/
		__skb_queue_after(&tp->out_of_order_queue, skb1, skb);

		/* And clean segments covered by new one as whole. 检测与后面的数据包重叠*/
		while ((skb1 = skb->next) !=
			   (struct sk_buff *)&tp->out_of_order_queue &&
			   after(end_seq, TCP_SKB_CB(skb1)->seq)) {
			if (before(end_seq, TCP_SKB_CB(skb1)->end_seq)) {
				tcp_dsack_extend(sk, TCP_SKB_CB(skb1)->seq,
						 end_seq);
				break;
			}
			__skb_unlink(skb1, &tp->out_of_order_queue);
			tcp_dsack_extend(sk, TCP_SKB_CB(skb1)->seq,
					 TCP_SKB_CB(skb1)->end_seq);
			__kfree_skb(skb1);
		}

add_sack:
		if (tcp_is_sack(tp))
			/* 根据乱序队列的现状更新SACK的blocks */
			tcp_sack_new_ofo_skb(sk, seq, end_seq);
	}
}

Linux TCP数据包接收处理tcp_rcv_established

http://www.cppblog.com/fwxjj/archive/2013/02/18/197906.aspx

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

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

1
2
3
4
5
6
static inline void __tcp_fast_path_on(struct tcp_sock *tp, u32 snd_wnd)
{
	tp->pred_flags = htonl((tp->tcp_header_len << 26) |
				   ntohl(TCP_FLAG_ACK) |
				   snd_wnd);
}

可以看出头部预测依赖的是头部长度字段和通告窗口。也就是说标志位除了ACK和PSH外,如果其他的存在的话,就不能用

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

1 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工作的条件
1
2
3
4
5
6
7
8
9
10
static inline void tcp_fast_path_check(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);

	if (skb_queue_empty(&tp->out_of_order_queue) &&
		tp->rcv_wnd &&
		atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf &&
		!tp->urg_data)
		tcp_fast_path_on(tp);
}

1 没有乱序数据包
2 接收窗口不为0
3 还有接收缓存空间
4 没有紧急数据

反之,则进入slow path处理;另外当连接新建立时处于slow path。

从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()中更新了通告窗口。

fast path处理流程

A 判断能否进入fast path

1
2
if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&
		TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {

TCP_HP_BITS的作用就是排除flag中的PSH标志位。只有在头部预测满足并且数据包以正确的顺序(该数据包的第一个序号就是下个要接收的序号)到达时才进入fast path。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int tcp_header_len = tp->tcp_header_len;

/* Timestamp header prediction: tcp_header_len
 * is automatically equal to th->doff*4 due to pred_flags
 * match.
 */

/* Check timestamp */
//相等说明tcp timestamp option被打开。
if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {
	/* No? Slow path! */
	//这里主要是parse timestamp选项,如果返回0则表明pase出错,此时我们进入slow_path
	if (!tcp_parse_aligned_timestamp(tp, th))
		goto slow_path;

	/* If PAWS failed, check it more carefully in slow path */
	//如果上面pase成功,则tp对应的rx_opt域已经被正确赋值,此时如果rcv_tsval(新的接收的数据段的时间戳)比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.
	 */
}

该代码段是依据时戳选项来检查PAWS(Protect Against Wrapped Sequence numbers)。 如果发送来的仅是一个TCP头的话(没有捎带数据或者接收端检测到有乱序数据这些情况时都会发送一个纯粹的ACK包)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* Bulk data transfer: sender */
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:
	 */
	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.
	 */
	tcp_ack(sk, skb, 0);
	__kfree_skb(skb);
	tcp_data_snd_check(sk);
	return 0;
} else { /* Header too small */
	TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
	goto discard;
}

主要的工作如下:
1 保存对方的最近时戳 tcp_store_ts_recent。通过前面的if判断可以看出tcp总是回显2次时戳回显直接最先到达的数据包的时戳,
rcv_wup只在发送数据(这时回显时戳)时重置为rcv_nxt,所以接收到前一次回显后第一个数据包后,rcv_nxt增加了,但是
rcv_wup没有更新,所以后面的数据包处理时不会调用该函数来保存时戳。
2 ACK处理。这个函数非常复杂,包含了拥塞控制机制,确认处理等等。
3 检查是否有数据待发送 tcp_data_snd_check。

如果该数据包中包含了数据的话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
		} else {
			int eaten = 0;
			int copied_early = 0;
			/* 此数据包刚好是下一个读取的数据,并且用户空间可存放下该数据包*/
			if (tp->copied_seq == tp->rcv_nxt &&
				len - tcp_header_len <= tp->ucopy.len) {
#ifdef CONFIG_NET_DMA
				if (tcp_dma_try_early_copy(sk, skb, tcp_header_len)) {
					copied_early = 1;
					eaten = 1;
				}
#endif          /* 如果该函数在进程上下文中调用并且sock被用户占用的话*/
				if (tp->ucopy.task == current &&
					sock_owned_by_user(sk) && !copied_early) {
					/* 进程有可能被设置为TASK_INTERRUPTIBLE */
					__set_current_state(TASK_RUNNING);
					/* 直接copy数据到用户空间*/
					if (!tcp_copy_to_iovec(sk, skb, tcp_header_len))
						eaten = 1;
				}
				if (eaten) {
					/* 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);
					/* 更新RCV RTT,Dynamic Right-Sizing算法*/
					tcp_rcv_rtt_measure_ts(sk, skb);

					__skb_pull(skb, tcp_header_len);
					tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
					NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITSTOUSER);
				}
				if (copied_early)
					tcp_cleanup_rbuf(sk, skb->len);
			}
			if (!eaten) { /* 没有直接读到用户空间*/
				if (tcp_checksum_complete_user(sk, skb))
					goto csum_error;

				/* 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);

				if ((int)skb->truesize > sk->sk_forward_alloc)
					goto step5;

				NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITS);

				/* Bulk data transfer: receiver */
				__skb_pull(skb, tcp_header_len);
				/* 进入receive queue 排队,以待tcp_recvmsg读取*/
				__skb_queue_tail(&sk->sk_receive_queue, skb);
				skb_set_owner_r(skb, sk);
				tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
			}
			/* 数据包接收后续处理*/
			tcp_event_data_recv(sk, skb);
			/* ACK 处理*/
			if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {
				/* Well, only one small jumplet in fast path... */
				tcp_ack(sk, skb, FLAG_DATA);
				tcp_data_snd_check(sk);
				if (!inet_csk_ack_scheduled(sk))
					goto no_ack;
			}
			/* ACK发送处理*/
			if (!copied_early || tp->rcv_nxt != tp->rcv_wup)
				__tcp_ack_snd_check(sk, 0);
no_ack:
#ifdef CONFIG_NET_DMA
			if (copied_early)
				__skb_queue_tail(&sk->sk_async_wait_queue, skb);
			else
#endif
			/* eaten为1,表示数据直接copy到了用户空间,这时无需提醒用户进程数据的到达,否则需调用sk_data_ready来通知,因为此时数据到达了receive queue*/
			if (eaten)
				__kfree_skb(skb);
			else
				sk->sk_data_ready(sk, 0);
			return 0;
		}

tcp_event_data_recv函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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;
	/* 接收到了数据,设置ACK需调度标志*/
	inet_csk_schedule_ack(sk);

	tcp_measure_rcv_mss(sk, skb);

	tcp_rcv_rtt_measure(tp);

	now = tcp_time_stamp;
	/* 以下为根据接收间隔更新icsk_ack.ato,该值主要用于判断pingpong模式见函数tcp_event_data_sent */
	if (!icsk->icsk_ack.ato) {
		/* The _first_ data packet received, initialize
		 * delayed ACK engine.
		 */
		tcp_incr_quickack(sk);
		icsk->icsk_ack.ato = TCP_ATO_MIN;
	} else {
		int m = now - icsk->icsk_ack.lrcvtime;

		if (m <= TCP_ATO_MIN / 2) {
			/* The fastest case is the first. */
			icsk->icsk_ack.ato = (icsk->icsk_ack.ato >> 1) + TCP_ATO_MIN / 2;
		} else if (m < icsk->icsk_ack.ato) {
			icsk->icsk_ack.ato = (icsk->icsk_ack.ato >> 1) + m;
			if (icsk->icsk_ack.ato > icsk->icsk_rto)
				icsk->icsk_ack.ato = icsk->icsk_rto;
		} else if (m > icsk->icsk_rto) {
			/* Too long gap. Apparently sender failed to
			 * restart window, so that we send ACKs quickly.
			 */
			tcp_incr_quickack(sk);
			sk_mem_reclaim(sk);
		}
	}
	icsk->icsk_ack.lrcvtime = now;

	TCP_ECN_check_ce(tp, skb);
	/* 每次接收到来自对方的一个TCP数据报,且数据报长度大于128字节时,我们需要调用tcp_grow_window,增加rcv_ssthresh的值,一般每次为rcv_ssthresh增长两倍的mss,增加的条件是rcv_ssthresh小于window_clamp,并且 rcv_ssthresh小于接收缓存剩余空间的3/4,同时tcp_memory_pressure没有被置位(即接收缓存中的数据量没有太大)。 tcp_grow_window中对新收到的skb的长度还有一些限制,并不总是增长rcv_ssthresh的值*/
	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的值。两者配合,达到一个滑动窗口大小缓慢增长的效果。

__tcp_ack_snd_check用来判断ACK的发送方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
 * Check if sending an ack is needed.
 */
static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible)
{
	struct tcp_sock *tp = tcp_sk(sk);

	/* More than one full frame received... */
	if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss
		 /* ... and right edge of window advances far enough.
		  * (tcp_recvmsg() will send ACK otherwise). Or...
		  */
		 && __tcp_select_window(sk) >= tp->rcv_wnd) ||
		/* We ACK each frame or... */
		tcp_in_quickack_mode(sk) ||
		/* We have out of order data. */
		(ofo_possible && skb_peek(&tp->out_of_order_queue))) {
		/* Then ack it now */
		tcp_send_ack(sk);
	} else {
		/* Else, send delayed ack. */
		tcp_send_delayed_ack(sk);
	}
}

这里有个疑问,就是当ucopy应用读到需要读取到的数据包后,也即在一次处理中

1
2
if (tp->copied_seq == tp->rcv_nxt &&
		len - tcp_header_len <= tp->ucopy.len) {

的第二个条件的等号为真 len - tcp_header_len == tp->ucopy.len,然后执行流程到后面eaten为1,所以函数以释放skb结束,没有调用sk_data_ready函数。假设这个处理调用流程如下:
tcp_recvmsg-> sk_wait_data -> sk_wait_event -> release_sock -> __release_sock-> sk_backlog_rcv-> tcp_rcv_established那么即使此时用户得到了所需的数据,但是在tcp_rcv_established返回前没有提示数据已得到,

1
2
3
4
5
6
7
8
9
10
11
#define sk_wait_event(__sk, __timeo, __condition)       /
	({  int __rc;                                       /
		release_sock(__sk);                             /
		__rc = __condition;                             /
		if (!__rc) {                                    /
			*(__timeo) = schedule_timeout(*(__timeo));  /
		}                                               /
		lock_sock(__sk);                                /
		__rc = __condition;                             /
		__rc;                                           /
	})

但是在回到sk_wait_event后,由于__condition为 !skb_queue_empty(&sk->sk_receive_queue),所以还是会调用schedule_timeout来等待。这点显然是浪费时间,所以这个condition应该考虑下这个数据已经读满的情况,而不能光靠观察receive queue来判断是否等待。

接下来分析slow path

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
slow_path:
	if (len < (th->doff << 2) || tcp_checksum_complete_user(sk, skb))
		goto csum_error;

	/*
	 *  Standard slow path.
	 */
	/* 检查到达的数据包 */
	res = tcp_validate_incoming(sk, skb, th, 1);
	if (res <= 0)
		return -res;

step5:  /* 如果设置了ACK,则调用tcp_ack处理,后面再分析该函数*/
	if (th->ack)
		tcp_ack(sk, skb, FLAG_SLOWPATH);

	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);
	return 0;

先看看tcp_validate_incoming函数,在slow path处理前检查输入数据包的合法性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/* Does PAWS and seqno based validation of an incoming segment, flags will
 * play significant role here.
 */
static int tcp_validate_incoming(struct sock *sk, struct sk_buff *skb,
				  struct tcphdr *th, int syn_inerr)
{
	struct tcp_sock *tp = tcp_sk(sk);

	/* RFC1323: H1. Apply PAWS check first. */
	if (tcp_fast_parse_options(skb, th, tp) && tp->rx_opt.saw_tstamp &&
		tcp_paws_discard(sk, skb)) {
		if (!th->rst) {
			NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSESTABREJECTED);
			tcp_send_dupack(sk, skb);
			goto discard;
		}
		/* Reset is accepted even if it did not pass PAWS. */
	}

	/* Step 1: check sequence number */
	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)
			tcp_send_dupack(sk, skb);
		goto discard;
	}

	/* Step 2: check RST bit */
	if (th->rst) {
		tcp_reset(sk);
		goto discard;
	}

	/* ts_recent update must be made after we are sure that the packet
	 * is in window.
	 */
	tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq);

	/* step 3: check security and precedence [ignored] */

	/* step 4: Check for a SYN in window. */
	if (th->syn && !before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
		if (syn_inerr)
			TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
		NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONSYN);
		tcp_reset(sk);
		return -1;
	}

	return 1;

discard:
	__kfree_skb(skb);
	return 0;
}

第一步:检查PAWS tcp_paws_discard

1
2
3
4
5
6
7
8
static inline int tcp_paws_discard(const struct sock *sk,
				   const struct sk_buff *skb)
{
	const struct tcp_sock *tp = tcp_sk(sk);
	return ((s32)(tp->rx_opt.ts_recent - tp->rx_opt.rcv_tsval) > TCP_PAWS_WINDOW &&
		get_seconds() < tp->rx_opt.ts_recent_stamp + TCP_PAWS_24DAYS &&
		!tcp_disordered_ack(sk, skb));
}

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.

以下转载自CU论坛http://linux.chinaunix.net/bbs/viewthread.php?tid=1130308

在实际进行PAWS预防时,Linux是通过如下代码调用来完成的

1
2
3
4
5
tcp_rcv_established
  |
  |-->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方来都需要处理(立刻启动重发动作),而不能简单丢弃掉。


第2步 检查数据包的序号是否正确,该判断失败后调用tcp_send_dupack发送一个duplicate acknowledge(未设置RST标志位时)。

1
2
3
4
5
static inline int tcp_sequence(struct tcp_sock *tp, u32 seq, u32 end_seq)
{
	return  !before(end_seq, tp->rcv_wup) &&
		!after(seq, tp->rcv_nxt + tcp_receive_window(tp));
}

由rcv_wup的更新时机(发送ACK时的tcp_select_window)可知位于序号rcv_wup前面的数据都已确认,所以待检查数据包的结束序号至少要大于该值;同时开始序号要落在接收窗口内。

第3步 如果设置了RST,则调用tcp_reset处理

第4步 更新ts_recent,

第5步 检查SYN,因为重发的SYN和原来的SYN之间不会发送数据,所以这2个SYN的序号是相同的,如果不满足则reset连接。