FreeBSD的netgraph真是太帥了,它到底是個什麼玩藝呢?知道Linux的Netfilter的不少,那麼就用Netfilter來類比吧。netgraph是一個基於圖的鉤子系統,正如其名稱所展示的那樣,什麼樣的圖呢?很簡單,就是通過邊連接的節點,和數據結構裡面學到的一樣。netgraph系統掛接在內核協議棧的特定點上,哪些點呢?這個和Netfilter很類似,但是卻不是Netfilter精心設計的那5個點,而是更簡單的每一層處理的輸入點和輸出點,如下圖所示:
netgraph到底長什麼樣子呢?到目前為止,我們只是知道了一張圖掛上去了,這僅僅是個接口,一個開始,既然掛上去了,數據包就從此處進入這張圖了,把它叫做地圖更加適合,因此從此以後,數據包就要在游歷於這張地圖了,最終的結果有兩個:
1.數據包從地圖的某處出來,重新進入系統標准的協議棧的當初被攔截的那個地方;
2.數據包再也沒有出來回到原點,要麼被地圖吃掉了(進入了某一房間?),要麼就是從某處出去,進入協議棧的別的地方。以上兩點很類似於Netfilter的ACCEPT,STOLEN這樣的結果,仔細想想不是麼?netgraph和標准協議棧的銜接如下圖所示:
既然知道了netgraph的位置,那麼下面就看看它的樣子吧。還是先給出一幅圖
該圖中有兩種元素,一種是節點,另一種是連接到節點的邊的兩端的頂點。在netgraph的術語中,節點就是Node,而頂點叫做hook,一條邊連接兩個hook,hook通過CONNECT/MKPEER構成一條邊。從上圖中可以看出,一條邊的兩端必然有兩個hook,從命名上可以看出這些“邊的端點”其實就是真正處理數據的地方,而Node其實就是一個“數據+操作”的封裝,一個Node可以有多個hook,通過這些hook連接到其它的Node。
我們可以用OO的思想來理解這些個netgraph的概念,Node就是一個對象,每一個Node都有它所屬的Type,可以將Type理解成類。而hook其實就是一個Node對象的私有數據,整個graph通過“各個hook的對接”來完成,FreeBSD提供了豐富的命令來完成netgraph的構建,說白了其實就是以下幾步驟:
1.生成一系列的Node對象;
2.為每一個Node定義一個或多個hook;
3.將特定的Node通過hook連接在一起。如此一來整個graph就構建好了,FreeBSD提供了struct ng_type,它便是代表了一個類,然後你每生成一個特定ng_type的實例就相當於生成了一個對象,通過對該結構體裡面的一些字段的理解,我們就可以完整理解數據包在這個graph中的游歷過成了。struct ng_type定義如下:
- struct ng_type {
- u_int32_t version; /* must equal NG_API_VERSION */
- const char *name; /* Unique type name */
- modeventhand_t mod_event; /* Module event handler (optional) */
- ng_constructor_t *constructor; /* Node constructor */
- ng_rcvmsg_t *rcvmsg; /* control messages come here */
- ng_close_t *close; /* warn about forthcoming shutdown */
- ng_shutdown_t *shutdown; /* reset, and free resources */
- ng_newhook_t *newhook; /* first notification of new hook */
- ng_findhook_t *findhook; /* only if you have lots of hooks */
- ng_connect_t *connect; /* final notification of new hook */
- ng_rcvdata_t *rcvdata; /* data comes here */
- ng_disconnect_t *disconnect; /* notify on disconnect */
- const struct ng_cmdlist *cmdlist; /* commands we can convert */
- LIST_ENTRY(ng_type) types; /* linked list of all types */
- int refs; /* number of instances */
- };
注釋很清楚了,自不必說,如果我們看看其中一些回調函數的定義,就更能理解了。“構造函數”和“析構函數”都有,每一個“成員函數”的參數列表的第一個參數類型都是node_p,這難道不是this麼?這裡唯一要注意的就是rcvdata回調函數,該函數接收從另一個Node發送過來的數據,接收者是hook,而不是Node,再次強調,Node之間通過hook相連接,而不是通過node本身,然而每一個hook都要唯一綁定一個Node對象,因此我們可以從hook解析出唯一的Node對象,卻不能從Node中直接得到hook(一個Node對象擁有N多hook呢),要分清一對一和一對多的關系。因此rcvdata的第一個參數是hook_p就是合理的了。
Node和Node之間通過hook傳遞控制信息,而網絡數據包則是通過一個hook向其peer hook發送消息的方式完成的,當然所謂的發送消息大多數情況下就是函數直接調用。既然一條邊兩端有兩個hook,那麼每一個hook就有一個peer,每當我們將數據包發送到一個hook的時候,實際的效果就是數據包被發送到了該hook的peer,這是netgraph的核心邏輯實現的,我們可以從下面的這個核心宏中看到這一點:
- #define NG_FWD_ITEM_HOOK_FLAGS(error, item, hook, flags) \
- do { \
- (error) = \
- ng_address_hook(NULL, (item), (hook), NG_NOFLAGS); \
- if (error == 0) { \
- SAVE_LINE(item); \
- (error) = ng_snd_item((item), (flags)); \
- } \
- (item) = NULL; \
- } while (0)
其中ng_address_hook完成了peer的定位,這個peer可以通過ngctl命令來設置。