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

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