SYN-Flood遭遇战——Linux内核SYN-Cookie实现探究
SYN Flood好使啊,成本低廉,简单暴力,杀伤力强,更重要的是:无解,一打一个准!这种攻击充分利用了TCP协议的弱点,可以很轻易将你的网络打趴下。如果监控和应急不到位的话,那就等着被用户骂吧。虽说是无解,但还是可以想想办法在TCP协议上做点手脚在稍微防范下比较小规模的攻击的。至少不会沦落到随便找个小P孩搞一些PC机随便打一下,你的主机就跨了吧。SYN Flood的基本原理就是耗尽你主机
SYN Flood好使啊,成本低廉,简单暴力,杀伤力强,更重要的是:无解,一打一个准!这种攻击充分利用了TCP协议的弱点,可以很轻易将你的网络打趴下。如果监控和应急不到位的话,那就等着被用户骂吧。
虽说是无解,但还是可以想想办法在TCP协议上做点手脚在稍微防范下比较小规模的攻击的。至少不会沦落到随便找个小P孩搞一些PC机随便打一下,你的主机就跨了吧。
SYN Flood的基本原理就是耗尽你主机的半开连接资源。那么最简单的方法便是减少TCP握手的超时,让攻击包消耗的资源尽量稍微快点释放。这样能将系统抵抗能力提高个几倍。但是面对洪水一样的攻击包,一两倍的抵抗能力提高是浮云啊。
所以人们就想在握手协议上做点手脚,让攻击的包不会占用资源就好了。常用的方法是SYN Cookie。思路也比较简单暴力(以暴制暴):第一个SYN包来了之后,不分配资源,返回一个经过构造的ACK序号,然后看回复的ACK号能不能对上号,对上了再分配资源,否则那个SYN包便是攻击了,果断放弃。来看看Linux2.6内核中的SYN Cookie功能是如何实现的吧。
tcp会话握手协议的处理在net/ipv4/tcp_ipv4.c中,而cookie的生成和检查在net/ipv4/syncookies.c中。
当新的连接请求(SYN包)到达时内核是这么处理的(删除了大部分代码):
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
|
int
tcp_v4_conn_request(
struct
sock *sk,
struct
sk_buff *skb)
{
//...
if
(!want_cookie || tmp_opt.tstamp_ok)
TCP_ECN_create_request(req, tcp_hdr(skb));
if
(want_cookie) {
isn = cookie_v4_init_sequence(sk, skb, &req->mss);
req->cookie_ts = tmp_opt.tstamp_ok;
}
else
if
(!isn) {
struct
inet_peer *peer = NULL;
/* VJ's idea. We save last timestamp seen
* from the destination in peer table, when entering
* state TIME-WAIT, and check against it before
* accepting new connection request.
*
* If "isn" is not zero, this request hit alive
* timewait bucket, so that all the necessary checks
* are made in the function processing timewait state.
*/
if
(tmp_opt.saw_tstamp &&
tcp_death_row.sysctl_tw_recycle &&
(dst = inet_csk_route_req(sk, req)) != NULL &&
(peer = rt_get_peer((
struct
rtable *)dst)) != NULL &&
peer->daddr.a4 == saddr) {
inet_peer_refcheck(peer);
if
((u32)get_seconds() - peer->tcp_ts_stamp < TCP_PAWS_MSL &&
(s32)(peer->tcp_ts - req->ts_recent) >
TCP_PAWS_WINDOW) {
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
goto
drop_and_release;
}
}
/* Kill the following clause, if you dislike this way. */
else
if
(!sysctl_tcp_syncookies &&
(sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <
(sysctl_max_syn_backlog >> 2)) &&
(!peer || !peer->tcp_ts_stamp) &&
(!dst || !dst_metric(dst, RTAX_RTT))) {
/* Without syncookies last quarter of
* backlog is filled with destinations,
* proven to be alive.
* It means that we continue to communicate
* to destinations, already remembered
* to the moment of synflood.
*/
LIMIT_NETDEBUG(KERN_DEBUG
"TCP: drop open request from %pI4/%u\n"
,
&saddr, ntohs(tcp_hdr(skb)->source));
goto
drop_and_release;
}
isn = tcp_v4_init_sequence(skb);
}
tcp_rsk(req)->snt_isn = isn;
if
(tcp_v4_send_synack(sk, dst, req,
(
struct
request_values *)&tmp_ext) ||
want_cookie)
goto
drop_and_free;
//...
}
|
看到了,如果开启了SYN Cookie,内核会将初始的流水号做成一个cookie,这个cookie是由cookie_v4_init_sequence函数生成。否则,就分配资源了。
如果对方没有响应,则timeout,资源也不会占用,如果回应了,我们看看处理的代码:
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
|
static
struct
sock *tcp_v4_hnd_req(
struct
sock *sk,
struct
sk_buff *skb)
{
struct
tcphdr *th = tcp_hdr(skb);
const
struct
iphdr *iph = ip_hdr(skb);
struct
sock *nsk;
struct
request_sock **prev;
/* Find possible connection requests. */
struct
request_sock *req = inet_csk_search_req(sk, &prev, th->source,
iph->saddr, iph->daddr);
if
(req)
return
tcp_check_req(sk, skb, req, prev);
nsk = inet_lookup_established(sock_net(sk), &tcp_hashinfo, iph->saddr,
th->source, iph->daddr, th->dest, inet_iif(skb));
if
(nsk) {
if
(nsk->sk_state != TCP_TIME_WAIT) {
bh_lock_sock(nsk);
return
nsk;
}
inet_twsk_put(inet_twsk(nsk));
return
NULL;
}
#ifdef CONFIG_SYN_COOKIES
if
(!th->syn)
sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));
#endif
return
sk;
}
|
在最后,做了cookie的检查,如果检查通过会返回正常的sock,否则返回NULL。下面看看cookie是如何生成和检查的。
先是生成函数(在syncookies.c中):
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
|
/*
* MSS Values are taken from the 2009 paper
* 'Measuring TCP Maximum Segment Size' by S. Alcock and R. Nelson:
* - values 1440 to 1460 accounted for 80% of observed mss values
* - values outside the 536-1460 range are rare (<0.2%).
*
* Table must be sorted.
*/
static
__u16
const
msstab[] = {
64,
512,
536,
1024,
1440,
1460,
4312,
8960,
};
static
__u32 secure_tcp_syn_cookie(__be32 saddr, __be32 daddr, __be16 sport,
__be16 dport, __u32 sseq, __u32 count,
__u32 data)
{
/*
* Compute the secure sequence number.
* The output should be:
* HASH(sec1,saddr,sport,daddr,dport,sec1) + sseq + (count * 2^24)
* + (HASH(sec2,saddr,sport,daddr,dport,count,sec2) % 2^24).
* Where sseq is their sequence number and count increases every
* minute by 1.
* As an extra hack, we add a small "data" value that encodes the
* MSS into the second hash value.
*/
return
(cookie_hash(saddr, daddr, sport, dport, 0, 0) +
sseq + (count << COOKIEBITS) +
((cookie_hash(saddr, daddr, sport, dport, count, 1) + data)
& COOKIEMASK));
}
/*
* Generate a syncookie. mssp points to the mss, which is returned
* rounded down to the value encoded in the cookie.
*/
__u32 cookie_v4_init_sequence(
struct
sock *sk,
struct
sk_buff *skb, __u16 *mssp)
{
const
struct
iphdr *iph = ip_hdr(skb);
const
struct
tcphdr *th = tcp_hdr(skb);
int
mssind;
const
__u16 mss = *mssp;
tcp_synq_overflow(sk);
for
(mssind = ARRAY_SIZE(msstab) - 1; mssind ; mssind--)
if
(mss >= msstab[mssind])
break
;
*mssp = msstab[mssind];
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_SYNCOOKIESSENT);
return
secure_tcp_syn_cookie(iph->saddr, iph->daddr,
th->source, th->dest, ntohl(th->seq),
jiffies / (HZ * 60), mssind);
}
|
比较简单,注释也很清楚,将源地址/端口,目标地址/端口,还有当前时间(单位:分钟),还有MSS(最大报文长度)对应的ID传给secure_tcp_syn_cookie来算了个hash作为cookie。
接着看检查:
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
|
/*
* Check if a ack sequence number is a valid syncookie.
* Return the decoded mss if it is, or 0 if not.
*/
static
inline
int
cookie_check(
struct
sk_buff *skb, __u32 cookie)
{
const
struct
iphdr *iph = ip_hdr(skb);
const
struct
tcphdr *th = tcp_hdr(skb);
__u32 seq = ntohl(th->seq) - 1;
__u32 mssind = check_tcp_syn_cookie(cookie, iph->saddr, iph->daddr,
th->source, th->dest, seq,
jiffies / (HZ * 60),
COUNTER_TRIES);
return
mssind < ARRAY_SIZE(msstab) ? msstab[mssind] : 0;
}
/*
* This retrieves the small "data" value from the syncookie.
* If the syncookie is bad, the data returned will be out of
* range. This must be checked by the caller.
*
* The count value used to generate the cookie must be within
* "maxdiff" if the current (passed-in) "count". The return value
* is (__u32)-1 if this test fails.
*/
static
__u32 check_tcp_syn_cookie(__u32 cookie, __be32 saddr, __be32 daddr,
__be16 sport, __be16 dport, __u32 sseq,
__u32 count, __u32 maxdiff)
{
__u32 diff;
/* Strip away the layers from the cookie */
cookie -= cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq;
/* Cookie is now reduced to (count * 2^24) ^ (hash % 2^24) */
diff = (count - (cookie >> COOKIEBITS)) & ((__u32) - 1 >> COOKIEBITS);
if
(diff >= maxdiff)
return
(__u32)-1;
return
(cookie -
cookie_hash(saddr, daddr, sport, dport, count - diff, 1))
& COOKIEMASK;
/* Leaving the data behind */
}
|
cookie_check中的cookie参数是这样来的:cookie = ntohl(th->ack_seq) – 1。好了,直接从返回的ACK号里面解出上次种下的mssid,看看对不对。以暴制暴,清爽无比。
不过,SYN Cookie也不是救世主,这只能对付很小规模的SYN Flood。攻击流量一大,连CPU都要爆了。还是要在网络节点上做流量清洗啊。
更多推荐
所有评论(0)