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