本篇內(nèi)容介紹了“如何實現(xiàn)Netfilter”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
創(chuàng)新互聯(lián)公司是專業(yè)的欽南網(wǎng)站建設(shè)公司,欽南接單;提供成都網(wǎng)站設(shè)計、網(wǎng)站制作,網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進行欽南網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團隊,希望更多企業(yè)前來合作!
我們先來回顧一下 Netfilter 的原理,Netfilter 是通過在網(wǎng)絡(luò)協(xié)議棧的不同階段注冊鉤子函數(shù)來實現(xiàn)對數(shù)據(jù)包的處理與過濾,如 圖1 所示:
(圖1 Netfilter掛載點)
在 圖1 中,藍(lán)色部分就是 Netfilter 掛載鉤子函數(shù)的位置,所以 Netfilter 定義了 5 個常量來表示這 5 個位置,如下代碼:
// 文件:include/linux/netfilter_ipv4.h #define NF_IP_PRE_ROUTING 0 #define NF_IP_LOCAL_IN 1 #define NF_IP_FORWARD 2 #define NF_IP_LOCAL_OUT 3 #define NF_IP_POST_ROUTING 4
上面代碼中的常量與 圖1 中掛載鉤子函數(shù)的位置一一對應(yīng),如常量 NF_IP_PRE_ROUTING 對應(yīng)著 圖1 的 PRE_ROUTING 處。
前面說過,Netfilter 是通過在網(wǎng)絡(luò)協(xié)議中的不同位置掛載鉤子函數(shù)來對數(shù)據(jù)包進行過濾和處理,而且每個掛載點能夠掛載多個鉤子函數(shù),所以 Netfilter 使用鏈表結(jié)構(gòu)來存儲這些鉤子函數(shù),如 圖2 所示:
(圖2 Netfilter鉤子函數(shù)鏈)
如 圖2 所示,Netfilter 的每個掛載點都使用一個鏈表來存儲鉤子函數(shù)列表。在內(nèi)核中,定義了一個名為 nf_hooks 的數(shù)組來存儲這些鏈表,如下代碼:
// 文件:net/core/netfilter.c struct list_head nf_hooks[32][5];
struct list_head 結(jié)構(gòu)是內(nèi)核的通用鏈表結(jié)構(gòu)。
從 nf_hooks 變量定義為一個二維數(shù)組,第一維是用來表示不同的協(xié)議(如 IPv4 或者 IPv6,本文只討論 IPv4,所以可以把 nf_hooks 當(dāng)成是一維數(shù)組),而第二維用于表示不同的掛載點,如 圖2 中的 5 個掛載點。
接下來我們介紹一下鉤子函數(shù)在 Netfilter 中的存儲方式。
前面我們介紹過,Netfilter 通過鏈表來存儲鉤子函數(shù),而鉤子函數(shù)是通過結(jié)構(gòu) nf_hook_ops 來描述的,其定義如下:
// 文件:include/linux/netfilter.h struct nf_hook_ops { struct list_head list; // 連接相同掛載點的鉤子函數(shù) nf_hookfn *hook; // 鉤子函數(shù)指針 int pf; // 協(xié)議類型 int hooknum; // 鉤子函數(shù)所在鏈 int priority; // 優(yōu)先級 };
下面我們對 nf_hook_ops 結(jié)構(gòu)的各個字段進行說明:
list:用于把處于相同掛載點的鉤子函數(shù)鏈接起來。
hook:鉤子函數(shù)指針,就是用于處理或者過濾數(shù)據(jù)包的函數(shù)。
pf:協(xié)議類型,用于指定鉤子函數(shù)掛載在 nf_hooks 數(shù)組第一維的位置,如 IPv4 協(xié)議設(shè)置為 PF_INET。
hooknum:鉤子函數(shù)所在鏈(掛載點),如 NF_IP_PRE_ROUTING。
priority:鉤子函數(shù)的優(yōu)先級,用于管理鉤子函數(shù)的調(diào)用順序。
其中 hook 字段的類型為 nf_hookfn,nf_hookfn 類型的定義如下:
// 文件:include/linux/netfilter.h 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_hookfn 函數(shù)的各個參數(shù)的作用:
hooknum:鉤子函數(shù)所在鏈(掛載點),如 NF_IP_PRE_ROUTING。
skb:數(shù)據(jù)包對象,就是要處理或者過濾的數(shù)據(jù)包。
in:接收數(shù)據(jù)包的設(shè)備對象。
out:發(fā)送數(shù)據(jù)包的設(shè)備對象。
okfn:當(dāng)掛載點上所有的鉤子函數(shù)都處理過數(shù)據(jù)包后,將會調(diào)用這個函數(shù)來對數(shù)據(jù)包進行下一步處理。
當(dāng)定義好一個鉤子函數(shù)結(jié)構(gòu)后,需要調(diào)用 nf_register_hook 函數(shù)來將其注冊到 nf_hooks 數(shù)組中,nf_register_hook 函數(shù)的實現(xiàn)如下:
// 文件:net/core/netfilter.c int nf_register_hook(struct nf_hook_ops *reg) { struct list_head *i; br_write_lock_bh(BR_NETPROTO_LOCK); // 對 nf_hooks 進行上鎖 // priority 字段表示鉤子函數(shù)的優(yōu)先級 // 所以通過 priority 字段來找到鉤子函數(shù)的合適位置 for (i = nf_hooks[reg->pf][reg->hooknum].next; i != &nf_hooks[reg->pf][reg->hooknum]; i = i->next) { if (reg->priority < ((struct nf_hook_ops *)i)->priority) break; } list_add(®->list, i->prev); // 把鉤子函數(shù)添加到鏈表中 br_write_unlock_bh(BR_NETPROTO_LOCK); // 對 nf_hooks 進行解鎖 return 0; }
nf_register_hook 函數(shù)的實現(xiàn)比較簡單,步驟如下:
對 nf_hooks 進行上鎖操作,用于保護 nf_hooks 變量不受并發(fā)競爭。
通過鉤子函數(shù)的優(yōu)先級來找到其在鉤子函數(shù)鏈表中的正確位置。
把鉤子函數(shù)插入到鏈表中。
對 nf_hooks 進行解鎖操作。
插入過程如 圖3 所示:
(圖3 鉤子函數(shù)插入過程)
如 圖3 所示,我們要把優(yōu)先級為 20 的鉤子函數(shù)插入到 PRE_ROUTING 這個鏈中,而 PRE_ROUTING 鏈已經(jīng)存在兩個鉤子函數(shù),一個優(yōu)先級為 10, 另外一個優(yōu)先級為 30。
通過與鏈表中的鉤子函數(shù)的優(yōu)先級進行對比,發(fā)現(xiàn)新的鉤子函數(shù)應(yīng)該插入到優(yōu)先級為 10 的鉤子函數(shù)后面,所以就 如圖3 所示就把新的鉤子函數(shù)插入到優(yōu)先級為 10 的鉤子函數(shù)后面。
鉤子函數(shù)已經(jīng)被保存到不同的鏈上,那么什么時候才會觸發(fā)調(diào)用這些鉤子函數(shù)來處理數(shù)據(jù)包呢?
要觸發(fā)調(diào)用某個掛載點上(鏈)的所有鉤子函數(shù),需要使用 NF_HOOK 宏來實現(xiàn),其定義如下:
// 文件: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_HOOK 宏的各個參數(shù)的作用:
pf:協(xié)議類型,就是 nf_hooks 數(shù)組的第一個維度,如 IPv4 協(xié)議就是 PF_INET。
hook:要調(diào)用哪一條鏈(掛載點)上的鉤子函數(shù),如 NF_IP_PRE_ROUTING。
indev:接收數(shù)據(jù)包的設(shè)備對象。
outdev:發(fā)送數(shù)據(jù)包的設(shè)備對象。
okfn:當(dāng)鏈上的所有鉤子函數(shù)都處理完成,將會調(diào)用此函數(shù)繼續(xù)對數(shù)據(jù)包進行處理。
而 NF_HOOK 宏的實現(xiàn)也比較簡單,首先判斷一下鉤子函數(shù)鏈表是否為空,如果是空的話,就直接調(diào)用 okfn 函數(shù)來處理數(shù)據(jù)包,否則就調(diào)用 nf_hook_slow 函數(shù)來處理數(shù)據(jù)包。我們來看看 nf_hook_slow 函數(shù)的實現(xiàn):
// 文件:net/core/netfilter.c int nf_hook_slow(int pf, unsigned int hook, struct sk_buff *skb, struct net_device *indev, struct net_device *outdev, int (*okfn)(struct sk_buff *)) { struct list_head *elem; unsigned int verdict; int ret = 0; elem = &nf_hooks[pf][hook]; // 獲取要調(diào)用的鉤子函數(shù)鏈表 // 遍歷鉤子函數(shù)鏈表,并且調(diào)用鉤子函數(shù)對數(shù)據(jù)包進行處理 verdict = nf_iterate(&nf_hooks[pf][hook], &skb, hook, indev, outdev, &elem, okfn); ... // 如果處理結(jié)果為 NF_ACCEPT, 表示數(shù)據(jù)包通過所有鉤子函數(shù)的處理, 那么就調(diào)用 okfn 函數(shù)繼續(xù)處理數(shù)據(jù)包 // 如果處理結(jié)果為 NF_DROP, 表示數(shù)據(jù)包被拒絕, 應(yīng)該丟棄此數(shù)據(jù)包 switch (verdict) { case NF_ACCEPT: ret = okfn(skb); break; case NF_DROP: kfree_skb(skb); ret = -EPERM; break; } return ret; }
nf_hook_slow 函數(shù)的實現(xiàn)也比較簡單,過程如下:
首先調(diào)用 nf_iterate 函數(shù)來遍歷鉤子函數(shù)鏈表,并調(diào)用鏈表上的鉤子函數(shù)來處理數(shù)據(jù)包。
如果處理結(jié)果為 NF_ACCEPT,表示數(shù)據(jù)包通過所有鉤子函數(shù)的處理, 那么就調(diào)用 okfn 函數(shù)繼續(xù)處理數(shù)據(jù)包。
如果處理結(jié)果為 NF_DROP,表示數(shù)據(jù)包沒有通過鉤子函數(shù)的處理,應(yīng)該丟棄此數(shù)據(jù)包。
既然 Netfilter 是通過調(diào)用 NF_HOOK 宏來調(diào)用鉤子函數(shù)鏈表上的鉤子函數(shù),那么內(nèi)核在什么地方調(diào)用這個宏呢?
比如數(shù)據(jù)包進入 IPv4 協(xié)議層的處理函數(shù) ip_rcv 函數(shù)中就調(diào)用了 NF_HOOK 宏來處理數(shù)據(jù)包,代碼如下:
// 文件:net/ipv4/ip_input.c int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt) { ... return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish); }
如上代碼所示,在 ip_rcv 函數(shù)中調(diào)用了 NF_HOOK 宏來處理輸入的數(shù)據(jù)包,其調(diào)用的鉤子函數(shù)鏈(掛載點)為 NF_IP_PRE_ROUTING。而 okfn 設(shè)置為 ip_rcv_finish,也就是說,當(dāng) NF_IP_PRE_ROUTING 鏈上的所有鉤子函數(shù)都成功對數(shù)據(jù)包進行處理后,將會調(diào)用 ip_rcv_finish 函數(shù)來繼續(xù)對數(shù)據(jù)包進行處理。
“如何實現(xiàn)Netfilter”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!