解永亮,付國楷,房利國
(中國電子科技集團公司第三十研究所,四川 成都 610041)
在實際的網絡環境中,防火墻是網絡設備所提供的非常重要的功能之一。一般情況下,對性能要求較高的防火墻使用現場可編程門陣列(Field Programmable Gate Array,FPGA)對網絡包進行處理,而對于那些需要提供防火墻功能的普通網絡設備則由運行在中央處理器(Central Processing Unit,CPU)上的軟件完成這項工作。在各種防火墻軟件中,Linux 內核中的Netfilter 子系統又是一個經常被選擇的方案。由于Netfilter 子系統所提供的數據包過濾功能放在了整個網絡包處理路徑中比較靠后的位置,因此如果在實際運行中防火墻需要大量丟棄接收到的網絡包,就會導致CPU 運算能力被極大浪費,這在處理能力本就比較低的國產處理器上表現得尤為明顯。
快速數據路徑(eXpress Data Path,XDP)是一套內核態、高性能、可編程柏克萊封包過濾器(Berkeley Packet Filter,BPF)包處理框架[1]。該框架是在網絡包處理路徑上添加“處理點”(HOOK),這一點與Netfilter 相同,但XDP HOOK 位于更早的位置。這個位置可以是網卡驅動程序甚至是網卡內部,因此XDP程序可以直接從接收緩沖區中將網絡包拿下來,無須執行任何耗時的操作,比如分配套接字緩沖區(Socket Buffer,SKB),然后將包推送到網絡協議棧,或者將包推送給通用接收分擔(Generic Receive Offload,GRO)引擎等[1]。除此之外,XDP的可編程特性可以實現在不修改Linux 內核的情況下重構包處理邏輯,這為防火墻軟件升級提供了便利。
本文將簡單介紹XDP 核心要素,并在此基礎上結合現有Netfilter 子系統設計出一種新的框架結構,以達到高速網絡包分析以及對現有防火墻上層軟件不造成任何影響的目的,最后通過在飛騰處理器平臺上的性能測試對兩種方案進行了對比和分析。
XDP 程序只在網絡數據包的接收(Ingress)路徑上被觸發執行。它可以運行在從硬件到協議棧的3 個不同位置,分別對應著3 種工作模式[2]:
(1)通用XDP(Generic XDP):XDP 程序運行在內核協議棧。
(2)XDP 分 擔(Offloaded XDP):XDP 程 序運行在網卡硬件上。
(3)本地XDP(Native XDP):XDP 程序運行在網卡驅動中。本文將使用運行在該工作模式的XDP 對Netfilter 進行改進。由其所參與的內核協議棧數據接收路徑[3]如圖1 所示。

圖1 XDP 網絡包接收路徑
網卡驅動從硬件上接收到網絡包后首先交給XDP 程序處理,XDP 程序對網絡包進行分析,并通過返回值的方式告訴內核接下來該如何操作:如果返回值為XDP_DROP,則直接丟棄該網絡包;如果返回XDP_PASS[4],則由網卡驅動為該數據包分配SKB 數據結構,并提交內核協議棧做進一步的處理。用戶空間的進程通過套接字等手段與協議棧交互,獲取來自網卡的網絡包數據。
通常所說的XDP 程序指的是BPF 程序,它是一個由C 語言子集編寫的程序。
XDP 程序由源代碼變為最終可執行的二進制代碼需要以下兩個步驟:
(1)預編譯:通過低級虛擬機(Low Level Virtual Machine,LLVM)將C 語言源代碼翻譯成BPF 指令集,該指令集是一個通用目的精簡指令集(Reduced Instruction Set Computer,RISC)。
(2)加載:通過內核中的即時編譯器(Just In Time Compiler)將BPF 指令映射成最終處理器可以理解的原生指令。
XDP 程序與用戶空間的應用程序之間只能使用Map 進行通信。Map 是由核心內核(core kernel)提供的駐留在內核空間的高效鍵值倉庫(key/value store)[1],BPF 程序可以直接對其訪問,而應用程序需要通過文件描述符(File Descriptor,FD)訪問該數據。XDP 與應用程序之間的通信方式,如圖2所示。

圖2 XDP 與應用程序之間的通信方式
Map 可分為per-CPU 及non-per-CPU兩種類型[1],也可以按照其組織數據的方式分為通用型和非通用型兩種。經常使用的通用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
新構架由Netfilter 命令分析和處理模塊、XDP處理模塊、XDP 后臺守護進程、網卡驅動XDP 支持模塊4 部分組成。整體構架如圖3 所示,其中虛線為控制流,實線為網絡數據包。

圖3 整體框架結構及數據流
下面分別介紹每個模塊需要完成的功能、所處的位置、實現方式以及模塊間的關系。
Netfilter 命令分析和處理模塊位于內核Netfilter子系統與用戶進程進行交互的控制接口處,用于對來自用戶進程的控制命令進行分流:如果操作的是FILTER 表的INPUT 或FORWARD 鏈(網絡數據包目的地址為本機時觸發INPUT 鏈,不是本機但需要本機轉發時觸發FORWARD 鏈[6]),則攔截該命令,并把用戶實際的操作意圖轉換為XDP 規則,交由XDP 后臺守護進程處理;如果操作的是其他表或者鏈,比如NAT 表、MINGLE 表、PREROUTING 鏈、POSTROUTING 鏈等,則放行,依舊由NetFilter 子系統進行處理。
XDP 處理模塊是一個位于內核層的XDP 程序,它根據XDP 后臺守護進程配置的規則對網絡包進行分析和匹配,并根據對應規則決定后續處理方式。它由XDP 后臺守護進程加載,通過Map 與后臺進程進行交互。
XDP 后臺守護進程是一個應用層程序,系統開機后自動運行,接收來自Netfilter 命令分析和處理模塊的請求,根據請求的內容向指定網口安裝或拆卸XDP 處理模塊,并通過Map 向其添加、修改或刪除規則。
網卡驅動XDP 支持模塊屬于內核層網卡驅動的一部分。該模塊在從網卡硬件上接收到網絡包之后,以及在分配SKB 之前按照Linux 內核提供的XDP 支持功能調用XDP 處理模塊,并根據返回值決定后續處理方式。
整個構架所涉及的應用層、內核層軟件開發和測試使用的硬件平臺:CPU 為飛騰1500A(四核,主頻1.5 GHz),內存為DDR3-800(4GB)。網口使用CPU 自帶的千兆以太網控制器。操作系統版本為Linux-4.14.10。
本節只具體描述整個框架中最核心的Netfilter命令分析和處理模塊和XDP 處理模塊。
該模塊從應用層傳遞到Linux 內核的參數中提取關鍵數據,然后根據現有的規則形成規則增量信息。整個過程如圖4 所示。

圖4 處理流程
下面對每一步如何從內核數據結構中提取相關信息進行說明,詳細數據結構定義請參考內核源代碼。
(1)提取Table 名稱。用戶層傳遞的數據結構為struct ipt_replace,其中name 字段保存的就是需要操作的Netfilter 表名。
(2)提取Chain 名稱。因為不管是對Netfilter表的添加、刪除還是修改,應用層都會把相應表中所有的Chain 及其包含的所有規則傳遞給內核層。因此,想要知道這次調用操作的是哪個Chain,就必須對比本次調用與上次調用哪些數據發生了變化。利用struct ipt_replace 中的valid_hooks 字段可以知道當前表中哪些Chain 是合法的及這些Chain在表中出現的順序,再使用內核提供的xt_entry_foreach()宏遍歷所有的struct ipt_entry(Netfilter 規則用該數據結構表示)進行比較。圖5 展示了Filter 表中Chain的組織方式,需要注意的是每一個Chain 都是以一個全0的struct ipt_entry 結束。

圖5 Filter 表中鏈的組織方式
(3)提取輸入/輸出接口名稱。struct ipt_entry中的ip 字段保存了這些信息,它是個struct ipt_ip數據結構,其中iniface、outiface、iniface_mask、outiface_mask 字段指示接口信息。
(4)提取源/目的IP 地址。struct ipt_ip 數據結構中的src、dst、smsk、dmsk 字段保存這些信息。
(5)提取協議號。struct ipt_ip 數據結構中的proto 字段保存這些信息。
(6)提取Target信息。Target 表示的是規則匹配成功后的處理方式。使用ipt_get_target()函數可以獲取規則對應的struct xt_entry_target 數據結構,其成員user 中的name 字段以字符串的形式表示這些信息。
(7)提取Match信息。如果規則涉及4 層協議,比如用戶數據報協議(User Datagram Protocol,UDP)、傳輸控制協議(Transmission Control Protocol,TCP)等,那么struct ipt_entry 中的elems字段保存這些數據,然后使用xt_ematch_foreach()宏遍歷所有struct xt_entry_match(Netfilter 表 示Match的數據結構)。該結構中user 成員的name字段用于指示Match的名稱。不同的4 層協議,xt_entry_match 關聯的數據(data 字段)并不一樣,比如UDP,關聯的數據結構是struct xt_udp,從中可以提取源/目的端口號等信息。
(8)計算規則增量信息。使用的方法與(2)中介紹的一樣。增量信息包括添加、刪除、修改3種類型。
4.2.1 數據結構定義
XDP 后臺守護進程通過兩個Map 與XDP 處理模塊交互,一個用于向XDP 處理模塊發送命令并接收響應(控制Map),另一個用于存儲規則表(規則Map)。
控制Map的參數如表1 所示。通過索引值0 來訪問256 Bytes的命令/應答空間。命令幀與響應幀的區分使用自定義幀頭來實現。因為使用的是命令/應答方式,在接收到響應之前不會發送下一條命令,因此最大條目只需要一個。

表1 控制Map
規則Map的參數如表2 所示。

表2 規則Map
規則定義如表3 所示。其中,next 和prev 字段主要用于解決大業務量時,XDP 處理模塊頻繁讀取規則表,而XDP 后臺處理進程會對規則表進行修改的問題,比如,需要刪除規則時,先修改后一條規則的prev 指向前一條規則,再修改前一條規則的next 字段為被刪除規則的next。

表3 規則定義
4.2.2 數據接收和處理
XDP 所使用的網絡包數據結構是struct xdp_md/xdp_buff,該結構只有data 和data_end[7]兩個字段,分別用于指示網絡包在連續內存空間中的起始和結束位置。XDP 處理模塊通過分析這段空間提取網絡包的ip 地址、協議、端口號等信息,然后在規則Map 中匹配規則,并根據規則的target 字段決定返回值。需要注意的是,遍歷規則Map 是使用規則中的next 字段來完成的,prev 字段僅在修改規則表時使用。
另外,XDP 中網絡包處理函數需要放在名稱為“xdp”開頭的“節”中,這樣加載程序才能自動找到該函數。
網絡防火墻在實際運行過程中會丟棄大量不合法的網絡包,因此測試中使用以下命令添加一條對目的端口為5126的UDP 包進行丟棄的規則,來評估兩種方式下每秒的丟包數(package per second,pps):
iptables -A INPUT -p udp --dport 5126 -j DROP[8]
測試結果如表4 所示。

表4 吞吐量對比測試結果
從表中的測試結果可以得出以下幾個結論:
(1)隨著包長的變大性能提升率逐漸降低,這是因為協議分析邏輯對大包和小包的處理時間基本相同(都只是分析位于網絡包前端的協議頭),而CPU的處理性能遠低于千兆網口小包的理論傳輸速率,這時CPU 是性能瓶頸。當包長為1 500 Bytes時,CPU的處理性能已經超過了千兆網口大包傳輸的理論性能,這時千兆網口是性能瓶頸,網絡包處理達到飽和。
(2)如果在實際運行過程中大量的網絡包通過規則匹配后的結果是“放行”,那么本文設計的新框架不會對防火墻的吞吐率有很大的提升,這是因為不管是內核中的Netfilter 還是XDP 程序對網絡包的分析原理基本上是一致的,并且分析后依舊是通過協議棧交給相應模塊處理,在這種情況下,XDP 程序的優勢有二:一是處理的包在線性空間中,而內核中使用SKB 描述的網絡包可能是分段的;二是XDP 程序沒有復雜的調用關系。
(3)如果不考慮對上層軟件的兼容性等問題,可以考慮利用基于應用層的數據平面開發套件(Data Plane Development Kit,DPDK)來實現網絡防火墻,也可以通過內存大頁以及POLL 模式的網卡驅動能夠進一步提高網絡包處理性能[9]。
本文設計了一種不局限于某種特定處理器和Linux 操作系統的中低端防火墻框架,并對其原理、軟件模塊組成及與其他子系統之間的關系和實現要點做了詳細介紹。本設計對所有基于內核Netfilter的防火墻軟件完全透明,在實際部署過程中不要求上層軟件進行相應適配,并且可以在不修改Linux內核的情況下對所支持的協議進行擴充和升級,具有較強的適應性和擴展性。通過與傳統方式的吞吐量對比測試可以看出,目前該框架能在不增加硬件成本的情況下顯著提高防火墻的性能,滿足現有網絡設備對高性能防火墻的需求。