?

基于PF_RING的高速網絡數據捕獲方法?

2019-03-26 08:44吳克河王冬冬
計算機與數字工程 2019年3期
關鍵詞:緩沖區網卡線程

吳克河 王冬冬

(華北電力大學控制與計算機工程學院 北京 102206)

1 引言

當前網絡傳輸速度已經普遍達到10 Gbps,主干網速度甚至達到了40 Gbps,種類繁多的網絡應用每天產生了海量網絡數據,對網絡數據進行數據挖掘、安全分析有著重要的學術和商業價值,這一切都離不開高性能的網絡數據包捕獲技術。目前網絡數據包捕獲的方式主要有兩種:一是采用專用硬件實現,如Endace的DAG數據采集卡可以對40Gbps的網絡數據進行高效捕獲,效率高,并具備一定數據過濾和處理功能,但售價高昂,擴展性較差,應用不是很廣泛[1~2];二是基于軟件實現,目前比較成熟的技術有Netmap,PF_RING以及DPDK等,成本相對專有硬件較低,并只在普通網卡上就能實現,擴展性很強。

近年國內外對網絡數據的捕獲的研究多是應用Netmap、PF_RING或DPDK,結合網卡多隊列、多線程、及無鎖編程技術,設計與實現網絡數據并行捕獲與處理框架。隨著硬件技術的發展,服務器CPU核心數可能是普通網卡隊列的數量的幾倍,僅僅基于網卡多隊列進行并行數據捕獲,并行處理的效率有限,并不能充分發揮服務器的性能優勢。而且目前大多數框架適用于對網絡數據進行捕獲、統計以及分析,對于需要轉發與發送數據的服務器來說并不適用。

本文在研究了各種網絡數據捕獲機制優劣的基礎上,利用多線程、高并發無鎖隊列以及PF_RING網絡數據捕獲技術,設計出了能夠充分利用服務器CPU多核心優勢的高速數據并行處理框架。

2 網絡數據包捕獲技術

2.1 問題分析

傳統Linux系統數據捕獲過程如圖1所示,數據從網卡到用戶緩沖區的步驟為

1)首先網卡驅動程序將數據從網卡寄存器拷貝到內核為網卡分配的緩沖區(FIFO或Ring Buffer)中,這是第一次拷貝。

圖1 傳統Linux數據捕獲過程

2)然后驅動程序發送中斷請求給CPU,中斷處理函數將網卡緩沖區的數據拷貝到內核專用數據結構(mbufs或skb_buffs)中[1],這是第二次拷貝。

3)內核協議棧根據數據包的頭結構進行解析,將報頭部分去除交由相應上層協議處理。

4)各層協議的部分處理完成以后,由Socket層的系統調用(recv或recvfrom)將數據從內核空間拷貝到用戶空間,這里發生了第三次拷貝。

上述過程中影響捕獲速度的因素主要有:

一是頻繁的資源分配與釋放。系統會為每一個到達的數據包動態分配一個存儲數據的緩沖區(mbuf或skb_buffs)及其分組描述符,直到數據傳遞到用戶態空間,才被釋放。

二是過多的數據拷貝。除了第一次拷貝是將數據從網卡寄存器拷貝到網卡緩沖區以外,其他拷貝都是從內存中的一部分拷貝到內存中的另外一部分[3]。頻繁的拷貝數據會占用帶寬有限的總線,占用CPU周期,同時也會頻繁刷新緩存,影響緩存命中率,進而增加處理時間。

三是頻繁中斷與上下文切換。每一個數據包到達網卡緩存后都會觸發一次中斷,以及一次從用戶態到內核態的上下文切換。當數據包流量變大時,大量CPU時間被用于處理中斷和上下文切換。同時由于硬中斷的優先級比較高,致使其他進程無法獲得CPU資源,接收緩沖區中數據包得不到及時處理,就會造成數據大量丟失[4]。

2.2 解決方法

針對以上因素,總結了目前高性能網絡數據捕獲技術中常用的提高捕獲效率的技術。

1)預分配固定大小的數據包存儲空間。消除每個數據包存儲時動態分配內存導致的系統開銷。

2)零拷貝,即減少數據拷貝次數。將網卡緩沖區、內核緩沖區以及用戶緩沖區進行合并,網卡驅動程序以及用戶應用程序使用同一共享緩沖空間進行數據操作,減少緩沖區之間的拷貝。

3)用戶空間IO。完全繞過Linux內核,報文存儲與處理工作都在用戶空間完成,避免內核態與用戶態上下文切換的開銷。網卡驅動工作在用戶態,避免因內核更新而頻繁修改網卡驅動問題。

4)使用大內存頁。主要優點是利用大內存頁提高內存使用效率,通過增加頁尺寸從而減少內存分頁映射表的條目,大幅減少TLB旁路緩沖器的查詢Miss,提高內存頁檢索效率[5]。

5)CPU親和性??梢匀斯し峙浣oCPU的每個核心要完成的任務,減少一個核心進行任務切換的開銷,充分發揮CPU多核心的優勢。

6)數據批處理。為了減少單個報文調用的開銷,提供批量接收和發送數據包的功能。

2.3 技術對比

為了實現對網絡數據的快速捕獲。Netmap對原數據結構做了精簡,并利用NIC環使用戶應用可以調用API直接訪問網卡緩沖區[5]。PF_RING利用DNA(Direct NIC Access,直接網卡訪問)技術,使用戶應用程序可以直接訪問網卡的寄存器和緩沖區[7]。而DPDK主要利用了大內存頁和用戶空間IO 技術[8]。

從表1中可以看出各技術都滿足10Gbps網絡速度的要求,速度方面并沒有明顯差異。其中使用DPDK技術需要具備一定的硬件以及內核知識,故學習和開發周期較長[9]。Netmap相對PF_RING和DPDK未提供完善的API開發接口,開發工作較多[10~11]。所以在充分考慮性能以及時間的基礎上,本論文應用PF_RING實現網絡數據捕獲框架。

表1 網絡數據捕獲技術對比

3 無鎖隊列

雖然鎖機制能夠很好地解決多線程場景下數據同步問題,但系統開銷比較大,無法滿足高并發場景,故近年來開始對無鎖數據結構進行研究[12]。

3.1 相關技術

無鎖隊列實現主要依賴的技術有:

1)CAS原子指令操作

CAS(Compare and Swap,比較并替換)原子指令,用來保障數據的一致性。指令有三個參數,當前內存值V、舊的預期值A、更新的值B,當且僅當預期值A和內存值V相同時,將內存值修改為B并返回 true,否則什么都不做,并返回false[13]。

2)內存屏障

執行運算的時候,每個CPU核心從內存讀到各自的緩存中,結束后再從緩存更新到內存,這會引起線程間數據的不同步,故需要內存屏障強制把寫緩沖區或高速緩存中的數據等寫回主內存。主要分為讀屏障和寫屏障:讀屏障可以讓cache中的數據失效,強制重新從主內存加載數據;寫屏障能使cache中的數據更新寫入主內存。在實現valotitle關鍵字中就用到了內存屏障,從而保證線程A對此變量的修改,其他線程獲取的值為最新的值。

3.2 實現分析

生產者head和tail兩個變量,分別標識申請與發布的過程,且類型為volatile uint32_t。入隊過程如圖2所示,步驟如下:

1)申請過程。prod1和prod2同時入隊,首先讀取出隊列頭部head以及計算出入隊后頭部需要移動的位置next。由于prod1率先執行CAS將隊列head進行更新,當prod2執行CAS更新隊列head時,發現已被其他生產者改變,而CAS是一個原子操作,prod2意識到當前生產區已被其他生產者占據,就會根據新head值重新計算自己生產的位置。

2)入隊過程。prod1和prod2可同時進行數據入隊,提高了入隊的效率。

3)發布過程。生產者生產結束,且在此之前的生產也都結束后,移動隊列頭部tail到實際生產的位置,來通知后面的生產者自己已經結束。如prod2結束時等到prod1完成后才進行tail的更新。

下面是上述過程的偽代碼,其中head和tail都定義為volatile uint32_t類型,使得對數據的修改可以及時通知其他線程:

do{

head=queue->prod.head

next=prod.head+len;

判斷是否有足夠剩余空間;

success = CAS (queue->prod.head, head,next);

}while(unlikely(success==0));數據入隊;

smp_wmb();//寫內存屏障

while(unlikely(queue->prod.tail!=head){

pause();

queue->prod.tail=next;

出隊的操作和入隊列十分接近,把判斷是否有剩余空間變為判斷隊列是否有數據,填入數據變成讀取數據,smp_wmb()變成smp_rmb()讀內存屏障即可。通過上述方法可以實現多生產者多消費者安全的高并發無鎖隊列。

4 高速數據捕獲框架的設計

4.1 問題分析

本框架基于配電安全網關進行開發,其網絡拓撲如圖3所示,其主要功能為對配電終端進身份認證,以及轉發認證成功后的配電終端與配電主站之間的業務數據??蚣苄枰邆涞墓δ苤饕幸韵聨c:

1)捕獲主站與終端發送的數據;

2)根據數據內容做出處理;

3)轉發主站與終端的數據,或向終端或主站發送自己的數據包,丟棄不合法的數據。

4.2 總體設計

設計目標:設計與完成具有通用性的基于PF_RING的高速網絡數據處理框架,框架能夠充分利用CPU的多核心優勢,提供簡單易用的API接口。主要適用的目標為部署在內外網安全邊界的網關服務器?;竟ぷ髁鞒讨饕鐖D4所示。

圖4 配電網關總體處理流程

4.3 詳細設計

4.3.1 整體結構

設計思想:數量應多于數據收發線程,多個線程同時進行數據處理,這樣可以增加處理效率;將每個處理線程綁定到不同的CPU核上,充分發揮服務器多CPU核心的優勢;處理過程中應盡量避免數據包的拷貝,在不同模塊傳遞的應該是數據在緩沖景區的地址。

圖5 基于PF_RING的數據收發框架

框架主要由以下幾部分構成:

1)Read Thread:數據接收線程。負責讀取網卡1和網卡2接收到的數據,即終端和主站發來的數據,并根據分發策略把數據添加到相應Recv Queue等待CPU處理。

2)Recv Queue:待處理隊列。應用無鎖隊列實現。應用多個Recv Queue的優點是可以軟件模擬網卡多隊列,實現并行處理,并可根據上層應用的實際需求自定義處理線程的數量十分靈活。

3)Work Thread:數據包處理線程。從Recv Queue讀取數據,調用數據處理接口對數據包報頭的解析和處理。如果沒有數據將調用usleep(),防止過度消耗CPU資源。

4)Send Queue:待發送隊列。多線程同時向網卡同一TX隊列發送數據會產生沖突,需要此隊列最為發送數據的緩沖隊列。該隊列應用第3節實現的高并發無鎖隊列實現,在多消費者多生產者的情況下是安全的,所以其他非Work Thead線程也可以直接向該隊列添加要發送的數據。

5)Send Thread:發送數據線程。從Send Queue中讀取需要發送的數據進行發送。

4.3.2 細節分析

1)線程的綁定

其 中 Read Thread、Recv Queue以 及 Work Thread完成工作如圖6所示,完成了數據的捕獲與分發處理。為了充分發揮CPU多核的優勢,將第一個CPU核心供操作系統使用,利用CPU親和性使Read thread和Write Thread工作在接下來的兩個CPU核心上,然后將Work Thread依次綁定到剩余CPU 核心[14]。

2)區分數據包來源

PF_RING應用pfring_zc_pkt_buff結構體來描述緩沖區中每個數據包的信息,其定義如下:

typedef struct{

uint16_t len; //數據包長度

u_nt16_t flags; //校驗后標志

uint32_t hash; //網卡計算的hash值

pfring_zc_timespec ts;//時間戳

uchar user[];//數據

}pfring_zc_pkt_buff;

在Recv queue和Send Queue隊列中存儲的使存出每個緩沖區信息的pfring_zc_pkt_buff類型指針,而不是真正的數據,避免了數據處理中的拷貝。其中的user變量允許用戶自己在原始數據頭部插入自己的數據,但要在PR_RING初始化時作出聲明。利用這個特性可以在數據包頭部添加接收網卡與發送網卡的信息。

圖6 數據的分發與處理

3)隊列長度的設置

在初始化PF_RING時,會創建Ring Buffer(環形緩沖區)來存儲網卡收到的數據,其長度為n,所以Recv Queue的長度應為2n,即能夠緩存兩個網卡收到的數據,相應的Send Queue的長度應為n乘以Work Thread的數量。

4.4 使用框架

為方便用戶使用該框架,使用戶不必關心具體的實現細節,設計與實現了應用接口,如下所示:

1)首先定義分配數據包和處理數據包回調函數接口,當接收到數據時會自動調用。

int hash_fun(const unsigned char*data,int data_len);

該接口可根據原始數據包內容和長度,計算Hash值并返回,根據該Hash值把數據分配給相應待處理隊列。

void recv_fun(packet*pkt);

在該接口中,用戶可根據pkt獲取數的內容與長度,完成數據處理。

2)接著在程序中依次調用以下接口。

int init( hash_callback hash_fun,recv_callback recv_fun);

該接口完成分配接口與處理接口的注冊。

device*open_device(const char*dev_name);

該接口完成初始化網口工作。

int dispatch(unsigned int thread_count);

該接口依據處理線程數初始化框架。

3)發送數據的接口

int send_packet(device*send_to,packet*pkt);

該接口用于轉發從網卡收到的數據。

int send_data(device*send_to,const unsigned char*data,int data_len);

該接口用于發送服務器自身需要發送的數據。

4.5 分發策略

此小節主要討論的是如何將數據包分配給各待處理隊列。需要實現如下接口。

int hash_fun(const unsigned char*data,int data_len);

基本分配策略的實現如下:

1)平均分配。把每一個收到的數據包依次分給每一個Recv Queue。定義全局變量static int r=0,函數體的實現如下:

return r++;

這種方式實現簡單,效率高,但同一個TCP連接的數據數據包會分配到不同Worker Thread進行處理,需要線程之間進行通信才可以準確處理數據包亂序、重發等問題。

2)根據<源IP地址、目的IP地址、源端口、目的端口>四元組計算HASH值。函數體的具體實現如下:

int hash=0,i=0;

for(i=26;i<38;i++){

hash^=data[i];

return hash;

因為異或運算滿足結合律和交換律,且具有較好的隨機性能以及較低的計算復雜性[15~16],同時四元組在相同兩臺主機的不同傳輸方向上只是字節順序不同,且在原始數據中位置相對固定,一般存儲在原始數據的26到37字節,所以HASH算法可以設計為對四元組進行逐字節異或,就能使相同主機之間的IP數據或相同TCP連接數據就會交由同一個Worker Thread進行處理。這種方式會影響數據的接收與處理速度。

3)根據<源IP地址、目的IP地址>二元組進行分配。由于一個配電終端和配電主站之前的TCP連接只有一個,故可以應用IP二元組來對TCP數據進行分組,實現方式同上,i的范圍變為26到33即可。此種方式適用于特定場景。

5 實驗分析

在Linux系統上實現了這個高性能數據收發框架,使用了PF_RING ZC庫版本為6.4.1。測試中使用zsend、zcount作為數據發送和接收的工具。用三臺服務器模擬數據發送服務器、配電主站以及數據接收服務器。服務器系統均為Centos6.9,硬件配置:CPU:Intel(R)Xeon(R)CPU E3-1231 v3;內存,16GB;網卡,Intel 82599ES,最大傳輸速率 10Gbps。測試拓撲如圖7所示。

圖7 測試網絡拓撲

服務器CPU共有8個邏輯線程,PF_RING_NEW代表了本文設計的框架,共初始化了5個Recv Queue和Work Thread,采用平均分配策略。PF_RING、Netmap以及DPDK為應用官方接口實現的只具有數據轉發功能的工具軟件。測試過程如下:

1)數據傳輸速率從1Gbps逐步增加到10Gbps,數據包大小固定為128B,測試結果如圖8所示。

圖8 隨傳輸速率增大的丟包率

2)數據包大小從64B逐步增加到768B,數據傳輸速率固定為8Gbps,測試結果如圖9所示。

圖9 隨數據包長度增大的丟包率

由測試結果可以看出,應用PF_RING設計與實現的數據收發框架,在捕獲和傳輸不同速率或不同長度的數據包時丟包率較低,能夠滿足服務器在高速網絡環境的需求。但對比于只進行數據轉發的PF_RING、Netmap與DPDK來說有所增加,主要是因為數據在服務器上需要經過待處理隊列以及待發送隊列后才能進行發送。

6 結語

本文首先介紹了Linux傳統數據包捕獲機制存在的主要瓶頸,接著總結了目前流行的捕獲技術解決這些問題的方法,對各技術的特點做了全面對比以及分析。最后結合無鎖隊列設計了一種基于PF_RING的高速并行網絡數據包處理框架,對實現的細節、框架的使用以及分發策略的實現做了全面闡述。經過初步的實驗驗證,該數據收發框架在不同傳輸速率和不同數據包長度情況下的丟包率滿足服務器的需求。

猜你喜歡
緩沖區網卡線程
5G終端模擬系統隨機接入過程的設計與實現
實時操作系統mbedOS 互斥量調度機制剖析
淺析體育賽事售票系統錯票問題的對策研究
聯網全靠它 認識筆記本的無線網卡
緩沖區溢出漏洞攻擊及其對策探析
挑戰Killer網卡Realtek網游專用Dragon網卡
初涉緩沖區
本期導讀
Linux系統下緩沖區溢出漏洞攻擊的防范
USB故障又一原因
91香蕉高清国产线观看免费-97夜夜澡人人爽人人喊a-99久久久无码国产精品9-国产亚洲日韩欧美综合