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

来源:互联网 发布:阿里云杭州和北京对比 编辑:程序博客网 时间:2024/06/10 13:20

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) {
        structinet_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((structrtable *)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);
                gotodrop_and_release;
            }
        }
        /* Kill the following clause, if you dislike this way. */
        elseif (!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));
            gotodrop_and_release;
        }
 
        isn = tcp_v4_init_sequence(skb);
    }
    tcp_rsk(req)->snt_isn = isn;
 
    if(tcp_v4_send_synack(sk, dst, req,
                   (structrequest_values *)&tmp_ext) ||
        want_cookie)
        gotodrop_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
staticstruct sock *tcp_v4_hnd_req(structsock *sk, structsk_buff *skb)
{
    structtcphdr *th = tcp_hdr(skb);
    conststruct iphdr *iph = ip_hdr(skb);
    structsock *nsk;
    structrequest_sock **prev;
    /* Find possible connection requests. */
    structrequest_sock *req = inet_csk_search_req(sk, &prev, th->source,
                               iph->saddr, iph->daddr);
    if(req)
        returntcp_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);
            returnnsk;
        }
        inet_twsk_put(inet_twsk(nsk));
        returnNULL;
    }
 
#ifdef CONFIG_SYN_COOKIES
    if(!th->syn)
        sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));
#endif
    returnsk;
}

在最后,做了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(structsock *sk, structsk_buff *skb, __u16 *mssp)
{
    conststruct iphdr *iph = ip_hdr(skb);
    conststruct tcphdr *th = tcp_hdr(skb);
    intmssind;
    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);
 
    returnsecure_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.
 */
staticinline intcookie_check(structsk_buff *skb, __u32 cookie)
{
    conststruct iphdr *iph = ip_hdr(skb);
    conststruct 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);
 
    returnmssind < 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都要爆了。还是要在网络节点上做流量清洗啊。


原创粉丝点击