SYN Cookie的原理和實現


 

 

 
 
 

本文主要內容:SYN Cookie的原理,以及它的內核實現。

內核版本:3.6

 

 

SYN Flood

 

下面這段介紹引用自[1].

SYN Flood是一種非常危險而常見的Dos攻擊方式。到目前為止,能夠有效防范SYN Flood攻擊的手段並不多,

SYN Cookie就是其中最著名的一種。

 

SYN Flood攻擊是一種典型的拒絕服務(Denial of Service)攻擊。所謂的拒絕服務攻擊就是通過進行攻擊,使受害主機或

網絡不能提供良好的服務,從而間接達到攻擊的目的。

SYN Flood攻擊利用的是IPv4中TCP協議的三次握手(Three-Way Handshake)過程進行的攻擊。

TCP服務器收到TCP SYN request包時,在發送TCP SYN + ACK包回客戶機前,TCP服務器要先分配好一個數據區專門

服務於這個即將形成的TCP連接。一般把收到SYN包而還未收到ACK包時的連接狀態稱為半打開連接(Half-open Connection)。

在最常見的SYN Flood攻擊中,攻擊者在短時間內發送大量的TCP SYN包給受害者。受害者(服務器)為每個TCP SYN包分配

一個特定的數據區,只要這些SYN包具有不同的源地址(攻擊者很容易偽造)。這將給TCP服務器造成很大的系統負擔,最終

導致系統不能正常工作。

 

SYN Cookie

 

SYN Cookie原理由D.J. Bernstain和Eric Schenk提出。

SYN Cookie是對TCP服務器端的三次握手做一些修改,專門用來防范SYN Flood攻擊的一種手段。它的原理是,在TCP服務器

接收到TCP SYN包並返回TCP SYN + ACK包時,不分配一個專門的數據區,而是根據這個SYN包計算出一個cookie值。這個

cookie作為將要返回的SYN ACK包的初始序列號。當客戶端返回一個ACK包時,根據包頭信息計算cookie,與返回的確認序列

號(初始序列號 + 1)進行對比,如果相同,則是一個正常連接,然后,分配資源,建立連接。

 

實現的關鍵在於cookie的計算,cookie的計算應該包含本次連接的狀態信息,使攻擊者不能偽造。

cookie的計算:

服務器收到一個SYN包,計算一個消息摘要mac。

mac = MAC(A, k);

MAC是密碼學中的一個消息認證碼函數,也就是滿足某種安全性質的帶密鑰的hash函數,它能夠提供cookie計算中需要的安全性。

在Linux實現中,MAC函數為SHA1。

A = SOURCE_IP || SOURCE_PORT || DST_IP || DST_PORT || t || MSSIND

k為服務器獨有的密鑰,實際上是一組隨機數。

t為系統啟動時間,每60秒加1。

MSSIND為MSS對應的索引。

 

實現

 

(1)啟用條件

判斷是否使用SYN Cookie。如果SYN Cookie功能有編譯進內核(CONFIG_SYN_COOKIE),且選項

tcp_syncookies不為0,那么可使用SYN Cookie。同時設置SYN Flood標志(listen_opt->synflood_warned)。

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. /* Return true if a syncookie should be sent. */  
  2. bool tcp_syn_flood_action(struct sock *sk, const struct sk_buff *skb, const char *proto)  
  3. {  
  4.     const char *msg = "Dropping request";  
  5.     bool want_cookie = false;  
  6.     struct listen_sock *lopt;  
  7.   
  8. #ifdef CONFIG_SYN_COOKIE  
  9.     if (sysctl_tcp_syncookies) { /* 如果允許使用SYN Cookie */  
  10.         msg = "Sending cookies";  
  11.         want_cookie = true;  
  12.         NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPREQQFULLDOCOOKIES);  
  13.     } else  
  14. #endif  
  15.         NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPREQQFULLDROP);  
  16.   
  17.     lopt = inet_csk(sk)->icsk_accept_queue.listen_opt; /* 半連接隊列 */  
  18.   
  19.     if (! lopt->synflood_warned) {  
  20.         lopt->synflood_warned = 1; /* 設置SYN Flood標志 */  
  21.         pr_info("%s: Possible SYN flooding on port %d. %s.  Check SNMP counters.\n",  
  22.                        proto, ntohs(tcp_hdr(skb)->dest), msg);  
  23.     }  
  24.   
  25.     return want_cookie;  
  26. }  

 

(2)生成cookie

計算SYN Cookie的值。

函數調用路徑:

tcp_v4_conn_request

        |--> cookie_v4_init_sequence

                          |--> secure_tcp_syn_cookie

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. /* Generate a syncookie. mssp points to the mss, which is returned rounded down to the 
  2.  * value encoded in the cookie. 
  3.  */  
  4.   
  5. __u32 cookie_v4_init_sequence(struct sock *sk, struct sk_buff *skb, __u16 *mssp)  
  6. {  
  7.     const struct iphdr *iph = ip_hdr(skb);  
  8.     const struct tcphdr *th = tcp_hdr(skb);  
  9.     int mssind; /* mss index */  
  10.     const __u16 mss = *mssp;  
  11.   
  12.     tcp_synq_overflow(sk); /* 記錄半連接隊列溢出的最近時間 */  
  13.   
  14.     for (mssind = ARRAY_SIZE(msstab) - 1; mssind; mssind--)  
  15.         if (mss >= msstab[mssind])  
  16.             break;  
  17.     *mssp = msstab[mssind];  
  18.   
  19.     NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_SYNCOOKIESSENT);  
  20.   
  21.     return secure_tcp_syn_cookie(iph->saddr, iph->daddr, th->source, th->dest, ntohl(th->seq),  
  22.                       jiffies / (HZ * 60), mssind); /* 計算SYN Cookie的具體值 */  
  23. }  
[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. /* syncookie: remember time of last synqueue overflow */  
  2. static inline void tcp_synq_overflow(struct sock *sk)  
  3. {  
  4.     tcp_sk(sk)->rx_opt.ts_recent_stamp = jiffies;  
  5. }  
  6.   
  7. /*  
  8.  * MSS Values are taken from the 2009 paper 
  9.  * 'Measuring TCP Maximum Segment Size' by S. Alcock and R. Nelson: 
  10.  * - values 1440 to 1460 accounted for 80% of observed mss values 
  11.  * - values outside the 536-1460 range are rare (<0.2%). 
  12.  * 
  13.  * Table must be sorted. 
  14.  */  
  15. static __u16 const msstab[] = {  
  16.     64,  
  17.     512,  
  18.     536,  
  19.     1024,  
  20.     1440,  
  21.     1460,  
  22.     4312,  
  23.     8960,  
  24. };  
[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. static __u32 secure_tcp_syn_cookie(__be32 saddr, __be32 daddr, __be16 sport, __be16 dport,  
  2.                                    __u32 sseq, __u32 count, __u32 data)  
  3. {  
  4.     /* Compute the secure sequence number. 
  5.      * The output should be: 
  6.      * HASH(sec1, saddr, sport, daddr, dport, sec1) + sseq + (count * 2^24) + 
  7.      *     (HASH(sec2, saddr, sport, daddr, dport, count, sec2) % 2^24). 
  8.      * Where sseq is their sequence number and count increases every minute by 1. 
  9.      * As an extra hack, we add a small "data" value that encodes the MSS into the second hash value. 
  10.      */  
  11.     return (cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq + (count << COOKIEBITS) +  
  12.               ((cookie_hash(saddr, daddr, sport, dport, count, 1) + data) & COOKIEMASK));  
  13.   
  14. }  
  15.   
  16. #define COOKIEBITS 24 /* Upper bits store count */  
  17. #define COOKIEMASK (((__u32) 1 << COOKIEBITS) - 1)  
  18. #define SHA_DIGEST_WORDS 5  
  19. #define SHA_WORKSPACE_WORDS 16  

服務器的密鑰、SHA1計算。

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. __u32 syncookie_secret[2] [16 - 4 + SHA_DIGEST_WORDS];  
  2.   
  3. static __init int init_syncookies(void)  
  4. {  
  5.     get_random_bytes(syncookie_secret, sizeof(syncookie_secret));  
  6.     return 0;  
  7. }  
  8.   
  9. static DEFINE_PER_CPU(__u32 [16 + 5 + SHA_WORKSPACE_WORDS], ipv4_cookie_scratch);  
  10.   
  11. static u32 cookie_hash(__be32 saddr, _be32 daddr, __be16 sport, __be16 dport, u32 count, int c)  
  12. {  
  13.     __u32 *tmp = __get_cpu_var(ipv4_cookie_scratch);  
  14.   
  15.     memcpy(tmp + 4, syncookie_secret[c], sizeof(syncookie_secret[c])); /* c取值為0、1 */  
  16.     tmp[0] = (__force u32) saddr;  
  17.     tmp[1] = (__force u32) daddr;  
  18.     tmp[2] = ((__force u32) sport << 16) + (__force u32) dport;  
  19.     tmp[3] = count;  
  20.   
  21.     sha_transform(tmp + 16, (__u8 *)tmp, tmp + 16 + 5); /* generate a 160-bit digest from 512-bit block */  
  22.     return tmp[17];  
  23. }  

SHA1

安全哈希算法(Secure HASH Algorithm)主要適用於數字簽名。

對於長度小於2^64位的消息,SHA1會產生一個160位的消息摘要。當接收到消息的時候,這個消息摘要可以用來

驗證數據的完整性。在傳輸的過程中,數據可能會發生變化,那么這時候就會產生不同的消息摘要。

SHA1有如下特性:

1. 不可以從消息摘要中復原信息。

2. 兩個不同的消息不會產生同樣的消息摘要。

在Git中,也使用SHA1來標識每一次提交。

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. /* sha_transform - single block SHA1 transform  
  2.  * @digest: 160 bit digest to update  
  3.  * @data: 512 bits of data to hash  
  4.  * @array: 16 words of workspace (see note)  
  5.  *  
  6.  * This function generates a SHA1 digest for a single 512-bit block.  
  7.  * /  
  8. void sha_transform(__u32 *digest, const char *data, __u32 *array) {}  

 

(3)保存TCP選項信息

tcp_v4_send_synack

        |--> tcp_make_synack

                       |--> cookie_init_timestamp

如果SYNACK段使用SYN Cookie,並且使用時間戳選項,則把TCP選項信息保存在SYNACK段中tsval的低6位。

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. /* When syncookies are in effect and tcp timestamps are enabled we encode tcp options 
  2.  * in the lower bits of the timestamp value that will be sent in the syn-ack. 
  3.  * Since subsequent timestamps use the normal tcp_time_stamp value, we must make 
  4.  * sure that the resulting initial timestamp is <= tcp_time_stamp. 
  5.  */  
  6. __u32 cookie_init_timestamp(struct request_sock *req)  
  7. {  
  8.     struct inet_request_sock *ireq;  
  9.     u32 ts, ts_now = tcp_time_stamp;  
  10.     u32 options = 0;  
  11.     ireq = inet_rsk(req);  
  12.   
  13.     options = ireq->wscale_ok ? ireq->snd_wscale : 0xf;  
  14.     options |= ireq->sack_ok << 4;  
  15.     options |= ireq->ecn_ok << 5;  
  16.   
  17.     ts = ts_now & ~TSMASK;  
  18.     ts |= options;  
  19.   
  20.     if (ts > ts_now) {  
  21.         ts >>= TSBITS;  
  22.         ts--;  
  23.         ts <<= TSBITS;  
  24.         ts |= options;  
  25.     }  
  26.     return ts;  
  27. }  
  28.   
  29. #define TSBITS 6  
  30. #define TSMASK (((__u32) 1 << TSBITS) - 1)  


(4)驗證cookie

函數調用路徑:

tcp_v4_hnd_req

        |--> cookie_v4_check

                      |--> cookie_check

                                       |--> check_tcp_syn_cookie

 

SYN Cookie的設計非常巧妙, 我們來看看它是怎么驗證的。

首先,把ACK包的ack_seq - 1,得到原來計算的cookie。把ACK包的seq - 1,得到SYN段的seq。

cookie的計算公式為:

cookie = cookie_hash(saddr, daddr, sport, dport, 0, 0) + seq +

                (t1 << 24) + (cookie_hash(saddr, daddr, sport, dport, t1, 1) + mssind) % 24;

t1為服務器發送SYN Cookie的時間,單位為分鍾,保留在高12位。

mssind為MSS的索引(0 - 7),保留在低24位。

 

現在可以反過來求t1:

t1 = (cookie - cookie_hash(saddr, daddr, sport, dport, 0, 0) - seq) >> 24; /* 高12位表示時間 */

t2為收到ACK的時間,t2 - t1 < 4分鍾,才是合法的。也就是說ACK必須在4分鍾內到達才行。

 

驗證完時間后,還需驗證mssind:

cookie -= (cookie_hash(saddr, daddr, sport, dport, 0, 0) - seq);

mssind = (cookie - cookie_hash(saddr, daddr, sport, dport, t1, 1)) % 24; /* 低24位 */

mssind < 8,才是合法的。

 

如果t1和mssind都是合法的,則認為此ACK是合法的,可以直接完成三次握手。

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. /* Check if a ack sequence number is a valid syncookie. 
  2.  * Return the decoded mss if it is, or 0 if not. 
  3.  */  
  4.   
  5. static inline int cookie_check(struct sk_buff *skb, __u32 cookie)  
  6. {  
  7.     const struct iphdr *iph = ip_hdr(skb);  
  8.     const struct tcphdr *th = tcp_hdr(skb);  
  9.     __u32 seq = ntohl(th->seq) - 1; /* SYN的序號 */  
  10.   
  11.     __u32 mssind = check_tcp_syn_cookie(cookie, iph->saddr, iph->daddr, th->source, th->dest,  
  12.                           seq, jiffies / (HZ * 60), COUNTER_TRIES);  
  13.   
  14.     /* 如果不合法則返回0 */  
  15.     return mssind < ARRAY_SIZE(msstab) ? msstab[mssind] : 0;  
  16. }  
[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. /* 使用SYN Cookie時,ACK超過了這個時間到達,會被認為不合法。*/  
  2. /* This (misnamed) value is the age of syncookie which is permitted. 
  3.  * Its ideal value should be dependent on TCP_TIMEOUT_INIT and sysctl_tcp_retries1. 
  4.  * It's a rather complicated formula (exponential backoff) to compute at runtime so it's 
  5.  * currently hardcoded here. 
  6.  */  
  7. #define COUNTER_TRIES /* 4分鍾 */  
  8.   
  9. static __u32 check_tcp_syn_cookie(__u32 cookie, __be32 saddr, __be32 daddr, __be16 sport,  
  10.             __be16 dport, __u32 sseq, __u32 count, __u32 maxdiff)  
  11. {  
  12.     __u32 diff;  
  13.   
  14.     /* Strip away the layers from the cookie, 剝去固定值的部分 */  
  15.     cookie -= cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq;  
  16.   
  17.     /* Cookie is now reduced to (count * 2^24) + (hash % 2^24) */  
  18.     diff = (count - (cookie >> COOKIEBITS)) & ((__u32) -1 >> COOKIEBITS); /* 高12位是時間,單位為分鍾 */  
  19.     if (diff >= maxdiff)  
  20.         return (__u32)-1;  
  21.   
  22.     /* Leaving the data behind,返回的是原來的data,即mssind */  
  23.     return (cookie - cookie_hash(saddr, daddr, sport, dport, count - diff, 1)) & COOKIEMASK;  
  24. }  

 

(5)建立連接

接收到ACK后,SYN Cookie的處理函數為cookie_v4_check()。

首先要驗證cookie是否合法。

如果cookie是不合法的,返回監聽sk,會導致之后發送一個RST給客戶端。

如果cookie是合法的,則創建和初始化連接請求塊。接着為新的連接創建和初始化一個新的傳輸控制塊,

把它和連接請求塊關聯起來,最后把該連接請求塊鏈入全連接隊列中,等待accept()。

 

時間戳對SYN Cookie有着重要的意義,如果不支持時間戳選項,則通過SYN Cookie建立的連接就會

不支持大多數TCP選項。

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb, struct ip_options *opt)  
  2. {  
  3.     struct tcp_options_received tcp_opt;  
  4.     const u8 *hash_location;  
  5.     struct inet_request_sock *ireq;  
  6.     struct tcp_request_sock *treq;  
  7.     struct tcp_sock *tp = tcp_sk(sk);  
  8.     const struct tcphdr *th = tcp_hdr(skb);  
  9.     __u32 cookie = ntohl(th->ack_seq) - 1;  
  10.     struct sock *ret = sk;  
  11.     struct request_sock *req;  
  12.     int mss;  
  13.     struct rtable *rt;  
  14.     __u8 rcv_wscale;  
  15.     bool ecn_ok = false;  
  16.     struct flowi4 fl4;  
  17.   
  18.     if (! sysctl_tcp_syncookies || ! th->ack || th->rst)  
  19.         goto out;  
  20.   
  21.     /* 驗證cookie的合法性,必須同時符合: 
  22.      * 1. 最近3s內有發生半連接隊列溢出。 
  23.      * 2. 通過cookie反算的t1和mssind是合法的。 
  24.      */  
  25.     if (tcp_synq_no_recent_overflow(sk) || (mss = cookie_check(skb, cookie)) == 0) {  
  26.         NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_SYNCOOKIESFAILED);  
  27.         goto out;  
  28.     }  
  29.     NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_SYNCOOKIESRECV);  
  30.   
  31.     /* check for timestamp cookie support */  
  32.     memset(&tcp_opt, 0, sizeof(tcp_opt));  
  33.   
  34.     /* 全面解析TCP選項,並保存到tcp_opt中 */  
  35.     tcp_parse_options(skb, &tcp_opt, &hash_location, 0, NULL);  
  36.   
  37.     /* 如果有使用時間戳選項,則從ACK的tsecr中提取選項信息 */  
  38.     if (! cookie_check_timestamp(&tcp_opt, &ecn_ok))  
  39.         goto out;  
  40.   
  41.     ret = NULL;  
  42.     /* 從緩存塊中分配一個request_sock實例,指定此實例的操作函數集為tcp_request_sock_ops */  
  43.     req = inet_reqsk_alloc(&tcp_request_sock_ops);  
  44.     if (! req)  
  45.         goto out;  
  46.   
  47.     ireq = inet_rsk(req);  
  48.     treq = tcp_rsk(req);  
  49.     treq->rcv_isn = ntohl(th->seq) - 1; /* 客戶端的初始序列號 */  
  50.     treq->snt_isn = cookie; /* 本端的初始序列號 */  
  51.     req->mss = mss; /* 客戶端通告的MSS,通過解析cookie獲得 */  
  52.     ireq->loc_port = th->dest; /* 本端端口 */  
  53.     ireq->rmt_port = th->source; /* 客戶端端口 */  
  54.     ireq->loc_addr = ip_hdr(skb)->daddr; /* 本端IP */  
  55.     ireq->rmt_addr = ip_hdr(skb)->saddr; /* 客戶端IP */  
  56.     ireq->ecn_ok = ecn_ok; /* ECN選項,通過TS編碼獲得 */  
  57.     ireq->snd_wscale = tcp_opt.snd_wscale; /* 客戶端窗口擴大因子,通過TS編碼獲得 */  
  58.     ireq->sack_ok = tcp_opt.sack_ok; /* SACK允許選項,通過TS編碼獲得 */  
  59.     ireq->wscale_ok = tcp_opt.wscale_ok; /* 窗口擴大選項,通過TS編碼獲得 */  
  60.     ireq->tstamp_ok = tcp_opt.saw_tstamp; /* 時間戳選項,通過觀察ACK段有無攜帶時間戳 */  
  61.     req->ts_recent = tcp_opt.saw_tstamp ? tcp_opt.rcv_tsval : 0; /* 本端下個發送段的時間戳回顯值 */  
  62.     treq->snt_synack = tcp_opt.saw_tstamp ? tcp_opt.rcv_tsecr : 0; /* 本端發送SYNACK段的時刻 */  
  63.   
  64.     /* We throwed the options of the initial SYN away, so we hope the ACK carries the same options 
  65.      * again (see RFC1122 4.2.3.8) 
  66.      * 通過ACK段,獲取IP選項。 
  67.      */  
  68.     if (opt && opt->optlen) {  
  69.         int opt_size = sizeof(struct ip_options_rcu) + opt->optlen;  
  70.         ireq->opt = kmalloc(opt_size, GFP_ATOMIC);  
  71.   
  72.         if (ireq->opt != NULL && ip_options_echo(&ireq->opt->opt, skb)) {  
  73.             kfree(ireq->opt);  
  74.             ireq->opt = NULL;  
  75.         }  
  76.     }  
  77.   
  78.     /* SELinux相關 */  
  79.     if (security_inet_conn_request(sk, skb, req)) {  
  80.         reqsk_free(req);  
  81.         goto out;  
  82.     }  
  83.   
  84.     req->expires = 0UL; /* SYNACK的超時時間 */  
  85.     req->retrans = 0; /* SYNACK的重傳次數 */  
  86.   
  87.     /* We need to lookup the route here to get at the correct window size. 
  88.      * We should better make sure that the window size hasn't changed since we 
  89.      * received the original syn, but I see no easy way to do this. 
  90.      * 查找路由緩存。 
  91.      */  
  92.     flowi4_init_output(&fl4, 0, sk->sk_mark, RT_CONN_FLAGS(sk), RT_SCOPE_UNIVERSE,  
  93.         IPPROTO_TCP, inet_sk_flowi_flags(sk), (opt && opt->srr) ? opt->faddr : ireq->rmt_addr,  
  94.         ireq->loc_addr, th->source, th->dest);  
  95.     security_req_classify_flow(req, flowi4_to_flowi(&fl4));  
  96.     rt = ip_route_output_key(sock_net(sk), &fl4);  
  97.     if (IS_ERR(rt)) {  
  98.         reqsk_free(req);  
  99.         goto out;  
  100.     }  
  101.   
  102.     /* Try to redo what tcp_v4_send_synack did. */  
  103.     req->window_clamp = tp->window_clamp ? : dst_metric(&rt->dst, RTAX_WINDOW);  
  104.   
  105.     /* 獲取接收窗口的初始值,窗口擴大因子和接收窗口的上限 */  
  106.     tcp_select_initial_window(tcp_full_space(sk), req->mss, &req->rcv_wnd, &req->window_clamp,  
  107.         ireq->wscale_ok, &rcv_wscale, dst_metric(&rt->dst, RTAX_INITRWND));  
  108.     ireq->rcv_wscale = rcv_wscale;  
  109.   
  110.     /* 到了這里,三次握手基本完成。 
  111.      * 接下來為新的連接創建和初始化一個傳輸控制塊,並把它和連接請求塊關聯起來。 
  112.      * 最后把該連接請求塊移入全連接隊列中,等待accept()。 
  113.      */  
  114.     ret = get_cookie_sock(sk, skb, req, &rt->dst);      
  115.   
  116.     /* ip_queue_xmit() depends on our flow being setup 
  117.      * Normal sockets get it right from inet_csk_route_child_sock() 
  118.      */  
  119.     if (ret)  
  120.         inet_sk(ret)->cork.fl.u.ip4 = fl4;  
  121.   
  122. out:   
  123.     return ret;  
  124. }  
  125.   
  126. /* RFC 1122 initial RTO value, now used as a fallback RTO for the initial data 
  127.  * transmssion if no valid RTT sample has been accquired, most likely due to 
  128.  * retrans in 3WHS. 
  129.  */  
  130. #define TCP_TIMEOUT_FALLBACK ((unsigned) (3 * HZ))   
  131.   
  132. /* syncookies: no recent synqueue overflow on this listening socket?  
  133.  * 如果最近3s內沒有發生半連接隊列溢出,則為真。 
  134.  */  
  135. static inline bool tcp_synq_no_recent_overflow(const struct sock *sk)  
  136. {  
  137.     unsigned long last_overflow = tcp_sk(sk)->rx_opt.ts_recent_stamp;  
  138.     return time_after(jiffies, last_overflow + TCP_TIMEOUT_FALLBACK);  
  139. }  

 

如果SYNACK段使用SYN Cookie,並且使用時間戳選項,則把TCP選項信息保存在SYNACK段中tsval的低6位。

所以,現在收到ACK后,可以從ACK段的tsecr中提取出這些選項。

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. /* When syncookies are in effect and tcp timestamps are enabled we stored addtional tcp 
  2.  * options in the timestamp. 
  3.  * This extracts these options from the timestamp echo. 
  4.  * The lowest 4 bits store snd_wscale. 
  5.  * next 2 bits indicate SACK and ECN support. 
  6.  * return false if we decode an option that should not be. 
  7.  */  
  8. bool cookie_check_timestamp(struct tcp_options_received *tcp_opt, bool *ecn_ok)  
  9. {  
  10.     /* echoed timestamp, lowest bits contain options */  
  11.     u32 options = tcp_opt->rcv_tsecr & TSMASK;  
  12.   
  13.     /* 如果ACK沒有攜帶時間戳,則把tcp_opt中的tstamp_ok、sack_ok、wscale_ok 
  14.      * snd_wscale和cookie_plus置零。 
  15.      */  
  16.     if (! tcp_opt->saw_tstamp) {  
  17.         tcp_clear_options(tcp_opt);  
  18.         return true;  
  19.     }  
  20.   
  21.     if (! sysctl_tcp_timestamps)  
  22.         return false;  
  23.   
  24.     tcp_opt->sack_ok = (options & (1 << 4)) ? TCP_SACK_SEEN : 0;  
  25.     *ecn_ok = (options >> 5) & 1;  
  26.   
  27.     if (*ecn_ok && ! sysctl_tcp_ecn)  
  28.         return false;  
  29.   
  30.     if (tcp_opt->sack_ok && ! sysctl_tcp_sack)  
  31.         return false;  
  32.   
  33.     if ((options & 0xf) == 0xf)  
  34.         return true; /* no window scaling. */  
  35.   
  36.     tcp_opt->wscale_ok = 1;  
  37.     tcp_opt->snd_wscale = options & 0xf;  
  38.     return sysctl_tcp_window_scaling != 0;  
  39. }  

 

為新的連接創建和初始化一個傳輸控制塊,然后把完成三次握手的req和新sock關聯起來,

並把該連接請求塊移入全連接隊列中。

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. static inline struct sock *get_cookie_sock(struct sock *sk, struct sk_buff *skb,  
  2.      struct request_sock *req, struct dst_entry *dst)  
  3. {  
  4.     struct inet_connection_sock *icsk = inet_csk(sk);  
  5.     struct sock *child;  
  6.   
  7.     /* 為新的連接創建和初始化一個傳輸控制塊。 
  8.      * 對於TCP/IPv4,實例為ipv4_specific,調用tcp_v4_syn_recv_sock() 
  9.      */  
  10.     child = icsk->icsk_af_ops->syn_recv_sock(sk, skb, req, dst);  
  11.   
  12.     if (child)  
  13.         /* 把完成三次握手的連接請求塊,和新的sock關聯起來,並把它移入全連接隊列中。*/  
  14.         inet_csk_reqsk_queue_add(sk, req, child);   
  15.     else  
  16.         reqsk_free(req);  
  17.   
  18.     return child;  
  19. }  
  20.    
  21. static inline void inet_csk_reqsk_queue_add(struct sock *sk, struct request_sock *req, struct sock *child)  
  22. {  
  23.     reqsk_queue_add(&inet_csk(sk)->icsk_accept_queue, req, sk, child);  
  24. }  

 

把完成三次握手的連接請求塊,和新的sock關聯起來,並把它移入全連接隊列中,等待被accept()。

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. static inline void reqsk_queue_add(struct request_sock_queue *queue, struct request_sock *req,  
  2.       struct sock *parent, struct sock *child)  
  3. {  
  4.     req->sk = child; /* 連接請求塊request_sock,關聯了一個新sock */  
  5.     sk_acceptq_added(parent); /* 監聽sock的全連接隊列中的連接請求個數加一 */  
  6.   
  7.     /* 全連接隊列是一個FIFO隊列,把req加入到隊列尾部 */  
  8.     if (queue->rskq_accept_head == NULL)  
  9.         queue->rskq_accept_head = req;  
  10.     else  
  11.         queue->rskq_accept_tail->dl_next = req;  
  12.   
  13.     queue->rskq_accept_tail = req;  
  14.     req->dl_next = NULL;  
  15. }  
  16.   
  17. static inline void sk_acceptq_added(struct sock *sk)  
  18. {  
  19.     sk->sk_ack_backlog++;  
  20. }  

 

評價

 

SYN Cookie技術由於在建立連接的過程中不需要在服務器端保存任何信息,實現了無狀態的三次握手,從而有效的

防御了SYN Flood攻擊。但是該方法也存在一些弱點。由於cookie的計算只涉及到包頭部分信息,在建立連接的過程

中不在服務器端保存任何信息,所以失去了協議的許多功能,比如超時重傳。此外,由於計算cookie有一定的運算量,

增加了連接建立的延遲時間,因此,SYN Cookie技術不能作為高性能服務器的防御手段。通常采用動態資源分配機制,

當分配了一定的資源后再采用cookie技術,Linux就是這樣實現的。還有一個問題是,當我們避免了SYN Flood攻擊的

同時,也提供了另一種拒絕服務攻擊方式,攻擊者發送大量的ACK報文,服務器忙於計算驗證。盡管如此,在預防

SYN Flood供給方面,SYN Cookie技術仍然是有效的(引用自[1])。

 

擴展

 

Linux內核中的SYN Cookie機制主要的功能是防止本機遭受SYN Flood攻擊。

SYN Cookie Firewall利用SYN Cookie的原理,在內網和外網之間實現TCP三次握手過程的代理(proxy)。

一些SYN攻擊的防火牆也是基於SYN Cookie,只是把這個功能移動到內核之外的代理服務器上。

 

Reference

 

[1]. https://www.ibm.com/developerworks/cn/linux/l-syncookie/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM