kk Blog —— 通用基础

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

SSH端口转发 及 SSH代理

SSH端口转发

假设A、B为内网机,C为可登录公网机。那么A连B过程为:

假设 A、B、C 的ssh端口分别为portA、portB、portC。

1. 远程转发-R, 远程机转发到本地机

在被连的B上先运行如下命令

1
ssh -p portC -f -N -R 9000:localhost:portB userC@hostC-IP

这样到C机上9000端口的连接会被转发到B机的portB上。

2.本地转发-L, 本地机转发到远程机

在发起连接的A上运行如下命令

1
ssh -p portC -f -N -L 9999:localhost:9000 userC@hostC-IP

这样到A机9999端口的连接会被转发到C机的9000上。而C的9000又会被转发到B的portB上。
所以只要在A上运行:

1
ssh -p 9999 userB@127.0.0.1

就相当于ssh到了B机的portB上。

参数
1
2
3
4
f  表示后台用户验证,这个选项很有用,没有shell的不可登陆账号也能使用.
N 表示不执行脚本或命令
-L 本地转发
-R 远程转发
路由器设置省去C机

在路由器添加转发规则,端口为A机ssh监听端口,ip为A机内网IP。记下路由器公网IP。
然后只要在B机上直接连路由器公网IP+转发端口

1
ssh -p portA -f -N -R 9999:localhost:portB userA@routeA-IP

然后在A机上直接

1
ssh -p 9999 userB@127.0.0.1

A就能连上B

SSH代理–网站限制内网IP?代理一下就好了

1 远程机有公网IP

只要在本地运行

1
ssh -p port -qTfnN -D 7070 user@sshserver

然后在firefox的 首选项->高级->网络->设置 里面改成手动配置代理,只填"SOCKS"这一行即可。

2 若远程机为内网机

先按前面端口转发的方法,在本机映射一个到远程机的端口localport,然后命令改成

1
ssh -p localport -qTfnN -D 7070 user@127.0.0.1

这样firefox下要填127.0.0.1和7070

CentOS各种设置

使用gcc时,总是按中文提示。回归英文的提示,方法是: 首先使用env查看,发现LANGUAGE=zh_CN.UTF-8,接着执行export LANG=en_US.UTF-8就可以,以后的编译是就按英文来提示


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);
	}
}