SYN-Flood遭遇战——Linux内核SYN-Cookie实现探究

news/2024/5/17 16:56:26 标签: linux内核, tcp, dst, processing, linux, struct

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都要爆了。还是要在网络节点上做流量清洗啊。



http://www.niftyadmin.cn/n/1402559.html

相关文章

PCI的中断过程

摘 要&#xff1a;PCI总线具有即插即用&#xff08;PnP&#xff0c;plug and play&#xff09;的特性&#xff0c;这使得CPU访问扩展卡的机制与非即插即用系统有较大的不同&#xff0c;尤其是中断机制比较复杂。本文详细介绍了高性能PC机的中断结构和PLX9052的中断机制&#x…

怎样写 Linux LCD 驱动程序

怎样写 Linux LCD 驱动程序 作者: 刘鹏 日期: 2008-12-27 本文分析了frame buffer 设备驱动的主要数据结构&#xff0c;在此基础上介绍了LCD驱动程序的开发。 基本原理 通过 framebuffer &#xff0c;应用程序用 mmap 把显存映射到应用程序虚拟地址空间&#xff0c;将要显示的…

浅谈Linux PCI设备驱动(一)

http://blog.csdn.net/linuxdrivers/archive/2010/08/30/5849698.aspx 要弄清楚Linux PCI设备驱动&#xff0c;首先要明白&#xff0c;所谓的Linux PCI设备驱动实际包括Linux PCI设备驱动和设备本身驱动两部分。不知道读者理不理解这句话&#xff0c;本人觉得这句话很重要&…

浅谈Linux PCI设备驱动(二)

http://blog.csdn.net/linuxdrivers/archive/2010/10/01/5917478.aspx 我们在 浅谈Linux PCI设备驱动&#xff08;一&#xff09;中(以下简称 浅谈(一) )介绍了PCI的配置寄存器组&#xff0c;而Linux PCI初始化就是使用了这些寄存器来进行的。后面我们会举个例子来说明Linux …

tcp connection setup的实现(二)

文章出处&#xff1a;http://simohayha.iteye.com/blog/459683 首先来看下内核如何处理3次握手的半连接队列和accept队列(其实也就是server端的三次握手的状态变换).而半连接队列和accept队列在内核如何表示,我们上次已经介绍过了,这里就不介绍了. 首先我们知道当3层的数据…

Linux 内存管理 -- 高端内存的映射方式

高端内存是指物理地址大于 896M 的内存。 对于这样的内存&#xff0c;无法在“内核直接映射空间”进行映射。 为什么&#xff1f; 因为“内核直接映射空间”最多只能从 3G 到 4G&#xff0c;只能直接映射 1G 物理内存&#xff0c;对于大于 1G 的物理内存&#xff0c;无能为力…

ELF动态解析符号过程(修订版)

创建时间&#xff1a;2002-01-28文章属性&#xff1a;原创文章来源&#xff1a; http://www.xfocus.org/文章提交&#xff1a; alert7 (sztcww_at_sina.com)ELF动态解析符号过程(修订版)by alert7 2002-01-27★★ 前言本篇文章以linux为平台为例&#xff0c;演示ELF动态解析符号…