文章目錄
- 註解:不要過度減小NEW以及TCP的establish的CT狀態的timeout的原因
- 最後有個提問:
增加nf_conntrack_max固然可以緩解這個問題,或者說減小conntrack表項佔據核心記憶體的時間也可以緩解之,然而這種補救措施都是治標不治本的.
註解:不要過度減小NEW以及TCP的establish的CT狀態的timeout的原因
盡量不要減小NEW狀態時間,因為對於某些惡劣的網路,一個資料包的來回確實需要很長時間,對於TCP而言,此時RTT還沒有測量呢。如果NEW狀態的conntrack保留時間過短,就會導致大量NEW狀態的串連,而對於很多依賴ctstate的模組而言,這樣就會有問題,比如iptables的filter表中使用ESTABLISH狀態來放過前向包的返回包就會有問題,此時ip_conntrack很有可能由於NEW狀態時間過短而將返回包作為NEW狀態處理而不是ESTABLISH狀態,如此一來,返回包就無法通過了。如所示:
使用簡單的實驗可以很容易證實上面的圖示,以簡單的udp通訊為例,編寫一個udp-echo程式,伺服器簡單echo用戶端送達的字串:
for(;;) { n = recvfrom(sd, msg, MAXLINE, 0, pcliaddr, &len); sleep(5); sendto(sd, msg, n, 0, pcliaddr, len); }
然後在用戶端上執行echo $sec /proc/sys/net/ipv4/netfilter/ip_conntrack_udp_timeout
其中sec要比伺服器端的sleep參數更小即可。
如此udp用戶端將收不到伺服器eho回來的字串,因為用戶端只是允許存取狀態為establish的入流量,如果ip_conntrack_udp_timeout配置過於短暫,NEW狀態的conntrack過早被釋放,這樣將不會有establish狀態的流量了。對於UDP而言,由於它是不確認無串連允許丟包的,因此影響還不是很大,TCP也有類似的問題,那就是如果你串連一個很遠的且網路狀況很惡劣的TCP伺服器,然後你把ip_conntrack_tcp_timeout_synsent設定很小,這樣就幾乎完不成三向交握了,更進一步,如果你把ip_conntrack_tcp_timeout_established設定過小,那麼一旦三向交握建立串連之後,用戶端和伺服器之間很久不發包,當establish狀態到期後,conntrack被釋放,此時伺服器端主動發來一個包,該包的conntrack狀態會是什麼呢?因此給予tcp的establish狀態5天的時間,是可以理解的。需要注意的是,對於tcp而言,由於無法簡單的控制伺服器發送syn-ack的延時,因此需要在establish狀態而不是new狀態做文章了(實際上,ip_conntrack的establish狀態映射成了tcp的多個狀態,包括syn-ack,ack,established),試試看,效果和udp的一樣。
前面關於ip_conntrack扯的太遠了,我們的首要問題是conntrack full的問題。實際上,如果深入思考這個conntrack full的問題,就會發現,並不是conntrack容量太小或者表項保留時間過長引發的full。現實中的萬事萬物都不是無限的,對於電腦資源而言,更應該節約使用,不能讓無關人士浪費這種資源,另外既然核心預設了一個表項的存活時間,那肯定是經過測試的經驗值,自有它的道理。因此本質問題在於很多不需要conntrack的包也被conntrack了,這樣就會擠掉很多真正需要conntrack的流量。
那麼都是哪些流量需要conntrack呢?常用的就兩個,一個是任何使用ctstate或者state這些match的iptables規則,另外一個就是所有的iptables的nat表中的規則,如果我們事Crowdsourced Security Testing道哪些流量需要使用iptables的[ct]state來控制,並且也知道哪些流量需要做NAT,那麼餘下的流量就都是和conntrack無關的流量了,可以不被ip_conntrack來跟蹤。
幸運的是,Linux的Netfilter在PREROUTING以及OUTPUT這兩個HOOK的conntrack之前安插了一個優先順序更高的table,那就是raw,通過它就可以分離出不需要被conntrack的流量。如果你確定只有某個網卡進來的流量才需要做NAT,那麼就執行下面的規則:
iptables -t raw -A PREROUTING ! –I $網卡 -j NOTRACKiptables –t raw –A OUTPUT –j NOTRACK
這樣一來,資源就不會浪費在無關人士身上了,效能也會有所提高,因為凡是NOTRACK的流量,都不會去查詢conntrack的hash表,因為在ip(nf)_conntrack_in的內部的開始有一個判斷:
if ((*pskb)->nfct) return NF_ACCEPT;
而NOTRACK這個target的實現也很簡單:
(*pskb)->nfct = &ip_conntrack_untracked.info[IP_CT_NEW];
事實上將一個佔位者設定給skb的nfct,這樣可以保持其它代碼的一致性。
可見,必要時同時採取三種方式比較有效:1.增大conntrack_max;2.減少狀態儲存時間;3.分離無關流量。然而除了第三種方式,其餘兩種方式在操作時必須給自己十足的理由那麼做才行,對於1,比必須明白核心記憶體被佔有的方式,對於2,看看本文的前半部分。
iptables -A FORWARD -m state --state UNTRACKED -j ACCEPT
最後有個提問:
對於沒有keepalive的TCP串連而言,試想伺服器和用戶端在establish狀態之後5天內都沒有互相通訊,5天后的一天,伺服器主動發送了一個資料包給用戶端,然而此時防火牆/NAT裝置上的conntrack狀態已經到期被刪除,此時該資料包將會被認為是NEW狀態的資料包,被DROP,用戶端永遠收不到這個資料包,進而也不會發送ACK,伺服器端不斷重發,不斷被防火牆DROP,當重發次數達到一定次數後,伺服器RESET該串連,然而用戶端如何得知,只有用戶端主動發包才能打破這個僵局,然而誰能保證用戶端一定會主動發包?這是不是Linux的ip_conntrack的一種缺陷,設計5天時間的establish狀態是不是一種極限措施,然而誰又能保證5天內兩端不斷通訊呢?