kk Blog —— 通用基础

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

SYN cookies机制下连接的建立

http://blog.csdn.net/justlinux2010/article/details/12619761

在正常情况下,服务器端接收到客户端发送的SYN包,会分配一个连接请求块(即request_sock结构),用于保存连接请求信息,并且发送SYN+ACK包给客户端,然后将连接请求块添加到半连接队列中。客户端接收到SYN+ACK包后,会发送ACK包对服务器端的包进行确认。服务器端收到客户端的确认后,根据保存的连接信息,构建一个新的连接,放到监听套接字的连接队列中,等待用户层accept连接。这是正常的情况,但是在并发过高或者遭受SYN flood攻击的情况下,半连接队列的槽位数量很快就会耗尽,会导致丢弃新的连接请求,SYN cookies技术可以使服务器在半连接队列已满的情况下仍能处理新的SYN请求。

如果开启了SYN cookies选项,在半连接队列满时,SYN cookies并不丢弃SYN请求,而是将源目的IP、源目的端口号、接收到的客户端初始序列号以及其他一些安全数值等信息进行hash运算,并加密后得到服务器端的初始序列号,称之为cookie。服务器端在发送初始序列号为cookie的SYN+ACK包后,会将分配的连接请求块释放。如果接收到客户端的ACK包,服务器端将客户端的ACK序列号减1得到的值,与上述要素hash运算得到的值比较,如果相等,直接完成三次握手,构建新的连接。SYN cookies机制的核心就是避免攻击造成的大量构造无用的连接请求块,导致内存耗尽,而无法处理正常的连接请求。

启用SYN cookies是通过在启动环境中设置以下命令完成:

1
echo 1 > /proc/sys/net/ipv4/tcp_syncookies

注意,即使开启该机制并不意味着所有的连接都是用SYN cookies机制来完成连接的建立,只有在半连接队列已满的情况下才会触发SYN cookies机制。由于SYN cookies机制严重违背TCP协议,不允许使用TCP扩展,可能对某些服务造成严重的性能影响(如SMTP转发),对于防御SYN flood攻击的确有效。对于没有收到攻击的高负载服务器,不要开启此选项,可以通过修改tcp_max_syn_backlog、tcp_synack_retries和tcp_abort_on_overflow系统参数来调节。

下面来看看内核中是怎么通过SYN cookie机制来完成连接的建立。

客户端的连接请求由

1
2
3
4
tcp_v4_do_rcv()
	tcp_rcv_state_process()
		icsk->icsk_af_ops->conn_request()
			tcp_v4_conn_request()

函数处理。tcp_v4_conn_request()中有一个局部变量want_cookie,用来标识是否使用SYN cookies机制。want_cookie的初始值为0,如果半连接队列已满,并且开启了tcp_syncookies系统参数,则将其值设置为1,如下所示:

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
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
#ifdef CONFIG_SYN_COOKIES
	int want_cookie = 0;
#else
#define want_cookie 0 /* Argh, why doesn't gcc optimize this :( */
#endif

...... 

	/* TW buckets are converted to open requests without
	 * limitations, they conserve resources and peer is
	 * evidently real one.
	 */
	if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
#ifdef CONFIG_SYN_COOKIES
		if (sysctl_tcp_syncookies) {
			want_cookie = 1;
		} else
#endif
	   
		goto drop;
	}
......

drop:
	return 0;
}

如果没有开启SYN cookies机制,在半连接队列满时,会跳转到drop处,返回0。在调用tcp_v4_conn_request()的tcp_rcv_state_process()中会直接释放SKB包。

我们前面提高过,造成半连接队列满有两种情况(不考虑半连接队列很小的情况),一种是负载过高,正常的连接数过多;另一种是SYN flood攻击。如果是第一种情况,此时是否继续构建连接,则要取决于连接队列的情况及半连接队列的重传情况,如下所示:

1
2
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)
	goto drop;

sk_acceptq_is_full()函数很好理解,根据字面意思就可以看出,该函数是检查连接队列是否已满。inet_csk_reqsk_queue_young()函数返回半连接队列中未重传过SYN+ACK段的连接请求块数量。如果连接队列已满并且半连接队列中的连接请求块中未重传的数量大于1,则会跳转到drop处,丢弃SYN包。如果半连接队列中未重传的请求块数量大于1,则表示未来可能有2个完成的连接,这些新完成的连接要放到连接队列中,但此时连接队列已满。如果在接收到三次握手中最后的ACK后连接队列中没有空闲的位置,会忽略接收到的ACK包,连接建立会推迟,所以此时最好丢掉部分新的连接请求,空出资源以完成正在进行的连接建立过程。还要注意,这个判断并没有考虑半连接队列是否已满的问题。从这里可以看出,即使开启了SYN cookies机制并不意味着一定可以完成连接的建立。

如果可以继续连接的建立,调用inet_reqsk_alloc()分配连接请求块,如下所示:

1
2
3
req = inet_reqsk_alloc(&tcp_request_sock_ops);
if (!req)
	goto drop;

看到这里可能就有人疑惑,既然开启了SYN cookies机制,仍然分配连接请求块,那和正常的连接构建也没有什么区别了。这里之所以要分配连接请求块是用于发送SYN+ACK包给客户端,发送后会释放掉,并不会加入到半连接队列中。

接下来就是计算cookie的值,由cookie_v4_init_sequence()函数完成,如下所示:

1
2
3
4
5
6
7
if (want_cookie) {
#ifdef CONFIG_SYN_COOKIES
	syn_flood_warning(skb);
	req->cookie_ts = tmp_opt.tstamp_ok;
#endif
	isn = cookie_v4_init_sequence(sk, skb, &req->mss);
}

计算得到的cookie值会保存在连接请求块tcp_request_sock结构的snt_isn成员中,接着会调用__tcp_v4_send_synack()函数发送SYN+ACK包,然后释放前面分配的连接请求块,如下所示:

1
2
if (__tcp_v4_send_synack(sk, req, dst) || want_cookie)
	goto drop_and_free;

在服务器端发送完SYN+ACK包后,我们看到在服务器端没有保存任何关于这个未完成连接的信息,所以在接收到客户端的ACK包后,只能根据前面发送的SYN+ACK包中的cookie值来决定是否继续构建连接。

我们接下来看接收到ACK包后的处理情况。ACK包在tcp_v4_do_rcv()函数中调用的tcp_v4_hnd_req()中处理,如下所示:

1
2
3
4
5
6
7
8
9
10
static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
{
	......
 
#ifdef CONFIG_SYN_COOKIES
	if (!th->rst && !th->syn && th->ack)
		sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));
#endif
	return sk;
}

由于在服务器端没有保存未完成连接的信息,所以在半连接队列或ehash散列表中都不会找到对应的sock结构。如果开启了SYN cookies机制,则会检查接收到的数据包是否是ACK包,如果是,在cookie_v4_check()中会调用cookie_check()函数检查ACK包中的cookie值是否有效。如果有效,则会分配request_sock结构,并根据ACK包初始化相应的成员,开始构建描述连接的sock结构。创建过程和正常的连接创建过程一样。

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各种设置

Centos7系统rc.local不起作用

chmod +x /etc/rc.d/rc.local

开头加 #!/bin/bash

启动此项服务:

systemctl list-units –type=service #来查看一下所有的开启启动项目里面有没有这个rc-local这个服务。

systemctl status rc-local.service #来查看一下当前是怎么个状态

systemctl enable rc-local.service

systemctl start rc-local.service


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