一、簡介
pcapReader是ndpi開源中的一個example。大家可以從<ndpi directory>/example/pcapReader.c中找到它的源代碼。通過pcaplib和ndpi相結合,進行深度包檢測。雖然只有短短的幾行代碼,但是他將展現的不僅是pcaplib和ndpi的使用方法,還有包分析的一些技巧。看完之後其實外國人寫的程序也就是那樣,並沒有什麼特別之處。我們先來一起看看基本的函數結構。
注:我們只對源碼中的Linux平臺部分進行解釋
在main函數中,通過調用test_lib()對程序進行整合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void test_lib() { struct timeval begin, end; u_int64_t tot_usec; setupDetection();//ndpi檢測協議的註冊,以及參數設置 openPcapFileOrDevice();//pcaplib的初始化準備 signal(SIGINT, sigproc);//包含在signal.h頭文件中,這裏主要交互式信號,如中斷做出反應。觸發sigproc函數關閉程序 gettimeofday(&begin, NULL);//記錄開始時間 runPcapLoop();//循環抓包並進行處理 gettimeofday(&end, NULL);//記錄結束時間 //計算抓包分析耗時 tot_usec = end.tv_sec*1000000 + end.tv_usec - (begin.tv_sec*1000000 + begin.tv_usec); closePcapFile();//關閉網卡 printResults(tot_usec);//輸出結果 terminateDetection();//關閉ndpi }
|
來自CODE的代碼片
pcapReader_test_lib.c
這裏限於篇幅,主要對runPcapLoop()函數中的動作進行分析。如果想理解其他函數或者更加詳細的技術細節,可以閱讀博客最後的源碼附錄。裏面有比較詳細的註釋。如果還有問題,可以留言或者發一下私信。歡迎大家一起討論。
runPcapLoop()函數中通過pcap_loop(_pcap_handle, -1, &pcap_packet_callback, NULL)進行循環抓包。pcap_loop是pcaplib中提供的api。_pcap_handle指向的是網卡設備,pcap_packet_callback是循環抓包之後的包處理函數,-1代表的是不停地抓直到抓包出錯的時候停止。接下來我們針對pcap_packet_callback函數中的包處理進行分析
1、ndpi_ethhdr進行數據鏈路層的拆包分析。針對Linux Cooked Capture 和vlan的特殊包結構。對包頭和信息進行了對應的偏移,並且記錄在ip_offset變量中。
2、ndpi_iphdr進行網絡層的拆包。這裏進行了ipv4和ipv6的檢測,我們接下來只對ipv4進行介紹。
3、GTP隧道協議的處理
4、packet_processing()函數進一步的包處理
注:2中的網絡層拆包存儲在iph變量中,並在packet_processing()中作爲ndpi協議檢測的數據源
packet_processing函數作爲ndpi分析的主體,這裏通過get_ndpi_flow函數分類會話。然後利用ndpi_detection_process_packet函數進行數據分析得到應用層協議。我們繼續往下看看get_ndpi_flow是怎樣建立起數據結構的。
注:get_ndpi_flow6針對ipv6進行了轉換,最後還是通過get_ndpi_flow建立
get_ndpi_flow函數:
1、通過傳輸層拆包獲得協議包的源和目的端口(tcp通過ndpi_tcphdr 、udp通過ndpi_udphdr分別進行拆包)
2、結合網絡層和傳輸層的數據,通過源目的ip和端口分類會話
3、以ndpi_flows_root爲hash數組,(lower_ip + upper_ip + iph->protocol + lower_port + upper_port) % NUM_ROOTS計算出會話對應的數組位置。然後對於數組的每個單元維護一個二叉查找鏈表。
4、通過ndpi_tfind函數對二叉樹進行查找,如果存在相對應的會話,則返回對應結果。如果不存在,則通過ndpi_tsearch把新的會話插入二叉樹中。
node_cmp函數中定義了比較的規則。ndpi_tfind和ndpi_tsearch在<ndpi directory>/src/lib/ndpi_main.c文件中進行的二叉查找的封裝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
typedef struct node_t { char *key; struct node_t *left, *right; } ndpi_node; /* find a node, or return 0 */ void *ndpi_tfind(const void *vkey, void *vrootp,int (*compar)(const void *, const void *)) { char *key = (char *)vkey; ndpi_node **rootp = (ndpi_node **)vrootp; if (rootp == (ndpi_node **)0) return ((ndpi_node *)0); while (*rootp != (ndpi_node *)0) { /* T1: */ int r; if ((r = (*compar)(key, (*rootp)->key)) == 0) /* T2: */ return (*rootp); /* key found */ rootp = (r < 0) ? &(*rootp)->left : /* T3: follow left branch */ &(*rootp)->right; /* T4: follow right branch */ } return (ndpi_node *)0; } void *ndpi_tsearch(const void *vkey, void **vrootp,int (*compar)(const void *, const void *)) { ndpi_node *q; char *key = (char *)vkey; ndpi_node **rootp = (ndpi_node **)vrootp; if (rootp == (ndpi_node **)0) return ((void *)0); while (*rootp != (ndpi_node *)0) { /* Knuth's T1: */ int r; if ((r = (*compar)(key, (*rootp)->key)) == 0) /* T2: */ return ((void *)*rootp); /* we found it! */ rootp = (r < 0) ? &(*rootp)->left : /* T3: follow left branch */ &(*rootp)->right; /* T4: follow right branch */ } q = (ndpi_node *) ndpi_malloc(sizeof(ndpi_node)); /* T5: key not found */ if (q != (ndpi_node *)0) { /* make new node */ *rootp = q; /* link new node to old */ q->key = key; /* initialize new node */ q->left = q->right = (ndpi_node *)0; } return ((void *)q); }
|
來自CODE的代碼片
BinarySearchTree.c
1、setupDetection();//ndpi檢測協議的註冊,以及參數設置
通過ndpi提供的一系列函數,註冊需要深度檢測的協議。大略如下
ndpi_init_detection_module激活cache支持,主要針對一些佔用緩存的協議如skype
ndpi_set_protocol_detection_bitmask2註冊需要進行檢測的協議
ndpi_detection_get_sizeof_ndpi_id_struct
ndpi_detection_get_sizeof_ndpi_flow_struct:獲取ndpi_flow_struct和ndpi_id_struct的大小在爲二叉樹插入新節點時,申請空間和變量的初始化
2、openPcapFileOrDevice();//pcaplib的初始化準備
errbuf[PCAP_ERRBUF_SIZE]:pcaplib存放錯誤信息的緩衝區
pcap_open_live打開對應的網卡設備
注:如果打開失敗,或者命令中指定利用pcap_open_offline從文件中讀入數據
pcap_datalink獲取當前數據鏈路的類型,一般爲以太網v2
pcap_compile和pcap_setfilter分別用於編譯和設置抓包的過濾規則
3、signal(SIGINT, sigproc);//包含在signal.h頭文件中,這裏主要交互式信號,如中斷做出反應。觸發sigproc函數關閉程序
如果產生中斷,則調用如上函數關閉pcap和ndpi並且輸出結果。
4、closePcapFile();
通過pcap_close函數清除_pcap_handle指針並關閉抓包。
5、printResults(tot_usec);//輸出結果
6、terminateDetection();
通過ndpi_tdestroy釋放hash數組及其數組上的二叉查找樹節點,最後通過ndpi_exit_detection_module結束ndpi程序。
7、static void parseOptions(int argc, char **argv) /*命令行的實現,這裏argc和argv從main中argc和argv參數傳遞進來。*/
getopt函數是命令行分析 第三個參數解釋:
1).單個字符,表示選項
2).單個字符後接一個冒號:表示該選項後必須跟一個參數。參數緊跟在選項後或者以空格隔開。該參數的指針賦給optarg。
3).單個字符後跟兩個冒號,表示該選項後可以跟一個參數,也可以不跟。如果跟一個參數,參數必須緊跟在選項後不能以空格隔開。該參數的指針賦給optarg。
1).單個字符,表示選項
2).單個字符後接一個冒號:表示該選項後必須跟一個參數。參數緊跟在選項後或者以空格隔開。該參數的指針賦給optarg。
3).單個字符後跟兩個冒號,表示該選項後可以跟一個參數,也可以不跟。如果跟一個參數,參數必須緊跟在選項後不能以空格隔開。該參數的指針賦給optarg。
getopt中選項得到的參數傳遞給全局變量optarg