linux內核netfilter模塊分析之:HOOKs點的注冊及調用

1: 為什么要寫這個東西?
最近在找工作,之前netfilter 這一塊的代碼也認真地研究過,應該每個人都是這樣的你懂 不一定你能很準確的表達出來。 故一定要化些時間把這相關的東西總結一下。

0:相關文檔

?linux 下 nf_conntrack_tuple 跟蹤記錄? 其中可以根據內核提供的數據結構獲取連接跟蹤記錄。

?iptables 中的NAT使用總結? ? iptable的在防火墻上面的應用。

1:iptable中三個tables所掛接的HOOKs

其實這個問題很簡單的運行iptables打開看看就知道,此處的hook與內核的hook是對應起來的。



因此在內核中注冊的5個HOOK點如下:

enum nf_inet_hooks?{
NF_INET_PRE_ROUTING,
NF_INET_LOCAL_IN,
NF_INET_FORWARD,
NF_INET_LOCAL_OUT,
NF_INET_POST_ROUTING,

NF_INET_NUMHOOKS
};

在向下看linux內核中的實現之前在看看一個數據包在進過linux內核中neitfilter的處理過程。

其中這5個HOOK點的執行點說明如下:
數據報從進入系統,進行IP校驗以后,首先經過第一個HOOK函數NF_IP_PRE_ROUTING進行處理;
然后就進入路由代碼,其決定該數據報是需要轉發還是發給本機的;
若該數據報是發被本機的,則該數據經過HOOK函數NF_IP_LOCAL_IN處理以后然后傳遞給上層協議;
若該數據報應該被轉發則它被NF_IP_FORWARD處理;

經過轉發的數據報經過最后一個HOOK函數NF_IP_POST_ROUTING處理以后,再傳輸到網絡上。

本地產生的數據經過HOOK函數NF_IP_LOCAL_OUT 處理后,進行路由選擇處理,然后經過NF_IP_POST_ROUTING處理后發送出去。




上面的圖可以知道,一個數據包在內核中進行的hook的處理點。
2 :proc文件下的跟蹤記錄

上面的就是連接跟蹤記錄,其中記錄linux系統建立的每一條連接,其中包括源IP,目的IP,源port,目的port,協議ID,其這些可以稱為5元組。有關這個在linux內含中的定義是的結構體?struct nf_conn ? 中的變量?

?? /* Connection tracking(鏈接跟蹤)用來跟蹤、記錄每個鏈接的信息(目前僅支持IP協議的連接跟蹤)。
? ?? ?? ?? ?每個鏈接由“tuple”來唯一標識,這里的“tuple”對不同的協議會有不同的含義,例如對tcp,udp
? ?? ?? ?? ?? ???來說就是五元組: (源IP,源端口,目的IP, 目的端口,協議號),對ICMP協議來說是: (源IP, 目
? ?? ?? ?? ?的IP, id, type, code), 其中id,type與code都是icmp協議的信息。鏈接跟蹤是防火墻實現狀態檢
? ?? ?? ?? ?測的基礎,很多功能都需要借助鏈接跟蹤才能實現,例如NAT、快速轉發、等等。*/

/* XXX should I move this to the tail ? - Y.K */
/* These are my tuples; original and reply */
struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX]; 此變量就保存著上面的跟蹤記錄,


3:hooks點的定義及注冊


其中每個不同協議的不同HOOK點最終都會注冊到全局的nf_hooks鏈表變量之中:同時注冊到同一個HOOK的處理函數會根據優先級的不同的進行先后處理。

extern struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];

其中定義的協議如下:

enum {
NFPROTO_UNSPEC = ?0,
NFPROTO_IPV4 ? = ?2, //ipV4
NFPROTO_ARP ? ?= ?3, //ARP
NFPROTO_BRIDGE = ?7, //brigde
NFPROTO_IPV6 ? = 10,
NFPROTO_DECNET = 12,
NFPROTO_NUMPROTO,

};

NF_MAX_HOOKS 宏的定義已經在前面說明。HOOK點的定義。


與下面的HOOK的struct nf_hook_ops對比一下就可以看到差異:變量pf的值不同,優先級不同,即內核中根據不同的協議類型可以注冊不同的掛載點進行不同的優先級數據包的處理。


其注冊使用函數為:nf_register_hooks()函數在內核中多個地方出現,因為用戶可以根據自己的需要對特定的協議在特定的位置添加HOOK出現函數。


nf_register_hook()函數的實現就是:

[cpp]?view plaincopy
  1. int?nf_register_hook(struct?nf_hook_ops?*reg)??
  2. {??
  3. ????struct?nf_hook_ops?*elem;??
  4. ????int?err;??
  5. ??
  6. ????err?=?mutex_lock_interruptible(&nf_hook_mutex);??
  7. ????if?(err?<?0)??
  8. ????????return?err;遍歷已經注冊的的HOOK,OPS,將新加入的根據優先級添加到鏈表最后??
  9. ????list_for_each_entry(elem,?&nf_hooks[reg->pf][reg->hooknum],?list)?{??
  10. ????????if?(reg->priority?<?elem->priority)??
  11. ????????????break;??
  12. ????}??
  13. ????list_add_rcu(?->list,?elem->list.prev);??
  14. ????mutex_unlock(&nf_hook_mutex);??
  15. ????return?0;??
  16. }??

上面就是HOOK點的注冊函數,即根據協議類型和HOOK點注冊到全局數組中nf_hooks[][]中。

4:注冊的HOOK點何時被使用?

在linux內核中當需要使用注冊的HOOK點時,使用函數:

#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \
NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)


NF_HOOK->NF_HOOK_THRESH->nf_hook_thresh->nf_hook_slow——這個是最終的執行函數。

先看看下面函數都返回值:

/* Responses from hook functions. */
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define NF_STOP 5
#define NF_MAX_VERDICT NF_STOP

[cpp]?view plaincopy
  1. int?nf_hook_slow(u_int8_t?pf,?unsigned?int?hook,?struct?sk_buff?*skb,??
  2. ?????????struct?net_device?*indev,??
  3. ?????????struct?net_device?*outdev,??
  4. ?????????int?(*okfn)(struct?sk_buff?*),??
  5. ?????????int?hook_thresh)??
  6. {??
  7. ????struct?list_head?*elem;??
  8. ????unsigned?int?verdict;??
  9. ????int?ret?=?0;??
  10. ??
  11. ????/*?We?may?already?have?this,?but?read-locks?nest?anyway?*/??
  12. ????rcu_read_lock();??
  13. ??
  14. ????elem?=?&nf_hooks[pf][hook];//是不是很熟悉就是上面的全局變量專門用來注冊全局HOOK點的變量。??
  15. next_hook:/*?開始遍歷對應的netfilter的規則,即對應的proto和hook掛載點?*/??
  16. ????verdict?=?nf_iterate(&nf_hooks[pf][hook],?skb,?hook,?indev,outdev,?&elem,?okfn,?hook_thresh);??
  17. ????if?(verdict?==?NF_ACCEPT?||?verdict?==?NF_STOP)?{??
  18. ????????ret?=?1;??
  19. ????}?else?if?(verdict?==?NF_DROP)?{??
  20. ????????kfree_skb(skb);??
  21. ????????ret?=?-EPERM;??
  22. ????}?else?if?((verdict?&?NF_VERDICT_MASK)?==?NF_QUEUE)?{??
  23. ????????if?(!nf_queue(skb,?elem,?pf,?hook,?indev,?outdev,?okfn,??
  24. ??????????????????verdict?>>?NF_VERDICT_BITS))??
  25. ????????????goto?next_hook;??
  26. ????}??
  27. ????rcu_read_unlock();??
  28. ????return?ret;??
  29. }??


現在來看看nf_iterate()函數:

[cpp]?view plaincopy
  1. unsigned?int?nf_iterate(struct?list_head?*head,??
  2. ????????????struct?sk_buff?*skb,??
  3. ????????????unsigned?int?hook,??
  4. ????????????const?struct?net_device?*indev,??
  5. ????????????const?struct?net_device?*outdev,??
  6. ????????????struct?list_head?**i,??
  7. ????????????int?(*okfn)(struct?sk_buff?*),??
  8. ????????????int?hook_thresh)??
  9. {??
  10. ????unsigned?int?verdict;??
  11. //其中head就是全局的2維數組nf_hooks,??
  12. ????/*?
  13. ?????*?The?caller?must?not?block?between?calls?to?this?
  14. ?????*?function?because?of?risk?of?continuing?from?deleted?element.?
  15. ?????*/??
  16. ????list_for_each_continue_rcu(*i,?head)?{??
  17. ????????struct?nf_hook_ops?*elem?=?(struct?nf_hook_ops?*)*i;??
  18. ??
  19. ????????if?(hook_thresh?>?elem->priority)??
  20. ????????????continue;??
  21. ??
  22. ????????/*?Optimization:?we?don't?need?to?hold?module?
  23. ???????????reference?here,?since?function?can't?sleep.?--RR?*/??
  24. ????????verdict?=?elem->hook(hook,?skb,?indev,?outdev,?okfn);//根據協議和HOOK點執行掛在的處理函數??
  25. ????????if?(verdict?!=?NF_ACCEPT)?{//返回結果進行判斷。??
  26. #ifdef?CONFIG_NETFILTER_DEBUG??
  27. ????????????if?(unlikely((verdict?&?NF_VERDICT_MASK)??
  28. ????????????????????????????>?NF_MAX_VERDICT))?{??
  29. ????????????????NFDEBUG("Evil?return?from?%p(%u).\n",??
  30. ????????????????????elem->hook,?hook);??
  31. ????????????????continue;??
  32. ????????????}??
  33. #endif??
  34. ????????????if?(verdict?!=?NF_REPEAT)??
  35. ????????????????return?verdict;??
  36. ????????????*i?=?(*i)->prev;??
  37. ????????}??
  38. ????}??
  39. ????return?NF_ACCEPT;??
  40. }??

對各個返回值的解釋如下:

NF_DROP:直接drop掉這個數據包;
NF_ACCEPT:數據包通過了掛載點的所有規則;
NF_STOLEN:這個還未出現,留在以后解釋;
NF_QUEUE:將數據包enque到用戶空間的enque handler;
NF_REPEAT:為netfilter的一個內部判定結果,需要重復該條規則的判定,直至不為NF_REPEAT;
NF_STOP:數據包通過了掛載點的所有規則。但與NF_ACCEPT不同的一點時,當某條規則的判定結果為NF_STOP,那么可以直接返回結果NF_STOP,無需進行后面的判定了。而NF_ACCEPT需要所以的規則都為ACCEPT,才能返回NF_ACCEPT。

在數據包流經內核協議棧的整個過程中,在內中定義的HOOK中的如:PRE_ROUTING、LOCAL_IN、FORWARD、LOCAL_OUT和POST_ROUTING會根據數據包的協議簇PF_INET到這些關鍵點去查找是否注冊有鉤子函數。如果沒有,則直接返回okfn函數指針所指向的函數繼續走協議棧;如果有,則調用nf_hook_slow函數,從而進入到Netfilter框架中去進一步調用已注冊在該過濾點下的鉤子函數,再根據其返回值來確定是否繼續執行由函數指針okfn所指向的函數

一個IP數據包的接受過程如下:





本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/384989.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/384989.shtml
英文地址,請注明出處:http://en.pswp.cn/news/384989.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

指定結構體元素的位字段

struct B {char a:4; //a這個成員值占了4bitchar b:2;char c:2; } 占了1個字節 struct B {int a:4; //a這個成員值占了4bitchar b:2;char c:2; } 占了8個字節 控制LED燈的結構體&#xff1a; struct E {char a1:1;char a2:1;char a3:1;char a4:1;char a5:1;char a6:1;char a7:1…

網絡抓包工具 wireshark 入門教程

Wireshark&#xff08;前稱Ethereal&#xff09;是一個網絡數據包分析軟件。網絡數據包分析軟件的功能是截取網絡數據包&#xff0c;并盡可能顯示出最為詳細的網絡數據包數據。Wireshark使用WinPCAP作為接口&#xff0c;直接與網卡進行數據報文交換。網絡管理員使用Wireshark來…

結構體中指針

結構體中帶有指針的情況 #include<stdio.h>struct man {char *name;int age; };int main() {struct man m {"tom",20};printf("name %s, age %d\n",m.name,m.age);return 0; } 運行結果&#xff1a; exbotubuntu:~/wangqinghe/C/20190714$ gcc st…

python使用opencv提取視頻中的每一幀、最后一幀,并存儲成圖片

提取視頻每一幀存儲圖片 最近在搞視頻檢測問題&#xff0c;在用到將視頻分幀保存為圖片時&#xff0c;圖片可以保存&#xff0c;但是會出現(-215:Assertion failed) !_img.empty() in function cv::imwrite問題而不能正常運行&#xff0c;在檢查代碼、檢查路徑等措施均無果后&…

結構體參數

結構體作為函數參數&#xff1a; #include<stdio.h> #include<stdlib.h> #include<string.h>struct student {char name[10];int age; };void print_student(struct student s) {printf("name %s,age %d\n",s.name,s.age); } void set_studen…

線程間通信之eventfd

線程間通信之eventfd man手冊中的解釋&#xff1a; eventfd()創建了一個“eventfd對象”&#xff0c; 通過它能夠實現用戶態程序間(我覺得這里主要指線程而非進程)的等待/通知機制&#xff0c;以及內核態向用戶態通知的機制&#xff08;未考證&#xff09;。 此對象包含了一個…

【linux 開發】定時器使用setitimer

setitimer Linux 為每一個進程提供了 3 個 setitimer 間隔計時器&#xff1a; ITIMER_REAL&#xff1a;減少實際時間&#xff0c;到期的時候發出 SIGALRM 信號。ITIMER_VIRTUAL&#xff1a;減少有效時間 (進程執行的時間)&#xff0c;產生 SIGVTALRM 信號。ITIMER_PROF&#…

文件操作(寫)

/*** file.c ***/ #include<stdio.h>int main() {//用寫的方式打開一個文件 //w的意思是文件如果不存在&#xff0c;就建立一個文件&#xff0c;如果文件存在就覆蓋FILE *p fopen("/home/exbot/wangqinghe/C/20190716/file1.txt","w");fputs(&qu…

定時器timerfd

1.為什么要加入此定時器接口 linux2.6.25版本新增了timerfd這個供用戶程序使用的定時接口&#xff0c;這個接口基于文件描述符&#xff0c;當超時事件發生時&#xff0c;該文件描述符就變為可讀。我首次接觸這個新特性是在muduo網絡庫的定時器里看到的&#xff0c;那么新增一個…

文件操作(讀)

讀一行&#xff1a; #include<stdio.h> #include<string.h> #include<stdlib.h> const int maxn 10; int main() {char s[1024] {0};FILE *p fopen("/home/exbot/wangqinghe/C/20190716/file.txt","r");//第一個參數是一個內存地址&…

timerfd與epoll

linux timerfd系列函數總結 網上關于timerfd的文章很多&#xff0c;在這兒歸納總結一下方便以后使用&#xff0c;順便貼出一個timerfd配合epoll使用的簡單例子 一、timerfd系列函數 timerfd是Linux為用戶程序提供的一個定時器接口。這個接口基于文件描述符&#xff0c;通過文…

文件操作(解密加密)

文件加密&#xff1a; #include<stdio.h> #include<string.h> #include<stdlib.h>void code(char *s) {while(*s){(*s);s;} }int main() {char s[1024] {0};FILE *p fopen("/home/exbot/wangqinghe/C/20190716/file.txt","r");FILE *p…

linux僵尸進程產生的原因以及如何避免產生僵尸進程defunct

給進程設置僵尸狀態的目的是維護子進程的信息&#xff0c;以便父進程在以后某個時間獲取。這些信息包括子進程的進程ID、終止狀態以及資源利用信息(CPU時間&#xff0c;內存使用量等等)。如果一個進程終止&#xff0c;而該進程有子進程處于僵尸狀態&#xff0c;那么它的所有僵尸…

linux下僵尸進程(Defunct進程)的產生與避免

在測試基于 DirectFBGstreamer 的視頻聯播系統的一個 Demo 的時候&#xff0c;其中大量使用 system 調用的語句&#xff0c;例如在 menu 代碼中的 system("./play") &#xff0c;而且多次執行&#xff0c;這種情況下&#xff0c;在 ps -ef 列表中出現了大量的 defunc…

文件操作函數

fopen()函數參數&#xff1a; r 只讀的方式打開文件。 打開成功返回文件指針&#xff0c; 打開失敗返回NULL r 以讀寫方式打開文件。 文件必須存在 rb 以二進制模式讀寫文件&#xff0c;文件必須存在 rw 讀寫一個二進制文件&#xff0c;允許讀和寫 w 打開只寫文件&…

讀過的最好的epoll講解

首先我們來定義流的概念&#xff0c;一個流可以是文件&#xff0c;socket&#xff0c;pipe等等可以進行I/O操作的內核對象。 不管是文件&#xff0c;還是套接字&#xff0c;還是管道&#xff0c;我們都可以把他們看作流。 之后我們來討論I/O的操作&#xff0c;通過read&#xf…

文件操作函數(讀寫)

文件文本排序&#xff1a; 數組冒泡&#xff1a; #include<stdio.h>void swap(int *a,int *b) {int temp *a;*a *b;*b temp; }void bubble(int *p,int n) {int i;int j;for(i 0; i < n; i){for(j 1; j < n - i; j){if(p[j - 1] > p[j]){swap(&p[j-1],&…

文件操作(升級)

計算字符串“25 32 ” #include<stdio.h> #include<string.h>int calc_string(char *s) {char buf1[100] {0};char oper 0;char buf2[100] {0};int len strlen(s);int i;for(i 0; i < len; i){if( s[i] || - s[i] || * s[i] || / s[i] ){strncpy…

C語言指針轉換為intptr_t類型

C語言指針轉換為intptr_t類型 1、前言 今天在看代碼時&#xff0c;發現將之一個指針賦值給一個intptr_t類型的變量。由于之前沒有見過intptr_t這樣數據類型&#xff0c;憑感覺認為intptr_t是int類型的指針。感覺很奇怪&#xff0c;為何要將一個指針這樣做呢&#xff1f;如是果…

nginx epoll詳解

nginx epoll 事件模型 nginx做為一個異步高效的事件驅動型web服務器&#xff0c;在linux平臺中當系統支持epoll時nginx默認采用epoll來高效的處理事件。nginx中使用ngx_event_t結構來表示一個事件&#xff0c;先介紹下ngx_event_t結構體中成員的含義&#xff1a; struct ngx_ev…