FreeBSD的netgraph真是太帥了,它到底是個什么玩藝呢?知道Linux的Netfilter的不少,那么就用Netfilter來類比吧。netgraph是一個基于圖的鉤子系統(tǒng),正如其名稱所展示的那樣,什么樣的圖呢?很簡單,就是通過邊連接的節(jié)點,和數(shù)據(jù)結(jié)構(gòu)里面學(xué)到的一樣。netgraph系統(tǒng)掛接在內(nèi)核協(xié)議棧的特定點上,哪些點呢?這個和Netfilter很類似,但是卻不是Netfilter精心設(shè)計的那5個點,而是更簡單的每一層處理的輸入點和輸出點,如下圖所示:
netgraph到底長什么樣子呢?到目前為止,我們只是知道了一張圖掛上去了,這僅僅是個接口,一個開始,既然掛上去了,數(shù)據(jù)包就從此處進(jìn)入這張圖了,把它叫做地圖更加適合,因此從此以后,數(shù)據(jù)包就要在游歷于這張地圖了,最終的結(jié)果有兩個:
1.數(shù)據(jù)包從地圖的某處出來,重新進(jìn)入系統(tǒng)標(biāo)準(zhǔn)的協(xié)議棧的當(dāng)初被攔截的那個地方;
2.數(shù)據(jù)包再也沒有出來回到原點,要么被地圖吃掉了(進(jìn)入了某一房間?),要么就是從某處出去,進(jìn)入?yún)f(xié)議棧的別的地方。
以上兩點很類似于Netfilter的ACCEPT,STOLEN這樣的結(jié)果,仔細(xì)想想不是么?netgraph和標(biāo)準(zhǔn)協(xié)議棧的銜接如下圖所示:
既然知道了netgraph的位置,那么下面就看看它的樣子吧。還是先給出一幅圖
該圖中有兩種元素,一種是節(jié)點,另一種是連接到節(jié)點的邊的兩端的頂點。在netgraph的術(shù)語中,節(jié)點就是Node,而頂點叫做hook,一條邊連接兩個hook,hook通過CONNECT/MKPEER構(gòu)成一條邊。從上圖中可以看出,一條邊的兩端必然有兩個hook,從命名上可以看出這些“邊的端點”其實就是真正處理數(shù)據(jù)的地方,而Node其實就是一個“數(shù)據(jù)+操作”的封裝,一個Node可以有多個hook,通過這些hook連接到其它的Node。
我們可以用OO的思想來理解這些個netgraph的概念,Node就是一個對象,每一個Node都有它所屬的Type,可以將Type理解成類。而hook其實就是一個Node對象的私有數(shù)據(jù),整個graph通過“各個hook的對接”來完成,F(xiàn)reeBSD提供了豐富的命令來完成netgraph的構(gòu)建,說白了其實就是以下幾步驟:
1.生成一系列的Node對象;
2.為每一個Node定義一個或多個hook;
3.將特定的Node通過hook連接在一起。
如此一來整個graph就構(gòu)建好了,F(xiàn)reeBSD提供了struct ng_type,它便是代表了一個類,然后你每生成一個特定ng_type的實例就相當(dāng)于生成了一個對象,通過對該結(jié)構(gòu)體里面的一些字段的理解,我們就可以完整理解數(shù)據(jù)包在這個graph中的游歷過成了。struct ng_type定義如下:
注釋很清楚了,自不必說,如果我們看看其中一些回調(diào)函數(shù)的定義,就更能理解了。“構(gòu)造函數(shù)”和“析構(gòu)函數(shù)”都有,每一個“成員函數(shù)”的參數(shù)列表的第一個參數(shù)類型都是node_p,這難道不是this么?這里唯一要注意的就是rcvdata回調(diào)函數(shù),該函數(shù)接收從另一個Node發(fā)送過來的數(shù)據(jù),接收者是hook,而不是Node,再次強(qiáng)調(diào),Node之間通過hook相連接,而不是通過node本身,然而每一個hook都要唯一綁定一個Node對象,因此我們可以從hook解析出唯一的Node對象,卻不能從Node中直接得到hook(一個Node對象擁有N多hook呢),要分清一對一和一對多的關(guān)系。因此rcvdata的第一個參數(shù)是hook_p就是合理的了。
Node和Node之間通過hook傳遞控制信息,而網(wǎng)絡(luò)數(shù)據(jù)包則是通過一個hook向其peer hook發(fā)送消息的方式完成的,當(dāng)然所謂的發(fā)送消息大多數(shù)情況下就是函數(shù)直接調(diào)用。既然一條邊兩端有兩個hook,那么每一個hook就有一個peer,每當(dāng)我們將數(shù)據(jù)包發(fā)送到一個hook的時候,實際的效果就是數(shù)據(jù)包被發(fā)送到了該hook的peer,這是netgraph的核心邏輯實現(xiàn)的,我們可以從下面的這個核心宏中看到這一點:
其中ng_address_hook完成了peer的定位,這個peer可以通過ngctl命令來設(shè)置。
就這樣,一個數(shù)據(jù)包在整個netgraph中通過“離開一個Node的某個hook,進(jìn)入另一個Node的某個hook的rcvdata”的方式游歷,Node在這里的作用就是封裝私有數(shù)據(jù)和統(tǒng)一的操作,當(dāng)然,你可以重載掉一個Node內(nèi)統(tǒng)一的rcvdata回調(diào)函數(shù),而是為每一個hook都設(shè)置一個私有的rcvdata回調(diào)函數(shù),再次強(qiáng)調(diào),是hook在rcvdata,而不是Node在rcvdata,Node的rcvdata是一個該Node所有hook通用的回調(diào)函數(shù),如果沒有hook私有的rcvdata,該通用函數(shù)將被調(diào)用,ng_snd_item最終將進(jìn)入下面的邏輯:
由此看出,Node有一個默認(rèn)的對所有hook都適用的rcvdata回調(diào)函數(shù),然而各個hook可以重載掉這個默認(rèn)的rcvdata回調(diào)函數(shù)。
接下來我們看一下netgraph如何和協(xié)議棧對接,不要把操作系統(tǒng)想得太神奇,實際上完成這種工作只需要一個回調(diào)函數(shù)即可。以以太網(wǎng)接收為例,以太網(wǎng)接收處理函數(shù)中會調(diào)用ng_ether_input_p回調(diào)函數(shù),你只需要將其定義一下即可,對于很多場合都使用的ng_ether,它將此函數(shù)定義為:
最后通過NG_SEND_DATA_ONLY將數(shù)據(jù)包發(fā)送給priv->lower這個hook,最終數(shù)據(jù)包會進(jìn)入priv->lower的peer,調(diào)用priv->lower->peer的rcvdata回調(diào)函數(shù),在一切開始工作之前,你首先需要構(gòu)建好整個graph。對于以太網(wǎng)發(fā)送函數(shù),也有類似的_p回調(diào)函數(shù)。
netgraph和Netfilter的區(qū)別在于它可以將graph“掛接”在特定的interface上,而Netfilter卻把HOOK直接掛在協(xié)議棧本身,interface在Netfilter中只是一個match。如此一比較,效率差異就很明顯了。以以太網(wǎng)為例,在ether_input中就會調(diào)用netgraph,如果加載了ng_ether的話,就會調(diào)用下面的函數(shù):
如果本ifp上沒有掛接任何graph,則直接返回標(biāo)準(zhǔn)協(xié)議棧處理,如果掛接了一個graph,則數(shù)據(jù)包將進(jìn)入該graph,你可以將firewall rule配置在此graph里面。對于Netfilter而言,在網(wǎng)卡接收這一層,沒有任何HOOK,只有到了IP層,才會進(jìn)入PREROUTING/INPUT/FORWARD...等HOOK,哪怕你配置了一條rule,所有的包都將接受檢查以確定是否匹配,在Netfilter的rule中,所謂的interface只是一個match。
需要說明的是,netgraph也可以像Netfilter那樣工作,你只需要將其掛在ip_in(out)put上即可。
我們給出兩個例子來看看netgraph如何實現(xiàn)bridge和bonding,這些在Linux上都是通過虛擬net_device來實現(xiàn)的,其發(fā)送邏輯都是該虛擬net_device的hard_xmit實現(xiàn)的,而其數(shù)據(jù)接收邏輯則是硬編碼在netif_receive_skb中的,bridge是通過handle_bridge這個硬編碼hook進(jìn)入的,而bonding是通過skb_bond來實現(xiàn)的。也就是說Linux是通過對既有的協(xié)議棧進(jìn)行硬修改來實現(xiàn)的,而netgraph則不需要這樣,對于FreeBSD,我們只需要構(gòu)建一張graph就可以實現(xiàn)bridge或者bonding,首先我們先看看bridge的實現(xiàn)邏輯,如下圖所示:
我個人以為圖示已經(jīng)很清晰了。需要注意的是,netgraph將本地的網(wǎng)卡作為了局域網(wǎng)上一張普通的網(wǎng)卡來看待,并沒有刻意區(qū)分流量是本機(jī)發(fā)出的還是從其它機(jī)器發(fā)出的,因此,如果你只是想將bridge作為一個二層設(shè)備,那么可以斷開Hook-ethX-low和Hook-ethX-upper之間的邊即可,netgraph實現(xiàn)的bridge,你看不到虛擬設(shè)備,這種實現(xiàn)更純粹,偉大的BSD將這種思想帶給了其衍生出來的Cisco IOS。
下面是bonding的實現(xiàn)邏輯:
由于bonding網(wǎng)卡大多數(shù)負(fù)責(zé)的是本地IP層發(fā)出的數(shù)據(jù),需要和路由轉(zhuǎn)發(fā)表相配合,因此需要有一塊虛擬網(wǎng)卡,這個是通過ng_eiface的構(gòu)造函數(shù)ng_eiface_constructor實現(xiàn)的。依然無其它話可說。
以上兩個圖展示了netgraph的魅力,既然這樣,也就可以依照這種方式實現(xiàn)VLAN,IPSec等了,要比Linux的Netfilter加設(shè)備驅(qū)動模型的實現(xiàn)方式更“可插拔”,有netgraph,F(xiàn)reeBSD可以將所有的協(xié)議處理在一張張的graph中進(jìn)行,數(shù)據(jù)包在graph中游歷在每一個Node被接收到的hook處理,主要你能根據(jù)協(xié)議處理邏輯構(gòu)建好一張圖,將這張圖掛接在協(xié)議棧,甚至掛接在驅(qū)動上,你就能很方便的實現(xiàn)網(wǎng)絡(luò)的任意擴(kuò)展...
最后看一下netgraph的依賴關(guān)系,在netgraph中,每張圖都是相對獨立的,數(shù)據(jù)包從某處進(jìn)入一張圖A,然后從某處出來,在另一處再進(jìn)入圖B,此時它將不能再使用圖A。這和Netfilter不同,Netfilter基于HOOK設(shè)計,使用一些match來進(jìn)行filter,比如NAT就需要ip_conntrack,ctdir需要ip_conntrack等等,ip_conntrack一直都面臨table full的問題,因此你要用raw表的NOTRACK這個target來免除追蹤不感興趣流量來緩解這個問題。有下面的需求:
從網(wǎng)段M發(fā)出到網(wǎng)段N的流量(兩個方向)打上tag待策略路由來處理,從網(wǎng)段N發(fā)出到網(wǎng)段M的流量(兩個方向)不打tag。
分析:
很顯然要使用ctdir這個match,否則將會過濾掉返回流量,于是有以下target為NOTRACK的match:
!dst N/interface $內(nèi)網(wǎng)口
然而意味著從網(wǎng)段N發(fā)出到達(dá)M的返回流量也將被conntrack,這是因為ctdir和conntrack相互依賴才導(dǎo)致了這樣的問題,在raw表中,你甚至都不知道數(shù)據(jù)包到底是走INPUT還是FORWARD,所以你很難讓所有這一切關(guān)聯(lián)起來,雖然conntrack可以保持一個流信息在內(nèi)存中,但是卻可能存在大量不相關(guān)的流也被保存。如果使用netgraph呢?很簡單,我們可以寫在兩個命令中:
No.x check-status
No.z skip No.y from N to M #對于返回流量,只檢測conntrack
No.y netgraph tag from M to N keep-status
如此即可。FreeBSD不需要conntrack,它內(nèi)建了一個動態(tài)ruleset,凡是keep-status的流量都將自動將返回流量加入動態(tài)ruleset中,實際上也就是“保持了一個流信息在內(nèi)存中”,F(xiàn)reeBSD的conntrack和單獨的rule相關(guān)聯(lián)而不是和整個協(xié)議棧關(guān)聯(lián),這實際上也是netgraph的思想,我們看一下rule相關(guān)的conntrack和協(xié)議棧香瓜的conntrack的區(qū)別:
IPFW:沒有全局的conntrack信息,然而需要查詢動態(tài)ruleset,以匹配返回流量;
Netfilter:需要查詢?nèi)值腸onntrack表,可以取出一切頭包經(jīng)過時流量的匹配結(jié)果,不需要也沒有動態(tài)ruleset
我們看一下全局的conntrack和全局的ruleset所針對的對象有何不同。很簡單,全局的conntrack針對除了NOTRACK的所有的數(shù)據(jù)包,然而如果NOTRACK需要指明方向,就會需要循環(huán)依賴,問題將無解。全局的動態(tài)ruleset僅僅針對匹配到的數(shù)據(jù)包,對其它的沒有匹配到的數(shù)據(jù)包除了一個查詢性能影響之外沒有其他影響,事到如今,我想查詢性能應(yīng)該不是問題吧,再說動態(tài)ruleset一般都比全局conntrackset小得多,查詢conntrackset都不怕,查詢動態(tài)ruleset就怕了么?換句話說,Netfilter的ip_conntrack是寧可枉殺一千,不能使一人漏網(wǎng),而ipfw則是精確的匹配。效率啊,BSD不愧是網(wǎng)絡(luò)領(lǐng)頭軍!
netgraph到底長什么樣子呢?到目前為止,我們只是知道了一張圖掛上去了,這僅僅是個接口,一個開始,既然掛上去了,數(shù)據(jù)包就從此處進(jìn)入這張圖了,把它叫做地圖更加適合,因此從此以后,數(shù)據(jù)包就要在游歷于這張地圖了,最終的結(jié)果有兩個:
1.數(shù)據(jù)包從地圖的某處出來,重新進(jìn)入系統(tǒng)標(biāo)準(zhǔn)的協(xié)議棧的當(dāng)初被攔截的那個地方;
2.數(shù)據(jù)包再也沒有出來回到原點,要么被地圖吃掉了(進(jìn)入了某一房間?),要么就是從某處出去,進(jìn)入?yún)f(xié)議棧的別的地方。
以上兩點很類似于Netfilter的ACCEPT,STOLEN這樣的結(jié)果,仔細(xì)想想不是么?netgraph和標(biāo)準(zhǔn)協(xié)議棧的銜接如下圖所示:
既然知道了netgraph的位置,那么下面就看看它的樣子吧。還是先給出一幅圖
該圖中有兩種元素,一種是節(jié)點,另一種是連接到節(jié)點的邊的兩端的頂點。在netgraph的術(shù)語中,節(jié)點就是Node,而頂點叫做hook,一條邊連接兩個hook,hook通過CONNECT/MKPEER構(gòu)成一條邊。從上圖中可以看出,一條邊的兩端必然有兩個hook,從命名上可以看出這些“邊的端點”其實就是真正處理數(shù)據(jù)的地方,而Node其實就是一個“數(shù)據(jù)+操作”的封裝,一個Node可以有多個hook,通過這些hook連接到其它的Node。
我們可以用OO的思想來理解這些個netgraph的概念,Node就是一個對象,每一個Node都有它所屬的Type,可以將Type理解成類。而hook其實就是一個Node對象的私有數(shù)據(jù),整個graph通過“各個hook的對接”來完成,F(xiàn)reeBSD提供了豐富的命令來完成netgraph的構(gòu)建,說白了其實就是以下幾步驟:
1.生成一系列的Node對象;
2.為每一個Node定義一個或多個hook;
3.將特定的Node通過hook連接在一起。
如此一來整個graph就構(gòu)建好了,F(xiàn)reeBSD提供了struct ng_type,它便是代表了一個類,然后你每生成一個特定ng_type的實例就相當(dāng)于生成了一個對象,通過對該結(jié)構(gòu)體里面的一些字段的理解,我們就可以完整理解數(shù)據(jù)包在這個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 */
};
注釋很清楚了,自不必說,如果我們看看其中一些回調(diào)函數(shù)的定義,就更能理解了。“構(gòu)造函數(shù)”和“析構(gòu)函數(shù)”都有,每一個“成員函數(shù)”的參數(shù)列表的第一個參數(shù)類型都是node_p,這難道不是this么?這里唯一要注意的就是rcvdata回調(diào)函數(shù),該函數(shù)接收從另一個Node發(fā)送過來的數(shù)據(jù),接收者是hook,而不是Node,再次強(qiáng)調(diào),Node之間通過hook相連接,而不是通過node本身,然而每一個hook都要唯一綁定一個Node對象,因此我們可以從hook解析出唯一的Node對象,卻不能從Node中直接得到hook(一個Node對象擁有N多hook呢),要分清一對一和一對多的關(guān)系。因此rcvdata的第一個參數(shù)是hook_p就是合理的了。
Node和Node之間通過hook傳遞控制信息,而網(wǎng)絡(luò)數(shù)據(jù)包則是通過一個hook向其peer hook發(fā)送消息的方式完成的,當(dāng)然所謂的發(fā)送消息大多數(shù)情況下就是函數(shù)直接調(diào)用。既然一條邊兩端有兩個hook,那么每一個hook就有一個peer,每當(dāng)我們將數(shù)據(jù)包發(fā)送到一個hook的時候,實際的效果就是數(shù)據(jù)包被發(fā)送到了該hook的peer,這是netgraph的核心邏輯實現(xiàn)的,我們可以從下面的這個核心宏中看到這一點:
#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命令來設(shè)置。
就這樣,一個數(shù)據(jù)包在整個netgraph中通過“離開一個Node的某個hook,進(jìn)入另一個Node的某個hook的rcvdata”的方式游歷,Node在這里的作用就是封裝私有數(shù)據(jù)和統(tǒng)一的操作,當(dāng)然,你可以重載掉一個Node內(nèi)統(tǒng)一的rcvdata回調(diào)函數(shù),而是為每一個hook都設(shè)置一個私有的rcvdata回調(diào)函數(shù),再次強(qiáng)調(diào),是hook在rcvdata,而不是Node在rcvdata,Node的rcvdata是一個該Node所有hook通用的回調(diào)函數(shù),如果沒有hook私有的rcvdata,該通用函數(shù)將被調(diào)用,ng_snd_item最終將進(jìn)入下面的邏輯:
if ((!(rcvdata = hook->hk_rcvdata)) &&
(!(rcvdata = NG_HOOK_NODE(hook)->nd_type->rcvdata))) {
error = 0;
NG_FREE_ITEM(item);
break;
}
由此看出,Node有一個默認(rèn)的對所有hook都適用的rcvdata回調(diào)函數(shù),然而各個hook可以重載掉這個默認(rèn)的rcvdata回調(diào)函數(shù)。
接下來我們看一下netgraph如何和協(xié)議棧對接,不要把操作系統(tǒng)想得太神奇,實際上完成這種工作只需要一個回調(diào)函數(shù)即可。以以太網(wǎng)接收為例,以太網(wǎng)接收處理函數(shù)中會調(diào)用ng_ether_input_p回調(diào)函數(shù),你只需要將其定義一下即可,對于很多場合都使用的ng_ether,它將此函數(shù)定義為:
static void ng_ether_input(struct ifnet *ifp, struct mbuf **mp)
{
const node_p node = IFP2NG(ifp);
const priv_p priv = NG_NODE_PRIVATE(node);
int error;
/* If "lower" hook not connected, let packet continue */
if (priv->lower == NULL)
return;
NG_SEND_DATA_ONLY(error, priv->lower, *mp); /* sets *mp = NULL */
}
最后通過NG_SEND_DATA_ONLY將數(shù)據(jù)包發(fā)送給priv->lower這個hook,最終數(shù)據(jù)包會進(jìn)入priv->lower的peer,調(diào)用priv->lower->peer的rcvdata回調(diào)函數(shù),在一切開始工作之前,你首先需要構(gòu)建好整個graph。對于以太網(wǎng)發(fā)送函數(shù),也有類似的_p回調(diào)函數(shù)。
netgraph和Netfilter的區(qū)別在于它可以將graph“掛接”在特定的interface上,而Netfilter卻把HOOK直接掛在協(xié)議棧本身,interface在Netfilter中只是一個match。如此一比較,效率差異就很明顯了。以以太網(wǎng)為例,在ether_input中就會調(diào)用netgraph,如果加載了ng_ether的話,就會調(diào)用下面的函數(shù):
static void ng_ether_input(struct ifnet *ifp, struct mbuf **mp)
{
const node_p node = IFP2NG(ifp);
const priv_p priv = NG_NODE_PRIVATE(node);
int error;
/* If "lower" hook not connected, let packet continue */
if (priv->lower == NULL) //如果這塊網(wǎng)卡上沒有任何hook,將不作處理直接返回。
return;
NG_SEND_DATA_ONLY(error, priv->lower, *mp); /* sets *mp = NULL */
}
如果本ifp上沒有掛接任何graph,則直接返回標(biāo)準(zhǔn)協(xié)議棧處理,如果掛接了一個graph,則數(shù)據(jù)包將進(jìn)入該graph,你可以將firewall rule配置在此graph里面。對于Netfilter而言,在網(wǎng)卡接收這一層,沒有任何HOOK,只有到了IP層,才會進(jìn)入PREROUTING/INPUT/FORWARD...等HOOK,哪怕你配置了一條rule,所有的包都將接受檢查以確定是否匹配,在Netfilter的rule中,所謂的interface只是一個match。
需要說明的是,netgraph也可以像Netfilter那樣工作,你只需要將其掛在ip_in(out)put上即可。
我們給出兩個例子來看看netgraph如何實現(xiàn)bridge和bonding,這些在Linux上都是通過虛擬net_device來實現(xiàn)的,其發(fā)送邏輯都是該虛擬net_device的hard_xmit實現(xiàn)的,而其數(shù)據(jù)接收邏輯則是硬編碼在netif_receive_skb中的,bridge是通過handle_bridge這個硬編碼hook進(jìn)入的,而bonding是通過skb_bond來實現(xiàn)的。也就是說Linux是通過對既有的協(xié)議棧進(jìn)行硬修改來實現(xiàn)的,而netgraph則不需要這樣,對于FreeBSD,我們只需要構(gòu)建一張graph就可以實現(xiàn)bridge或者bonding,首先我們先看看bridge的實現(xiàn)邏輯,如下圖所示:
我個人以為圖示已經(jīng)很清晰了。需要注意的是,netgraph將本地的網(wǎng)卡作為了局域網(wǎng)上一張普通的網(wǎng)卡來看待,并沒有刻意區(qū)分流量是本機(jī)發(fā)出的還是從其它機(jī)器發(fā)出的,因此,如果你只是想將bridge作為一個二層設(shè)備,那么可以斷開Hook-ethX-low和Hook-ethX-upper之間的邊即可,netgraph實現(xiàn)的bridge,你看不到虛擬設(shè)備,這種實現(xiàn)更純粹,偉大的BSD將這種思想帶給了其衍生出來的Cisco IOS。
下面是bonding的實現(xiàn)邏輯:
由于bonding網(wǎng)卡大多數(shù)負(fù)責(zé)的是本地IP層發(fā)出的數(shù)據(jù),需要和路由轉(zhuǎn)發(fā)表相配合,因此需要有一塊虛擬網(wǎng)卡,這個是通過ng_eiface的構(gòu)造函數(shù)ng_eiface_constructor實現(xiàn)的。依然無其它話可說。
以上兩個圖展示了netgraph的魅力,既然這樣,也就可以依照這種方式實現(xiàn)VLAN,IPSec等了,要比Linux的Netfilter加設(shè)備驅(qū)動模型的實現(xiàn)方式更“可插拔”,有netgraph,F(xiàn)reeBSD可以將所有的協(xié)議處理在一張張的graph中進(jìn)行,數(shù)據(jù)包在graph中游歷在每一個Node被接收到的hook處理,主要你能根據(jù)協(xié)議處理邏輯構(gòu)建好一張圖,將這張圖掛接在協(xié)議棧,甚至掛接在驅(qū)動上,你就能很方便的實現(xiàn)網(wǎng)絡(luò)的任意擴(kuò)展...
最后看一下netgraph的依賴關(guān)系,在netgraph中,每張圖都是相對獨立的,數(shù)據(jù)包從某處進(jìn)入一張圖A,然后從某處出來,在另一處再進(jìn)入圖B,此時它將不能再使用圖A。這和Netfilter不同,Netfilter基于HOOK設(shè)計,使用一些match來進(jìn)行filter,比如NAT就需要ip_conntrack,ctdir需要ip_conntrack等等,ip_conntrack一直都面臨table full的問題,因此你要用raw表的NOTRACK這個target來免除追蹤不感興趣流量來緩解這個問題。有下面的需求:
從網(wǎng)段M發(fā)出到網(wǎng)段N的流量(兩個方向)打上tag待策略路由來處理,從網(wǎng)段N發(fā)出到網(wǎng)段M的流量(兩個方向)不打tag。
分析:
很顯然要使用ctdir這個match,否則將會過濾掉返回流量,于是有以下target為NOTRACK的match:
!dst N/interface $內(nèi)網(wǎng)口
然而意味著從網(wǎng)段N發(fā)出到達(dá)M的返回流量也將被conntrack,這是因為ctdir和conntrack相互依賴才導(dǎo)致了這樣的問題,在raw表中,你甚至都不知道數(shù)據(jù)包到底是走INPUT還是FORWARD,所以你很難讓所有這一切關(guān)聯(lián)起來,雖然conntrack可以保持一個流信息在內(nèi)存中,但是卻可能存在大量不相關(guān)的流也被保存。如果使用netgraph呢?很簡單,我們可以寫在兩個命令中:
No.x check-status
No.z skip No.y from N to M #對于返回流量,只檢測conntrack
No.y netgraph tag from M to N keep-status
如此即可。FreeBSD不需要conntrack,它內(nèi)建了一個動態(tài)ruleset,凡是keep-status的流量都將自動將返回流量加入動態(tài)ruleset中,實際上也就是“保持了一個流信息在內(nèi)存中”,F(xiàn)reeBSD的conntrack和單獨的rule相關(guān)聯(lián)而不是和整個協(xié)議棧關(guān)聯(lián),這實際上也是netgraph的思想,我們看一下rule相關(guān)的conntrack和協(xié)議棧香瓜的conntrack的區(qū)別:
IPFW:沒有全局的conntrack信息,然而需要查詢動態(tài)ruleset,以匹配返回流量;
Netfilter:需要查詢?nèi)值腸onntrack表,可以取出一切頭包經(jīng)過時流量的匹配結(jié)果,不需要也沒有動態(tài)ruleset
我們看一下全局的conntrack和全局的ruleset所針對的對象有何不同。很簡單,全局的conntrack針對除了NOTRACK的所有的數(shù)據(jù)包,然而如果NOTRACK需要指明方向,就會需要循環(huán)依賴,問題將無解。全局的動態(tài)ruleset僅僅針對匹配到的數(shù)據(jù)包,對其它的沒有匹配到的數(shù)據(jù)包除了一個查詢性能影響之外沒有其他影響,事到如今,我想查詢性能應(yīng)該不是問題吧,再說動態(tài)ruleset一般都比全局conntrackset小得多,查詢conntrackset都不怕,查詢動態(tài)ruleset就怕了么?換句話說,Netfilter的ip_conntrack是寧可枉殺一千,不能使一人漏網(wǎng),而ipfw則是精確的匹配。效率啊,BSD不愧是網(wǎng)絡(luò)領(lǐng)頭軍!
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

