解永亮,付國楷,房利國
(中國電子科技集團公司第三十研究所,四川 成都 610041)
在實際的網(wǎng)絡環(huán)境中,防火墻是網(wǎng)絡設備所提供的非常重要的功能之一。一般情況下,對性能要求較高的防火墻使用現(xiàn)場可編程門陣列(Field Programmable Gate Array,F(xiàn)PGA)對網(wǎng)絡包進行處理,而對于那些需要提供防火墻功能的普通網(wǎng)絡設備則由運行在中央處理器(Central Processing Unit,CPU)上的軟件完成這項工作。在各種防火墻軟件中,Linux 內(nèi)核中的Netfilter 子系統(tǒng)又是一個經(jīng)常被選擇的方案。由于Netfilter 子系統(tǒng)所提供的數(shù)據(jù)包過濾功能放在了整個網(wǎng)絡包處理路徑中比較靠后的位置,因此如果在實際運行中防火墻需要大量丟棄接收到的網(wǎng)絡包,就會導致CPU 運算能力被極大浪費,這在處理能力本就比較低的國產(chǎn)處理器上表現(xiàn)得尤為明顯。
快速數(shù)據(jù)路徑(eXpress Data Path,XDP)是一套內(nèi)核態(tài)、高性能、可編程柏克萊封包過濾器(Berkeley Packet Filter,BPF)包處理框架[1]。該框架是在網(wǎng)絡包處理路徑上添加“處理點”(HOOK),這一點與Netfilter 相同,但XDP HOOK 位于更早的位置。這個位置可以是網(wǎng)卡驅(qū)動程序甚至是網(wǎng)卡內(nèi)部,因此XDP程序可以直接從接收緩沖區(qū)中將網(wǎng)絡包拿下來,無須執(zhí)行任何耗時的操作,比如分配套接字緩沖區(qū)(Socket Buffer,SKB),然后將包推送到網(wǎng)絡協(xié)議棧,或者將包推送給通用接收分擔(Generic Receive Offload,GRO)引擎等[1]。除此之外,XDP的可編程特性可以實現(xiàn)在不修改Linux 內(nèi)核的情況下重構(gòu)包處理邏輯,這為防火墻軟件升級提供了便利。
本文將簡單介紹XDP 核心要素,并在此基礎上結(jié)合現(xiàn)有Netfilter 子系統(tǒng)設計出一種新的框架結(jié)構(gòu),以達到高速網(wǎng)絡包分析以及對現(xiàn)有防火墻上層軟件不造成任何影響的目的,最后通過在飛騰處理器平臺上的性能測試對兩種方案進行了對比和分析。
XDP 程序只在網(wǎng)絡數(shù)據(jù)包的接收(Ingress)路徑上被觸發(fā)執(zhí)行。它可以運行在從硬件到協(xié)議棧的3 個不同位置,分別對應著3 種工作模式[2]:
(1)通用XDP(Generic XDP):XDP 程序運行在內(nèi)核協(xié)議棧。
(2)XDP 分 擔(Offloaded XDP):XDP 程 序運行在網(wǎng)卡硬件上。
(3)本地XDP(Native XDP):XDP 程序運行在網(wǎng)卡驅(qū)動中。本文將使用運行在該工作模式的XDP 對Netfilter 進行改進。由其所參與的內(nèi)核協(xié)議棧數(shù)據(jù)接收路徑[3]如圖1 所示。
圖1 XDP 網(wǎng)絡包接收路徑
網(wǎng)卡驅(qū)動從硬件上接收到網(wǎng)絡包后首先交給XDP 程序處理,XDP 程序?qū)W(wǎng)絡包進行分析,并通過返回值的方式告訴內(nèi)核接下來該如何操作:如果返回值為XDP_DROP,則直接丟棄該網(wǎng)絡包;如果返回XDP_PASS[4],則由網(wǎng)卡驅(qū)動為該數(shù)據(jù)包分配SKB 數(shù)據(jù)結(jié)構(gòu),并提交內(nèi)核協(xié)議棧做進一步的處理。用戶空間的進程通過套接字等手段與協(xié)議棧交互,獲取來自網(wǎng)卡的網(wǎng)絡包數(shù)據(jù)。
通常所說的XDP 程序指的是BPF 程序,它是一個由C 語言子集編寫的程序。
XDP 程序由源代碼變?yōu)樽罱K可執(zhí)行的二進制代碼需要以下兩個步驟:
(1)預編譯:通過低級虛擬機(Low Level Virtual Machine,LLVM)將C 語言源代碼翻譯成BPF 指令集,該指令集是一個通用目的精簡指令集(Reduced Instruction Set Computer,RISC)。
(2)加載:通過內(nèi)核中的即時編譯器(Just In Time Compiler)將BPF 指令映射成最終處理器可以理解的原生指令。
XDP 程序與用戶空間的應用程序之間只能使用Map 進行通信。Map 是由核心內(nèi)核(core kernel)提供的駐留在內(nèi)核空間的高效鍵值倉庫(key/value store)[1],BPF 程序可以直接對其訪問,而應用程序需要通過文件描述符(File Descriptor,F(xiàn)D)訪問該數(shù)據(jù)。XDP 與應用程序之間的通信方式,如圖2所示。
圖2 XDP 與應用程序之間的通信方式
Map 可分為per-CPU 及non-per-CPU兩種類型[1],也可以按照其組織數(shù)據(jù)的方式分為通用型和非通用型兩種。經(jīng)常使用的通用Map 有[5]:
(1)BPF_MAP_TYPE_HASH
(2)BPF_MAP_TYPE_ARRAY
(3)BPF_MAP_TYPE_PERCPU_HASH
(4)BPF_MAP_TYPE_PERCPU_ARRAY
(5)BPF_MAP_TYPE_LRU_HASH
(6)BPF_MAP_TYPE_LRU_PERCPU_HASH
新構(gòu)架由Netfilter 命令分析和處理模塊、XDP處理模塊、XDP 后臺守護進程、網(wǎng)卡驅(qū)動XDP 支持模塊4 部分組成。整體構(gòu)架如圖3 所示,其中虛線為控制流,實線為網(wǎng)絡數(shù)據(jù)包。
圖3 整體框架結(jié)構(gòu)及數(shù)據(jù)流
下面分別介紹每個模塊需要完成的功能、所處的位置、實現(xiàn)方式以及模塊間的關系。
Netfilter 命令分析和處理模塊位于內(nèi)核Netfilter子系統(tǒng)與用戶進程進行交互的控制接口處,用于對來自用戶進程的控制命令進行分流:如果操作的是FILTER 表的INPUT 或FORWARD 鏈(網(wǎng)絡數(shù)據(jù)包目的地址為本機時觸發(fā)INPUT 鏈,不是本機但需要本機轉(zhuǎn)發(fā)時觸發(fā)FORWARD 鏈[6]),則攔截該命令,并把用戶實際的操作意圖轉(zhuǎn)換為XDP 規(guī)則,交由XDP 后臺守護進程處理;如果操作的是其他表或者鏈,比如NAT 表、MINGLE 表、PREROUTING 鏈、POSTROUTING 鏈等,則放行,依舊由NetFilter 子系統(tǒng)進行處理。
XDP 處理模塊是一個位于內(nèi)核層的XDP 程序,它根據(jù)XDP 后臺守護進程配置的規(guī)則對網(wǎng)絡包進行分析和匹配,并根據(jù)對應規(guī)則決定后續(xù)處理方式。它由XDP 后臺守護進程加載,通過Map 與后臺進程進行交互。
XDP 后臺守護進程是一個應用層程序,系統(tǒng)開機后自動運行,接收來自Netfilter 命令分析和處理模塊的請求,根據(jù)請求的內(nèi)容向指定網(wǎng)口安裝或拆卸XDP 處理模塊,并通過Map 向其添加、修改或刪除規(guī)則。
網(wǎng)卡驅(qū)動XDP 支持模塊屬于內(nèi)核層網(wǎng)卡驅(qū)動的一部分。該模塊在從網(wǎng)卡硬件上接收到網(wǎng)絡包之后,以及在分配SKB 之前按照Linux 內(nèi)核提供的XDP 支持功能調(diào)用XDP 處理模塊,并根據(jù)返回值決定后續(xù)處理方式。
整個構(gòu)架所涉及的應用層、內(nèi)核層軟件開發(fā)和測試使用的硬件平臺:CPU 為飛騰1500A(四核,主頻1.5 GHz),內(nèi)存為DDR3-800(4GB)。網(wǎng)口使用CPU 自帶的千兆以太網(wǎng)控制器。操作系統(tǒng)版本為Linux-4.14.10。
本節(jié)只具體描述整個框架中最核心的Netfilter命令分析和處理模塊和XDP 處理模塊。
該模塊從應用層傳遞到Linux 內(nèi)核的參數(shù)中提取關鍵數(shù)據(jù),然后根據(jù)現(xiàn)有的規(guī)則形成規(guī)則增量信息。整個過程如圖4 所示。
圖4 處理流程
下面對每一步如何從內(nèi)核數(shù)據(jù)結(jié)構(gòu)中提取相關信息進行說明,詳細數(shù)據(jù)結(jié)構(gòu)定義請參考內(nèi)核源代碼。
(1)提取Table 名稱。用戶層傳遞的數(shù)據(jù)結(jié)構(gòu)為struct ipt_replace,其中name 字段保存的就是需要操作的Netfilter 表名。
(2)提取Chain 名稱。因為不管是對Netfilter表的添加、刪除還是修改,應用層都會把相應表中所有的Chain 及其包含的所有規(guī)則傳遞給內(nèi)核層。因此,想要知道這次調(diào)用操作的是哪個Chain,就必須對比本次調(diào)用與上次調(diào)用哪些數(shù)據(jù)發(fā)生了變化。利用struct ipt_replace 中的valid_hooks 字段可以知道當前表中哪些Chain 是合法的及這些Chain在表中出現(xiàn)的順序,再使用內(nèi)核提供的xt_entry_foreach()宏遍歷所有的struct ipt_entry(Netfilter 規(guī)則用該數(shù)據(jù)結(jié)構(gòu)表示)進行比較。圖5 展示了Filter 表中Chain的組織方式,需要注意的是每一個Chain 都是以一個全0的struct ipt_entry 結(jié)束。
圖5 Filter 表中鏈的組織方式
(3)提取輸入/輸出接口名稱。struct ipt_entry中的ip 字段保存了這些信息,它是個struct ipt_ip數(shù)據(jù)結(jié)構(gòu),其中iniface、outiface、iniface_mask、outiface_mask 字段指示接口信息。
(4)提取源/目的IP 地址。struct ipt_ip 數(shù)據(jù)結(jié)構(gòu)中的src、dst、smsk、dmsk 字段保存這些信息。
(5)提取協(xié)議號。struct ipt_ip 數(shù)據(jù)結(jié)構(gòu)中的proto 字段保存這些信息。
(6)提取Target信息。Target 表示的是規(guī)則匹配成功后的處理方式。使用ipt_get_target()函數(shù)可以獲取規(guī)則對應的struct xt_entry_target 數(shù)據(jù)結(jié)構(gòu),其成員user 中的name 字段以字符串的形式表示這些信息。
(7)提取Match信息。如果規(guī)則涉及4 層協(xié)議,比如用戶數(shù)據(jù)報協(xié)議(User Datagram Protocol,UDP)、傳輸控制協(xié)議(Transmission Control Protocol,TCP)等,那么struct ipt_entry 中的elems字段保存這些數(shù)據(jù),然后使用xt_ematch_foreach()宏遍歷所有struct xt_entry_match(Netfilter 表 示Match的數(shù)據(jù)結(jié)構(gòu))。該結(jié)構(gòu)中user 成員的name字段用于指示Match的名稱。不同的4 層協(xié)議,xt_entry_match 關聯(lián)的數(shù)據(jù)(data 字段)并不一樣,比如UDP,關聯(lián)的數(shù)據(jù)結(jié)構(gòu)是struct xt_udp,從中可以提取源/目的端口號等信息。
(8)計算規(guī)則增量信息。使用的方法與(2)中介紹的一樣。增量信息包括添加、刪除、修改3種類型。
4.2.1 數(shù)據(jù)結(jié)構(gòu)定義
XDP 后臺守護進程通過兩個Map 與XDP 處理模塊交互,一個用于向XDP 處理模塊發(fā)送命令并接收響應(控制Map),另一個用于存儲規(guī)則表(規(guī)則Map)。
控制Map的參數(shù)如表1 所示。通過索引值0 來訪問256 Bytes的命令/應答空間。命令幀與響應幀的區(qū)分使用自定義幀頭來實現(xiàn)。因為使用的是命令/應答方式,在接收到響應之前不會發(fā)送下一條命令,因此最大條目只需要一個。
表1 控制Map
規(guī)則Map的參數(shù)如表2 所示。
表2 規(guī)則Map
規(guī)則定義如表3 所示。其中,next 和prev 字段主要用于解決大業(yè)務量時,XDP 處理模塊頻繁讀取規(guī)則表,而XDP 后臺處理進程會對規(guī)則表進行修改的問題,比如,需要刪除規(guī)則時,先修改后一條規(guī)則的prev 指向前一條規(guī)則,再修改前一條規(guī)則的next 字段為被刪除規(guī)則的next。
表3 規(guī)則定義
4.2.2 數(shù)據(jù)接收和處理
XDP 所使用的網(wǎng)絡包數(shù)據(jù)結(jié)構(gòu)是struct xdp_md/xdp_buff,該結(jié)構(gòu)只有data 和data_end[7]兩個字段,分別用于指示網(wǎng)絡包在連續(xù)內(nèi)存空間中的起始和結(jié)束位置。XDP 處理模塊通過分析這段空間提取網(wǎng)絡包的ip 地址、協(xié)議、端口號等信息,然后在規(guī)則Map 中匹配規(guī)則,并根據(jù)規(guī)則的target 字段決定返回值。需要注意的是,遍歷規(guī)則Map 是使用規(guī)則中的next 字段來完成的,prev 字段僅在修改規(guī)則表時使用。
另外,XDP 中網(wǎng)絡包處理函數(shù)需要放在名稱為“xdp”開頭的“節(jié)”中,這樣加載程序才能自動找到該函數(shù)。
網(wǎng)絡防火墻在實際運行過程中會丟棄大量不合法的網(wǎng)絡包,因此測試中使用以下命令添加一條對目的端口為5126的UDP 包進行丟棄的規(guī)則,來評估兩種方式下每秒的丟包數(shù)(package per second,pps):
iptables -A INPUT -p udp --dport 5126 -j DROP[8]
測試結(jié)果如表4 所示。
表4 吞吐量對比測試結(jié)果
從表中的測試結(jié)果可以得出以下幾個結(jié)論:
(1)隨著包長的變大性能提升率逐漸降低,這是因為協(xié)議分析邏輯對大包和小包的處理時間基本相同(都只是分析位于網(wǎng)絡包前端的協(xié)議頭),而CPU的處理性能遠低于千兆網(wǎng)口小包的理論傳輸速率,這時CPU 是性能瓶頸。當包長為1 500 Bytes時,CPU的處理性能已經(jīng)超過了千兆網(wǎng)口大包傳輸?shù)睦碚撔阅?,這時千兆網(wǎng)口是性能瓶頸,網(wǎng)絡包處理達到飽和。
(2)如果在實際運行過程中大量的網(wǎng)絡包通過規(guī)則匹配后的結(jié)果是“放行”,那么本文設計的新框架不會對防火墻的吞吐率有很大的提升,這是因為不管是內(nèi)核中的Netfilter 還是XDP 程序?qū)W(wǎng)絡包的分析原理基本上是一致的,并且分析后依舊是通過協(xié)議棧交給相應模塊處理,在這種情況下,XDP 程序的優(yōu)勢有二:一是處理的包在線性空間中,而內(nèi)核中使用SKB 描述的網(wǎng)絡包可能是分段的;二是XDP 程序沒有復雜的調(diào)用關系。
(3)如果不考慮對上層軟件的兼容性等問題,可以考慮利用基于應用層的數(shù)據(jù)平面開發(fā)套件(Data Plane Development Kit,DPDK)來實現(xiàn)網(wǎng)絡防火墻,也可以通過內(nèi)存大頁以及POLL 模式的網(wǎng)卡驅(qū)動能夠進一步提高網(wǎng)絡包處理性能[9]。
本文設計了一種不局限于某種特定處理器和Linux 操作系統(tǒng)的中低端防火墻框架,并對其原理、軟件模塊組成及與其他子系統(tǒng)之間的關系和實現(xiàn)要點做了詳細介紹。本設計對所有基于內(nèi)核Netfilter的防火墻軟件完全透明,在實際部署過程中不要求上層軟件進行相應適配,并且可以在不修改Linux內(nèi)核的情況下對所支持的協(xié)議進行擴充和升級,具有較強的適應性和擴展性。通過與傳統(tǒng)方式的吞吐量對比測試可以看出,目前該框架能在不增加硬件成本的情況下顯著提高防火墻的性能,滿足現(xiàn)有網(wǎng)絡設備對高性能防火墻的需求。