SYN-Flood遭遇战——Linux内核SYN-Cookie实现探究
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都要爆了。还是要在网络节点上做流量清洗啊。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)