王余雷WANG Yu-lei;程鵬CHENG Peng;婁方亮LOU Fang-liang;謝海峰XIE Hai-feng
(中興通訊股份有限公司,南京210012)
通訊系統(tǒng)使用DPDK(Data Plane Development Kit,數(shù)據平面開發(fā)套件)接管網卡后,要實現(xiàn)對網卡進行抓包的功能,一般來說,需要基于業(yè)務流程增加抓包函數(shù),且抓包函數(shù)是通過五元組(目的端口,源端口,目的地址,源地址,協(xié)議類型)等簡單的特征字段或規(guī)則進行報文過濾,其中特征字段或規(guī)則是固化在源代碼中;從而,當增加特殊的特征字段或規(guī)則時,則需要重新修改源代碼和制作版本等。為了更好地應對大流量復雜場景的精準報文過濾,本文實現(xiàn)了基于DPDK 的eBPF 報文過濾器進行靈活的抓包功能,已在通訊系統(tǒng)中得到廣泛的應用。
BPF (Berkeley Packet Filter),即伯克利數(shù)據包過濾器,最初構想提出于1992 年,其目的是為了提供一種過濾包的方法,并且要避免從內核空間到用戶空間的無用的數(shù)據包復制行為。在2013 年,Alexei Starovoitov 對BPF 進行了徹底地改造,并增加了新的功能,該新版本被命名為eBPF,即extended BPF;為了后向兼容,傳統(tǒng)的BPF 仍被保留,該老版本被重命名為cBPF,即classical BPF。對比于cBPF,eBPF 帶來的改變是革命性的:其一是eBPF 可為內核跟蹤、事件監(jiān)控、網絡性能、流量控制等領域帶來了激動人心的變革;其二是在接口的設計以及易用性上有了較大的改進。
Linux 內核的BPF,是Linux 內核提供的基于從用戶空間注入內核的BPF 指令的動態(tài)注入技術,在內核中實現(xiàn)了BPF 指令的虛擬機,對收到的BPF 指令先用一個校驗器進行檢查,檢查指令無誤后附著到一個套接字上,接著對套接字上每個收到的包執(zhí)行BPF 指令。當一個數(shù)據包到達網絡接口時,數(shù)據鏈路層的驅動會把它向系統(tǒng)的協(xié)議棧傳送。但如果BPF 監(jiān)聽接口,驅動首先調用BPF。BPF 首先進行過濾操作,然后把數(shù)據包存放在過濾器相關的緩沖區(qū)中,最后設備驅動再次獲得控制。BPF 虛擬機是一個輕量級的,高效的狀態(tài)機,對BPF 過濾代碼進行解釋處理等。Linux 內核的BPF 的總體架構如圖1 所示。
DPDK 的eBPF 模塊與Linux 內核的eBPF 類似,最大的區(qū)別在于DPDK 的eBPF 模塊在用戶態(tài)實現(xiàn)了eBPF虛擬機執(zhí)行引擎,用來執(zhí)行eBPF 程序對應的字節(jié)碼,支持eBPF 規(guī)范的基本功能集,但未兼容cBPF 規(guī)范的指令集。其eBPF 提供以下的基本操作:創(chuàng)建一個新的eBPF執(zhí)行上下文,并將用戶提供的eBPF 代碼加載到其中;銷毀eBPF 執(zhí)行上下文及其運行時結構,并釋放關聯(lián)的內存;執(zhí)行與提供的輸入參數(shù)關聯(lián)的eBPF 字節(jié)碼;提供有關給定eBPF 上下文的本機編譯代碼的信息;從ELF 文件加載eBPF 程序并安裝回調以在給定的ethdev 端口/隊列上執(zhí)行它。
目前DPDK 的eBPF 模塊實現(xiàn)了兩個執(zhí)行eBPF 程序的回調入口,分別在rte_eth_tx_burst 函數(shù)和rte_eth_rx_burst函數(shù)中,在收到報文后和發(fā)送報文前,均可以執(zhí)行回調函數(shù)。而這些回調函數(shù)是由BPF 模塊在加載BPF 目標文件時賦值。(圖2)
本文實現(xiàn)了類似通用的tcpdump 工具抓包功能,采用DPDK 的eBPF 虛擬機執(zhí)行引擎,不需要重新編譯軟件,通過人機界面方式,實現(xiàn)靈活地抓取滿足過濾條件的報文,且以pcap 格式文件保存至指定目錄下。即詳細的設計思路是:在協(xié)議棧的收發(fā)報文處,加入Hook 回調函數(shù),根據用戶輸入的抓包過濾條件,例如:指定報文的任意字段(外層頭/內層頭/報文內容等),對BPF 程序進行了修改,從而就能實現(xiàn)對報文的不同操作,對于滿足抓包過濾條件的報文,則以pcap 格式文件進行保存。(圖3)
圖1 Linux 的BPF 總體框圖
圖2 DPDK 的eBPF 總體框圖
表1 cBPF 指令轉換至eBPF 指令關系表
圖3 方案設計示意圖
根據實際需求,當前所面臨的問題是采用DPDK 的eBPF 模塊方式來實現(xiàn),DPDK 的eBPF 虛擬機執(zhí)行引擎只支持eBPF 規(guī)范的指令集,而不支持cBPF 規(guī)范的指令集,故需要實現(xiàn)了將cBPF 指令集轉換成兼容eBPF 規(guī)范的指令集;同時考慮到所實現(xiàn)的功能的易用性和簡捷性,不僅要采用符合用戶習慣的方式,即采用類似于tcpdump 的方式,而且要實現(xiàn)以pcap 文件格式保存至SFTP 目錄下的功能,更利于用戶的離線分析或調試等,本文提出了具體的解決方案。
為了將用戶輸入的報文過濾條件生成的cBPF 指令集轉換成eBPF 指令集,本文根據cBPF 和eBPF 的指令格式給出了每一條cBPF 指令轉換成eBPF 指令的關系表及進行相應的程序設計,其中每一條cBPF 指令可能對應一條或者多條eBPF 指令,主要的指令之間的轉換參見表1。
cBPF 指令格式的結構體:
struct sock_filter { /* Filter block */
__u16 code; /* Actual filter code */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};
eBPF 指令格式的結構體:
struct bpf_insn {
__u8 code; /* opcode */
__u8 dst_reg:4; /* dest register */
__u8 src_reg:4; /* source register */
__s16 off; /* signed offset */
__s32 imm; /* signed immediate constant */
};
根據前文的總體設計,本文提出的具體解決方案是:當用戶啟動抓包后,首先,用戶輸入報文過濾條件后生成cBPF 指令,再通過轉換程序變換為eBPF 指令。然后由檢驗器檢查eBPF 指令的正確性,若不正確,則返回錯誤;若正確,則創(chuàng)建一個新的BPF 執(zhí)行上下文,并將eBPF 指令加載其中。接著,將BPF 執(zhí)行上下文關聯(lián)到回調函數(shù),安裝回調函數(shù)到輸入參數(shù)給定的端口隊列回調入口。最后,當程序執(zhí)行到端口隊列的回調入口時,若回調函數(shù)存在,則執(zhí)行回調函數(shù),即執(zhí)行BPF 執(zhí)行上下文的eBPF 指令。從而,可實現(xiàn)靈活的抓包功能。當用戶停止抓包后,卸載回調函數(shù),銷毀BPF 執(zhí)行上下文及運行時結構,并且釋放關聯(lián)的內存。
根據實際需求,本文對eBPF 報文過濾器的架構、原理和當前所面臨的問題等進行了研究,實現(xiàn)了基于DPDK的eBPF 報文過濾器實現(xiàn)了靈活的抓包功能,更好地解決大流量復雜場景的精準報文過濾的問題。