1. 程式人生 > >效能壓力測試TPS優化之路---SYN__

效能壓力測試TPS優化之路---SYN__

SYN Cookie的原理和實現

2014年01月06日 16:56:15 zhangskd 閱讀數:28214 標籤: TCPIPlinux核心 更多

個人分類: TCP/IPKernel

所屬專欄: TCP協議優化

版權宣告:本文為博主原創文章,轉載請註明出處。 https://blog.csdn.net/zhangskd/article/details/16986931

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

核心版本:3.6

Author:zhangskd @ csdn blog

 

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)。

 
  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

 
  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. }

 
  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. };

 
  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

  20.  

伺服器的金鑰、SHA1計算。

 
  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來標識每一次提交。

 
  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) {}

  9.  

 

(3)儲存TCP選項資訊

tcp_v4_send_synack

        |--> tcp_make_synack

                       |--> cookie_init_timestamp

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

 
  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)

  31.  


(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是合法的,可以直接完成三次握手。

 
  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. }

 
  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 /* 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選項。

 
  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中提取出這些選項。

 
  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關聯起來,

並把該連線請求塊移入全連線佇列中。

 
  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()。

 
  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/

=====================================================================================================================================================================

tcpsyncookies----常見核心引數的修改

linanx0人評論9885人閱讀2018-06-29 19:19:30

*tcpsyncookies

是一個開關,是否開啟SYN Cookie功能,該功能可以防止部分SYN×××。

tcpsynackretries和tcpsynretries定義SYN的重試次數。

YN Cookie是對TCP伺服器端的三次握手做一些修改,專門用來防範SYN Flood×××的一種手段。它的原理是,在TCP伺服器
接收到TCP SYN包並返回TCP SYN + ACK包時,不分配一個專門的資料區,而是根據這個SYN包計算出一個cookie值。這個
cookie作為將要返回的SYN ACK包的初始序列號。當客戶端返回一個ACK包時,根據包頭資訊計算cookie,與返回的確認序列
號(初始序列號 + 1)進行對比,如果相同,則是一個正常連線,然後,分配資源,建立連線。

原理:在Tcp伺服器收到Tcp Syn包並返回Tcp Syn+ack包時,不專門分配一個數據區,而是根據這個Syn包計算出一個cookie值。在收到Tcp ack包時,Tcp伺服器在根據那個cookie值檢查這個Tcp ack包的合法性。如果合法,再分配專門的資料區進行處理未來的TCP連線。 
預設為0,1表示開啟

net.ipv4.tcpfintimeout

修改timewait狀的存在時間,預設的2MSL 
注意:timewait存在且生存時間為2MSL是有原因的,見我上一篇部落格為什麼會有timewait狀態的存在,所以修改它有一定的風險,還是根據具體的情況來分析。

tcpretries1
放棄迴應一個TCP連線請求前﹐需要進行多少次重試。RFC 規定最低的數值是3﹐這也是預設值

tcpretries2
TCP失敗重傳次數,預設值15,意味著重傳15次才徹底放棄.可減少到5,以儘早釋放核心資源.
---------------------------------------------------------------------------------------------------------------------------------------------------------------
nf_conntrack

      該模組在kernel 2.6.15(2006-01-03釋出) 被引入,支援ipv4和ipv6,取代只支援ipv4的ip_connktrack,用於跟蹤連線的狀態,供其他模組使用。

最常見的使用場景是 iptables 的 nat 和 state 模組:
nat 根據轉發規則修改IP包的源/目標地址,靠nf_conntrack的記錄才能讓返回的包能路由到發請求的機器。
state 直接用 nf_conntrack 記錄的連線狀態(NEW/ESTABLISHED/RELATED/INVALID)來匹配防火牆過濾規則。
在伺服器訪問量大時,如果核心netfilter模組conntrack相關引數配置不合理,就會導致新連線被drop掉
推薦bucket至少 262144,max至少 1048576,不夠再繼續加。

net.netfilter.nf_conntrack_count 的數字持續超過 nf_conntrack_max 的 20% 就該考慮調高上限了;
測試沒問題後可以寫入配置檔案 vim   /etc/sysctl.d/90-conntrack.conf  :
net.netfilter.nf_conntrack_buckets = 262144
net.netfilter.nf_conntrack_max=1048576
net.nf_conntrack_max=1048576
net.netfilter.nf_conntrack_tcp_timeout_fin_wait=30
net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
net.netfilter.nf_conntrack_tcp_timeout_close_wait=15
net.netfilter.nf_conntrack_tcp_timeout_established=300
#########################################################################################

如何修改核心引數:::

Linux在系統執行時修改核心引數(/proc/sys與/etc/sysctl.conf),而不需要重新引導系統,這個功能是通過/proc虛擬檔案系統實現的。
在/proc/sys目錄下存放著大多數的核心引數,並且設計成可以在系統執行的同時進行更改, 可以通過更改/proc/sys中核心引數對應的檔案達到修改核心引數的目的(修改過後,儲存配置檔案就馬上自動生效),不過重新啟動機器後之前修改的引數值會失效,所以只能是一種臨時引數變更方案。(適合除錯核心引數優化值的時候使用,如果設定值有問題,重啟伺服器還原原來的設定引數值了。簡單方便。)

但是如果除錯核心引數優化值結束後,需要永久儲存引數值,就要通過修改/etc/sysctl.conf內的核心引數來永久儲存更改。但只是修改sysctl檔案內的引數值,確認儲存修改檔案後,設定的引數值並不會馬上生效,如果想使引數值修改馬上生效,並且不重啟伺服器,可以執行下面的命令:
#sysctl –p

常用的核心引數:
net.ipv4.tcpsyncookies = 1
#表示開啟SYN Cookies。當出現SYN等待佇列溢位時,啟用cookies來處理,可防範少量SYN×××,預設為0,表示關閉;
net.ipv4.tcptwreuse = 1
#表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連線,預設為0,表示關閉;
net.ipv4.tcptwrecycle = 1
#表示開啟TCP連線中TIME-WAIT sockets的快速回收,預設為0,表示關閉。
net.ipv4.tcpfintimeout = 30
#表示如果套接字由本端要求關閉,這個引數決定了它保持在FIN-WAIT-2狀態的時間。
net.ipv4.tcpkeepalivetime = 1200
#表示當keepalive起用的時候,TCP傳送keepalive訊息的頻度。預設是2小時,改為20分鐘。
net.ipv4.iplocalportrange = 1024    65000
#表示用於向外連線的埠範圍。預設情況下很小:32768到61000,改為1024到65000。
net.ipv4.tcpmaxtwbuckets = 5000
#表示系統同時保持TIMEWAIT套接字的最大數量,如果超過這個數字,
#TIMEWAIT套接字將立刻被清除並列印警告資訊。預設為180000,改為5000。
#對於Apache、Nginx等伺服器,上幾行的引數可以很好地減少TIMEWAIT套接字數量,
#但是對於Squid,效果卻不大。此項引數可以控制TIMEWAIT套接字的最大數量,避免Squid伺服器被大量的TIMEWAIT套接字拖死。*
-------------------------------------------------------------------------------------------------------------------------------------------------------------
如何儘量處理TIMEWAIT過多
sysctl改兩個核心引數就行了,如下:
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
簡單來說,就是開啟系統的TIMEWAIT重用和快速回收,至於怎麼重用和快速回收,這個問題我沒有深究,實際場景中這麼做確實有效果。
用netstat或者ss觀察就能得出結論。
還有些朋友同時也會開啟syncookies這個功能,如下:
net.ipv4.tcp_syncookies = 1
開啟這個syncookies的目的實際上是:“在伺服器資源(並非單指埠資源,拒絕服務有很多種資源不足的情況)不足的情況下,儘量
不要拒絕TCP的syn(連線)請求,儘量把syn請求快取起來,留著過會兒有能力的時候處理這些TCP的連線請求”。
如果併發量真的非常非常高,開啟這個其實用處不大。

=====================================================================================================================================================================

支付系統介面效能壓力測試TPS優化之路

 

           本文案例是我們品課學院在銀行系統性能測試第一個案例,由發生至解決,通過對業務邏輯的認知、測試環境的瞭解、測試指令碼的開發、服務的監控分析優化、作業系統的監控分析與優化、從基礎軟體到架構級優化改良、測試報告的編寫等,一路艱辛,但是最終柳暗花明。

 問題總結回顧

1.    每一次攻堅效能故障問題都是一次驚喜的探險,需要清醒頭腦、大局觀的分析意識、紮實的專業基礎等更要凝結你的意志和自信心,因為這是一個艱苦而有趣的過程。

2.    紙上得來終覺淺, 絕知此事要躬行,一切效能測試與問題的分析優化,不是看完一篇文章做完幾個專案就能提升,需要持續興趣吃苦的煎熬來不斷提煉優化自己的知識與技能才能慢慢的在博大精深的效能世界裡認知了解遊趟。

故事案例發生原因

故事原因:某知名城商行代支付管理系統,因上線一段時間後客戶量劇增,在生產執行過程中偶爾會出現資源爭用現象,客戶領導擔心目前環境無法滿足業務量日益增長髮展趨勢。 因此該行邀請一家合作公司幫忙測試分析問題,該供應商跟他們合作關係一直友好也很支援,派了一位十幾年效能測試經驗的行業專家過去,因業務邏輯、技術框、測試指令碼等編寫比較複雜,工作量也大支援了十幾天後,因該效能測試專家臨時有事情,只能忍痛隱退,這時該供應商剛好專案緊張人員無法抽取,找到我們品課學院,讓我們幫忙測試,得此機會我和檸檬老師兩個利用週五晚上和週末時間過去支援,檸檬老師主要負責壓力測試和測試資料維護工作,我負責效能定位分析優化和測試報告編寫,因時間緊迫,測試過程中沒有使用第三方工具,都是直接使用命令或者資料庫自帶的函式等進行監控分析。

 測試實施場景

 到客戶現場後,客戶開發人員很耐心的幫我講解了業務邏輯、技術框架等以及環境情況,也介紹了目前情況,在高併發下TPS 大概在300筆/S,而且上下波動很大,目前尚未查出具體問題原因,希望能在最短時間內幫忙定位出來,不然已經臨近上線,還好長期在金融行業做效能測試,能短時間內理清問題的來龍去脈,也最快的時間內切入到團隊中。因每個人寫指令碼、引數化方式風格不一樣,我也看了之前指令碼編寫很規範,但是每個人對指令碼的使用方式有點差異性,我花了點時間重新修改編寫LR指令碼、shell指令碼,估計是運氣好或者有相關專案經驗,在壓力測試過程中,就發現問題並提供解決方案,通過描述問題原理以及作業系統工作原理、交易指令碼使用原理等跟客戶開發人員交流後,就這樣客戶也在最短時間內從陌生到認可,並得到大力支援,運氣來了,做事就是給力。

這次介面實時交易測試資料準備相對比較簡單,不用準備存量的業務資料,只需把對應的業務指令碼T0交易的指令碼引數化好,確保高併發下不出現業務資料重複即可,而針對T0插入到過程表的資料,通過使用verify交易進行時時查詢處理到目標表,具體指令碼如下:

1、  實時交易都是介面報文,分為socket協議指令碼T0交易和http協議指令碼verify交易,其中HTTP協議指令碼是對T0的socket指令碼插入到過程表資料進行查詢後更新然後插入到目標表。

2、  批量指令碼,需要每秒鐘通過使用FTP協議進行模擬從不同伺服器定時觸發傳輸檔案大小為1.5M的txt檔案,檔案內容是類似二維表資料方式儲存,傳輸到目標伺服器後進行檔案解析插入到對應服務目標庫。

 

涉及效能測試業務指令碼,通過loadrunner 的socker進行編寫,如下

       blob.png                                       

 

測試過程問題分析

優化前:TPS只有304筆/秒,而且不穩定,在併發到一定時間後,TPS就掉下去,而且交易成功率80%不到,隨時間推移交易成功率也會持續降低,沒辦法滿足測試方案設定預期指標550筆/S,交易成功率99%,優化前通過loadrunner壓測結果如下TPS 304筆/S:

       blob.png

1、資料庫問題分析優化方法

在壓力測試過程中發現TPS不高,而且隨著使用者併發數增加TPS反而降低,交易成功率也逐漸下降,看了各伺服器資源使用都在理想狀態下,與實現懷疑下資料庫是否有問題,因測試交易是介面測試,SQL語法相對簡單,都是單表操作,查看了資料庫相應的parameter數值,發現都是預設配置,無法滿足高併發,於是建議他們DBA修改下SGA、連線數等資料庫引數,重新併發發現TPS有明顯提升,但是還是沒辦法滿足指標要求,因為磁碟IO高了,於是抓取語法分析,都是類似如下鎖:Select ….for update,說明問題是Lock For Update,Lock Row Share,,該鎖的工作原理是在併發時會建立開啟遊標,後返回集中的資料行都將處於行級(Row-X)獨佔式鎖定,其他物件只能查詢這些資料行,不能進行update、delete或select for update操作。

3級鎖有:Insert, Update, Delete, Lock Row Exclusive
該問題說明在沒有commit之前插入同樣的一條記錄會鎖等待現象, 於是建議專案組人員把每次inster單筆資料改為改為多筆同時commit,500筆一次,然後重新壓測,發現能支援更高併發遊湖,而且TPS也上去達到預期指標值,具體抓取如下問題分析。

 

1.1  表面性能問題監控

blob.png

監控發現數據庫伺服器磁碟IO寫入一直偏高,如下圖:

blob.png

oracle函式看到後臺資料庫塊寫資料問題情況,如下,

blob.png

1.2 核心問題定位分析

通過與開發人員分析,因為T0交易在高併發下線把資料寫入到過程表,這時在通過業務查詢功能,查詢出對應的過程表資料,通過促發for update,進行查詢更新到目標表。我們通過時時關注過程表資料增加量與被更新到目標表,移除量,就是看例如每秒增加10000筆下,一次效能被更新一刀目標表資料量有多少表,例如在高併發過程表資料都能維持在100筆,而且在停止併發時,過程表資料能及時被更新遷移到目標表,說明處理能力可接受,如果更新遷移到目標表時間一直小於過程表增加速度,說明併發數高或者能力處理有問題,因出現問題才會建議專案開發人員 每次500筆commit一次,來提升TPS 和資料插入處理能力。

blob.png

而在過程表資料量逐漸增加情況下,前端TPS 也在降低,失敗率也在增加,如下圖:

blob.png 

      2、應用方面問題分析優化

而應用方面,在通過java批處理方式排程實時處理過程表資料後,對於一些業務規則判斷程式碼也做了優化,後面我們在java應用中監控到java記憶體回收使用有點異常,發現JVM引數配置不合理,記憶體回收不及時問題,通過優化配置JVM後,以及把對應應用日誌記錄級別做了修改後,TPS可以穩定在800筆/S,以上。

3、其他問題分析優化方法    

經分析後,在To對應的SOCKET交易與過程表verify對應的HTTP交易指令碼併發數配比改為2:1,然後開發人員後臺排程時時java批處理及時處理掉T0交易的資料,確保過程表資料量都在100筆以下,

blob.png

這時的TPS,穩定在660筆/S,交易成功率也在99.9%,不會大批量出錯現象。

 blob.png 

效能優化攻堅戰後期:

測試環境硬體配置:應用伺服器三臺,資料庫一臺,負載均衡伺服器一臺,在高併發下,三臺伺服器處理很輕鬆,但是在更高使用者併發下,TPS還是上不去,資源利用率也不行,反而失敗率會增加,客戶領導希望能繼續挖掘問題,因此只能在繼續發揮想象力,從全域性角度看待問題,碰巧發現作業系統的檔案控制代碼數量不足,於是讓客戶幫忙修改了下,發現TPS有適當提升,可以達到800筆/S,但是還是不能滿足現狀,發現壓力測試時間久了,TPS就會抖動,而且越往後越厲害,說明資源釋放有點問題,需要時間釋放,然後才能回收,TPS才能提升,發現HTTP交易verify在處理過程表資料的時候,埠申請數量一直增加不能馬上釋放,以為是作業系統引數設定問題,於是就修改了作業系統引數,tcp_syncookies 等引數,但是在高併發下還是有問題,後來經推敲分析是verify指令碼處理過程表資料給下游時是走HTTP協議,高併發下需要申請不同埠等,只能架構調整分離,然後在負載分發方式處理,TPS終於從800筆/S,直接飆到大於5000筆/S,而且成功率達到99.99%,資源利用率也上去了,終於大功告成。

blob.png

 

我們的攻堅團隊

在測試過程中,客戶領導、兩位開發人員、我和檸檬一起加班熬夜分析攻堅各種技術問題,才能短時間內順利的把任務完成。

效能問題定位分析是一種技術活,效能優化分析是一種藝術活,達芬奇的藝術來源以長期技術的鍛鍊積累得來靈感,而效能測試分析優化也是如此。對於一個大專案的效能測試分析優化,不是一個人能完成的事情,是需要一個團隊協作,是需要一個擁有高度的執行力的團隊,是一個責任分明的團隊,在應對突發、嚴重、緊急情況時,能通過專門的攻堅團隊來解決這類問題,這個團隊應該包括專案組的核心專業人員,有較強的動手能力和研究能力,能夠變通解決問題,思路開闊。他們的作用是至關重要的,往往可能決定專案的成敗。

 

作者:郭柏雅(泊涯)

   擁有13年軟體從業經驗,12年的金融專案效能實施管理與測試質量控制諮詢經驗,通過相關行業的開發和高階測試認證,例如SQL SERVER開發認證,高階軟體評測師認證等。

          2010年加入上市公司高偉達集團公司,具有8年測試團隊建設管理經營經驗,先後在不同城商行的專案擔任過效能測試優化專家、專案群測試管理、企業級IT測試質量控制管理規劃諮詢建設,其中包含,建行、興業銀行、鄞州銀行、唐山銀行、華夏銀行、泉州銀行、廈門銀行、四川農信等商業銀行以及菸草系統、ERP製造業、醫療等專案,也經常到高校分享職業發展技能知識和廈門本地其他企業生產效能故障分析優化支援,例如典型案例如下:

     2006年,參與某國有大行26億信貸專案建設的效能測試;

     2007年,參與某國有大行證券交易管理系統效能測試;

     2008年,參與某國有大行卡系統日終處理效能測試與優化;

     2009年,參與某國有大行所有系統使用者統