輕鬆搞定雲下虛擬化網絡流量
- 2020 年 3 月 3 日
- 筆記
概述
虛擬交換機是在虛擬化場景下提供報文交換的虛擬網絡設備,它可以提供像物理交換機一樣的報文交換及流量鏡像功能。在虛擬化環境下,需要同時支持IDS和IPS時,這種旁路的流量鏡像的方式則無法達到要求。因此,該發明方法用來解決非旁路(阻斷式)處理虛擬交換機報文的問題,該方法可以完成對進入虛擬交換機的報文進行劫持,支持分析模塊多線程並行處理報文,並支持再次送回虛擬交換機繼續交換流程,或直接將報文丟棄,達到阻斷網絡通信的目的。
現有技術的技術方案
在虛擬交換機中加入NetFilter處理框架,使用NFQUEUE將虛擬交換機的流量通過套接字傳送到應用層處理。

如上圖,在交換機轉發過程中,加入NetFilter框架的NFQUEUE處理邏輯。網絡報文將會在NFQUEUE提供的隊列中進行排隊,等待通過套接字逐次發送到應用層。應用層通過Netlink套接字接口逐次讀取報文,並逐次將處理結果返回給NFQUEUE模塊。NFQUEUE模塊根據應用層的決策,決定是轉發還是丟棄報文。
現有技術的缺點主要有兩點,一是在虛擬交換機加入Netfilter處理框架,對虛擬交換機侵入性較大,增加了不必要的技術複雜度,技術成本較高(常用的虛擬交換機並非原生支持NetFilter處理框架)。二是NFQUEUE使用單隊列緩存網絡報文,並使用netlink套接字通信,因此應用層的網絡報文分析處理模塊為了保證報文時序有效性,不利於多線程並發處理,不能夠充分利用處理器資源並發處理網絡報文,導致網絡報文在單一隊列中積壓,並導致後續的網絡報文直接丟棄,丟包率較高,嚴重降低了虛擬交換機的網絡報文交換性能。在本技術方案中,將會解決這兩個問題,對虛擬交換機最低的侵入性,技術複雜度低,並達到高並發處理網絡報文的目的。
本技術方案的詳細闡述
在產品中應用該方案時,提供的文件包括一個內核模塊和一個應用層動態庫及API。網絡報文分析處理模塊首先利用命令將內核模塊加載起來,然後用提供的API,調用一系列的操作方法。
關鍵操作包括:
1. 設置支持最大並發線程數目(非必須,可以使用默認值)。
2. 設置網絡報文分發到多線程的策略(非必須,可以使用默認策略)。
3. 設置報文處理函數。
4. 在報文處理函數中,可以決定放行還是丟棄當前報文或延遲處理。
該技術是網絡報文分析處理的基礎技術,在之上可以構建應用層防火牆、Web檢測、入侵檢測等任何基於流量分析處理的安全防護檢測功能。
整體架構圖

如上圖,網絡報文進入虛擬交換機後,在收包入口處,首先檢查是否有註冊的收包處理函數,如果沒有,則繼續交換機的處理流程。當存在註冊的處理函數時,則調用該收包處理函數,並不再繼續後續的交換機流程,網絡報文被收包處理函數劫持。
註冊的收包處理函數拿到網絡報文後,首先從共享內存隊列中申請一個內存塊,將網絡報文拷貝到內存塊,然後將內存塊索引通知到應用層處理。應用層的網絡報文分析模塊可以根據內存塊的索引,訪問到內存塊及網絡報文的內容,分析完畢後將該報文索引重新返回到共享隊列,通知內核態處理。內核態線程收到通知後,再次從共享隊列中獲取網絡報文的裁決信息,如果是放行,則調用交換機的發送函數,將數據包發送給虛擬交換機繼續執行後續流程,否則直接釋放網絡報文和內存塊,達到阻斷網絡報文的目的。
方案中主要包括四部分,分別是基於內存映射的共享收發隊列、註冊的收包處理函數、多線程報文處理接口、內核態報文處理線程。
【基於內存映射的共享收發隊列】
在該方案中,由於在應用層處理報文更方便,有更多的分析工具可以利用,因此需要將網絡報文傳送到應用層。為了提高傳輸網絡報文性能,避免經過冗長的網絡協議棧路徑,使用共享內存的方式提供應用層和內核態高速通信,在內核態和應用層可以同步讀寫數據,使用單生產者單消費者隊列模型,因此不需要任何互斥鎖就可以保證操作一致性,大大提高了內核態和應用層的同步效率。

將共享內存分為兩個部分:一、隊列區域;二、存儲區域;隊列區域由若干個隊列組成;存儲區域是存放數據的空間。而隊列中只是存放數據在存儲區域的索引偏移地址。所以通過隊列元素中的偏移值可以在存儲區域中找到數據的存儲空間。
共享內存初始化是由內核的字符設備驅動負責處理的,由應用層處理接口負責開啟創建共享內存管理功能。具體步驟為:
Ø 應用層處理接口通過字符設備的ioctl調用,通知內核申請「共享內存」,並告知需要的共享內存大小,隊列的個數,隊列的大小信息。
Ø 內核接收到用戶空間的請求,首先判斷當前是否已經創建過共享內存信息。如果已經創建,判斷當前的共享內存信息是否與用戶空間的傳遞的信息一致。如果不一致,刪除以前的共享內存,並按照要求重新創建。
Ø 應用層處理接口調用mmap系統調用,將內核創建的共享內存映射到用戶空間中。
Ø 應用層處理接口從共享內存中提取必要的隊列信息,並進行保存。
存儲區域是一塊固定空間大小的內存區域。將空間分為若干個固定的block,每個block的大小為2048位元組。

每個block中分為兩個部分:block head信息和數據內容信息。
block head的內容:
Ø 報文長度:當前報文內容的大小。
Ø 裁決結果:報文是放行還是丟棄。
Ø 狀態信息:當前內存塊的使用狀態。
通信隊列分為發送隊列和接收隊列,分別用RX和TX表示,TX用於發包,RX用於收包。RX和TX總是成對的,支持多少並發處理線程,就有多少對TX和TX通道,每個處理線程使用一對通信通道。

隊列設計為環形隊列,環形隊列具有簡單和高效的作用,並且使用單一的生產者和消費者模型,避免了因同步而產生的開銷。
將隊列劃分為多個frame,frame是一個擁有固定大小的空間。

每個frame都一個狀態用來表示「使用者」,如上圖所示其中「綠色的」frame表示內核可以使用,而用戶空間程序不可以操作這些frame;其中「黃色的」frame表示用戶空間可以使用,而內核空間不可以操作這些frame。對隊列的操作,即是通信的過程。
每個frame中都存放着數據的索引(在存儲區域的位置)。另外預留一部空間用於存儲隊列的相關信息,如隊列對數,隊列長度,各個隊列信息(入隊偏移,出隊偏移)等。
存儲區域是將整塊內存分割成相同大小的內存塊的集合。初始化時,根據指定的共享內存塊大小及設置的並發線程數目確定隊列個數及存儲區域的內存塊數目。

將存儲空間中的可用block用鏈表進行串聯,形成可用空間鏈表,然後分配到每對收發隊列中。
【收包處理函數】
網絡報文進入虛擬交換機後,首先檢查是否存在收包處理函數,如果存在,則調用該函數,將該報文的後續行為都交給收包處理函數處理,如果收包處理函數返回錯誤,則繼續交換機後續處理。進入收包處理函數,則達到了劫持的目的,下一步則需要將該網絡報文通過以上建立的隊列,將該報文發送到應用層。

【應用層多線程處理接口】
該接口由應用層的網絡報文分析處理模塊調用,提供了接收網絡報文的接口和對網絡報文進行裁決的功能。首先通過open函數打開共享內存,通過mmap函數進行映射共享內存地址,從共享內存中獲取隊列相關信息並存儲,創建接收數據線程,用於收取共享內存隊列中的報文所在內存塊的索引,並進一步取得內存塊中的報文內容,在線程中調用報文分析處理函數,完成判定邏輯。
在接收網絡報文的線程中,不斷從隊列中讀取網絡報文,並調用報文處理函數,根據報文處理函數的返回值,決定是否將報文放行。

【內核態報文處理線程】
創建內核態線程,用於處理應用層對網絡報文的裁決結果。裁決結果如果是丟棄,內核線程則將報文釋放掉,將內存塊歸還給所在的隊列,如果是放行,則進一步調用虛擬交換機的發送接口,將報文再次送入交換機,執行後續的交換流程。
內核線程由應用層調用ioctl進行喚醒,內核處理線程的處理函數為:
int receive_data_from_user_thread(void *data)
此函數的主要功能就是處理隊列中的數據(將隊列中的數據發送給虛擬交換機)。
內核線程的喚醒採用了等待隊列的機制,可以提高喚醒的性能。
內核線程創建的模型為:
DECLARE_WAITQUEUE(wait,current); //申請局部的等待隊列元素 add_wait_queue(&rvshmem_rx_waitqueue,&wait);將等待隊列元素添加到全局的等待對中 set_current_state(TASK_INTERRUPTIBLE);設置當前的線程為睡眠 for(;;) { schedule();//調度,當前的線程會進入睡眠等待再次的被調度或者是被喚醒 if(kthread_should_stop())//當檢測到線程需要退出的時候, 會直接退出 break; ... ... //中間的處理業務 set_current_state(TASK_INTERRUPTIBLE); //設置當前的任務為可睡眠的 } remove_wait_queue(&rvshmem_rx_waitqueue,&wait); //當線程退出的時候,將等待隊列進行移除
可以啟動一個或者多個內核線程處理報文,這裡採用一個內核線程處理多個隊列的,所以在內核線程中我們循環處理每個隊列,每個隊列處理10個報文,並且只要有一個隊列中有數據,就會一直循環處理下去,當報文處理完畢後進入睡眠,等待再次喚醒。
/*這裡循環處理所有的隊列中的數據*/ for(flag = 0,i = 0; i < NET_QUEUE_NUM;i++) { /*每次處理10個報文*/ for(j = 0; j < 10;j++) { /*將報文發送給虛擬交換機進行處理*/ ret = dequeue_to_kernel(i); if(ret != SHMEM_QUEUE_EMPTY) flag = 1; //*只要有一個隊列中不為空,則不退出處理 } if(!flag) { /*當隊列中的所有數據都已經被處理,隊列中為空的時候 * 則退出處理,將將線程進行睡眠處理,等待下一次的調度*/ break; } }
處理內核數據的的函數為:
int dequeue_to_kernel(int queue_index)
此函數的主要功能就是從發送隊列中獲取數據,並將數據發送給虛擬交換機。
1. 實現了虛擬網絡環境下的非旁路抓取網絡流量功能,可實現阻斷式網絡檢測,技術複雜度低。
2. 可以支持應用層多線程並發執行網絡報文分析處理,提高了網絡檢測效率。
3. 支撐上層應用提供更豐富的安全能力。