轉自:http://blog.sina.com.cn/s/blog_a31ff26901013n07.html 1、概述 1. Netfilter/IPTables框架簡介 Netfilter/IPTables是繼2.0.x的IPfwadm、2.2.x的IPchains以後,新一代的Linux防火牆機制。Netfilter採用模塊化設計,具備良好的可擴充性。其重要工具模塊IPTables鏈接到Netfilter的架構中,並容許使用者對數據報進行過濾、地址轉換、處理等操做。 Netfilter提供了一個框架,將對網絡代碼的直接干涉降到最低,並容許用規定的接口將其餘包處理代碼以模塊的形式添加到內核中,具備極強的靈活性。 2. 主要源代碼文件 Linux內核版本:2.4.21 Netfilter主文件:net/core/netfilter.c Netfilter主頭文件:include/linux/netfilter.h IPv4相關: c文件:net/ipv4/netfilter #define PACKET_LOOPBACK 5 // 本機發給本身的報文 #define PACKET_FASTROUTE 6 // 快速路由報文 indev:輸入設備,收到數據報的網絡設備的net_device數據結構指針,即數據報到達的接口。 用於NF_IP_PRE_ROUTING和NF_IP_LOCAL_IN兩個HOOK outdev:輸出設備,數據報離開本地所要使用的網絡設備的net_device數據結構指針。 用於NF_IP_LOCAL_OUT和NF_IP_POST_ROUTING兩個HOOK 注意:在一般狀況下,在一次HOOK調用中,indev和outdev中只有一個參數會被使用 okfn:下一步要處理的函數。即若是有HOOK函數,則處理完全部的HOOK函數,且全部向該HOOK註冊過的篩選函數都返回NF_ACCEPT時,調用這個函數繼續處理;若是沒有註冊任何HOOK,則直接調用此函數。其5個參數將由宏NF_HOOK傳入。 3. HOOK點的實現 對應於各個不一樣協議的不一樣HOOK點是由一個二維數組nf_hooks存儲的(位於net/core/netfilter.c,Line 47),具體的HOOK點則由數據結構nf_hook_ops(位於include/linux/netfilter.h,Line 44)實現。如圖http://blog.chinaunix.net/photo/24896_061206192528.jpgnetfilter分析-1 所示: 其中,nf_hook_ops成員中: `int priority;`priority值越小,優先級越高,相關優先級在include/linux/netfilter_ipv4.h,Line52中枚舉定義: enum NF_IP_hook_priorities { NF_IP_PRI_FIRST = INT_MIN, NF_IP_PRI_CONNTRACK= -200, NF_IP_PRI_MANGLE = -150, NF_IP_PRI_NAT_DST = -100, NF_IP_PRI_FILTER = 0, NF_IP_PRI_NAT_SRC = 100, NF_IP_PRI_LAST = INT_MAX, }; `nf_hookfn *hook;` 爲處理函數的指針,其函數指針類型定義位於include/linux/netfilter.h,Line38,爲: typedef unsigned int nf_hookfn(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)); 這是nf_hook_ops中最關鍵的成員,其五個參數分別對應前面所解釋的NF_HOOK中弟2到6個參數, 調用HOOK的包篩選函數必須返回特定的值,這些值以宏的形式定義於頭文件include/linux/netfilter.h中(Line15),分別爲: NF_DROP(0):丟棄此數據報,禁止包繼續傳遞,不進入此後的處理流程; NF_ACCEPT(1):接收此數據報,容許包繼續傳遞,直至傳遞到鏈表最後,而進入okfn函數; 以上兩個返回值最爲常見 NF_STOLEN(2):數據報被篩選函數截獲,禁止包繼續傳遞,但並不釋放數據報的資源,這個數據報及其佔有的sk_buff仍然有效(e.g. 將分片的數據報一一截獲,而後將其裝配起來再進行其餘處理); NF_QUEQUE(3):將數據報加入用戶空間隊列,使用戶空間的程序能夠直接進行處理; 在nf_hook_slow()以及nf_reinject()函數(位於net/core/netfilter.c,Line449,Line505)中,當由調用nf_iterate()函數(位於net/core/netfilter.c,Line339,做用爲遍歷全部註冊的HOOK函數,並返回相應的NF_XX值)而返回的verdict值爲NF_QUEUE時(即當前正在執行的這個HOOK篩選函數要求將數據報加入用戶空間隊列),會調用nf_queue()函數(位於net/core/netfilter.c,Line407) nf_queue()函數將這個數據報加入用戶空間隊列nf_info(位於include/linux/netfilter.h,Line77),並保存其設備信息以備用 NF_REPEAT(4):再次調用當前這個HOOK的篩選函數,進行重複處理。 4. HOOK的註冊和註銷 HOOK的註冊和註銷分別是經過nf_register_hook()函數和nf_unregister_hook()函數(分別位於net/core/netfilter.c,Line60,76)實現的,其參數均爲一個nf_hook_ops結構,兩者的實現也很是簡單。 nf_register_hook()的工做是首先遍歷nf_hooks[][],由HOOK的優先級肯定在HOOK鏈表中的位置,而後根據優先級將該HOOK的nf_hook_ops加入鏈表; nf_unregister_hook()的工做更加簡單,其實就是將該HOOK的nf_hook_ops從鏈表中刪除。 4、IPTables系統 1. 表-規則系統 IPTables是基於Netfilter基本架構實現的一個可擴展的數據報高級管理系統,利用table、chain、rule三級來存儲數據報的各類規則。系統預約義了三個table: filter:數據報過濾表(文件net/ipv4/netfilter/iptable_filter.c) 監聽NF_IP_LOCAL_IN、NF_IP_FORWARD和NF_IP_LOCAL_OUT三個HOOK,做用是在全部數據報傳遞的關鍵點上對其進行過濾。 nat:網絡地址轉換表 監聽NF_IP_PRE_ROUTING、NF_IP_POST_ROUTING和NF_IP_LOCAL_OUT三個HOOK,做用是當新鏈接的第一個數據報通過時,在nat表中決定對其的轉換操做;然後面的其它數據報都將根據第一個數據報的結果進行相同的轉換處理。 mangle:數據報修改表(位於net/ipv4/netfilter/iptable_mangle.c) 監聽NF_IP_PRE_ROUTING和NF_IP_LOCAL_OUT兩個HOOK,做用是修改數據報報頭中的一些值。 2. 表的實現 表的基本數據結構是ipt_table(位於include/linux/netfilter_ipv4/ip_tables.h,Line413): struct ipt_table { struct list_head list; // 一個雙向鏈表 char name[IPT_TABLE_MAXNAMELEN]; // 被用戶空間使用的表函數的名字 struct ipt_replace *table; // 表初始化的模板,定義了一個初始化用的該 // 表的所默認的HOOK所包含的規則等信息, // 用戶經過系統調用進行表的替換時也要用 unsigned int valid_hooks; // 表所監聽的HOOK,實質是一個位圖 rwlock_t lock; // 整個表的讀/寫自旋鎖 struct ipt_table_info *private; // 表所存儲的數據信息,也就是實際的數據區, // 僅在處理ipt_table的代碼內部使用 struct module *me; // 若是是模塊,那麼取THIS_MODULE,不然取NULL }; 其中: `unsigned int valid_hooks;`這個位圖有兩個做用:一是檢查Netfilter中哪些HOOK對應着合法的entries;二是用來爲ipt_match以及ipt_target數據結構中的checkentry()函數覈算可能的HOOK。 `struct module *me;`當取值爲THIS_MODULE時,能夠阻止用戶rmmod一個仍然被某個規則指向的模塊的嘗試。 `struct ipt_replace *table;`的數據結構是被用戶空間用來替換一個表的,其定義位於include/linux/netfilter_ipv4/ip_tables.h,Line230: struct ipt_replace { char name[IPT_TABLE_MAXNAMELEN]; unsigned int valid_hooks; unsigned int num_entries; // 規則表入口的數量 unsigned int size; // 新的規則表的總大小 unsigned int hook_entry[NF_IP_NUMHOOKS]; // 表所監聽HOOK的規則入口, // 是對於entries[ ]的偏移 unsigned int underflow[NF_IP_NUMHOOKS]; // 規則表的最大下界 unsigned int num_counters; // 舊的計數器數目,即當前的舊entries的數目 struct ipt_counters *counters; // 舊的計數器 struct ipt_entry entries[0]; // 規則表入口 }; 上文所提到的filter、nat和mangle表分別是ipt_table這個數據結構的三個實例:packet_filter(位於net/ipv4/netfilter/iptable_filter.c,Line84)、nat_table(位於net/ipv4/netfilter/ip_nat_rule.c,Line104)以及packet_mangler(位於net/ipv4/netfilter/iptable_mangle.c,Line117) ipt_table_info(位於net/ipv4/netfilter/ip_tables.c,Line86)是實際描述規則表的數據結構: struct ipt_table_info { unsigned int size; unsigned int number; // 表項的數目 unsigned int initial_entries; // 初始表項數目 unsigned int hook_entry[NF_IP_NUMHOOKS]; // 所監聽HOOK的規則入口 unsigned int underflow[NF_IP_NUMHOOKS]; // 規則表的最大下界 char entries[0] ____cacheline_aligned; // 規則表入口,即真正的規則存儲結構 // ipt_entry組成塊的起始地址,對多CPU,每一個CPU對應一個 }; 3. 規則的實現 IPTables中的規則表能夠在用戶空間中使用,但它所採用的數據結構與內核空間中的是同樣的,只不過有些成員不會在用戶空間中使用。 一個完整的規則由三個數據結構共同實現,分別是: 一個ipt_entry結構,存儲規則的總體信息; 0或多個ipt_entry_match結構,存放各類match,每一個結構均可以存聽任意的數據,這樣也就擁有了良好的可擴展性; 1個ipt_entry_target結構,存放規則的target,相似的,每一個結構也能夠存聽任意的數據。 下面將依次對這三個數據結構進行分析: 存儲規則總體的結構ipt_entry,其形式是一個鏈表(位於include/linux/netfilter_ipv4/ip_tables.h,Line122): struct ipt_entry { struct ipt_ip ip; unsigned int nfcache; u_int16_t target_offset; u_int16_t next_offset; unsigned int comefrom; struct ipt_counters counters; unsigned char elems[0]; }; 其成員以下: `struct ipt_ip ip;`:這是對其將要進行匹配動做的IP數據報報頭的描述,其定義於include/linux/netfilter_ipv4/ip_tables.h,Line122,其成員包括源/目的IP及其掩碼,出入端口及其掩碼,協議族、標誌/取反flag等信息。 `unsigned int nfcache;`:HOOK函數返回的cache標識,用以說明通過這個規則後數據報的狀態,其可能值有三個,定義於include/linux/netfilter.h,Line23: #define NFC_ALTERED 0x8000 //已改變 #define NFC_UNKNOWN 0x4000 //不肯定 另外一個可能值是0,即沒有改變。 `u_int16_t target_offset;`:指出了target的數據結構ipt_entry_target的起始位置,即從ipt_entry的起始地址到match存儲結束的位置 `u_int16_t next_offset;`:指出了整條規則的大小,也就是下一條規則的起始地址,即ipt_entry的起始地址到match偏移再到target存儲結束的位置 `unsigned int comefrom;`:所謂的「back pointer」,據引用此變量的代碼(主要是net/ipv4/netfilter/ip_tables.c中)來看,它應該是指向數據報所經歷的上一個規則地址,由此實現對數據報行爲的跟蹤 `struct ipt_counters counters;`:說明了匹配這個規則的數據報的計數以及字節計數(定義於include/linux/netfilter_ipv4/ip_tables.h,Line100) `unsigned char elems[0];`:表示擴展的match開始的具體位置(由於它是大小不肯定的),固然,若是不存在擴展的match那麼就是target的開始位置 擴展match的存儲結構ipt_entry_match,位於include/linux/netfilter_ipv4/ip_tables.h,Line48: struct ipt_entry_match { union { struct { u_int16_t match_size; char name[IPT_FUNCTION_MAXNAMELEN]; } user; struct { u_int16_t match_size; struct ipt_match *match; } kernel; u_int16_t match_size; //總長度 } u; unsigned char data[0]; }; 其中描述match大小的`u_int16_t match_size;`,從涉及這個變量的源碼看來,在使用的時候須要注意使用一個宏IPT_ALIGN(位於include/linux/netfilter_ipv4/ip_tables.h,Line445)來進行4的對齊處理(0x3 & 0xfffffffc),這應該是因爲match、target擴展後大小的不肯定性決定的。 在結構中,用戶空間與內核空間爲不一樣的實現,內核空間中的描述擁有更多的信息。在用戶空間中存放的僅僅是match的名稱,而在內核空間中存放的則是一個指向ipt_match結構的指針 結構ipt_match位於include/linux/netfilter_ipv4/ip_tables.h,Line342: struct ipt_match { struct list_head list; const char name[IPT_FUNCTION_MAXNAMELEN]; int (*match)(const struct sk_buff *skb, const struct net_device *in, const struct net_device *out, const void *matchinfo, // 指向規則中match數據的指針, // 具體是什麼數據結構依狀況而定 int offset, // IP數據報的偏移 const void *hdr, // 指向協議頭的指針 u_int16_t datalen, // 實際數據長度,即數據報長度-IP頭長度 int *hotdrop); int (*checkentry)(const char *tablename, // 可用的表 const struct ipt_ip *ip, void *matchinfo, unsigned int matchinfosize, unsigned int hook_mask); // 對應HOOK的位圖 void (*destroy)(void *matchinfo, unsigned int matchinfosize); struct module *me; }; 其中幾個重要成員: `int (*match)(……);`:指向用該match進行匹配時的匹配函數的指針,match相關的核心實現。返回0時hotdrop置1,當即丟棄數據報;返回非0表示匹配成功。 `int (*checkentry)(……);`:當試圖插入新的match表項時調用這個指針所指向的函數,對新的match表項進行有效性檢查,即檢查參數是否合法;若是返回false,規則就不會被接受(譬如,一個TCP的match只會TCP包,而不會接受其它)。 `void (*destroy)(……);`:當試圖刪除一個使用這個match的表項時,即模塊釋放時,調用這個指針所指向的函數。咱們能夠在checkentry中動態地分配資源,並在destroy中將其釋放。 擴展target的存儲結構ipt_entry_target,位於include/linux/netfilter_ipv4/ip_tables.h,Line71,這個結構與ipt_entry_match結構相似,同時其中描述內核空間target的結構ipt_target(位於include/linux/netfilter_ipv4/ip_tables.h,Line375)也與ipt_match相似,只不過其中的target()函數返回值不是0/1,而是verdict。 而target的實際使用中,是用一個結構ipt_standard_target專門來描述,這纔是實際的target描述數據結構(位於include/linux/netfilter_ipv4/ip_tables.h,Line94),它實際上就是一個ipt_entry_target加一個verdict。 其中成員verdict這個變量是一個很巧妙的設計,也是一個很是重要的東東,其值的正負有着不一樣的意義。我沒有找到這個變量的中文名稱,在內核開發者的新聞組中稱這個變量爲「a magic number」。 它的可能值包括IPT_CONTINUE、IPT_RETURN以及前文所述的NF_DROP等值,那麼它的做用是什麼呢? 因爲IPTables是在用戶空間中執行的,也就是說Netfilter/IPTables這個框架須要用戶態與內核態之間的數據交換以及識別。而在具體的程序中,verdict做爲`struct ipt_standard_target`的一個成員,也是對於`struct ipt_entry_target`中的target()函數的返回值。這個返回值標識的是target()所對應的執行動做,包括系統的默認動做以及外部提交的自定義動做。 可是,在用戶空間中提交的數據每每是相似於「ACCPET」之類的字符串,在程序處理時則是以`#define NF_ACCEPT 1`的形式來進行的;而實際上,以上那些執行動做是以鏈表的數據結構進行存儲的,在內核空間中表現爲偏移。 因而,verdict實際上描述了兩個本質相同但實現不一樣的值:一個是用戶空間中的執行動做,另外一個則是內核空間中在鏈表中的偏移——而這就出現了衝突。 解決這種衝突的方法就是:用正值表示內核中的偏移,而用負值來表示數據報的那些默認動做,而外部提交的自定義動做則也是用正值來表示。這樣,在實際使用這個verdict時,咱們就能夠經過判斷值的正負來進行相應的處理了。 位於net/ipv4/netfilter/ip_tables.h中的函數ipt_do_table()中有一個典型的verdict使用(Line335,其中v是一個verdict的實例): if (v !=IPT_RETURN) { verdict = (unsigned)(-v) - 1; break; } 其中的IPT_RETURN定義爲: #define IPT_RETURN (-NF_MAX_VERDICT – 1) 而宏NF_MAX_VERDICT實際上就是: #define NF_MAX_VERDICT NF_REPEAT 這樣,實際上IPT_RETURN的值就是-NF_REPEAT-1,也就是對應REPEAT,這就是對執行動做的實際描述;而咱們能夠看到,在下面對verdict進行賦值時,它所使用的值是`(unsigned)(-v) – 1`,這就是在內核中實際對偏移進行定位時所使用的值。 那麼總之呢,表和規則的實現如圖http://blog.chinaunix.net/photo/24896_061206192551.jpg所示: 從上圖中不難發現,match的定位以下: 起始地址爲:當前規則(起始)地址+sizeof(struct ipt_entry); 結束地址爲:當前規則(起始)地址+ipt_entry->target_offset; 每個match的大小爲:ipt_entry_match->u.match_size。 target的定位則爲: 起始地址爲match的結束地址,即:當前規則(起始)地址+ipt_entry-> target_offset; 結束地址爲下一條規則的起始地址,即:當前規則(起始)地址+ipt_entry-> next_offset; 每個target的大小爲:ipt_entry_target->u.target_size。 這些對於理解match以及target相關函數的實現是頗有必要明確的。 同時,include/linux/netfilter_ipv4/ip_tables.h中提供了三個「helper functions」,可用於使對於entry、tartget和match的操做變得方便,分別是: 函數ipt_get_target():Line274,做用是取得target的起始地址,也就是上面所說的當前規則(起始)地址+ipt_entry-> target_offset; 宏IPT_MATCH_ITERATE():Line281,做用是遍歷規則的全部match,並執行同一個(參數中)給定的函數。其參數爲一個ipt_entry_match結構和一個函數,以及函數須要的參數。當返回值爲0時,表示遍歷以及函數執行順利完成;返回非0值時則意味着出現問題已終止。 宏IPT_ENTRY_ITERATE():Line300,做用是遍歷一個表中的全部規則,並執行同一個給定的函數。其參數爲一個ipt_entry結構、整個規則表的大小,以及一個函數和其所需參數。其返回值的意義與宏IPT_MATCH_ITERATE()相似。 那麼如何保證傳入的ipt_entry結構是整個規則表的第一個結構呢?據源碼看來,實際調用這個宏的時候傳入的第一個參數都是某個ipt_table_info結構的實例所指向的entries成員,這樣就保證了對整個規則表的完整遍歷。 4. 規則的使用 當一個特定的HOOK被激活時,數據報就開始進入Netfilter/IPTables系統進行遍歷,首先檢查`struct ipt_ip ip`,而後數據報將依次遍歷各個match,也就是`struct ipt_entry_match`,並執行相應的match函數,即ipt_match結構中的*match所指向的函數。當match函數匹配不成功時返回0,或者hotdrop被置爲1時,遍歷將會中止。 對match的遍歷完成後,會開始檢查`struct ipt_entry_target`,其中若是是一個標準的target,那麼會檢查`struct ipt_standard_target`中的verdict,若是verdict值是正的而偏移卻指向不正確的位置,那麼ipt_entry中的comefrom成員就有了用武之地——數據報返回所經歷的上一個規則。對於非標準的target呢,就會調用target()函數,而後根據其返回值進行後面的處理。 5. 規則的擴展 Netfilter/IPTables提供了對規則進行擴展的機制:能夠寫一個LKM來擴展內核空間的功能,也能夠寫一個共享庫來擴展用戶空間中IPTables的功能。 內核的擴展 要對內核空間的功能進行擴展,實際上就是寫一個具備表、match以及target增長功能的模塊,相關的函數爲(位於net/ipv4/netfilter/ip_tables.c,Line1318 to 1444): ipt_register_table()、ipt_unregister_table(),參數爲struct ipt_table *。 ipt_register_table()函數是這三對函數中最複雜的一個,涉及了內存、信號量等方方面面的東西,但總起來講就作了初始化表以及加入雙向鏈表兩件事。 其複雜一是由於涉及到多CPU的處理(每一個CPU擁有各自獨立的「規則空間」),須要首先將新的entries放入第一個CPU空間,在檢查完畢後再複製到其餘CPU中;二是就是上面所說對新table各個entry的檢查,包括邊界檢查以及完整性檢查等。 其中的重要函數有這麼幾個: translate_table()(位於net/ipv4/netfilter/ip_tables.c,Line797):這個函數的主要做用是檢查並應用用戶空間傳來的規則 : 對新表進行邊界檢查(由宏IPT_ENTRY_ITERATE()調用函數check_entry_size_and_blocks(),位於net/ipv4/netfilter/ip_tables.c,Line732),包括對齊、過大太小等,特別是保證賦給hook_entries和underflows值的正確性。 調用函數make_source_chains()(位於net/ipv4/netfilter/ip_tables.c,Line499)檢查新的表中是否存在規則環,同時將HOOK的規則遍歷順序存入comefrom變量。(這個函數我沒有仔細看,只是大概略了一下) 對ipt_entry依次進行ipt_ip頭、match以及target的完整性檢查(由宏IPT_ENTRY_ITERATE()調用函數check_entry(),位於net/ipv4/netfilter/ip_tables.c,Line676),保證ipt_entry的正確性。 將正確的 ipt_tables複製給其餘的CPU。 這個函數另外還在do_replace()函數(僅在一個源碼中沒有被調用過的函數中被調用,不予分析)中被調用。 replace_table()(位於net/ipv4/netfilter/ip_tables.c,Line877):這個函數的主要做用是:將獲得模塊初始值的ipt_table_info結構(newinfo)中的值傳給ipt_table中的private,並返回ip_table中舊的private。 list_prepend()(位於include/linux/netfilter_ipv4/listhelp.h,Line75):在這個函數被調用以前,整個初始化的過程就已經結束了,這個函數的主要做用是:互斥地調用Linux源碼中的list_add()函數(位於include/linux/list.h,Line55),將新的table加入到雙向鏈表之中。 ipt_register_match()、ipt_unregister_match(),參數爲struct ipt_match *。 ipt_register_target()、ipt_unregister_target(),參數爲struct ipt_target *。 這三對函數除了ipt_register_table()外的5個函數主要就是互斥地將table/match/target加入到雙向鏈表中或者從雙向鏈表中刪除。 其中向雙向鏈表中加入新節點是經過調用list_named_insert()函數(位於include/linux/netfilter_ipv4/listhelp.h,Line101)實現的。這個函數的主要工做是首先肯定待插入的match名字是否已經存在,只有不存在時才進行插入的操做。 用戶空間的擴展 用戶空間中的擴展用的是共享庫配合libiptc庫的機制,但這種機制是在單獨的IPTbales程序中提供的,內核源碼中並無提供,這裏就不作分析了。