Linux下Libpcap源碼分析和包過濾機制 (3)

大量的網絡監控程序目的不同,期望的數據包類型也不同,但絕大多數情況都都只需要所有數據包的一(小)部分。
數據包過濾機制

大量的網絡監控程序目的不同,期望的數據包類型也不同,但絕大多數情況都都只需要所有數據包的一(小)部分。例如:對郵件系統進行監控可能只需要端口號爲 25(smtp)和 110(pop3) 的 TCP 數據包,對 DNS 系統進行監控就只需要端口號爲 53 的 UDP數據包。包過濾機制的引入就是爲了解決上述問題,用戶程序只需簡單的設置一系列過濾條件,最終便能獲得滿足條件的數據包。包過濾操作可以在用戶空間執行,也可以在內核空間執行,但必須注意到數據包從內核空間拷貝到用戶空間的開銷很大,所以如果能在內核空間進行過濾,會極大的提高捕獲的效率。內核過濾的優勢在低速網絡下表現不明顯,但在高速網絡下是非常突出的。在理論研究和實際應用中,包捕獲和包過濾從語意上並沒有嚴格的區分,關鍵在於認識到捕獲數據包必然有過濾操作。基本上可以認爲,包過濾機制在包捕獲機制中佔中心地位。

包過濾機制實際上是針對數據包的布爾值操作函數,如果函數最終返回true,則通過過濾,反之則被丟棄。形式上包過濾由一個或多個謂詞判斷的並操作(AND)和或操作(OR)構成,每一個謂詞判斷基本上對應了數據包的協議類型或某個特定值,例如:只需要 TCP 類型且端口爲110的數據包或ARP類型的數據包。包過濾機制在具體的實現上與數據包的協議類型並無多少關係,它只是把數據包簡單的看成一個字節數組,而謂詞判斷會根據具體的協議映射到數組特定位置的值。如判斷ARP類型數據包,只需要判斷數組中第 13、14 個字節(以太頭中的數據包類型)是否爲0X0806。從理論研究的意思上看,包過濾機制是一個數學問題,或者說是一個算法問題,其中心任務是如何使用最少的判斷操作、最少的時間完成過濾處理,提高過濾效率。

BPF

libpcap 重點使用 BPF(BSD Packet Filter)包過濾機制,BPF 於 1992 年被設計出來,其設計目的主要是解決當時已存在的過濾機制效率低下的問題。BPF的工作步驟如下:當一個數據包到達網絡接口時,數據鏈路層的驅動會把它向系統的協議棧傳送。但如果 BPF 監聽接口,驅動首先調用 BPF。BPF 首先進行過濾操作,然後把數據包存放在過濾器相關的緩衝區中,最後設備驅動再次獲得控制。注意到BPF是先對數據包過濾再緩衝,避免了類似sun的NIT過濾機制先緩衝每個數據包直到用戶讀數據時再過濾所造成的效率問題。參考資料D是關於BPF設計思想最重要的文獻。

BPF 的設計思想和當時的計算機硬件的發展有很大聯繫,相對老式的過濾方式CSPF(CMU/Stanford Packet Filter)它有兩大特點。1:基於寄存器的過濾機制,而不是早期內存堆棧過濾機制,2:直接使用獨立的、非共享的內存緩衝區。同時,BPF 在過濾算法是也有很大進步,它使用無環控制流圖(CFG control flow graph),而不是老式的布爾表達式樹(boolean expression tree)。布爾表達式樹理解上比較直觀,它的每一個葉子節點即是一個謂詞判斷,而非葉子節點則爲 AND 操作或 OR操作。CSPF有三個主要的缺點。1:過濾操作使用的棧在內存中被模擬,維護棧指針需要使用若干的加/減等操作,而內存操作是現代計算機架構的主要瓶頸。2:布爾表達式樹造成了不需要的重複計算。3:不能分析數據包的變長頭部。BPF 使用的CFG 算法實際上是一種特殊的狀態機,每一節點代表了一個謂詞判斷,而左右邊分別對應了判斷失敗和成功後的跳轉,跳轉後又是謂詞判斷,這樣反覆操作,直到到達成功或失敗的終點。CFG算法的優點在於把對數據包的分析信息直接建立在圖中,從而不需要重複計算。直觀的看,CFG 是一種"快速的、一直向前"的算法。

過濾代碼的編譯

BPF 對 CFG 算法的代碼實現非常複雜,它使用僞機器方式。BPF 僞機器是一個輕量級的,高效的狀態機,對 BPF 過濾代碼進行解釋處理。BPF 過濾代碼形式爲"opcode jt jfk",分別代表了操作碼和尋址方式、判斷正確的跳轉、判斷失敗的跳轉、操作使用的通用數據域。BPF 過濾代碼從邏輯上看很類似於彙編語言,但它實際上是機器語言,注意到上述 4 個域的數據類型都是 int 和 char 型。顯然,由用戶來寫過濾代碼太過複雜,因此 libpcap 允許用戶書寫高層的、容易理解的過濾字符串,然後將其編譯爲BPF代碼。

libpcap使用了4個源程序gencode.c、optimize.c、grammar.c、scanner.c完成編譯操作,其中前兩個實現了對過濾字符串的編譯和優化,後兩個主要是爲編譯提供從協議相關過濾條件到協議無關(的字符數組)位置信息的映射,並且它們由詞彙分析器生成器 flex 和 bison 生成。參考資料 C 有對此兩個工具的講解。


flex -Ppcap_ -t scanner.l > $$.scanner.c; mv $$.scanner.c scanner.c
bison -y -p pcap_ -d grammar.y
mv y.tab.c grammar.c
mv y.tab.h tokdefs.h

編譯過濾字符串調用了函數 pcap_compile()[getcode.c],形式爲:



int pcap_compile(pcap_t *p, struct bpf_program *program,
	     char *buf, int optimize, bpf_u_int32 mask)


其中 buf 指向用戶過濾字符串,編譯後的 BPF 代碼存在在結構 bpf_program中,標誌 optimize 指示是否對 BPF 代碼進行優化。


/* [pcap-bpf.h] */
struct bpf_program {
u_int bf_len; /* BPF 代碼中謂詞判斷指令的數目 */
struct bpf_insn *bf_insns; /* 第一個謂詞判斷指令 */
};
	
/* 謂詞判斷指令結構,含意在前面已描述 [pcap-bpf.h] */
struct bpf_insn {
u_short	code;
u_char 	jt;
u_char 	jf;
bpf_int32 k;
};

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章