linux内核编程--4netfiter钩子函数
1. 背景
京東金融資深C/C++開(kāi)發(fā)工程師 崗位被面試到,本來(lái)在《深入linux內(nèi)核架構(gòu)》一書(shū)中見(jiàn)過(guò),但由于整本書(shū)看的不是很懂,也沒(méi)實(shí)驗(yàn),當(dāng)時(shí)吱吱唔唔的回答了,答案不是很理想。后來(lái)也就沒(méi)有了后來(lái)······
2. 概述
netfilter是自2.4內(nèi)核的一個(gè)數(shù)據(jù)包過(guò)濾框架。可以過(guò)濾數(shù)據(jù)包,網(wǎng)絡(luò)地址和端口轉(zhuǎn)換(nat和napt技術(shù)),以及其他操作數(shù)據(jù)包的功能。主要工作原理是在內(nèi)核模塊注冊(cè)回調(diào)函數(shù)(hook函數(shù))到內(nèi)核,內(nèi)核執(zhí)行到相關(guān)點(diǎn)時(shí)會(huì)觸發(fā)這個(gè)回調(diào)函數(shù),然后根據(jù)回調(diào)函數(shù)里的邏輯,對(duì)包含網(wǎng)絡(luò)協(xié)議棧的sk_buff結(jié)構(gòu)進(jìn)行操作(丟失、修改等)。
iptables的內(nèi)核模塊就是使用的netfilter。
3. 相關(guān)API與數(shù)據(jù)結(jié)構(gòu)
int nf_register_hook(struct nf_hook_ops *reg); --------------注冊(cè)鉤子函數(shù) void nf_unregister_hook(struct nf_hook_ops *reg); ----------去注冊(cè)鉤子函數(shù) struct nf_hook_ops { ----------鉤子點(diǎn)結(jié)構(gòu)體struct list_head list; /* 標(biāo)準(zhǔn)鏈表—由于鏈接所有鉤子點(diǎn) *//* User fills in from here down. */nf_hookfn *hook; /* 鉤子函數(shù)回調(diào)指針 */struct net_device *dev; /* 網(wǎng)絡(luò)設(shè)備指針 */void *priv; /* 私有數(shù)據(jù)----在鉤子回調(diào)函數(shù)中使用 */u_int8_t pf; /* 協(xié)議族 */unsigned int hooknum; /* hook點(diǎn) *//* Hooks are ordered in ascending priority. */int priority; /* 優(yōu)先級(jí) */ };其他API請(qǐng)參考文件:<linux/netfilter.h>
4. 原理
4.1 內(nèi)核hook點(diǎn)
linux網(wǎng)絡(luò)協(xié)議棧中有5個(gè)hook點(diǎn),其報(bào)文處理流程如下:
對(duì)應(yīng)內(nèi)核源碼<linux/netfilter_bridge.h>:
/* Bridge Hooks */ /* After promisc drops, checksum checks. */------網(wǎng)卡混雜丟包和校驗(yàn)和檢查之后(所有進(jìn)入?yún)f(xié)議棧的數(shù)據(jù)包都會(huì)經(jīng)過(guò)PRE_ROUTING) #define NF_BR_PRE_ROUTING 0 /* If the packet is destined for this box. */------經(jīng)過(guò)路由判決,確定發(fā)往本機(jī) #define NF_BR_LOCAL_IN 1 /* If the packet is destined for another interface. */--------經(jīng)過(guò)路由判決,不是發(fā)往本機(jī),需要從其他接口轉(zhuǎn)發(fā)出去 #define NF_BR_FORWARD 2 /* Packets coming from a local process. */--------由本機(jī)外發(fā)出去的報(bào)文路徑 #define NF_BR_LOCAL_OUT 3 /* Packets about to hit the wire. */--------所有數(shù)據(jù)包離開(kāi)本機(jī)前的路徑 #define NF_BR_POST_ROUTING 4/* Not really a hook, but used for the ebtables broute table */-------非hook點(diǎn),用于其他目的 #define NF_BR_BROUTING 5 #define NF_BR_NUMHOOKS 6
4.2 內(nèi)核支持的鉤子協(xié)議
源碼:
enum {NFPROTO_UNSPEC = 0,NFPROTO_INET = 1,????????NFPROTO_IPV4 = 2,NFPROTO_ARP = 3,NFPROTO_NETDEV = 5,NFPROTO_BRIDGE = 7,NFPROTO_IPV6 = 10,NFPROTO_DECNET = 12,NFPROTO_NUMPROTO, };4.3 鉤子函數(shù)注冊(cè)/去注冊(cè)
源碼:
int nf_register_hook(struct nf_hook_ops *reg) ----------------注冊(cè)鉤子函數(shù) {struct net *net, *last;int ret;rtnl_lock();for_each_net(net) {????????????????????????-------------------遍歷所有網(wǎng)絡(luò)命名空間(虛擬化技術(shù))ret = nf_register_net_hook(net, reg);????????---------注冊(cè)鉤子函數(shù)if (ret && ret != -ENOENT)goto rollback;}list_add_tail(®->list, &nf_hook_list);rtnl_unlock();return 0; rollback:last = net;for_each_net(net) {if (net == last)break;nf_unregister_net_hook(net, reg);}rtnl_unlock();return ret; } int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg) {struct list_head *hook_list;struct nf_hook_entry *entry;struct nf_hook_ops *elem;entry = kmalloc(sizeof(*entry), GFP_KERNEL);if (!entry)return -ENOMEM;entry->orig_ops = reg;entry->ops = *reg;hook_list = nf_find_hook_list(net, reg); -------內(nèi)部根據(jù)鉤子節(jié)點(diǎn)協(xié)議和鉤子點(diǎn)找到二維數(shù)組對(duì)應(yīng)的鏈表頭if (!hook_list) {kfree(entry);return -ENOENT;}mutex_lock(&nf_hook_mutex);list_for_each_entry(elem, hook_list, list) {????????-------------按照優(yōu)先級(jí)插入到鏈表if (reg->priority < elem->priority)break;}list_add_rcu(&entry->ops.list, elem->list.prev);mutex_unlock(&nf_hook_mutex); #ifdef CONFIG_NETFILTER_INGRESSif (reg->pf == NFPROTO_NETDEV && reg->hooknum == NF_NETDEV_INGRESS)net_inc_ingress_queue(); #endif #ifdef HAVE_JUMP_LABELstatic_key_slow_inc(&nf_hooks_needed[reg->pf][reg->hooknum]); #endifreturn 0; }原理:
1.? 內(nèi)核將遍歷網(wǎng)絡(luò)命名空間鏈,為每個(gè)net命名空間掛接鉤子節(jié)點(diǎn)。那我們疑問(wèn):每個(gè)命名空間的鉤子點(diǎn)在內(nèi)核中是怎么組織的呢?查看源碼后我們知道struct net結(jié)構(gòu)中有專(zhuān)門(mén)掛接netfilter鉤子結(jié)構(gòu)的成員struct netns_nf nf;
struct net { … #ifdef CONFIG_NETFILTERstruct netns_nf nf; ---------netfilter結(jié)構(gòu) … }; struct netns_nf {...struct list_head hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; -----------二維數(shù)組鉤子鏈表 };函數(shù)nf_find_hook_list就是根據(jù)我們將要掛接的鉤子節(jié)點(diǎn)找對(duì)其對(duì)應(yīng)的掛接位置,其結(jié)構(gòu)圖如下:
即:內(nèi)核將每個(gè)協(xié)議的所有5個(gè)鉤子組成二維數(shù)組鏈,每次要掛接一個(gè)鉤子節(jié)點(diǎn),則根據(jù)協(xié)議/鉤子類(lèi)型找到對(duì)應(yīng)的掛接鏈,然后根據(jù)優(yōu)先級(jí)(優(yōu)先級(jí)值越小,優(yōu)先級(jí)越高)將鉤子節(jié)點(diǎn)插入鏈表中,完成掛接。
4.4 鉤子回調(diào)函數(shù)
原型:
typedef unsigned int nf_hookfn(void *priv,struct sk_buff *skb,const struct nf_hook_state *state);每監(jiān)控到一條數(shù)據(jù)包,就會(huì)調(diào)用一次回調(diào)函數(shù),數(shù)據(jù)包信息存儲(chǔ)在sk_buff結(jié)構(gòu)中,參考sk_buff相關(guān)介紹。這個(gè)函數(shù)的返回值決定了函數(shù)結(jié)束后此數(shù)據(jù)包的走向,有如下幾種返回值:
/* Responses from hook functions. */#define NF_DROP 0 ----丟棄,釋放sk_buff結(jié)構(gòu)#define NF_ACCEPT 1 ----繼續(xù)正常傳輸數(shù)據(jù)報(bào)(按照5個(gè)鉤子點(diǎn)正常傳輸)#define NF_STOLEN 2 ----模塊接管該數(shù)據(jù)報(bào),告訴Netfilter“忘掉”該數(shù)據(jù)報(bào)。該回調(diào)函數(shù)將從此開(kāi)始對(duì)數(shù)據(jù)包的處理,并且Netfilter應(yīng)當(dāng)放棄對(duì)該數(shù)據(jù)包做任何的處理。但是,這并不意味著該數(shù)據(jù)包的資源已經(jīng)被釋放。這個(gè)數(shù)據(jù)包以及它獨(dú)自的sk_buff數(shù)據(jù)結(jié)構(gòu)仍然有效,只是回調(diào)函數(shù)從Netfilter獲取了該數(shù)據(jù)包的所有權(quán)。#define NF_QUEUE 3 ----對(duì)該數(shù)據(jù)報(bào)進(jìn)行排隊(duì)(通常用于將數(shù)據(jù)報(bào)給用戶(hù)空間的進(jìn)程進(jìn)行處理)#define NF_REPEAT 4 ----再次調(diào)用該回調(diào)函數(shù),應(yīng)當(dāng)謹(jǐn)慎使用這個(gè)值,以免造成死循環(huán)#define NF_STOP 5 ----終止hook鏈處理,不會(huì)釋放sk_buff數(shù)據(jù)5 測(cè)試
我們開(kāi)發(fā)一個(gè)模塊程序,用于監(jiān)控內(nèi)核收到的特定的ICMP報(bào)文(可以由其他主機(jī)ping而產(chǎn)生),每匹配到一個(gè)報(bào)文,則用printk打印一條信息,然后用dmesg查看。
源碼(非完整模塊代碼,不清楚如何編譯成模塊請(qǐng)閱讀筆者之前的文章):
/* IP地址轉(zhuǎn)字符串 */ STATIC void IP2Str(char *ipaddr, int size, uint32_t ip) { snprintf(ipaddr, size, "%d.%d.%d.%d", ( ip >> 24 ) & 0xff , ( ip >> 16 ) & 0xff , ( ip >> 8 ) & 0xff , ip & 0xff);return; } /* 鉤子回調(diào)函數(shù) */ STATIC UINT myHookCallBack(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) {struct iphdr *pstIpHdr = NULL;CHAR szIpstr[BUF_LEN_20] = {0,};if (unlikely(NULL == skb)){return NF_ACCEPT;}pstIpHdr = ip_hdr(skb);if (unlikely(NULL == pstIpHdr)){return NF_ACCEPT;}if (pstIpHdr->protocol != IPPROTO_ICMP){return NF_ACCEPT;}/* 網(wǎng)絡(luò)序轉(zhuǎn)主機(jī)序,然后轉(zhuǎn)成字符串 */IP2Str(szIpstr, sizeof(szIpstr), ntohl(pstIpHdr->saddr));if (0 == strcmp(szIpstr, "192.168.1.7")) /* 字符串比較 */{printk(KERN_INFO "Recv icmp packet from 192.168.1.7\n"); /* printk打印 */}return NF_ACCEPT; } struct nf_hook_ops g_stMyNfHook = {.hook = myHookCallBack,.pf = NFPROTO_IPV4,.hooknum = NF_BR_PRE_ROUTING,.priority = NF_IP_PRI_FIRST, }; STATIC VOID hello_NfHook_Init(VOID) { INT iRet;iRet = nf_register_hook(&g_stMyNfHook);????/* 注冊(cè) */if (0 != iRet){printk(KERN_WARNING "nf_register_hook failed\n");return;}return; } STATIC VOID hello_NfHook_Fini(VOID) {nf_unregister_hook(&g_stMyNfHook); /* 去注冊(cè) */return; }測(cè)試結(jié)果:(linux內(nèi)核版本:?4.4.0-21)
說(shuō)明:筆者模擬公司辦公環(huán)境搭建有兩臺(tái)電腦? ?linux(192.168.1.6)? ------ windows(192.168.1.7)? ,用windows ping linux主機(jī),有如下結(jié)果。-----結(jié)果表明,鉤子函數(shù)測(cè)試完全正確。
參考:
http://blog.csdn.net/wuruixn/article/details/7957368
http://blog.csdn.net/stone8761/article/details/72821733
總結(jié)
以上是生活随笔為你收集整理的linux内核编程--4netfiter钩子函数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C++ reverse memcpy
- 下一篇: Linux下删除非空文件目录