目錄構建riscv64-unknown-linux-musl編譯工具鏈直接下載官方工具鏈嘗試自己編譯T-head Gcc下載編譯binutils編譯交叉gcc編譯musl手動合成fip.bin和boot.sd編譯u-boot生成cvi_board_memmap.h,cvipart.h和imgs.h繼 ...
1 eBPF簡介
eBPF(extended Berkeley Packet Filter)是一種革命性的內核技術,它允許開發人員編寫可動態載入到內核中的自定義代碼,從而改變內核的運行方式。(如果你對內核還不太瞭解,不用擔心,本章很快就會講到)。
這使得新一代高性能網路、可觀察性和安全工具成為可能。而且,正如你將看到的,如果你想用這些基於eBPF的工具來檢測應用程式,你不需要以任何方式修改或重新配置應用程式,這要歸功於eBPF在內核中的有利位置。
使用 eBPF 可以做的事情包括
- 對系統的幾乎所有方面進行性能跟蹤
- 具有內置可見性的高性能網路
- 檢測和(可選)預防惡意活動
1.1 eBPF的起源:伯克利數據包過濾器
我們今天所說的"eBPF"起源於BSD包過濾器,1993年由勞倫斯伯克利國家實驗室的Steven McCanne和Van Jacobson撰寫的一篇論文首次對其進行了描述。這篇論文討論了一種可以運行過濾器的偽機器,過濾器是為決定接受或拒絕網路數據包而編寫的程式。這些程式是用BPF指令集編寫的,這是一套32位指令的通用指令集,與彙編語言非常相似。下麵是直接摘自該論文的一個示例:
ldh [12]
jeq #ETHERTYPE IP, L1, L2
L1: ret #TRUE
L2: ret #0
這一小段代碼過濾掉了不是互聯網協議(IP)數據包的數據包。該過濾器的輸入是一個乙太網數據包,第一條指令 (ldh) 從數據包的第12個位元組開始載入一個2位元組的值。在下一條指令(jeq)中,該值與代表IP數據包的值進行比較。如果匹配,執行將跳轉到標有L1的指令,並通過返回非零值(此處標識為 #TRUE)來接受數據包。如果不匹配,則該數據包不是IP數據包,會被拒絕,返回0。
你可以根據數據包的其他方面做出更複雜的決定,你也可以在論文中找到更複雜的過濾程式的例子。重要的是,過濾器的作者可以編寫自己的定製程式在內核中執行,這就是eBPF的核心功能。
BPF是"伯克利數據包過濾器"(Berkeley Packet Filter)的縮寫,於1997年首次引入Linux,內核版本為2.1.75,當時它被用在tcpdump工具中,作為捕獲要追蹤的數據包的有效方法。
2012年,內核3.5版本引入了seccomp-bpf。這使得BPF程式可以決定是否允許或拒絕用戶空間應用程式進行系統調用。我們將在第10章對此進行更詳細的探討。這是BPF從狹義的數據包過濾發展到今天的通用平臺的第一步。從這時起,BPF名稱中的 "包過濾 "一詞就不再那麼有意義了!
1.2 從BPF到eBPF
從2014年內核3.18版開始,BPF演進為我們所說的"擴展BPF"。這涉及幾個重大變化:
- 為了在64位機器上更高效,BPF指令集進行了全面修改,解釋器也完全重寫。
- 引入了eBPF映射,這是一種數據結構,BPF程式和用戶空間應用程式都可以訪問,從而可以在它們之間共用信息。你將在第2章瞭解映射。
- 增加了bpf()系統調用,這樣用戶空間程式就可以與內核中的eBPF程式交互。
- 添加了幾個 BPF 輔助函數。
- 添加了eBPF校驗器,以確保eBPF程式可以安全運行
這為eBPF奠定了基礎,但開發工作並未因此放緩!從那時起,eBPF有了長足的發展。
1.3 eBPF向生產系統的演進
自2005年以來,Linux內核中就存在一種名為kprobes(內核探測)的功能,允許在內核代碼中的幾乎所有指令上設置陷阱。開發人員可以編寫內核模塊,將函數附加到kprobes上,用於調試或性能測量。
2015年,kprobes增加了附加eBPF程式的功能,這是Linux系統追蹤方式革命的起點。與此同時,內核網路堆棧中開始添加鉤子,允許eBPF程式處理網路功能的更多方面。
到2016年,基於eBPF的工具已被用於生產系統。布蘭登-格雷格(Brendan Gregg)在Netflix的跟蹤工作在基礎設施和運營圈廣為人知,他關於eBPF"為Linux帶來超級能力"的說法也廣為人知。同年Cilium項目發佈,這是首個在容器環境中使用eBPF替換整個數據路徑的網路項目。
次年Facebook(現為 Meta將Katran列為開源項目。Katran作為第4層負載平衡器,滿足了Facebook對高度可擴展和快速解決方案的需求。自2017年以來,Facebook.com的每一個數據包都經過eBPF/XDP.4。
2018年,eBPF成為Linux內核中的一個獨立子系統,其維護者是來自Isovalent的Daniel Borkmann和來自Meta的Alexei Starovoitov(後來,同樣來自Meta的Andrii Nakryiko也加入了他們的行列)。同年BPF類型格式(BTF BPF Type Format)問世,它使eBPF程式更具可移植性。
2020年,LSM BPF正式推出,eBPF程式可以連接到Linux 安全模塊(LSM Linux Security Module)內核介面。這表明 eBPF的第三個主要用例已經確定:很明顯,除了網路和可觀察性之外,eBPF還是安全工具的絕佳平臺。
多年來,由於300多名內核開發人員以及相關用戶空間工具(如 bpftool,我們將在第 3 章中介紹)、編譯器和編程語言庫的眾多貢獻者的努力,eBPF的能力得到了大幅提升。程式曾被限制在4,096條指令以內,但現在這一限制已增加到100萬條驗證指令,而且由於支持尾調用和函數調用,這一限制實際上已變得無關緊要。
阿列克謝-斯塔羅沃伊托夫(Alexei Starovoitov)從軟體定義網路(SDN)的根源出發,對BPF的歷史進行了精彩的介紹。在這次演講中,他討論了讓內核接受早期eBPF補丁所使用的策略,並透露eBPF的正式生日是2014年9月26日,這一天標志著第一套涵蓋驗證器、BPF系統調用和地圖的補丁被接受。
Daniel Borkmann還討論了BPF的歷史及其支持聯網和跟蹤功能的演變。我強烈推薦他的演講"eBPF和Kubernetes: 擴展微服務的小助手",其中充滿了有趣的信息。
1.4 命名很難
eBPF的應用範圍遠遠超出了數據包過濾的範疇,以至於這個縮寫現在基本上毫無意義,它已經成為一個獨立的術語。由於目前廣泛使用的Linux內核都支持"擴展"部分,eBPF和BPF這兩個術語在很大程度上可以互換使用。在內核源代碼和eBPF編程中,常用術語是BPF。例如,我們將在第4章中看到,與eBPF交互的系統調用是bpf(),輔助函數以bpf_開頭,不同類型的(e)BPF 程式以BPF_PROG_TYPE 開頭。在內核社區之外,"eBPF"這個名字似乎已經深入人心,例如社區網站ebpf.io和eBPF基金會的名稱。
1.5 Linux內核
要理解eBPF,你需要牢固掌握Linux內核和用戶空間之間的區別。我在"什麼是 eBPF?"報告中對此進行了闡述,併在接下來的幾段中對部分內容進行了改編。
Linux內核是應用程式與運行硬體之間的軟體層。應用程式運行在稱為用戶空間的非特權層中,不能直接訪問硬體。相反,應用程式會使用系統調用(syscall)介面發出請求,要求內核代表它採取行動。硬體訪問可能涉及讀寫文件、發送或接收網路流量,甚至只是訪問記憶體。內核還負責協調併發進程,使許多應用程式能同時運行。如圖所示。
作為應用程式開發人員,我們通常不會直接使用系統調用介面,因為編程語言為我們提供了更容易編程的高級抽象和標準庫介面。因此,很多人都不知道內核在我們程式運行時做了多少工作。如果你想瞭解內核被調用的頻率,可以使用strace工具來顯示應用程式進行的所有系統調用。
$ strace -c echo "hello"
hello
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
0.00 0.000000 0 1 read
0.00 0.000000 0 1 write
0.00 0.000000 0 5 close
0.00 0.000000 0 9 mmap
0.00 0.000000 0 3 mprotect
0.00 0.000000 0 1 munmap
0.00 0.000000 0 3 brk
0.00 0.000000 0 4 pread64
0.00 0.000000 0 1 1 access
0.00 0.000000 0 1 execve
0.00 0.000000 0 2 1 arch_prctl
0.00 0.000000 0 1 set_tid_address
0.00 0.000000 0 3 openat
0.00 0.000000 0 4 newfstatat
0.00 0.000000 0 1 set_robust_list
0.00 0.000000 0 1 prlimit64
0.00 0.000000 0 1 getrandom
0.00 0.000000 0 1 rseq
------ ----------- ----------- --------- --------- ----------------
100.00 0.000000 0 43 2 total
由於應用程式對內核的依賴程度如此之高,這意味著如果我們能觀察到應用程式與內核的交互,就能瞭解到應用程式的行為方式。有了eBPF,我們就可以在內核中添加儀器,從而獲得這些信息。
例如如果能攔截打開文件的系統調用,就能準確瞭解應用程式訪問了哪些文件。但如何進行攔截呢?讓我們考慮一下,如果我們想修改內核,添加新代碼,以便在調用系統調用時創建某種輸出,會涉及到哪些問題。
1.6 為內核添加新功能
對任何代碼庫進行修改都需要對現有代碼有一定程度的熟悉,所以除非你已經是內核開發人員,否則這很可能是一個挑戰。
此外,如果你想把你的改動貢獻給上游,你將面臨一個不純粹是技術上的挑戰。Linux是一種通用操作系統,可用於各種環境和情況。這意味著,如果你想讓你的改動成為Linux正式版本的一部分,就不僅僅是寫出能正常運行的代碼那麼簡單了。這些代碼必須得到社區(更具體地說是Linux的創建者和主要開發者Linus Torvalds)的認可,被認為是對所有人都有利的改變。這並不是必然的,在提交的內核補丁中,只有三分之一被接受。
假設你已經找到了攔截打開文件的系統調用的好技術方法。經過幾個月的討論和艱苦的開發工作,我們假設內核接受了這一修改。好極了!但要多久才能在每個人的機器上實現呢?
Linux內核每兩三個月就會發佈一個新版本,但即使某項改動已被納入其中一個版本,它距離在大多數人的生產環境中使用仍有一段時間。這是因為我們大多數人並不直接使用Linux內核,而是使用Debian、Red Hat、Alpine和Ubuntu等Linux發行版,這些發行版將Linux內核與其他各種組件打包在一起。你很可能會發現,你最喜歡的發行版使用的內核已經是幾年前的版本了。
參考資料
- 軟體測試精品書籍文檔下載持續更新 https://github.com/china-testing/python-testing-examples 請點贊,謝謝!
- 本文涉及的python測試開發庫 謝謝點贊! https://github.com/china-testing/python_cn_resouce
- python精品書籍下載 https://github.com/china-testing/python_cn_resouce/blob/main/python_good_books.md
- Linux精品書籍下載 https://www.cnblogs.com/testing-/p/17438558.html
1.7 內核模塊
如果你不想等上好幾年才能把你的改動加入內核,還有另一種選擇。Linux內核是為接受內核模塊而設計的,內核模塊可以按需載入和卸載。如果你想改變或擴展內核行為,編寫模塊無疑是一種方法。內核模塊可以獨立於Linux內核的正式版本發佈,供其他人使用,因此不必被上游的主要代碼庫所接受。
這裡最大的挑戰在於,這仍然是完全的內核編程。用戶在使用內核模塊時歷來非常謹慎,原因很簡單:如果內核代碼崩潰,就會導致機器及其上運行的所有程式癱瘓。用戶如何確信內核模塊可以安全運行?
安全運行"並不僅僅意味著不會崩潰--用戶想知道內核模塊從安全形度來看是否安全。它是否包含攻擊者可能利用的漏洞?我們是否相信模塊作者不會在模塊中加入惡意代碼?由於內核是特權代碼,它可以訪問機器上的一切,包括所有數據,因此內核中的惡意代碼會引起嚴重關切。這一點也適用於內核模塊。
內核的安全性是Linux發行版需要很長時間才能發佈新版本的一個重要原因。如果其他人已經在各種環境下運行了數月或數年的內核版本,那麼問題應該已經被清除了。發行版的維護者就可以確信,他們提供給用戶/客戶的內核是經過加固的,也就是說,運行起來是安全的。
eBPF提供了一種非常不同的安全方法:eBPF校驗器,它可以確保只有在運行安全的情況下才載入eBPF程式--它不會讓機器崩潰或鎖定在硬迴圈中,也不會讓數據泄露。
1.7 動態載入eBPF程式
eBPF程式可以動態載入到內核或從內核中移除。一旦程式被附加到事件中,無論事件發生的原因是什麼,它們都會被該事件觸發。例如,如果將一個程式附加到打開文件的系統調用上,那麼只要有進程試圖打開文件,該程式就會被觸發。至於程式載入時該進程是否已在運行,則無關緊要。與升級內核,然後必須重啟機器才能使用新功能相比,這是一個巨大的優勢。
這也是使用eBPF的可觀察性或安全工具的一大優勢--它可以立即查看機器上發生的一切。在運行容器的環境中,這包括對容器內以及主機上運行的所有進程的可見性。本章稍後我將深入探討這對雲原生部署的影響。
此外,如圖所示,人們可以通過eBPF快速創建新的內核功能,而不需要其他所有Linux用戶都接受相同的更改。
1.8 eBPF程式的高性能
eBPF程式是一種非常高效的添加方式。一旦載入並經過JIT編譯,程式就能以本地機器指令的形式在CPU上運行。此外,處理每個事件都無需在內核和用戶空間之間轉換(這是一項昂貴的操作)。
2018年的論文介紹了eXpress Data Path(XDP),其中包括eBPF在網路中實現性能改進的一些示例。例如,與普通Linux 內核實現相比,在XDP中實施路由"可將性能提高2.5倍",而在負載平衡方面,"XDP比IPVS的性能提高了4.3倍"。
在性能跟蹤和安全可觀察性方面,eBPF的另一個優勢是可以在內核中過濾相關事件,然後再將其發送到用戶空間。畢竟,只過濾某些網路數據包才是最初BPF實現的重點。如今eBPF程式可以收集系統中各種事件的信息,並可以使用複雜、定製的程式過濾器,只向用戶空間發送相關的信息子集。
1.9 雲原生環境中的eBPF
如今許多企業都不選擇直接在伺服器上執行程式來運行應用程式。取而代之的是,許多組織使用雲原生方法:容器、Kubernetes或ECS等編排器,或Lambda、雲函數、Fargate等無伺服器方法。這些方法都使用自動化來選擇每個工作負載運行的伺服器;在無伺服器中,我們甚至不知道每個工作負載運行的伺服器是什麼。
儘管如此,還是有伺服器參與其中,而且每台伺服器(無論是虛擬機還是裸機)都運行著內核。在容器中運行應用時,如果它們運行在同一臺(虛擬)機器上,就會共用同一個內核。在Kubernetes環境中,這意味著某個節點上所有pod中的所有容器都在使用同一個內核。當我們在內核中加入eBPF程式時,該節點上的所有容器化工作負載對這些eBPF程式都是可見的,如圖所示。
節點上所有進程的可見性,再加上動態載入eBPF程式的能力,讓我們在雲原生計算中真正擁有了基於eBPF工具的超強能力:
- 我們不需要改變應用程式,甚至不需要改變它們的配置方式,就能使用eBPF工具對它們進行檢測。
- 只要將其載入到內核並附加到事件中,eBPF 程式就可以開始觀察已有的應用程式進程。
這與側卡(sidecar)模式形成了鮮明對比,後者被用於在Kubernetes應用程式中添加日誌、跟蹤、安全和服務網格功能等功能。在"側卡"方法中,儀器以容器的形式運行,並被"註入"到每個應用程式pod中。這一過程包括修改定義應用程式pod的 YAML,並添加側卡容器的定義。這種方法當然比在應用程式pod更方便。
sidecar方法也有一些缺點:
- 必須重新啟動應用程式 pod 才能添加側載。
- 必須修改應用程式 YAML。這通常是一個自動化流程,但如果出了差錯,側卡就不會被添加,這意味著pod不會被檢測到。例如,一個部署可能會被註釋為表明接納控制器應將邊卡YAML添加到該部署的pod規範中。但是,如果部署沒有正確標註,側卡就不會被添加,因此儀器也就無法看到它。
- 當pod中有多個容器時,它們可能會在不同時間達到就緒狀態,其順序可能無法預測。Pod的啟動時間可能會因註入側車而大大減慢,更有甚者,可能會導致競賽條件或其他不穩定因素。例如,Open Service Mesh文檔描述了應用容器在Envoy代理容器準備就緒之前,如何避免所有流量被丟棄。當服務網格等網路功能作為側載實現時,必然意味著進出應用容器的所有流量都必須通過內核中的網路堆棧才能到達網路代理容器,從而增加了流量的延遲;如圖所示。
所有這些問題都是旁路模型的固有問題。幸運的是,現在有了eBPF作為平臺,我們就有了可以避免這些問題的新模式。此外,由於基於eBPF的工具可以看到(虛擬)機器上發生的一切,因此壞人更難躲避。例如,如果攻擊者設法在您的一臺主機上部署了加密貨幣挖礦應用程式,他們可能不會使用您在應用程式工作負載上使用的側卡對其進行檢測。如果你依賴基於邊卡的安全工具來防止應用程式進行意外的網路連接,那麼如果沒有註入邊卡,該工具是不會發現挖礦應用程式連接到其礦池的。相比之下,eBPF實現的網路安全功能可以監控主機上的所有流量,因此可以輕鬆阻止這種加密貨幣挖礦操作。出於安全原因丟棄網路數據包的功能。
1.10 小結
希望本章能讓你瞭解eBPF作為平臺如此強大的原因。它允許我們改變內核的行為,為我們提供了構建定製工具或自定義策略的靈活性。基於eBPF的工具可以觀察內核中的任何事件,從而觀察(虛擬)機器上運行的所有應用程式,無論它們是否被容器化。
到目前為止,我們已經在相對概念化的層面上討論了eBPF。在下一章中,我們將更具體地探討基於eBPF的應用程式的各個組成部分。
9 值得慶幸的是,對現有功能的安全補丁提供得更快。
10 Høiland-Jørgensen T、Brouer JD、Borkmann D 等:《eXpress 數據路徑:操作系統內核中的快速可編程數據包處理》。第 14 屆新興網路實驗與技術國際會議(CoNEXT '18)論文集》。電腦械協會;2018:54-66.
釘釘或微信號: pythontesting 微信公眾號:pythontesting