pcapReader——源碼分析

一、簡介 

       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)pcap_packet_callback函數

pcap_packet_callback函數中,按順序分成4個主要部分:
         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建立

2)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
         

3)整體數據結構

          

三、其他函數

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函數關閉程序   
 1
 2
 3
 4
 5
 6
 7
 8
 9
void sigproc(int sig) {
static int called = 0;
if(called) return; else called = 1;
shutdown_app = 1;
closePcapFile();
printResults(0);
terminateDetection();
exit(0);
}
 來自CODE的代碼片
pcapReader_signal.c
如果產生中斷,則調用如上函數關閉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。
getopt中選項得到的參數傳遞給全局變量optarg

四、源碼附錄

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