1. 程式人生 > >我和ip_conntrack不得不說的一些事

我和ip_conntrack不得不說的一些事

                面對讓人無語的ip_conntrack,我有一種說不出的感覺!自從接觸它到現在,已經兩年多了,其間我受到過它的恩惠,也被它蹂躪過,被它玩過,但是又不忍心捨棄它,因為我找不到更好的替代。工作中,學習中,用到了ip_conntrack的幾乎所有特性,然而這些都不能拿來主義得使用,過程中多少有些美中不足,多少會留下一些遺憾,總結下來,我遇到的典型而非全部的問題如下所列:1.不能馬上生效NAT問題;2.需要confirm才能用的問題;3.conntrack cache面對save/restore mark問題4.雙向NAT問題5.filter表DROP掉的流頭包所屬的流無法被conntrack...這些問題,最終讓我”發明“出很多小技巧,以下是我的handle方案:針對問題1.寫出了平滑生效NAT的模組;這個是我在在《Linux系統如何平滑生效NAT》http://blog.csdn.net/dog250/article/details/9394853中提出的,後續又進行了一些修正。針對問題2.馬上著手解決,本文目的有時候,你可能希望使用conntrack工具監控新到的資料包,於是你寫出了:conntrack -E -e NEW
但是即使有資料包進來,可能也沒有任何事件結果輸出,因為這些資料包被filter給DROP掉了,於是這個問題就是問題5。針對問題3.”提出“依賴condition match和conntrack timeout的慢速匹配模式我們不能保證總能從save/restore mark的優化中獲益,一般而言,我們設定下面的經典規則:iptables -t mangle -A PREROUTING -j CONNMARK --restore-markiptables -t mangle -A PREROUTING -m mark ! --mark 0 -j ACCEPTiptables -t mangle $以下都是逐條的慢速匹配規則,匹配到就打mark
啟用了ip_conntrack,任何資料包都要繫結到一個唯一的conntrack,由於ip_conntrack的儲存是基於超時時間(UDP,ICMP而言)或者協議(TCP而言)的,因此就會導致在上述規則增加前的一個流的頭包過去以後,新增加的依賴ip_conntrack的上述iptables規則不能生效,必須等到該conntrack過期或者協議關閉之後,下一次重新建立conntrack流時才能生效,這會引起很多莫名其妙的問題。通過一個condition match,當新iptables規則被新增時,將其置為0,關閉IPMARK帶來的優化,強行讓資料包匹配所有的策略。關閉時間設計為(max_timeout+5)秒,其中max_timeout秒為conntrack項過期的最長時間,5秒是一個誤差修正值。為了使conntrack項的最長過期時間為max_timeout秒,需要對sysctl引數進行conntrack的timeout調整,且需要禁用掉conntrack的協議限制。最終,引入了兩種策略匹配模式,在新規則新增時,會進入慢速匹配模式,所謂慢速匹配模式,就是不依賴ip_conntrack流狀態的規則完全遍歷匹配模式,反之快速匹配模式則是依賴ip_conntrack狀態的匹配模式,直接從conntrack結構體中獲取上次的匹配結果。針對問題4.模仿Cisco風格寫出了雙向靜態NAT模組
但是沒有和conntrack關聯,這是一種遺憾,以至於每個包都不得不去查詢靜態NAT雜湊,其實有更好的方案,那就是和conntrack關聯,新增一個static NAT的extension加入conntrack,無非就是對兩個方向發起的流均查一次static NAT表,而不是查iptables設定的NAT rule表。針對問題5.馬上著手解決,本文目的ip_conntrack有一個confirm邏輯,即當資料流的頭包離開協議棧的時候,會被confirm,只有被confirm的conntrack才會加入到conntrack雜湊,目前來講,離開協議棧的地點有兩個,第一個是被forward出去,即從一個網絡卡發出去,第二個是進入使用者態,即被本地socket接收。而被DROP的包不會到達這兩個點,所以就不會到達confirm點,進而永遠都不會建立conntrack條目,也就是說,被filter DROP的資料流頭包代表的整個流都無法使用conntrack的任何特性。此時你可能想到了用save/restore mark的方式,然而這也不行,因為沒有被confirm,所以就根本就沒有conntrack被加入雜湊,試問,你能將mark save到哪裡去呢?        然而這樣做是不合理的,要知道,被DROP也是一種離開協議棧的方式啊!實際上也需要confirm的。如此一來,我就可以將”被DROP“這個事實,記錄在conntrack的extension裡面,然後在下一個包進來的時候,在PREROUTING中取出這個結果,直接DROP而不用再去匹配filter ruleset,提高了效率。到底應該怎麼做呢?實際上並不需要做一個extension,僅僅在資料流頭包被DROP的時候將其conntrack給confirm一下即可。餘下的事情就可以交給IPMARK了,比如可以在mangle表和filter表設定以下的ruleset:mangle: iptables -t mangle -I PREROUTING -j CONNMARK --restore-markiptables -t mangle -A PREROUTING -m mark ! --mark 0 -j ACCEPTiptables -t mangle -A PREROUTING -m state --state ESTABLISHED -m condition --condition fastmatch -j ACCEPT....數百條mangle規則,匹配則打markiptables  -t mangle -A PREROUTING  -m mark  ! --mark 0 -j CONNMARK --save-markfilter:iptables -A FORWARD -m mark  ! --mark 0 -j DROPiptables-A FORWARD -m state --state ESTABLISHED -m condition --condition fastmatch -j ACCEPT...成百上千條filter規則,匹配則打mark;iptables -A FORWARD -m mark  ! --mark 0 -j CONNMARK --save-markiptables -A FORWARD -m mark  ! --mark 0 -j DROP如果說為了判斷該資料包是否要DROP掉就去遍歷匹配成百上千條規則,那就會大大影響效率,何不用conntrack的policy cache呢?遺憾的是,資料包被DROP導致一個流無法confirm,因此無法使用conntrack的特性,如果被DROP的資料包也能繫結到一個conntrack,那麼上述的ruleset就能省大事兒了,需要做的僅僅是在DROP的時候confirm一下而已,程式碼修改非常簡單,我一般喜歡抓住最小交集做最小的改動,需要修改的地方只有兩個:a>第一處修改:$K/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c中增加一個notifier_block,本來我想新增一個HOOK點的,可是為了不把Netfilter搞亂,我還是用了體制外的一種方法,那就是notifier_block,因為我太喜歡Netfilter的設計了,多一點就多了,少一點就殘了,所以我不對它的5個HOOK點進行任何拓展和想象,具體的修改方式如下:
//初始化listBLOCKING_NOTIFIER_HEAD(conn_notify_list);EXPORT_SYMBOL(conn_notify_list);//定義notifier_blockstatic struct notifier_block nf_filter_drop_notifier = {    .notifier_call    = nf_confirm_handler,};//在nf_conntrack_l3proto_ipv4_init中註冊這個notifier_blockblocking_notifier_chain_register(&conn_notify_list,            &nf_filter_drop_notifier);
b>第二處修改:$K/net/ipv4/netfilter/ip_tables.c的ipt_do_table的最後幾行修改一下:
#ifdef DEBUG_ALLOW_ALL        return NF_ACCEPT;#else        if (hotdrop)                return NF_DROP;        else return verdict;#endif
改為:
#ifdef DEBUG_ALLOW_ALL        return NF_ACCEPT;#else        if (hotdrop || verdict == NF_DROP) {                blocking_notifier_call_chain(&conn_notify_list, hook, skb);                return NF_DROP;        }        else return verdict;#endif
最後給出nf_confirm_handler的實現:
static intnf_confirm_handler(struct notifier_block *this, unsigned long hook, void *argv){    struct sk_buff *skb = (struct sk_buff *)argv;    switch (hook) {    case NF_INET_FORWARD:    case NF_INET_LOCAL_IN:        //本應該將in,out,skb等封裝在一個struct裡面傳過來的,但是希望早點看到結果        //就省略了,反正就用到一個skb而已,其它的暫時不管了。        ipv4_confirm(hook, skb, NULL, NULL, NULL);        break;    default:       break;    }    return NOTIFY_DONE;}
另外別忘了宣告一下這個conn_notify_list,我是在#include <linux/netfilter_ipv4.h>中宣告的:
extern struct blocking_notifier_head conn_notify_list;
編譯十分順利,用起來效果也十分明顯,在5000條iptables規則下,效能幾乎沒有任何損耗,而且也沒有報任何錯誤,因為它簡單,所以它好用。結語:conntrack主導下的整體方案(和硬體介面)我看過BSD的Netgraph,介面要比Netfilter的好用,於是我就想把Netfilter的HOOK函式的呼叫機制更改一下,不再用協議棧呼叫,而是通過event的方式來觸發!在所有的HOOK的所有event中,都可以統一使用ip_conntrack,即一切策略均可以儲存在conntrack,實現資料流頭包的一次匹配,後續包的conntrack查詢,策略提取,動作執行的序列化操作。        基本思想就是“一切均可快取在conntrack”,其中可以快取的類別包括:轉發策略:接受還是丟棄路由策略:從哪個介面發出NAT策略:如何實現地址轉換流控策略:和流控相關的配置封裝策略:VPN或者GRE感興趣流匹配策略:是否感興趣流以上的這些都可以僅僅針對一個流的流頭進行匹配,然後將結果cache到conntrack,後續的包就不必再去匹配了,而是僅僅需要查詢到對應的conntrack,取出conntrack中快取的策略,直接使用,本文以上的DROP notifiler就是為實現這個目標完成的第一步。現在我們看看現有的Netfilter有什麼問題,其實沒有什麼問題,一切都很好,唯一的問題是可能很多人都不知道如何從一個HOOK點直接呼叫另一個HOOK點的HOOK函式,人們認為只有協議棧才可以呼叫HOOK點,也就是說被PRE/POST/IN/OUT/FORWARD等字面意義迷惑了,而實際上,NF_HOOK是在任何地方都可以呼叫的,看看bridge的實現,bridge-call-iptables就是這麼玩的,到處都是從一個HOOK點函式直接呼叫NF_HOOK的例子。        好了,有了這個前提,我所謂的event機制其實早就已經有模板了,無非就是在一個HOOK函式中去觸發另一個HOOK的函式,所以改變的就是一個名字,一個HOOK函式就是一個event!下面是一個例子。我們可以僅僅針對一個流頭進行路由查詢,然後將dst_entry快取在conntrack裡面,後續的包只要對應到該conntrack,就可以取出dst_entry,此時就不必繼續進入PREROUTING-ROUTING-FORWARDING...了,而是直接觸發POSTROUTING的轉發事件,直接轉發,這樣其實也就類似BSD的Netgraph了,只是名字叫法不一樣而已。Netfilter其實早就把框架給搭好了,現在需要做的就是寫幾個HOOK函式而已,當然,並不一定非要和iptables關聯,和procfs,sysfs等關聯均可,無非就是一個核心態和使用者態的溝通方式而已!        最後是一個硬體介面問題,我之所以考慮上面那些,就是為了提高效率,要說效率,再好的演算法也比不上三流演算法注入的硬體,以上的想法和硬體結合是最好的了,所有的查表,轉發等全部由硬體實現。由於硬體不靈活,所以更是需要介面的靈活,可以互相呼叫構成一張圖,這才能發揮硬體最大的作用。        所有的查詢全部歸為conntrack的查詢,流頭慢速匹配規則,然後快取到conntrack,或者慢速匹配模式(見上述的condition match實現的慢速匹配)匹配非流頭,然後快取到conntrack,總是,二者是不見不散,類似Linux kernel的dirver和device之間的probe關係一樣。