1. 程式人生 > >手寫一個Pcap捕包工具及效能優化

手寫一個Pcap捕包工具及效能優化

前言

這是在專案上遇到的問題。因為專案的緣故,需要實現pcap捕包的功能,在使用後發現libpcap自身API使用比較複雜,就手寫了一個API並嘗試進行效能優化。部分程式碼在githuab上。pcapDumper

pcap檔案格式

一個pcap檔案的結構如下:
pic(圖片來自部落格)

如上圖,一個pcap檔案,有兩個部分組成,一個Pcap 頭部,和一堆包。其中每一個包都有兩個欄位,分別是Packet Head和 Packet Data。它們的資料結構如下:

struct _pcap_file_header {
    uint32_t magic;
    uint16_t version_major;
    uint16_t version_minor;
    int32_t thiszone;     /* gmt to local correction */
uint32_t sigfigs; /* accuracy of timestamps */ uint32_t snaplen; /* max length saved portion of each pkt */ uint32_t linktype; /* data link type (LINKTYPE_*) */ }; struct _pcap_pkthdr { struct _timeval ts; /* time stamp using 32 bits fields */ uint32_t caplen; /* length of portion present */
uint32_t len; /* length this packet (off wire) */ };

pcap_file_header儲存著包結構的頭,magic欄位是位元組順序。version_major version_minor分別是主版本號和次版本號。 thiszone代表時區, snaplen是資料包的最大長度,一般設定為65535. linktype表示鏈路型別。
可以看到pcap_file_header儲存的是,錄製這個包的時候的網路狀態。

pcap_pkthdr 儲存的是資料包的結構。包括資料包的時間戳和包長。在這個結構之後,緊跟著就是一段長為len

的資料欄位,表示的是這個pkthdr所指的資料包,是二層資料包結構。這個len同時也指向了下一個資料包的位置,即偏移到len+1的位置。

在寫入的時候,就是按照上圖所示,一個pcap_file_hdr,然後pcap_pkthdr1,pkt_data1,pcap_pkthdr2,pkt_data2,這麼寫入的。

pcap_file_hdr->pcap_pkthdr->pkt_data->pcap_pkthdr->pkt_data->pcap_pkthdr->pkt_data->pcap_pkthdr->pkt_data->...

所以在讀取pcap檔案的時候,實際上是線性的訪問資料包的。只有讀取了第n個數據包,你才能訪問的到底n+1個數據包。

注意的坑

pcap_pkthdr中的timeval是32位。它的實際結構應該是這樣的:

struct _timeval {
        uint32_t tv_sec;     /* seconds */
        uint32_t tv_usec;    /* microseconds */
};

在64位系統中,timeval中兩個欄位都是uint64_t的值。所以如果沒有注意到這一點,會出現欄位錯誤。導致儲存的pcap包無法被wireshark等工具正常識別。

效能優化

在效能上,主要的瓶頸在於寫入硬碟。如果程式在每次拿到一個包就寫入到硬碟中,就會造成頻繁的小檔案的寫入。這樣發揮不了硬碟的最大速度。可以使用一個記憶體buf來把資料先快取在記憶體中,等到了一定大小(比如16M)或者pcap檔案寫入完畢後再寫入硬碟。

架構上的優化

程式上的優化最高能夠讓寫包速度達到100Mbps。而相對的,硬碟的最高速度應該是在1000Mbps以上,網絡卡的效能則可以達到10000Mbps上。所以實際上寫包速率是滿足不了高效能捕包的寫包需求的。這個時候就需要從架構上優化了。

有兩種思路可以從架構上優化,第一種是使用多執行緒並行。每個執行緒都進行捕包->解析包->寫入包->再捕包的過程。這個過程能夠提升一定的效能。但是會有幾個問題:
1. 頻繁的中斷。
2. 資源爭用。

每個執行緒在寫入一個包的時候,在三個過程,需要申請不同的資源,分別是網絡卡資源,CPU計算資源和硬碟IO資源。系統的中斷和切換資源需要消耗一定的時間。此外,資源爭用的問題也會發生,這主要體現在硬碟的資源上。在錄製包的場景下,硬碟資源是瓶頸資源。這個時候多個執行緒在頻繁請求硬碟資源,會導致硬碟的寫入效能下降。比如我有100的時間片,這100的時間片中,可能只有80的時間片用在了真正的寫入硬碟中,其他的時間片都花費在了資源排程上。這會使每個執行緒的效能不及原來單執行緒的時候,而且硬碟也無法發揮自己百分百的效能。

另一種是使用多執行緒併發。每個執行緒都只幹一件事情。捕包有捕包的執行緒,解析包有解析包的執行緒,寫入包有寫入包的執行緒。這樣線上程間的資源競爭就能夠被避免。在實際的專案中,甚至把寫入包之後的記憶體釋放都單獨分出一個執行緒。就是為了讓硬碟IO的效率達到最大。實際的效果,能夠使程式支援1000Mbps的錄包速度。如果需要再提高效能,就可能需要使用SSD,或者分散式儲存,磁碟陣列等來解決了。