深度數據包檢測(DPI)

深度數據包檢測(DPI)

深度數據包檢測(Deep packet inspection,縮寫爲 DPI)是一種特殊的網絡技術,一般網絡設備只會查看以太網頭部、IP頭部而不會分析TCP/UDP裏面的內容這種被稱爲淺數據包檢測;與之對應的DPI會檢查TCP/UDP裏面的內容,所以稱爲深度數據包檢測。

DPI一般是一個硬件或者軟件,一般用“旁掛”的方式接入到網絡。它會對網絡中的每個數據包進行檢查,識別出應用層協議,根據識別的協議採取一定的措施(比如記錄HTTP訪問行爲)。對於TCP協議它可以識別完整的TCP交互過程(比如HTTP請求從請求到響應中間會有多次TCP數據包發送)。

nDPI

nDPI是一個C語言編寫的DPI庫,用來實現軟件DPI系統。它是從OpenDPI擴展而來,二者的架構和實現基本上差不多。https://github.com/ntop/nDPI此處是它的老巢,大家可以自己下載編譯它。怎麼編譯,它的README寫的非常清楚了我就不廢話了(唯一一個文檔~~囧)。

編譯安裝之後它生成/usr/local/lib/libndpi.(a,so)庫文件(a靜態庫文件,so動態鏈接庫);/usr/local/include/會安裝相關的頭文件。

我個人喜歡用 靜態庫文件,這樣會把所有的二進制代碼合併到一個可執行文件中運行的時候不需要安裝一大堆庫。另外我也不喜歡把東西放到/usr/local/lib下,所以我提供的代碼是通過cmake做了一個“all in one”的編譯。github上放到是1.6版本的,如果你想更新代碼只要替換src文件夾就行了。

如何用nDPI

nDPI的代碼寫的很爛,也沒有什麼架構就是一團亂麻(其實稍微寫寫都要比這個好)。但是它至少還能正常工作而且是唯一一個“開放”的DPI庫,所以無論什麼原因你選擇了它都必須忍受它的“醜陋”。

nDPI幾乎沒有文檔說明,只帶了一個“ndpiReader”的例子,寫的“洋洋灑灑”如行雲流水一般(就不吐槽了)。這就是我這篇文章的寫作原因,希望能給使用nDPI的同學一點幫助。

初始化

nDPI最重要的一個數據結構是ndpi_detection_module_struct_t它通過ndpi_init_detection_module構造出來

第一個參數用來計算nDPI分析協議的各種超時時間,一般精確到毫秒就可以了1000(nDPI協議分析部分和“全局部分”耦合非常緊,這個數據其實只有“協議分析模塊”需要)

第二個、三個參數是封裝過的“內存分配”函數;nDPI的內存管理非常亂,有些地方是我們自己申請內存而由nDPI內部幫我們釋放。所以必須nDPI並不直接使用malloc、free之類的申請、釋放內存而是交由程序員自己提供函數;

第三個參數是調試函數,如果定義了NDPI_ENABLE_DEBUG_MESSAGES那麼nDPI會調用這個函數輸出一些調試信息;

所有的nDPI API都是這種“鬼畜”風格,幾乎是各種糾結。。。萬幸我們只需要使用很少的API就可以完成任務了。

配置協議分析模塊

nDPI支持多種協議,都在protocols文件夾中。編譯的時候所有協議都會被放到nDPI庫中。使用的時候我們可以自己設置需要開啓那些分析模塊

NDPI_PROTOCOL_BITMASK定義開啓協議的“位圖”,通過NDPI_BITMASK_ADD函數可以添加支持的協議,最後調用ndpi_set_protocol_detection_bitmask2配置位圖。

ndpi_set_protocol_detection_bitmask2函數的第一個參數就是ndpi_detection_module_struct_t(上面我們初始化的那個數據結構);第二個參數是位圖標誌。

特別注意:開啓的協議越多識別速度越慢;nDPI識別協議的時候是一個串行結構,無論是否被成功是被都會認認真真遍歷完我們配置好的協議

子協議

子協議是某個協議的細分,比如我們想要分析所有“Google”的HTTP請求那麼第一步是分析出“HTTP”請求,第二步是判斷HOST包含google.com。這裏的第二步就是“子協議”。

nDPI唯一的一份“QuickStartGuide”對這個有進一步解釋,子協議識別是以配置文件的方式提供給nDPI的。比如

它還支持端口的方式(TCP的81、8181直接被標記爲HTTP不再做內容檢測)

nDPI此處的實現使用了一個非常有名的算法—— Aho-Corasick。以第一幅圖爲例子,裏面配置了兩條規則“Google”和“Veneer”,我們有一個字符串(HOST),怎麼判斷這個字符串符合那個規則呢?最簡單的辦法是循環所有的規則,如果規則條目很多那麼速度會非常慢。Aho-Corasick就是這樣一種算法,它可以在O(n)中完成所有的匹配任務。

通過ndpi_load_protocols_file函數加載“子協議”。

開始識別

識別協議的API非常簡單——ndpi_detection_process_packet函數。就是這個坑爹的函數,變態程度幾乎可以說用令人髮指來形容。

  • ndpi_struct全局的結構體
  • flow比較特殊,我們後面講
  • packet指向IP頭部的指針
  • packetlen數據包大小
  • current_tick_l當前時間(精確到毫秒)用於判斷“過期的TCP請求”
  • src,dst其實沒有什麼用途,文檔上說是跟狀態機有關其實沒有半毛錢關係。唯一的用途是更新“分析協議”的配置。一般設置爲NULL就行了

TCP協議是一個流(flow)式的協議,經過從三次握手開始通訊雙方都是“請求->響應”的結構。DPI可以跟蹤其中的一個或者幾個數據包,也可以實現全部跟蹤(後續我會交叉使用TCP會話、會話、flow,三個名詞其實是一樣的)。

nDPI內部不會記錄完整的TCP數據包,而是用一個定義非常模糊的ndpi_flow_struct類型來表示一個TCP會話(這個數據結構還包含了“協議分析”部分數據,所以定義非常模糊)。爲了便於分析完整的TCP請求我們定義了一個自己的數據結構dpi_flow_t,ndpi_flow_struct作爲它的一個成員。用僞代碼表示分析過程:

落到代碼上就是get_ndpi_flow函數;實現上我們會對目標、源端口排序再做hash;這是由於數據包是“相互通訊”的所以發送方、接收方是相對而言,否則識別到的可能是“一方”的數據。

一般我們用一個二叉樹存放所有正在分析的TCP會話,nDPI移植了FreeBSD中的一組函數ndpi_tfind、ndpi_tsearch、ndpi_twalk、ndpi_tdelete等用來實現常用的數據結構操作。

完成分析

調用完ndpi_detection_process_packet函數後我們需要檢查返回值,如果它不等於NDPI_PROTOCOL_UNKNOWN證明就找到了協議類型。

DPI分析一般分爲三種情況:

  • 第一個數據包就能確定協議類型,這種情況下找到“協議類型”後續直接把flow從二叉樹中刪除
  • 第N個數據包確定協議,flow數據包會一直存在在二叉樹中,直到知道協議類型。這種情況下每次我們調用ndpi_detection_process_packet傳遞的flow其實是相同的數據
  • 一直無法確定協議類型,flow經歷過一段時間之後超時,我們認爲無法識別出協議釋放相關數據。

前兩種情況可以合二爲一,都是判斷出協議類型後釋放資源;第三種請比較特殊,我們需要遍歷整個二叉樹尋找“過期”的flow然後刪除。這就是例子中最後一部分的含義(遍歷的時候我們每次尋找一個flow,用變量idle_scan_idx表示)。

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