標准IP路由查找的過程為我們提供了一個極好的“匹配-動作”的例程。即匹配到一個路由項,然後將數據包發給該路由項指示的下一跳。
如果我們把上面對IP路由查找的過程向上抽象一個層次,就會發現,其實它還可以有別的用。抽象後的表述為:以數據包的源地址或者目標地址為鍵值去查詢一張表,查到結果項以後執行結果項指示的一個動作。一個結果項為:
struct result_node {
uint32 network;
uint32 netmask;
void *action;
};
以上這個思想多虧了路由查找中的“最長前綴匹配”原則,該原則是隱式的,但是該原則保證了最精確的匹配。
在我的特殊場景中,我希望一個網絡段(一個大的網絡,一個小一點的網絡,一台主機)發出的數據流和一個字符串描述關聯起來,該字符串可以是描述,可以是用戶名,它甚至可以是別的任意什麼東西...以往這種情況會被認為是不符合UNIX哲學的,並且在以往,內存使用方式太小氣,內存太奢侈。但是現如今,不需要吝啬內存了,我們便可以在內核裡面塞入任何可以塞入的東西,只要設計得當,讓它符合UNIX哲學精神即可。
總的來講,我希望從一個到達的數據包上取出一個事先配置好的字符串。
實際上我的想法一開始就是錯誤的。正確性在於我知道我是錯的。為何一直以來我一直在“修補”Linux內核協議棧以及Netfilter擴展的各種不良或者不完備的實現呢,比如“立即生效NAT”,比如雙向靜態NAT,比如不完備的conntrack confirm機制,不一而足。這些缺陷難道Linux內核以及Netfilter社區的那幫大牛們意識不到嗎?絕對不是這樣,因為他們遵循的是Worse is better原則,該原則的核心就是簡單主宰一切,為了簡單可以捨棄該捨棄的一切。
而我的做法,即實現很多協議棧不包含的東西,看起來就是違背的就是Worse is better原則,我用Completeness(完備性)原則替換了Simplicity(簡單性)原則,而這兩個的原則的正確表述應該是:
既然是給數據流綁定一個字符串,很顯然,擴展nf_conntrack是絕佳的選擇,如何擴展它我在前文已經做了詳述。接下來需要考慮的是如何設置規則,很顯然,寫一個INFO iptables模塊是一個選擇:
iptables -t ...-A .... -j INFO --set-info "aaaaaaaaaaaa"
INFO模塊的思想是:從skb取出conntrack結構體,進而取出acct擴展(我總是喜歡拿account擴展開刀),然後把set-info參數指示的信息拷貝到acct擴展中。
但是,如果有10000個info信息需要設置,我就要建立10000條規則,每一個數據包都要在內核中順序的遍歷以上所有的規則,iptables規則太多(超過5000條)的話,確實會嚴重影響網絡性能。當然你可以靈活安排規則的順序以便使數據包快速結束一個鏈的匹配過程,比如以下這樣:
iptables ... -m state --state ESTABLISHED -j ACCEPT (設置在第一條,因為只有針對一個流的第一個NEW狀態的數據包才會有set-info的必要)
.... -j INFO --set-info aaaaa
.... -j INFO --set-info bbbbb
.... -j INFO --set-info ccccc
這確實是一種技巧,玩過iptables的都知道,但是由於存在以下4個問題導致我不得不尋找其它的方案:
1.當INFO和INFO之外的其它target並不一致同意通過以上的方式跳出規則匹配鏈的時候,就需要安排另一條自定義鏈來解決。
2.由於上述第1點的頻繁操作,最終會演變成針對iptables的編程,規則集本身會越來越復雜,像寫得不好的C++代碼那樣,調整一條規則的順序都將是極其困難的。
3.退一萬步說話,對一個NEW狀態的包,它真的就需要遍歷規則(注意,iptables是遍歷)匹配,我需要一種比O(n)高效的算法(nf-HiPAC在5000+條規則情況下會好很多)。
4.Netflter的nf-HiPAC項目可能是個創舉,但是我沒有時間詳細研究它。
UNIX思想教導我們要將問題拆成互相獨立的小問題,然後讓它們相互配合去解決最終的大問題,但是相互配合本身有時會成為新的問題,需要付出巨大的管理成本。並不是每一類問題都可以拆成cat file|grep key那樣的方式的。
當我發現IP路由的查找過程其實就是一個“匹配-動作”的模式之後,我便改了“動作”的解釋,將“發往下一跳”改為“取出info信息”,注意,只是取出info信息,並沒有設置它到conntrack,為何不把這兩件事一起做呢?因為這兩件事之間的關系和cat file|grep key之間的關系很像。上一小節的4個問題,我是這麼回答的:
1.使用路由查找模塊是完全獨立於iptables,和iptables沒有半毛錢關系;
2.使用路由查找模塊不需要和任何其它模塊接口,你只需要調用以下接口:
int nf_route_table_search(const u_int32_t src, char *info, int len);
如果src找到,則將和該地址所在的最長前綴匹配的網絡關聯的info信息取出來,如果沒有與之關聯的,則返回非0
3.效率更不用說,完全就是Linux內核的hash路由查找算法,用了十幾年了,跑在各種環境下。
4.雖然我沒有時間去研究nf-HiPAC項目,但是我對路由查找算法卻早已精通,一個思想是:永遠使用自己最熟悉的技術。
更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2014-04/100911p2.htm