【转】Linux Netfilter实现机制和扩展技术
轉(zhuǎn)自https://www.ibm.com/developerworks/cn/linux/l-ntflt/index.html
作者:?楊沙洲
?
2.4.x的內(nèi)核相對于2.2.x在IP協(xié)議棧部分有比較大的改動, Netfilter-iptables更是其一大特色,由于它功能強大,并且與內(nèi)核完美結(jié)合,因此迅速成為Linux平臺下進行網(wǎng)絡(luò)應(yīng)用擴展的主要利器,這些擴展不僅包括防火墻的實現(xiàn)--這只是Netfilter-iptables的基本功能--還包括各種報文處理工作(如報文加密、報文分類統(tǒng)計等),甚至還可以借助Netfilter-iptables機制來實現(xiàn)虛擬專用網(wǎng)(VPN)。本文將致力于深入剖析Netfilter-iptables的組織結(jié)構(gòu),并詳細介紹如何對其進行擴展。Netfilter目前已在ARP、IPv4和IPv6中實現(xiàn),考慮到IPv4是目前網(wǎng)絡(luò)應(yīng)用的主流,本文僅就IPv4的Netfilter實現(xiàn)進行分析。
要想理解Netfilter的工作原理,必須從對Linux IP報文處理流程的分析開始,Netfilter正是將自己緊密地構(gòu)建在這一流程之中的。
1. IP Packet Flowing
IP協(xié)議棧是Linux操作系統(tǒng)的主要組成部分,也是Linux的特色之一,素以高效穩(wěn)定著稱。Netfilter與IP協(xié)議棧是密切結(jié)合在一起的,要想理解Netfilter的工作方式,必須理解IP協(xié)議棧是如何對報文進行處理的。下面將通過一個經(jīng)由IP Tunnel傳輸?shù)腡CP報文的流動路徑,簡要介紹一下IPv4協(xié)議棧(IP層)的結(jié)構(gòu)和報文處理過程。
IP Tunnel是2.0.x內(nèi)核就已經(jīng)提供了的虛擬局域網(wǎng)技術(shù),它在內(nèi)核中建立一個虛擬的網(wǎng)絡(luò)設(shè)備,將正常的報文(第二層)封裝在IP報文中,再通過TCP/IP網(wǎng)絡(luò)進行傳送。如果在網(wǎng)關(guān)之間建立IP Tunnel,并配合ARP報文的解析,就可以實現(xiàn)虛擬局域網(wǎng)。
我們從報文進入IP Tunnel設(shè)備準備發(fā)送開始。
1.1報文發(fā)送
ipip模塊創(chuàng)建tunnel設(shè)備(設(shè)備名為tunl0~tunlx)時,設(shè)置報文發(fā)送接口(hard_start_xmit)為ipip_tunnel_xmit(),流程見下圖:
圖1 報文發(fā)送流程
1.2 報文接收
報文接收從網(wǎng)卡驅(qū)動程序開始,當(dāng)網(wǎng)卡收到一個報文時,會產(chǎn)生一個中斷,其驅(qū)動程序中的中斷服務(wù)程序?qū)⒄{(diào)用確定的接收函數(shù)來處理。以下仍以IP Tunnel報文為例,網(wǎng)卡驅(qū)動程序為de4x5。流程分成兩個階段:驅(qū)動程序中斷服務(wù)程序階段和IP協(xié)議棧處理階段,見下圖:
圖2 報文接收流程之驅(qū)動程序階段
圖3 報文接收流程之協(xié)議棧階段
如果報文需要轉(zhuǎn)發(fā),則在上圖紅箭頭所指處調(diào)用ip_forward():
圖4 報文轉(zhuǎn)發(fā)流程
從上面的流程可以看出,Netfilter以NF_HOOK()的形式出現(xiàn)在報文處理的過程之中。
2. Netfilter Frame
Netfilter是2.4.x內(nèi)核引入的,盡管它提供了對2.0.x內(nèi)核中的ipfw以及2.2.x內(nèi)核中的ipchains的兼容,但實際上它的工作和意義遠不止于此。從上面對IP報文的流程分析中可以看出,Netfilter和IP報文的處理是完全結(jié)合在一起的,同時由于其結(jié)構(gòu)相對獨立,又是可以完全剝離的。這種機制也是Netfilter-iptables既高效又靈活的保證之一。
在剖析Netfilter機制之前,我們還是由淺入深的從Netfilter的使用開始。
2.1 編譯
在Networking Options中選定Network packet filtering項,并將其下的IP:Netfilter Configurations小節(jié)的所有選項設(shè)為Module模式。編譯并安裝新內(nèi)核,然后重啟,系統(tǒng)的核內(nèi)Netfilter就配置好了。以下對相關(guān)的內(nèi)核配置選項稍作解釋,也可以參閱編譯系統(tǒng)自帶的Help:
【Kernel/User netlink socket】建立一類PF_NETLINK套接字族,用于核心與用戶進程通信。當(dāng)Netfilter需要使用用戶隊列來管理某些報文時就要使用這一機制;
【Network packet filtering (replaces ipchains)】Netfilter主選項,提供Netfilter框架;
【Network packet filtering debugging】Netfilter主選項的分支,支持更詳細的Netfilter報告;
【IP: Netfilter Configuration】此節(jié)下是netfilter的各種選項的集合:
【Connection tracking (required for masq/NAT)】連接跟蹤,用于基于連接的報文處理,比如NAT;
【IP tables support (required for filtering/masq/NAT)】這是Netfilter的框架,NAT等應(yīng)用的容器;
【ipchains (2.2-style) support】ipchains機制的兼容代碼,在新的Netfilter結(jié)構(gòu)上實現(xiàn)了ipchains接口;
【ipfwadm (2.0-style) support】2.0內(nèi)核防火墻ipfwadm兼容代碼,基于新的Netfilter實現(xiàn)。
2.2 總體結(jié)構(gòu)
Netfilter是嵌入內(nèi)核IP協(xié)議棧的一系列調(diào)用入口,設(shè)置在報文處理的路徑上。網(wǎng)絡(luò)報文按照來源和去向,可以分為三類:流入的、流經(jīng)的和流出的,其中流入和流經(jīng)的報文需要經(jīng)過路由才能區(qū)分,而流經(jīng)和流出的報文則需要經(jīng)過投遞,此外,流經(jīng)的報文還有一個FORWARD的過程,即從一個NIC轉(zhuǎn)到另一個NIC。Netfilter就是根據(jù)網(wǎng)絡(luò)報文的流向,在以下幾個點插入處理過程:
NF_IP_PRE_ROUTING,在報文作路由以前執(zhí)行;
NF_IP_FORWARD,在報文轉(zhuǎn)向另一個NIC以前執(zhí)行;
NF_IP_POST_ROUTING,在報文流出以前執(zhí)行;
NF_IP_LOCAL_IN,在流入本地的報文作路由以后執(zhí)行;
NF_IP_LOCAL_OUT,在本地報文做流出路由前執(zhí)行。
如圖所示:
圖5 Netfilter HOOK位置
Netfilter框架為多種協(xié)議提供了一套類似的鉤子(HOOK),用一個struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS]二維數(shù)組結(jié)構(gòu)存儲,一維為協(xié)議族,二維為上面提到的各個調(diào)用入口。每個希望嵌入Netfilter中的模塊都可以為多個協(xié)議族的多個調(diào)用點注冊多個鉤子函數(shù)(HOOK),這些鉤子函數(shù)將形成一條函數(shù)指針鏈,每次協(xié)議棧代碼執(zhí)行到NF_HOOK()函數(shù)時(有多個時機),都會依次啟動所有這些函數(shù),處理參數(shù)所指定的協(xié)議棧內(nèi)容。
每個注冊的鉤子函數(shù)經(jīng)過處理后都將返回下列值之一,告知Netfilter核心代碼處理結(jié)果,以便對報文采取相應(yīng)的動作:
NF_ACCEPT:繼續(xù)正常的報文處理;
NF_DROP:將報文丟棄;
NF_STOLEN:由鉤子函數(shù)處理了該報文,不要再繼續(xù)傳送;
NF_QUEUE:將報文入隊,通常交由用戶程序處理;
NF_REPEAT:再次調(diào)用該鉤子函數(shù)。
2.3 IPTables
Netfilter-iptables由兩部分組成,一部分是Netfilter的"鉤子",另一部分則是知道這些鉤子函數(shù)如何工作的一套規(guī)則--這些規(guī)則存儲在被稱為iptables的數(shù)據(jù)結(jié)構(gòu)之中。鉤子函數(shù)通過訪問iptables來判斷應(yīng)該返回什么值給Netfilter框架。
在現(xiàn)有(kernel 2.4.21)中已內(nèi)建了三個iptables:filter、nat和mangle,絕大部分報文處理功能都可以通過在這些內(nèi)建(built-in)的表格中填入規(guī)則完成:
filter,該模塊的功能是過濾報文,不作任何修改,或者接受,或者拒絕。它在NF_IP_LOCAL_IN、NF_IP_FORWARD和NF_IP_LOCAL_OUT三處注冊了鉤子函數(shù),也就是說,所有報文都將經(jīng)過filter模塊的處理。
nat,網(wǎng)絡(luò)地址轉(zhuǎn)換(Network Address Translation),該模塊以Connection Tracking模塊為基礎(chǔ),僅對每個連接的第一個報文進行匹配和處理,然后交由Connection Tracking模塊將處理結(jié)果應(yīng)用到該連接之后的所有報文。nat在NF_IP_PRE_ROUTING、NF_IP_POST_ROUTING注冊了鉤子函數(shù),如果需要,還可以在NF_IP_LOCAL_IN和NF_IP_LOCAL_OUT兩處注冊鉤子,提供對本地報文(出/入)的地址轉(zhuǎn)換。nat僅對報文頭的地址信息進行修改,而不修改報文內(nèi)容,按所修改的部分,nat可分為源NAT(SNAT)和目的NAT(DNAT)兩類,前者修改第一個報文的源地址部分,而后者則修改第一個報文的目的地址部分。SNAT可用來實現(xiàn)IP偽裝,而DNAT則是透明代理的實現(xiàn)基礎(chǔ)。
mangle,屬于可以進行報文內(nèi)容修改的IP Tables,可供修改的報文內(nèi)容包括MARK、TOS、TTL等,mangle表的操作函數(shù)嵌入在Netfilter的NF_IP_PRE_ROUTING和NF_IP_LOCAL_OUT兩處。
內(nèi)核編程人員還可以通過注入模塊,調(diào)用Netfilter的接口函數(shù)創(chuàng)建新的iptables。在下面的Netfilter-iptables應(yīng)用中我們將進一步接觸Netfilter的結(jié)構(gòu)和使用方式。
2.4 Netfilter配置工具
iptables是專門針對2.4.x內(nèi)核的Netfilter制作的核外配置工具,通過socket接口對Netfilter進行操作,創(chuàng)建socket的方式如下:
socket(TC_AF, SOCK_RAW, IPPROTO_RAW)
其中TC_AF就是AF_INET。核外程序可以通過創(chuàng)建一個"原始IP套接字"獲得訪問Netfilter的句柄,然后通過getsockopt()和setsockopt()系統(tǒng)調(diào)用來讀取、更改Netfilter設(shè)置,詳情見下。
iptables功能強大,可以對核內(nèi)的表進行操作,這些操作主要指對其中規(guī)則鏈的添加、修改、清除,它的命令行參數(shù)主要可分為四類:指定所操作的IP Tables(-t);指定對該表所進行的操作(-A、-D等);規(guī)則描述和匹配;對iptables命令本身的指令(-n等)。在下面的例子中,我們通過iptables將訪問10.0.0.1的53端口(DNS)的TCP連接引導(dǎo)到192.168.0.1地址上。
iptables -t nat -A PREROUTING -p TCP -i eth0 -d 10.0.0.1 --dport 53 -j DNAT --to-destination 192.168.0.1
由于iptables是操作核內(nèi)Netfilter的用戶界面,有時也把Netfilter-iptables簡稱為iptables,以便與ipchains、ipfwadm等老版本的防火墻并列。
2.5 iptables核心數(shù)據(jù)結(jié)構(gòu)
2.5.1 表
在Linux內(nèi)核里,iptables用struct ipt_table表示,定義如下(include/linux/netfilter_ipv4/ip_tables.h):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | struct ipt_table { ????struct list_head list; ??????/* 表鏈 */ ????char name[IPT_TABLE_MAXNAMELEN]; ??????/* 表名,如"filter"、"nat"等,為了滿足自動模塊加載的設(shè)計, ?????/* 包含該表的模塊應(yīng)命名為iptable_'name'.o */ ????struct ipt_replace *table; ??????/* 表模子,初始為initial_table.repl */ ????unsigned int valid_hooks; ??????/* 位向量,標示本表所影響的HOOK */ ????rwlock_t lock; ??????/* 讀寫鎖,初始為打開狀態(tài) */ ????struct ipt_table_info *private; ??????/* iptable的數(shù)據(jù)區(qū),見下 */ ????struct module *me; ??????/* 是否在模塊中定義 */ }; struct ipt_table_info是實際描述表的數(shù)據(jù)結(jié)構(gòu)(net/ipv4/netfilter/ip_tables.c): struct ipt_table_info { ????unsigned int size; ??????/* 表大小 */ ????unsigned int number; ??????/* 表中的規(guī)則數(shù) */ ????unsigned int initial_entries; ??????/* 初始的規(guī)則數(shù),用于模塊計數(shù) */ ????unsigned int hook_entry[NF_IP_NUMHOOKS]; ??????/* 記錄所影響的HOOK的規(guī)則入口相對于下面的entries變量的偏移量 */ ????unsigned int underflow[NF_IP_NUMHOOKS]; ??????/* 與hook_entry相對應(yīng)的規(guī)則表上限偏移量,當(dāng)無規(guī)則錄入時, ?/* 相應(yīng)的hook_entry和underflow均為0 */ ????char entries[0] ____cacheline_aligned; ??????/* 規(guī)則表入口 */ }; |
例如內(nèi)建的filter表初始定義如下(net/ipv4/netfilter/iptable_filter.c):
| 1 2 3 4 5 6 7 8 9 10 11 | static struct ipt_table packet_filter = { { NULL, NULL },??? // 鏈表 "filter",??????? // 表名 ???&initial_table.repl,??? // 初始的表模板 ????FILTER_VALID_HOOKS,// 定義為((1 << NF_IP6_LOCAL_IN) | ???????(1 << NF_IP6_FORWARD) | (1 << NF_IP6_LOCAL_OUT)), ??????即關(guān)心INPUT、FORWARD、OUTPUT三點 ???RW_LOCK_UNLOCKED,// 鎖 NULL,??????? // 初始的表數(shù)據(jù)為空 ?????THIS_MODULE???? // 模塊標示 }; |
經(jīng)過調(diào)用ipt_register_table(&packet_filter)后,filter表的private數(shù)據(jù)區(qū)即參照模板填好了。
2.5.2 規(guī)則
規(guī)則用struct ipt_entry結(jié)構(gòu)表示,包含匹配用的IP頭部分、一個Target和0個或多個Match。由于Match數(shù)不定,所以一條規(guī)則實際的占用空間是可變的。結(jié)構(gòu)定義如下(include/linux/netfilter_ipv4):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | struct ipt_entry { ????????struct ipt_ip ip; ????????????/* 所要匹配的報文的IP頭信息 */ ????????unsigned int nfcache; ????????????/* 位向量,標示本規(guī)則關(guān)心報文的什么部分,暫未使用 */ ????????u_int16_t target_offset; ????????????/* target區(qū)的偏移,通常target區(qū)位于match區(qū)之后,而match區(qū)則在ipt_entry的末尾; ????????????初始化為sizeof(struct ipt_entry),即假定沒有match */ ????????u_int16_t next_offset; ????????????/* 下一條規(guī)則相對于本規(guī)則的偏移,也即本規(guī)則所用空間的總和, ????????????初始化為sizeof(struct ipt_entry)+sizeof(struct ipt_target),即沒有match */ ????unsigned int comefrom; ????????????/* 位向量,標記調(diào)用本規(guī)則的HOOK號,可用于檢查規(guī)則的有效性 */ ????struct ipt_counters counters; ????????????/* 記錄該規(guī)則處理過的報文數(shù)和報文總字節(jié)數(shù) */ ????unsigned char elems[0]; ????????????/*target或者是match的起始位置 */ } |
規(guī)則按照所關(guān)注的HOOK點,被放置在struct ipt_table::private->entries之后的區(qū)域,比鄰排列。
2.5.3 規(guī)則填寫過程
在了解了iptables在核心中的數(shù)據(jù)結(jié)構(gòu)之后,我們再通過遍歷一次用戶通過iptables配置程序填寫規(guī)則的過程,來了解這些數(shù)據(jù)結(jié)構(gòu)是如何工作的了。
一個最簡單的規(guī)則可以描述為拒絕所有轉(zhuǎn)發(fā)報文,用iptables命令表示就是:
| 1 | iptables -A FORWARD -j DROP; |
iptables應(yīng)用程序?qū)⒚钚休斎朕D(zhuǎn)換為程序可讀的格式(iptables-standalone.c::main()::do_command(),然后再調(diào)用libiptc庫提供的iptc_commit()函數(shù)向核心提交該操作請求。在libiptc/libiptc.c中定義了iptc_commit()(即TC_COMMIT()),它根據(jù)請求設(shè)置了一個struct ipt_replace結(jié)構(gòu),用來描述規(guī)則所涉及的表(filter)和HOOK點(FORWARD)等信息,并在其后附接當(dāng)前這條規(guī)則--一個struct ipt_entry結(jié)構(gòu)(實際上也可以是多個規(guī)則entry)。組織好這些數(shù)據(jù)后,iptc_commit()調(diào)用setsockopt()系統(tǒng)調(diào)用來啟動核心處理這一請求:
| 1 2 3 4 5 6 7 8 | setsockopt( sockfd,??????? //通過socket(TC_AF, SOCK_RAW, IPPROTO_RAW)創(chuàng)建的套接字, //其中TC_AF即AF_INET ???TC_IPPROTO,??? //即IPPROTO_IP ???SO_SET_REPLACE,?? //即IPT_SO_SET_REPLACE repl,????????? //struct ipt_replace結(jié)構(gòu) sizeof(*repl) + (*handle)->entries.size)??? //ipt_replace加上后面的ipt_entry |
核心對于setsockopt()的處理是從協(xié)議棧中一層層傳遞上來的,調(diào)用過程如下圖所示:
圖6 規(guī)則填寫過程
nf_sockopts是在iptables進行初始化時通過nf_register_sockopt()函數(shù)生成的一個struct nf_sockopt_ops結(jié)構(gòu),對于ipv4來說,在net/ipv4/netfilter/ip_tables.c中定義了一個ipt_sockopts變量(struct nf_sockopt_ops),其中的set操作指定為do_ipt_set_ctl(),因此,當(dāng)nf_sockopt()調(diào)用對應(yīng)的set操作時,控制將轉(zhuǎn)入net/ipv4/netfilter/ip_tables.c::do_ipt_set_ctl()中。
對于IPT_SO_SET_REPLACE命令,do_ipt_set_ctl()調(diào)用do_replace()來處理,該函數(shù)將用戶層傳入的struct ipt_replace和struct ipt_entry組織到filter(根據(jù)struct ipt_replace::name項)表的hook_entry[NF_IP_FORWARD]所指向的區(qū)域,如果是添加規(guī)則,結(jié)果將是filter表的private(struct ipt_table_info)項的hook_entry[NF_IP_FORWARD]和underflow[NF_IP_FORWARD]的差值擴大(用于容納該規(guī)則),private->number加1。
2.5.4 規(guī)則應(yīng)用過程
以上描述了規(guī)則注入核內(nèi)iptables的過程,這些規(guī)則都掛接在各自的表的相應(yīng)HOOK入口處,當(dāng)報文流經(jīng)該HOOK時進行匹配,對于與規(guī)則匹配成功的報文,調(diào)用規(guī)則對應(yīng)的Target來處理。仍以轉(zhuǎn)發(fā)的報文為例,假定filter表中添加了如上所述的規(guī)則:拒絕所有轉(zhuǎn)發(fā)報文。
如1.2節(jié)所示,經(jīng)由本地轉(zhuǎn)發(fā)的報文經(jīng)過路由以后將調(diào)用ip_forward()來處理,在ip_forward()返回前,將調(diào)用如下代碼:
| 1 2 3 4 5 6 | NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2, ip_forward_finish) NF_HOOK是這樣一個宏(include/linux/netfilter.h): #define NF_HOOK(pf, hook, skb, indev, outdev, okfn)???????? \ (list_empty(&nf_hooks[(pf)][(hook)])??????????????????? \ ?? (okfn)(skb)????????????????????????????? \ ?: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn))) |
也就是說,如果nf_hooks[PF_INET][NF_IP_FORWARD]所指向的鏈表為空(即該鉤子上沒有掛處理函數(shù)),則直接調(diào)用ip_forward_finish(skb)完成ip_forward()的操作;否則,則調(diào)用net/core/netfilter.c::nf_hook_slow()轉(zhuǎn)入Netfilter的處理。
這里引入了一個nf_hooks鏈表二維數(shù)組:
| 1 | struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS]; |
每一個希望使用Netfilter掛鉤的表都需要將表處理函數(shù)在nf_hooks數(shù)組的相應(yīng)鏈表上進行注冊。對于filter表來說,在其初始化(net/ipv4/netfilter/iptable_filter.c::init())時,調(diào)用了net/core/netfilter.c::nf_register_hook(),將預(yù)定義的三個struct nf_hook_ops結(jié)構(gòu)(分別對應(yīng)INPUT、FORWARD、OUTPUT鏈)連入鏈表中:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | struct nf_hook_ops { ????????struct list_head list; ????????????//鏈表 ????????nf_hookfn *hook; ????????????//處理函數(shù)指針 ????????int pf; ????????????//協(xié)議號 ????????int hooknum; ????????????//HOOK號 ????????int priority; ????????????//優(yōu)先級,在nf_hooks鏈表中各處理函數(shù)按優(yōu)先級排序 }; |
對于filter表來說,FORWARD點的hook設(shè)置成ipt_hook(),它將直接調(diào)用ipt_do_table()。幾乎所有處理函數(shù)最終都將調(diào)用ipt_do_table()來查詢表中的規(guī)則,以調(diào)用對應(yīng)的target。下圖所示即為在FORWARD點上調(diào)用nf_hook_slow()的過程:
圖7 規(guī)則應(yīng)用流程
2.5.5 Netfilter的結(jié)構(gòu)特點
由上可見,nf_hooks鏈表數(shù)組是聯(lián)系報文處理流程和iptables的紐帶,在iptables初始化(各自的init()函數(shù))時,一方面調(diào)用nf_register_table()建立規(guī)則容器,另一方面還要調(diào)用nf_register_hook()將自己的掛鉤愿望表達給Netfilter框架。初始化完成之后,用戶只需要通過用戶級的iptables命令操作規(guī)則容器(添加規(guī)則、刪除規(guī)則、修改規(guī)則等),而對規(guī)則的使用則完全不用操心。如果一個容器內(nèi)沒有規(guī)則,或者nf_hooks上沒有需要表達的愿望,則報文處理照常進行,絲毫不受Netfilter-iptables的影響;即使報文經(jīng)過了過濾規(guī)則的處理,它也會如同平時一樣重新回到報文處理流程上來,因此從宏觀上看,就像在行車過程中去了一趟加油站。
Netfilter不僅僅有此高效的設(shè)計,同時還具備很大的靈活性,這主要表現(xiàn)在Netfilter-iptables中的很多部分都是可擴充的,包括Table、Match、Target以及Connection Track Protocol Helper,下面一節(jié)將介紹這方面的內(nèi)容。
3. Netfilter-iptables Extensions
Netfilter提供的是一套HOOK框架,其優(yōu)勢是就是易于擴充。可供擴充的Netfilter構(gòu)件主要包括Table、Match、Target和Connection Track Protocol Helper四類,分別對應(yīng)四套擴展函數(shù)。所有擴展都包括核內(nèi)、核外兩個部分,核內(nèi)部分置于<kernel-root>/net/ipv4/netfilter/下,模塊名為ipt_'name'.o;核外部分置于<iptables-root>/extensions/下,動態(tài)鏈接庫名為libipt_'name'.so。
3.1 Table
Table在以上章節(jié)中已經(jīng)做過介紹了,它作為規(guī)則存儲的媒介,決定了該規(guī)則何時能起作用。系統(tǒng)提供的filter、nat、mangle涵蓋了所有的HOOK點,因此,大部分應(yīng)用都可以圍繞這三個已存在的表進行,但也允許編程者定義自己的擁有特殊目的的表,這時需要參考已有表的struct ipt_table定義創(chuàng)建新的ipt_table數(shù)據(jù)結(jié)構(gòu),然后調(diào)用ipt_register_table()注冊該新表,并調(diào)用ipt_register_hook()將新表與Netfilter HOOK相關(guān)聯(lián)。
對表進行擴展的情形并不多見,因此這里也不詳述。
3.2 Match & Target
Match和Target是Netfilter-iptables中最常使用的功能,靈活使用Match和Target,可以完成絕大多數(shù)報文處理功能。
3.2.1 Match數(shù)據(jù)結(jié)構(gòu)
核心用struct ipt_match表征一個Match數(shù)據(jù)結(jié)構(gòu):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | struct ipt_match { ??struct list_head list; ????/* 通常初始化成{NULL,NULL},由核心使用 */ ??const char name[IPT_FUNCTION_MAXNAMELEN]; ????/* Match的名字,同時也要求包含該Match的模塊文件名為ipt_'name'.o */ ??int (*match)(const struct sk_buff *skb, ?????????const struct net_device *in, ?????????const struct net_device *out, ?????????const void *matchinfo, ?????????int offset, ?????????const void *hdr, ?????????u_int16_t datalen, ?????????int *hotdrop); ????/* 返回非0表示匹配成功,如果返回0且hotdrop設(shè)為1, ????則表示該報文應(yīng)當(dāng)立刻丟棄 */ ??int (*checkentry)(const char *tablename, ????????const struct ipt_ip *ip, ????????void *matchinfo, ????????unsigned int matchinfosize, ????????unsigned int hook_mask); ????/* 在使用本Match的規(guī)則注入表中之前調(diào)用,進行有效性檢查, ????/* 如果返回0,規(guī)則就不會加入iptables中 */ ??void (*destroy)(void *matchinfo, unsigned int matchinfosize); ????/* 在包含本Match的規(guī)則從表中刪除時調(diào)用, ????與checkentry配合可用于動態(tài)內(nèi)存分配和釋放 */ ??struct module *me; ????/* 表示當(dāng)前Match是否為模塊(NULL為否) */ }; |
定義好一個ipt_match結(jié)構(gòu)后,可調(diào)用ipt_register_match()將本Match注冊到ipt_match鏈表中備用,在模塊方式下,該函數(shù)通常在init_module()中執(zhí)行。
3.2.2 Match的用戶級設(shè)置
要使用核心定義的Match(包括已有的和自定義的),必須在用戶級的iptables程序中有所說明,iptables源代碼也提供了已知的核心Match,但未知的Match則需要自行添加說明。
在iptables中,一個Match用struct iptables_match表示:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | struct iptables_match { ??struct iptables_match *next; ????/* Match鏈,初始為NULL */ ??ipt_chainlabel name; ????/* Match名,和核心模塊加載類似,作為動態(tài)鏈接庫存在的 ????Iptables Extension的命名規(guī)則為libipt_'name'.so(對于ipv6為libip6t_'name'.so), ????以便于iptables主程序根據(jù)Match名加載相應(yīng)的動態(tài)鏈接庫 */ ??const char *version; ????/* 版本信息,一般設(shè)為NETFILTER_VERSION */ ??size_t size; ????/* Match數(shù)據(jù)的大小,必須用IPT_ALIGN()宏指定對界 */ ??size_t userspacesize; ????/*由于內(nèi)核可能修改某些域,因此size可能與確切的用戶數(shù)據(jù)不同, ????這時就應(yīng)該把不會被改變的數(shù)據(jù)放在數(shù)據(jù)區(qū)的前面部分, ????而這里就應(yīng)該填寫被改變的數(shù)據(jù)區(qū)大小;一般來說,這個值和size相同 */ ??void (*help)(void); ????/* 當(dāng)iptables要求顯示當(dāng)前match的信息時(比如iptables -m ip_ext -h), ????就會調(diào)用這個函數(shù),輸出在iptables程序的通用信息之后 */ ??void (*init)(struct ipt_entry_match *m, unsigned int *nfcache); ????/* 初始化,在parse之前調(diào)用 */ ??int (*parse)(int c, char **argv, int invert, unsigned int *flags, ?????????const struct ipt_entry *entry, ?????????unsigned int *nfcache, ?????????struct ipt_entry_match **match); ????/* 掃描并接收本match的命令行參數(shù),正確接收時返回非0,flags用于保存狀態(tài)信息 */ ??void (*final_check)(unsigned int flags); ????/* 當(dāng)命令行參數(shù)全部處理完畢以后調(diào)用,如果不正確,應(yīng)該退出(exit_error()) */ ??void (*print)(const struct ipt_ip *ip, ??????????const struct ipt_entry_match *match, int numeric); ????/* 當(dāng)查詢當(dāng)前表中的規(guī)則時,顯示使用了當(dāng)前match的規(guī)則的額外的信息 */ ??void (*save)(const struct ipt_ip *ip, ?????????const struct ipt_entry_match *match); ????/* 按照parse允許的格式將本match的命令行參數(shù)輸出到標準輸出, ????用于iptables-save命令 */ ??const struct option *extra_opts; ????/*? NULL結(jié)尾的參數(shù)列表,struct option與getopt(3)使用的結(jié)構(gòu)相同 */ ??/* 以下參數(shù)由iptables內(nèi)部使用,用戶不用關(guān)心 */ ??unsigned int option_offset; ??struct ipt_entry_match *m; ??unsigned int mflags; ??unsigned int used; } struct option { ????const char *name; ????/* 參數(shù)名稱,用于匹配命令行輸入 */ ????int has_arg;????????? /* 本參數(shù)項是否允許帶參數(shù),0表示沒有,1表示有,2表示可有可無 */ ????int *flag; ????/* 指定返回的參數(shù)值內(nèi)容,如果為NULL,則直接返回下面的val值, ????否則返回0,val存于flag所指向的位置 */ ????int val; ????/* 缺省的參數(shù)值 */ } |
如對于--opt <value>參數(shù)來講,在struct option中定義為{"opt",1,0,'1'},表示opt帶參數(shù)值,如果出現(xiàn)-opt <value>參數(shù),則返回'1'用于parse()中的int c參數(shù)。
實際使用時,各個函數(shù)都可以為空,只要保證name項與核心的對應(yīng)Match名字相同就可以了。在定義了iptables_match之后,可以調(diào)用register_match()讓iptables主體識別這個新Match。當(dāng)iptables命令中第一次指定使用名為ip_ext的Match時,iptables主程序會自動加載libipt_ip_ext.so,并執(zhí)行其中的_init()接口,所以register_match()操作應(yīng)該放在_init()中執(zhí)行。
3.2.3 Target數(shù)據(jù)結(jié)構(gòu)
Target數(shù)據(jù)結(jié)構(gòu)struct ipt_target和struct ipt_match基本相同,不同之處只是用target函數(shù)指針代替match函數(shù)指針:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | struct ipt_target { ????…… unsigned int (*target)(struct sk_buff **pskb, ???????????????unsigned int hooknum, ???????????????const struct net_device *in, ???????????????const struct net_device *out, ???????????????const void *targinfo, ???????????????void *userdata); ????/* 如果需要繼續(xù)處理則返回IPT_CONTINUE(-1), ????否則返回NF_ACCEPT、NF_DROP等值, ????它的調(diào)用者根據(jù)它的返回值來判斷如何處理它處理過的報文*/ …… } |
與ipt_register_match()對應(yīng),Target使用ipt_register_target()來進行注冊,但文件命名、使用方法等均與Match相同。
3.2.4 Target的用戶級設(shè)置
Target的用戶級設(shè)置使用struct iptables_target結(jié)構(gòu),與struct iptables_match完全相同。register_target()用于注冊新Target,方法也與Match相同。
3.3 Connection Track Protocol Helper
前面提到,NAT僅對一個連接(TCP或UDP)的第一個報文進行處理,之后就依靠Connection Track機制來完成對后續(xù)報文的處理。Connection Track是一套可以和NAT配合使用的機制,用于在傳輸層(甚至應(yīng)用層)處理與更高層協(xié)議相關(guān)的動作。
關(guān)于Connection Track,Netfilter中的實現(xiàn)比較復(fù)雜,而且實際應(yīng)用頻率不高,因此這里就不展開了,以后專文介紹。
3.4 iptables patch機制
對于Netfilter-iptables擴展工作,用戶當(dāng)然可以直接修改源代碼并編譯安裝,但為了標準化和簡便起見,在iptables源碼包提供了一套patch機制,希望用戶按照其格式要求進行擴展,而不必分別修改內(nèi)核和iptables代碼。
和Netfilter-iptables的結(jié)構(gòu)特點相適應(yīng),對iptables進行擴展也需要同時修改內(nèi)核和iptables程序代碼,因此patch也分為兩個部分。在iptables-1.2.8中,核內(nèi)補丁由patch-o-matic包提供,iptables-1.2.8的源碼中的extensions目錄則為iptables程序本身的補丁。
patch-o-matic提供了一個'runme'腳本來給核心打patch,按照它的規(guī)范,核內(nèi)補丁應(yīng)該包括五個部分,且命名有一定的規(guī)范,例如,如果Target名為ip_ext,那么這五個部分的文件名和功能分別為:
- ip_ext.patch?
主文件,內(nèi)容為diff格式的核心.c、.h源文件補丁,實際使用時類似給內(nèi)核打patch(patch -p0 <ip_ext.patch); - ip_ext.patch.config.in?
對<kernel-root>/net/ipv4/netfilter/Config.in文件的修改,第一行是原Config.in中的一行,以指示補丁添加的位置,后面則是添加在以上匹配行之后的內(nèi)容。這個補丁的作用是使核心的配置界面中支持新增加的補丁選項; - ip_ext.patch.configure.help?
對<kernel-root>/Documentation/Configure.help的修改,第一行為原Configure.help中的一行幫助索引,以下幾行的內(nèi)容添加在這一行相關(guān)的幫助之后。這個補丁的作用是補充內(nèi)核配置時對新增加的選項的說明; - ip_ext.patch.help?
用于runme腳本顯示本patch的幫助信息; - ip_ext.patch.makefile
- 對<kernel-root>/net/ipv4/netfilter/Makefile的修改,和前兩個文件的格式相同,用于在指定的位置上添加用于生成ipt_ip_ext.o的make指令。
示例可以參看patch-o-matic下的源文件。
iptables本身的擴展稍微簡單一些,那就是在extensions目錄下增加一個libipt_ip_ext.c的文件,然后在本子目錄的Makefile的PF_EXT_SLIB宏中附加一個ip_ext字符串。
第一次安裝時,可以在iptables的根目錄下運行make pending-patches命令,此命令會自動調(diào)用runme腳本,將所有patch-o-matic下的patch文件打到內(nèi)核中,之后需要重新配置和編譯內(nèi)核。
如果只需要安裝所要求的patch,可以在patch-o-matic目錄下直接運行runme ip_ext,它會完成ip_ext patch的安裝。之后,仍然要重編內(nèi)核以使patch生效。
iptables本身的make/make install過程可以編譯并安裝好libipt_ip_ext.so,之后,新的iptables命令就可以通過加載libipt_ip_ext.so來識別ip_ext target了。
Extensions還可以定義頭文件,一般這個頭文件核內(nèi)核外都要用,因此,通常將其放置在<kernel-root>/include/linux/netfilter_ipv4/目錄下,在.c文件里指定頭文件目錄為linux/netfilter_ipv4/。
靈活性是Netfilter-iptables機制的一大特色,因此,擴展Netfilter-iptables也是它的應(yīng)用的關(guān)鍵。為了與此目標相適應(yīng),Netfilter-iptables在結(jié)構(gòu)上便于擴展,同時也提供了一套擴展的方案,并有大量擴展樣例可供參考。
4. 案例:用Netfilter實現(xiàn)VPN
虛擬專用網(wǎng)的關(guān)鍵就是隧道(Tunnel)技術(shù),即將報文封裝起來通過公用網(wǎng)絡(luò)。利用Netfilter-iptables對報文的強大處理能力,完全可以以最小的開發(fā)成本實現(xiàn)一個高可配置的VPN。
本文第一部分即描述了IP Tunnel技術(shù)中報文的流動過程,從中可見,IP Tunnel技術(shù)的特殊之處有兩點:
- 一個特殊的網(wǎng)絡(luò)設(shè)備tunl0~tunlx--發(fā)送時,用指定路由的辦法將需要封裝的內(nèi)網(wǎng)報文交給該網(wǎng)絡(luò)設(shè)備來處理,在"網(wǎng)卡驅(qū)動程序"中作封裝,然后再作為正常的IP報文交給真正的網(wǎng)絡(luò)設(shè)備發(fā)送出去;
- 一個特殊的IP層協(xié)議IPIP--從外網(wǎng)傳來的封裝報文擁有一個特殊的協(xié)議號(IPIP),報文最終在該協(xié)議的處理程序(ipip_rcv())中解封,恢復(fù)內(nèi)網(wǎng)IP頭后,將報文注入IP協(xié)議棧底層(netif_rx())重新開始收包流程。
從中不難看出,在報文流出tunlx設(shè)備之后(即完成封裝之后)需要經(jīng)過OUTPUT的Netfilter HOOK點,而在報文解封之前(ipip_rcv()得到報文之前),也要經(jīng)過Netfilter的INPUT HOOK點,因此,完全有可能在這兩個HOOK上做文章,完成報文的封裝和解封過程。報文的接收過程可以直接沿用IPIP的處理方法,即自定義一個專門的協(xié)議,問題的關(guān)鍵即在于如何獲得需要封裝的外發(fā)報文,從而與正常的非VPN報文相區(qū)別。我們的做法是利用Netfilter-iptables對IP頭信息的敏感程度,在內(nèi)網(wǎng)中使用標準的內(nèi)網(wǎng)專用IP段(如192.168.xxx.xxx),從而通過IP地址將其區(qū)分開。基于IP地址的VPN配置既方便現(xiàn)有系統(tǒng)管理、又便于今后VPN系統(tǒng)升級后的擴充,而且可以結(jié)合Netfilter-iptables的防火墻設(shè)置,將VPN和防火墻有機地結(jié)合起來,共同維護一個安全的專用網(wǎng)絡(luò)。
在我們的方案中,VPN采用LAN-LAN方式(當(dāng)然,Dial-in方式在技術(shù)上并沒有什么區(qū)別),在LAN網(wǎng)關(guān)處設(shè)置我們的VPN管理組件,從而構(gòu)成一個安全網(wǎng)關(guān)。LAN內(nèi)部的節(jié)點既可以正常訪問防火墻限制以外非敏感的外網(wǎng)(如Internet的大部分站點),又可以通過安全網(wǎng)關(guān)的甄別,利用VPN訪問其他的專用網(wǎng)LAN。
由于本應(yīng)用與原有的三個表在功能和所關(guān)心的HOOK點上有所不同,因此我們仿照filter表新建了一個vpn表,VPN功能分布在以下四個部分中:
- iptables ENCRYPT Target:對于發(fā)往安全子網(wǎng)的報文,要求經(jīng)過ENCRYPT target處理,加密原報文,產(chǎn)生認證碼,并將報文封裝在公網(wǎng)IPIP_EXT報文頭中。ENCRYPT Target配置在vpn表的OUTPUT和FORWARD HOOK點上,根據(jù)目的方IP地址來區(qū)分是否需要經(jīng)過ENCRYPT target加密處理。
- IPIP_EXT協(xié)議:在接收該協(xié)議報文的處理函數(shù)IPIP_EXT_rcv()中用安全子網(wǎng)的IP地址信息代替公網(wǎng)間傳輸?shù)乃淼缊笪念^中的IP地址,然后重新注入IP協(xié)議棧底層。
- iptables IPIP_EXT Match:匹配報文頭的協(xié)議標識是否為自定義的IPIP_EXT。經(jīng)過IPIP_EXT_rcv()處理之后的報文必須是IPIP_EXT協(xié)議類型的,否則應(yīng)丟棄。
- iptables DECRYPT Target:對于接收到的來自安全子網(wǎng)的報文,經(jīng)過IPIP_EXT協(xié)議處理之后,將IP頭恢復(fù)為安全子網(wǎng)之間通信的IP頭,再進入DECRYPT target處理,對報文進行完全解密和解封。
整個報文傳輸?shù)牧鞒炭梢杂孟聢D表示:
圖8 VPN報文流動過程
對于外出報文(源于本地或內(nèi)網(wǎng)),使用內(nèi)部地址在FORWARD/OUTPUT點匹配成功,執(zhí)行ENCRYPT,從Netfilter中返回后作為本地IPIP_EXT協(xié)議的報文繼續(xù)往外發(fā)送。
對于接收到的報文,如果協(xié)議號為IPPROTO_IPIP_EXT,則匹配IPIP_EXT的Match成功,否則將在INPUT點被丟棄;繼續(xù)傳送的報文從IP層傳給IPIP_EXT的協(xié)議處理代碼接收,在其中恢復(fù)內(nèi)網(wǎng)IP的報文頭后調(diào)用netif_rx()重新流入?yún)f(xié)議棧。此時的報文將在INPUT/FORWARD點匹配規(guī)則,并執(zhí)行DECRYPT,只有通過了DECRYPT的報文才能繼續(xù)傳送到本機的上層協(xié)議或者內(nèi)網(wǎng)。
附:iptables設(shè)置指令(樣例):
| 1 2 3 4 5 | iptables -t vpn -P FORWARD DROP iptables -t vpn -A OUTPUT -d 192.168.0.0/24 -j ENCRYPT iptables -t vpn -A INPUT -s 192.168.0.0/24 -m ipip_ah -j DECRYPT iptables -t vpn -A FORWARD -s 192.168.0.0/24 -d 192.168.1.0 -j DECRYPT iptables -t vpn -A FORWARD -s 192.168.1.0/24 -d 192.168.0.0/24 -j ENCRYPT |
其中192.168.0.0/24是目的子網(wǎng),192.168.1.0/24是本地子網(wǎng)
相關(guān)主題
- [Linus Torvalds,2003] Linux內(nèi)核源碼v2.4.21
- [Paul Russell,2002] Linux netfilter Hacking HOWTO v1.2
- [Paul Russell,2002] iptables源碼v1.2.1a
- [Paul Russell,2000] LinuxWorld: San Jose August 2000,Netfilter Tutorial
- [Oskar Andreasson,2001] iptables Tutorial 1.0.9
轉(zhuǎn)載于:https://www.cnblogs.com/qxxnxxFight/p/11002065.html
總結(jié)
以上是生活随笔為你收集整理的【转】Linux Netfilter实现机制和扩展技术的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 矢量图标下载网站
- 下一篇: 【Linux】一步一步学Linux——U