eBPF 技術報告(上)

本文主要內容來自於 《What is eBPF?》開源電子書,英文版本可參見 https://isovalent.com/ebpf

1. 介紹

僅僅數年,eBPF 已一躍成爲了現代基礎設施領域中最熱門的技術之一。CNCF 技術監督委員會曾將 eBPF 預測爲 2021 年將起飛的技術之一,超過 2500 人報名參加了 2021 年的 eBPF 峯會虛擬會議,與此同時世界上巨頭公司共同創建了 eBPF 基金會 [1]。顯而易見,人們對 eBPF 技術抱有很大的興趣。

在該篇的報告中,作者詳細介紹了爲何 eBPF 技術讓人感到振奮,以及該技術爲現在計算機環境帶來了怎樣的超能力,此外還將介紹了構建 eBPF 工具時所涉及的內容。由於是技術報告,不少內容都是簡單介紹,沒有涉及到太多的細節,但作者也提供了一些更加深入學習的參考信息。

1.1 eBPF

eBPF 爲 Extended Berkeley Packet Filter 的縮寫。從字面意思表示網絡報文過濾,但如今,eBPF 已被當做技術統稱,所代表的已經遠超字面代表的含義。

當前,eBPF 是一個框架,允許用戶在操作系統的內核中加載和運行自定義程序,可用於擴展甚至修改內核行爲。

當 eBPF 程序被加載到內核中時,由驗證器(verifier)確保其運行是安全的,否則,驗證器就會拒絕加載。驗證通過後,一旦 eBPF 程序被加載至內核,並附着到事件之上,當附着的事件發生時,對應的 eBPF 程序就被觸發運行。

eBPF 最初是爲 Linux 開發的,報告中將重點關注在 Linux 操作系統;但微軟正在開發 Windows 的 eBPF 實現 [2]。

1.2 基於 eBPF 工具集

能夠動態地改變內核行爲的能力帶來了更加強大的能力。eBPF 則允許我們在對應用透明的前提下,在內核中收集應用程序特定的運行信息。我們還可在可觀察性基礎上創建 eBPF 安全工具,檢測和防止內核中惡意活動。基於 eBPF,我們還可以創建強大的、高性能的網絡功能,在內核內處理網絡數據包,從而避免將數據包轉換到用戶空間的昂貴成本。

從內核的角度觀察應用程序並非是全新的概念,Perf 也可在應用透明的方式,在內核中收集程序行爲和性能信息。Perf 等工具固定了蒐集數據種類以及格式。但使用 eBPF,我們可擁有更加靈活和定製化的工具。

eBPF 可編程性帶來了令人難以置信的強大,但它也很複雜。對我們大多數人來說,主要是使用 eBPF 工具。當前有越來越多的項目和供應商在 eBPF 平臺上創建了新一代的可用於觀測、安全性、網絡等工具,可供我們使用。

2. 修改內核是困難的旅程

eBPF 允許在 Linux 內核中運行用戶編寫代碼,eBPF 技術的出現改變了修改內核功能的遊戲規則。

2.1 Linux 內核

Linux 內核是應用程序和其所運行的硬件之間的軟件層。應用程序運行在被稱爲 "用戶空間" 的非特權層,不能直接訪問硬件。應用程序通過系統調用(syscall)請求內核代表其執行。

作爲應用程序的開發者,我們通常不會直接使用系統調用接口,這是因爲編程語言提供了更加易用的高層次抽象和_標準庫_。如果想了解內核調用的頻率,你可以使用 strace 工具來跟蹤程序所有的系統調用。

應用程序很大程度上依賴於內核,這意味着如果能夠觀察到應用程序與內核的交互流程,我們就可瞭解到更多程序的運行的行爲。如想要捕獲到打開文件的系統調用,考慮一下如果通過修改內核,添加新的代碼並打印相關輸出,我們會需要做哪些工作?

2.2 通過內核添加新的函數

Linux 內核很複雜,在撰寫本文時,大約有 3000 萬代碼行 [3]。提交新的功能或修改代碼都需要對現有代碼熟悉,如你不是一個內核開發者,這將會是一個巨大的挑戰。

但提交內核功能並不只是純粹的技術問題。Linux 是一個通用的操作系統,需要在不同的環境和條件下運行。社區必須保證你修改的代碼是爲了所有人的都可獲益。這並不是提交都可通過的事情,大概只有三分之一的提交的內核補丁被接受 [4]。

即使經過幾個月的討論和艱苦的開發,提交代碼順利合併到內核中,但是通過發行版到最終使用用戶,有可能經過以年記的時間週期。可能會發現,你最喜歡的發行版使用的是幾年前的內核版本。例如 RHEL 8.5 版本,日期是 2021 年底發佈,附帶的內核版本爲 4.18 版本,發佈於 2018 年 8 月。

正如圖 2-1 中的漫畫所示,新功能從想法階段進入生產環境的 Linux 內核,簡直需要數年時間。

fig2-1.png

圖 2-1 向內核添加特性功能 (cartoon by Vadim Shchekoldin, Isovalent)

2.3 內核模塊

如果你不想等待數年的時間讓更改進入內核,那麼還可能的方式是通過內核模塊,可以按需加載和卸載。在我們爲打開文件檢測系統調用的示例中,你可以編寫一個內核模塊來執行此操作。

這裏最大的挑戰仍然是完整的內核編程。用戶接受內核仍然面臨內核崩潰和安全運行的諸多挑戰,比如程序 Bug 或者惡意代碼。

內核的安全性是 Linux 發行版需要如此長時間纔會整合新的內核版本的重要原因之一。如果其他人已經在各種情況下運行內核版本數月或數年,那麼大多數問題已經都會被解決。發行版維護者可以確信他們提供給用戶 / 客戶的內核是經過加固,並且可以安全運行。

eBPF 提供了一種非常不同的安全方法:eBPF 驗證器(verifier),其可確保僅在安全運行時才加載 eBPF 程序。

2.4 eBPF 驗證和安全

由於 eBPF 允許我們在內核中運行任意代碼, eBPF 驗證器(verifier)機制用於保障不會導致用戶機器崩潰並且損害數據。

驗證器分析 eBPF 程序以確保無論任何輸入,程序都將始終在有限指令數量內安全地終止。例如,如果一個程序解引用一個指針,驗證器會要求程序首先要檢查指針以確保其不爲空。

驗證器還會確保 eBPF 程序只能訪問可訪問的內存。例如,想象一個在網絡棧中觸發的 eBPF 程序,並傳遞了包含真正傳輸數據的內核 socket buffer。這個 eBPF 程序可以調用特定的輔助函數,如 bpf_skb_load_bytes() 來從套接字緩衝區讀取數據字節。另一個由系統調用觸發的 eBPF 程序,由於沒有可用的套接字緩衝區,將不允許使用 bpf_skb_load_bytes() 輔助函數。驗證器還需要確保程序只讀取該套接字緩衝區中的數據字節——不允許訪問任意內存。目的是確保 eBPF 程序從安全角度來看是安全的。

當然,仍然可能會存在惡意的 eBPF 程序,如出於非正當理由獲取數據。請注意僅從可驗證來源加載受信任的 eBPF 程序,並且僅將管理 eBPF 工具的權限授予信任的具有 root 權限用戶。

2.5 動態加載 eBPF 程序

eBPF 程序可以動態地加載和卸載。一旦 eBPF 程序附着到事件,無論是何種原因觸發事件,都會運行附着的 eBPF 程序。例如,如果你將程序附加到系統調用以打開文件,則只要任何進程嘗試打開文件,就會觸發該程序。加載程序時該進程是否已經在運行並不重要。

這使得基於 eBPF 提供可觀察性或安全工具的具有一大優勢——eBPF 加載後就可以立即查看機器上發生的一切。

此外,如圖 2-2 所示,人們還可以通過 eBPF 非常快速地創建新的內核功能,而無需每個其他 Linux 用戶都接受相同的更改。

fig2-2.png

圖 2-2 使用 eBPF 爲內核添加函數 (cartoon by Vadim Shchekoldin, Isovalent)

現在你已經看到了 eBPF 是如何允許對內核進行動態的、自定義的改變的,讓我們來研究一下如果你想寫一個 eBPF 程序會涉及到哪些內容。

參考資料

[1] eBPF 基金會: https://ebpf.io/foundation/

[2] Windows 的 eBPF 實現: https://github.com/microsoft/ebpf-for-windows

[3] 代碼行: https://www.phoronix.com/scan.php?page=news_item&px=Linux-5.12-rc1-Code-Size

[4] 接受: https://dl.acm.org/doi/pdf/10.5555/2487085.2487111

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/rQ8paKRhS9RTSk2zR1cAEg