如何扩展Linux的ip_conntrack
Linux中有一個基于Netfilter的連接跟蹤機制,即ip_conntrack,每一個conntrack表示的就是一個流,該流里面保存了大量的信息字段,這些字段本地有效,指導著數據包的轉發策略,但是我覺得這些字段信息還不夠詳細,試想,一個nfmark字段好像就可以做到一切了,但是我如果想為一個數據流綁定一個字符串怎么辦呢?也許你會說使用iptables+ipset+nfmark可以完成一切,這也是UNIX/Linux哲學的風格,一種后現代主義的風格,但是最近我上了不歸路,非要在ip_conntrack里面擴充一個字段,為我們產品加入一個基于用戶名字符串的訪問控制和審計功能,于是我有了以下看似可以的方案,順便鄙視一下紙上談兵的人:
1.完全學ipmark的樣子,在sk_buff和nf_conn里面均加一個mark字段,分別代表數據包的mark和數據流的mark
作罷的原因:需要重新編譯內核,而我不希望為了一個小小的功能重新編譯內核,背后的思想是我比較崇尚熱插拔。
2.不動sk_buff,只在nf_conn里面加一個字段,skb僅僅作為一個中轉,在iptables的target通過skb找到nf_conn,設置nf_conn的info字段
作罷的原因:Linux嚴格控制內核模塊的版本,模塊依賴的頭文件一點都不能動,如果我改變了net/netfilter/nf_conntrack.h,那么新編譯的所有的依賴nf_conntrack.ko的模塊中的符號CRC碼都會變化從而無法通過內核的驗證,我不得不學Netfilter的一個項目xtables-addons中compat-xtables的樣子,把所有的會改變CRC碼的導出函數全部再重新實現一遍,然而,天啊,我起初的想法太天真了,沒完美了的循環依賴,以至于我想罵兩句:
第一:
ip_conntrack為何不讓人擴展?雖然它有一個extend機制,但是MD簡直就是自說自話,全部都是預定義好的,就下面的枚舉里面的幾類:
你加一個新的類型,就會改變內核頭文件,既然不讓擴展,為何還叫extend呢?你干脆直接放進nf_conn就可以了,搞成extend感覺上好像多么的模塊化,多么的可插拔,實際上你能擴展的東西只能是邏輯,而不能是數據結構!
第二:
Linux為何把extend寫的那么死呢?當我突然感到這是合理的時候,我就三緘其口了,后面我會說到,數據結構需要可以自解釋,即自己解釋自己。雖然人可以看到一個結構體馬上說出它的含義,但是程序卻很難將一堆數據對應到一個結構體!自解釋,如果不知道自解釋,那就說明你根本就TM就不懂計算機!雖然你可能很精通編程...
思路
既然不能擴展nf_conn的extend,也不能在nf_conn本身加新的字段,那么只能重新編譯內核了,在重新編譯內核的時候,加入且僅僅加入一個extend類型,作為一個中間層,在這個extend中實現一個可插拔的注冊機制,以后再想加入新的擴展就可以直接在這個extend的機制上進行了。然而,我還是不想編譯內核,這是一個思想!我希望做最小的改動。萬事都難不倒偏執的人,我采用了一個常規卻不常用的方法,那就是默默地擴展結構體的大小,這也正是在《JAVA編程思想》里面學到的一個思想。思想
這其實是一種OO的思想,找到一個基類,然后擴展它,在擴展繼承的過程中實現你自己的邏輯,我擴展的是內核的nf_conn_counter結構體:struct nf_conn_counter {u_int64_t packets;u_int64_t bytes; };我希望它成為下面的樣子:
struct nf_conn_counter {u_int64_t packets;u_int64_t bytes;unsigned char *info; }; 但是我又不能改變結構體的定義,所以我采用下面等價的辦法:
struct conn_info_extends_nf_conn_counter {struct nf_conn_counter base;char *info; }info是最關鍵的。我需要做的僅僅就是在為nf_conn_counter分配空間的時候為其多加一個指針的空間即可,至于這個指針指向什么,自有調用者解釋。在我的需求中,它可能就是一個字符串,存在info信息。acct_extend原始定義為(之所以選擇對acct開刀,是因為它足夠簡單,在字面上里面,其表示統計信息,加入一個info也無可厚非):
static struct nf_ct_ext_type acct_extend __read_mostly = {.len = sizeof(struct nf_conn_counter[IP_CT_DIR_MAX]),.align = __alignof__(struct nf_conn_counter[IP_CT_DIR_MAX]),.id = NF_CT_EXT_ACCT, };
將其修改為:
struct info_compat {struct nf_conn_counter nc[IP_CT_DIR_MAX];unsigned char * info; };static struct nf_ct_ext_type acct_extend __read_mostly = {.len = sizeof(struct info_compat),.align = __alignof__(struct info_compat),.id = NF_CT_EXT_ACCT, };
到此為止,我沒有修改任何內核頭文件,接下來我來寫一個測試模塊來進行測試:
#include <linux/ip.h> #include <linux/module.h> #include <linux/skbuff.h> #include <linux/version.h> #include <net/netfilter/nf_conntrack.h> #include <net/netfilter/nf_conntrack_acct.h>MODULE_AUTHOR("xtt"); MODULE_DESCRIPTION("gll"); MODULE_LICENSE("GPL"); MODULE_ALIAS("XTT and GLL");struct nf_info {struct nf_conn_counter nc[IP_CT_DIR_MAX];char *info; };static unsigned int ipv4_conntrack_info (unsigned int hooknum,struct sk_buff *skb,const struct net_device *in,const struct net_device *out,int (*okfn)(struct sk_buff *)) {u32 addr = ip_hdr(skb)->daddr;// 測試我家的路由器的地址192.168.1.1if (addr == 0x0101a8c0) {struct nf_conn *ct;enum ip_conntrack_info ctinfo;struct nf_conn_counter *acct;struct nf_info *info;unsigned char *cn = NULL;ct = nf_ct_get(skb, &ctinfo);if (!ct || ct == &nf_conntrack_untracked)return NF_ACCEPT;acct = nf_conn_acct_find(ct);if (acct) {info = (struct nf_info *)acct;info->info = (unsigned char*) kzalloc(32, GFP_ATOMIC);if (!info->info) {return NF_ACCEPT;} // 測試將1234567890作為字符串設置到conntrackmemcpy(info->info, "1234567890", min(32, strlen("1234567890")));}}return NF_ACCEPT; }static struct nf_hook_ops ipv4_conn_info __read_mostly = {.hook = ipv4_conntrack_info,.owner = THIS_MODULE,.pf = NFPROTO_IPV4,.hooknum = NF_INET_LOCAL_OUT,.priority = NF_IP_PRI_CONNTRACK + 1, };static int __init test_info_init(void) {int err;err = nf_register_hook(&ipv4_conn_info);if (err) {return err;}return err; }static void __exit test_info_exit(void) {nf_unregister_hook(&ipv4_conn_info); }module_init(test_info_init); module_exit(test_info_exit);
到底成功了沒有呢?我需要將這一切展示在/proc/net/nf_conntrack里面,但是由于我使用了acct機制,所以我需要打開內核的acct選項:
sysctl -w net.netfilter.nf_conntrack_acct=1
真正修改的地方在/net/netfilter/nf_conntrack_standalone.c的ct_seq_show函數:
if (seq_printf(s, "use=%u ", atomic_read(&ct->ct_general.use)))goto release;{struct nf_info {struct nf_conn_counter acct[2];char *info;};struct nf_conn_counter *acct;struct nf_info *info;acct = nf_conn_acct_find(ct);if (acct) {info = (struct nf_info *)acct;if (info->info) {if (seq_printf(s, "info=%s\n", info->info)) {goto release;}}} }
在測試的時候,/proc/net/nf_conntrack中擁有了一個信息:
ipv4 ? ? 2 tcp ? ? ?6 431985 ESTABLISHED src=192.168.1.109 dst=192.168.1.1 sport=33591 dport=50 packets=2 bytes=112 src=192.168.1.1dst=192.168.1.109 sport=50 dport=33591 packets=1 bytes=60 [ASSURED] mark=0 zone=0 use=2 ?info=1234567890
這就說明這種方法是可行的,改動的地方并不多,關鍵是你要找到一個危險性最少的開刀部位,然后按照OO的思想擴展它,給它它所有沒有的行為。曾經,我對nat的extend進行了偷梁換柱,但那是不對的,正確的做法是在原有結構體的地址后面緊跟著進行擴展,類似0長度數組那種。
? ? ? 我需要解釋一下程序的自解釋了。Linux在實現nf_conntrack的extend的時候,為何將類型數值以及定義的順序用一個枚舉寫死呢?換句話說那就是為何不允許用戶隨意定義extend呢?答案是:那很難!Why?試想,如果我把一個結構體給了一個extend type。請問我把這個type存在哪?除了事先自定義一個type序列,僅存的辦法就是把這個type序列存在extend本身了,這就遇到了一個循環定義的問題,我們對此是沒有辦法的,一個程序很難看到一對數字后,然后就可以把它們看作一個結構體,起碼的字段分界就無法解決。雖然可以用OO的思想之外,剩下的解決方案就是尋求一種自解釋的數據格式。我能想到的就是ASN.1了。實際上,ASN.1也是一種事先定義的類型格式序列,只不過該序列是經過標準化的而已,一個ASN.1序列是不需要解釋的,它可以自己解釋自己,需要的僅僅是文檔。一個ASN.1序列可以將一段數據解釋為一個結構體,或者反過來也可以。OpenSSL里面的d2i/i2d就是做這個的。難道不是嗎?
? ? ? 清明時節雨紛紛,就這樣在沒有雨的一整天過去了,沒有從《JAVA編程思想》中看到什么思想,依然沒有感悟到JAVA的思想,依然沒有。
轉載于:https://blog.51cto.com/dog250/1391022
總結
以上是生活随笔為你收集整理的如何扩展Linux的ip_conntrack的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 现在很多技术知识点缺乏来龙去脉的介绍
- 下一篇: android-Service和Thre