lvs syn-proxy之syn-cookie实现

一、为什么要使用syn-proxy

linux内核原生的ip_vs模块主要功能是实现负载均衡,但不能对类似DDOS这种flood类型的***包进行防护,而这种***包会被ip_vs模块转发至后端rs上,一般情况下rs缺少对于这种四层***的防御,或者说防护效率并不高。同时,在ip_vs模块向后端转发时,也会消耗lvs机器的CPU等硬件资源,对于lvs的转发性能也会造成一定影响。

为了解决上述问题,淘宝开源的FULLNAT模式版本的lvs在原生lvs上增加了一层flood***防护功能:syn-proxy。

二、什么是syn-cookie

SYN Cookie是对TCP服务器端的三次握手做一些修改,专门用来防范SYN Flood***的一种手段。它的原理是,在TCP服务器接收到TCP SYN包并返回TCP SYN + ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值。这个cookie作为将要返回的SYN ACK包的初始序列号。当客户端返回一个ACK包时,根据包头信息计算cookie,与返回的确认序列号(初始序列号 + 1)进行对比,如果相同,则是一个正常连接,然后,分配资源,建立连接。实现的关键在于cookie的计算,cookie的计算应该包含本次连接的状态信息,使***者不能伪造。

ip_vs在syn-proxy中也借鉴syn-cookie的机制实现了自己的syn-cookie,下图展示了syn-proxy的大体流程(红色标记为syn-cookie的生成和校验阶段):

lvs syn-proxy之syn-cookie实现

三、实现思路

  1. 利用NF_INET_PRE_ROUTING处HOOK点处理函数ip_vs_pre_routing来处理client发来的syn报文(此时不创建session,不为该连接分配资源
  2. 在ip_vs_synproxy_syn_rcv函数中计算syn-cookie值,同时该值也被用做回复给client syn_ack报文的init seq
  3. 在tcp_conn_schedule函数中,对ack报文的cookie校验(即ack报文的ack_seq-1进行校验),校验通过则创建session,为该连接分配资源,并把ack报文保存在Session结构体中且向RS发送syn报文

四、关键技术点实现原理

syn-cookie的生成

计算syn-cookie(即发送给client的init_seq)

ip_vs_synproxy_cookie_v4_init_sequence函数

/* 
* Generate a syncookie for ip_vs module. 
* Besides mss, we store additional tcp options in cookie "data".
* 
* Cookie "data" format: 
* |[21][20][19-16][15-0]|
* [21] SACKOK
* [20] TimeStampOK
* [19-16] snd_wscale
* [15-0] MSSIND 
*/ 
__u32 ip_vs_synproxy_cookie_v4_init_sequence(struct sk_buff *skb, 
struct ip_vs_synproxy_opt *opts) 
{ 
        const struct iphdr *iph = ip_hdr(skb);
        const struct tcphdr *th = tcp_hdr(skb);
        int mssind;
        const __u16 mss = opts->mss_clamp;
        __u32 data = 0;

        /* XXX sort msstab[] by probability? Binary search? */

        /* 根据syn包的mss确定mssind */
        for (mssind = 0; mss > msstab[mssind + 1]; mssind++)
        ; 
        opts->mss_clamp = msstab[mssind] + 1;

        /* 由于在syn-proxy中,收到syn时并不立即分配资源,因此需要存储tcp option 

         * 此处的data后续被用做cookie hash参数

         */
        data = mssind & IP_VS_SYNPROXY_MSS_MASK;
        data |= opts->sack_ok << IP_VS_SYNPROXY_SACKOK_BIT; /* 21 */
        data |= opts->tstamp_ok << IP_VS_SYNPROXY_TSOK_BIT;  /* 20 */
        data |= ((opts->snd_wscale & 0x0f) << IP_VS_SYNPROXY_SND_WSCALE_BITS); /* 16 */

        /* 此时data22bit分别为

         *    |[21]                [20]                   [19-16]        [15-0]|

         * SACKOK  TimeStampOK      snd_wscale     MSSIND 

         */
        return secure_tcp_syn_cookie(iph->saddr, iph->daddr,
                th->source, th->dest, ntohl(th->seq), 
                jiffies / (HZ * 60), data);  /* jiffies为系统开机时间 */
}
syn-cookie生成函数
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.
        */ 
        /* 该返回值即为发给client包的init seq,其中包含了hash_value1 + seq + count<<24 + (hash_value2 + data(22bit)) & COOKIEMASK(24bit)

         * hash_value1:根据四元组及count=0,c=0的sha1 hash值 (32bit)

         * sseq:client的syn包seq (32bit)

         * count:jiffies / (HZ * 60)即系统开机时间 (8bit)

         * hash_value2:根据四元组及count=count,c=1的sha1 hash值 (32bit)

         * data:根据tcp option拼接成的数据 (22bit) 【注】:tcp中的syn-cookie实现此处的data为mssind

         */
        return (cookie_hash(saddr, daddr, sport, dport, 0, 0) +
                sseq + (count << COOKIEBITS) +
                ((cookie_hash(saddr, daddr, sport, dport, count, 1) + data)
                & COOKIEMASK));
}

syn-cookie的校验

static __u32 check_tcp_syn_cookie(__u32 cookie, __be32 saddr, __be32 daddr,
                __be16 sport, __be16 dport, __u32 sseq,
                __u32 count, __u32 maxdiff)
{

        /* 此处传入的cookie为ack包的ack_seq-1,即lvs发送给client syn的seq,也即最原始的cookie值 */
        __u32 diff;

        /* Strip away the layers from the cookie */

        /* 由上面生成cookie的代码我们知道,cookie包含以下几个部分:

         * hash_value1(四元组,0,0) + seq(syn seq) + count(syn sys_time)<<24 + (hash_value2 + data(22bit)) & COOKIEMASK(24bit)

         * 此时hash_value1 = cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq; -----> hashkey均为 (四元组,0,0)且对于同一tcp流来说四元组相同

         * sseq = seq(syn seq) -----> 传入的sseq为ack包的seq - 1,即syn包的seq

         * 因此经过cookie -= cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq后cookie的值将变为:

         * cookie = count(syn sys_time)<<24 + (hash_value2 + data(22bit)) & COOKIEMASK(32bit)

         */        
        cookie -= cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq;

        /* Cookie is now reduced to (count * 2^24) ^ (hash % 2^24) */

        /* 

         * 分析此时的cookie组成:cookie = count(syn sys_time)<<24 + (hash_value2 + data(22bit)) & COOKIEMASK(24bit)

         * 二进制的cookie组成可以表示成这样:(假设count=4)

         *  count(syn sys_time)<<24 + (hash_value2 + data(22bit)) & COOKIEMASK(24bit)

         *                                   31 -  -  -  -  - -  - 23  - -  -  - -  - - 15  - -  -  - -  - -   7 -  - -  -   - - 0 

         *  count << 24               0  0 0 0 0 0 1 0  0  0 0 0 0 0 0 0 0  0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

         *  hash_value2              1  1 0 0 1 0 0 0  0  1 0 0 0 1 1 1 0  0 0 0 1 0 0 0 1 0 0 1 0 0 0 0             

         *         +                                                                +

         *  data                           0  0 0 0 0 0 0 0  0  0 1 0 1 0 1 0 0  0 0 1 0 0 1 0 0 1 0 0 0 0 0 0

         *  COOKIEMASK           0  0 0 0 0 0 1 0  0  1 1 1 1 1 1 1 1  1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

         *  通过上面的表示我们可以看出在cookie中的高8位存储着count值

         *  我们记该函数中的count为conut_syn,记secure_tcp_syn_cookie函数(该函数用于生成syn-cookie)中的count为count_ack

         *  很显然,count_syn - count_ack所得的结果即为client syn包与ack包到达lvs机器的时间之差diff,只要这个diff小于我们所期望的值,我们就认为cookie校验成功

         *  因此diff = count_ack - (cookie >> COOKIEBITS),其中(cookie >> COOKIEBITS) = count_syn

         */
        diff = (count - (cookie >> COOKIEBITS)) & ((__u32) - 1 >> COOKIEBITS); /* (__u32) - 1 >> COOKIEBITS 为8bit全1 */

        /* diff的单位为分钟,默认的maxdiff为4 */
        if (diff >= maxdiff)
               return (__u32)-1;
        /* 若syn-cookie检验成功,则返回值只剩下了22bit的data数据,data数据中包含syn tcp options,同时为该连接分配系统资源 */
        return (cookie -
                cookie_hash(saddr, daddr, sport, dport, count - diff, 1))
                & COOKIEMASK; /* Leaving the data behind */
}

五、总结

本文介绍了lvs syn-proxy中的syn-cookie的生成和校验过程,但syn-proxy中还涉及了rs client间的seq转换,rs回复rst报文的seq修正等等复杂的逻辑,需要时间去好好研究,同时也希望本篇文章能够让大家了解syn-cookie及其原理。

六、参考文档

http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/

https://github.com/alibaba/LVS

https://en.wikipedia.org/wiki/SYN_cookies

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章