歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux內核

不要盲目增加ip_conntrack_max-理解Linux內核內存

1.由ip_conntrack引出的Linux內存映射

有很多文章在討論關於ip_conntrack表爆滿之後丟棄數據包的問題,對此研究深入一些的知道Linux有個內核參數ip_conntrack_max,在擁有較大內存的機器中默認65536,於是瘋狂的增加這個參數,比如設置成10000…00,只要不報設置方面的錯誤,就一定要設置成最大值。這種方式實在是將軟件看成大神了,殊不知軟件的技術含量還不如鍋爐呢!
       如果考慮的再全面一些,比如經驗豐富的程序員或者網管,可能會想到內存的問題,他們知道所有的連接跟蹤信息都是保存於內存中的,因此會考慮單純放大這個ip_conntrack_max參數會占據多少內存,會權衡內存的占用,如果系統沒有太大的內存,就不會將此值設置的太高。
       但是如果你的系統有很大的內存呢?比如有8G的內存,分個1G給連接跟蹤也不算什麼啊,這是合理的,然而在傳統的32位架構Linux中是做不到,為什麼?因為你可能根本不懂Linux內核的內存管理方式。
       內存越來越便宜的今天,linux的內存映射方式確實有點過時了。然而事實就擺在那裡,ip_conntrack處於內核空間,它所需的內存必須映射到內核空間,而傳統的32位Linux內存映射方式只有1G屬於內核,這1G的地址空間中,前896M是和物理內存一一線性映射的,後面的若干空洞之後,有若干vmalloc的空間,這些vmalloc空間和一一映射空間相比,很小很小,算上4G封頂下面的很小的映射空間,一共可以讓內核使用的地址空間不超過1G。對於ip_conntrack來講,由於其使用slab分配器,因此它還必須使用一一映射的地址空間,這就是說,它最多只能使用不到896M的內存!

       為何Linux使用如此“落後”的內存映射機制這麼多年還不改進?其實這種對內核空間內存十分苛刻的設計在64位架構下有了很大的改觀,然而問題依然存在,即使64位架構,內核也無法做到透明訪問所有的物理內存,它同樣需要把物理內存映射到內核地址空間後才能訪問,對於一一映射,這種映射是事先確定的,對於大小有限(實際上很小)非一一映射空間,需要動態創建頁表,頁目錄等。另外還有一個解釋,那就是“內核本來就不該做ip_conntrack這種事”,那是協議棧的事,而不巧的是,Liunx的協議棧完全在內核中實現,可能在skb接收軟中斷中處理的ip_conntrack不能睡眠,因此也就不能將此任務交給進程,也就不能利用進程地址空間(進程地址空間[用戶態+內核態]可以訪問所有的物理內存)。

       Linux之所以對內核內存要求如此苛刻,目的就是不想讓你隨意使用,因為它寶貴,你才更要珍惜它們。

2.在32位架構Linux系統上的實驗

以下是為了證明以上的事實所作的實驗,可能實驗中使用的一些手段仍然不符合常識,然而我覺得成一家之言即可,畢竟這種方案永遠不會也不可能出現在公司的標准文檔上,那樣會讓人學會投機取巧或者稱偷懶,但是為了備忘,還得有個地方留著,那就寫成博客吧。
       還有一個參數會影響查找連接跟蹤的時間復雜度和空間復雜度,那就是ip_conntrack_buckets。該值描述了哈希桶的數量,理論上,這個值越大,哈希碰撞就會越小,查找時間就會越快,但是需要為每一個桶預分配一塊不是很大的內存,如果桶數量很大,就會占用很大的內存,並且這些內存還都是寶貴的“僅有1G空間內的內核內存”,和ip_conntrack結構體的分配策略不同,這個哈希桶可以分配在vmalloc空間而不一定非要在一一線性映射空間。

2.1.快速壓滿ip_conntrack的方法

使用loadrunner絕對是一種方式,然而術業有專攻,工作之余我又很討厭windows上的一切,因此需要采用其它方式,下班在家,只身一人,也不想使用netcat之類的“瑞士軍刀”,我怕端口占滿,又怕我的macbook狂熱,因此需要再想辦法。由於目的只是想測試ip_conntrack最多能占用多少內存,其實這個我早就知道了,只是想證實一下子,那麼辦法也就有了,那就是增加ip_conntrack結構體的大小,而這很容易,只需要在結構體後面增加一個很大的字段即可。下面的修改基於Red Hat Enterprise 5的2.6.18內核

2.2.測試前對ip_conntrack內核模塊的修改

編輯$build/include/linux/netfilter_ipv4/ip_conntrack.h文件,在結構體ip_conntrack的最後加上下面一句:
  1. char aaa[102400]; //這個102400是通過二分法得到的,如果設置成2xxxxx則在加載的時候就會使內核crash,因為這個數組是直接分配(類似棧上分配)的而不是動態分配的,它載入的時候很可能會沖掉內核的關鍵數據,因此還是選取一個可行的數值,然後慢慢加連接吧,畢竟擴大了起碼100000倍呢~~  
進入$src/net/ipv4/netfilter,執行:
  1. make –C /lib/modules/2.6.18-92.e15/build SUBDIRS=`pwd` modules  
如此一來加載ip_conntrack.ko之後,內核日志將打印出:
ip_conntrack version 2.4 (8192 buckets, 65536 max) - 102628 bytes per conntrack
由此看出ip_conntrack結構體已經增大了,這樣撐滿整個可用內存所需的網絡連接壓力就大大減小了,也就不用什麼loadrunner之類的東西了。為了盡快撐滿可以使用的內存,還要將關於ip_conntrack的所有timeout設置的比較長,相當長:
  1. sysctl -w net.ipv4.netfilter.ip_conntrack_generic_timeout=600000  
  2. sysctl -w net.ipv4.netfilter.ip_conntrack_icmp_timeout=300000  
  3. sysctl -w net.ipv4.netfilter.ip_conntrack_tcp_timeout_close=1000000  
  4. sysctl -w net.ipv4.netfilter.ip_conntrack_tcp_timeout_time_wait=120000  
  5. sysctl -w net.ipv4.netfilter.ip_conntrack_tcp_timeout_last_ack=300000  
  6. sysctl -w net.ipv4.netfilter.ip_conntrack_tcp_timeout_close_wait=60000  
  7. sysctl -w net.ipv4.netfilter.ip_conntrack_tcp_timeout_fin_wait=120000  
  8. sysctl -w net.ipv4.netfilter.ip_conntrack_tcp_timeout_established=432000  
  9. sysctl -w net.ipv4.netfilter.ip_conntrack_tcp_timeout_syn_recv=600000  
  10. sysctl -w net.ipv4.netfilter.ip_conntrack_tcp_timeout_syn_sent=120000  
這樣既有的一個流就會“永久保持”了,一直占著ip_conntrack結構體不放,直到可用的內存溢出。
在加載了ip_conntrack模塊之後,所有過往的數據包就會自動被追蹤,下面編寫以下腳本:
  1. for (( i=1; i<255; i++));  
  2. do  
  3.     for (( j=1; j<255; j++));  
  4.     do  
  5.         ping 192.168.$i.$j -c 1 -W 1  
  6.         curl --connect-timeout 1 http://138.$i.$j.80/   
  7.         curl --connect-timeout 1 http://38.$i.$j.80/   
  8.         curl --connect-timeout 1 http://$i.1.$j.80/   
  9.         curl --connect-timeout 1 http://$j.$i.9.8/   
  10.     done  
  11. done  
Copyright © Linux教程網 All Rights Reserved