最近,在布拉格Linux網(wǎng)絡(luò)會議Netdev 0x13上,我做了一個簡短的演講,題目是“Cloudflare上的Linux”。演講最后主要是關(guān)于BPF(柏克萊封包過濾器)的。似乎,不管問題是什么——BPF都是答案。
下面是這次演講的稍作調(diào)整的筆錄。
在Cloudflare,我們在服務(wù)器上運(yùn)行Linux。我們運(yùn)營兩類數(shù)據(jù)中心:大型“核心”數(shù)據(jù)中心,用以處理日志,分析攻擊,計(jì)算分析;以及“邊緣”服務(wù)器機(jī)群,從全球180個(截至19年10月為190多個)位置交付客戶內(nèi)容。
在這次演講中,我們將重點(diǎn)討論“邊緣”服務(wù)器。在這里,我們使用最新的Linux特性,優(yōu)化性能,并深切關(guān)注DoS(拒絕服務(wù)攻擊)的彈性。
由于我們的網(wǎng)絡(luò)配置,我們的邊緣服務(wù)很特別——我們廣泛使用anycast(任播)路由。任播意味著所有數(shù)據(jù)中心都公布相同的IP地址集。
這種設(shè)計(jì)有很大的優(yōu)勢。首先,它保證了最終用戶的最佳速度。無論身處何處,您都可以到達(dá)最近的數(shù)據(jù)中心。并且,任播幫助我們分散DoS流量。在遭受攻擊過程中,每個位置接收的流量僅占總流量的一小部分,因此我們可以更容易地提取和過濾掉不需要的流量。
任播使我們能夠在所有邊緣數(shù)據(jù)中心保持統(tǒng)一的網(wǎng)絡(luò)設(shè)置。我們在數(shù)據(jù)中心內(nèi)部應(yīng)用了相同的設(shè)計(jì)——我們的軟件堆棧在邊緣服務(wù)器上是統(tǒng)一的。每一個服務(wù)器上都運(yùn)行著所有的軟件。
原則上,每臺機(jī)器都能處理所有的任務(wù)——我們運(yùn)行著許多不同的、要求很高的任務(wù)。我們有完整的HTTP棧、神奇的Cloudflare Workers、兩組DNS服務(wù)器——權(quán)威DNS和解析器,以及許多其他的面向公眾的應(yīng)用程序,如Spectrum和Warp。
盡管每臺服務(wù)器都運(yùn)行著所有的軟件,但請求通常也會在堆棧的旅程中跨越許多機(jī)器。例如,在處理HTTP請求的5個階段中,每個階段都有不同的機(jī)器進(jìn)行處理。
讓我向您介紹入站數(shù)據(jù)包處理的早期階段:
(1)首先,數(shù)據(jù)包到達(dá)我們的路由器。路由器執(zhí)行ECMP(等價多路徑路由),并將數(shù)據(jù)包轉(zhuǎn)發(fā)到我們的Linux服務(wù)器上。我們使用ECMP將每個目標(biāo)IP分布到至少16臺機(jī)器上。這是一種基本的負(fù)載均衡技術(shù)。
(2)在服務(wù)器上,我們使用XDP eBPF接收數(shù)據(jù)包。在XDP中,我們執(zhí)行兩個階段。首先,我們運(yùn)行大容量的DoS緩解措施,丟棄屬于非常大的第3層攻擊的數(shù)據(jù)包。
(3)然后,同樣在XDP中,我們執(zhí)行第4層負(fù)載均衡。所有的非攻擊包都在計(jì)算機(jī)之間重定向。這是用來解決ECMP問題的,為我們提供了精細(xì)的負(fù)載均衡,并允許我們自然而正常地關(guān)閉服務(wù)器的服務(wù)。
(4)重定向之后,數(shù)據(jù)包到達(dá)指定的機(jī)器。此時,它們被普通的Linux網(wǎng)絡(luò)堆棧接收,通過常規(guī)的iptables防火墻,并被分派到合適的網(wǎng)絡(luò)套接字。
(5)最后,數(shù)據(jù)包被應(yīng)用程序接收。例如,HTTP連接由“協(xié)議”服務(wù)器處理,該服務(wù)器負(fù)責(zé)執(zhí)行TLS加密和處理HTTP、HTTP/2和QUIC協(xié)議。
在請求處理的早期階段,我們使用了最酷的Linux新功能。我們可以將有用的現(xiàn)代功能分為三類:
DoS處理
負(fù)載均衡
套接字分配
讓我們更詳細(xì)地討論DoS處理。如前所述,ECMP路由之后的第一步是Linux的XDP堆棧,其中,我們運(yùn)行DoS緩解措施。
從歷史上看,我們對容量攻擊的緩解是用經(jīng)典的BPF和iptables樣式的語法表示的。最近,我們對它們進(jìn)行了調(diào)整,從而在XDP eBPF環(huán)境中執(zhí)行,事實(shí)證明這非常困難。一起來看看我們冒險(xiǎn)的經(jīng)歷吧:
L4Drop: XDP DDoS緩解
xdpcap: XDP數(shù)據(jù)包捕獲
Arthur Fabre的演講:基于XDP的DoS緩解
實(shí)踐中的XDP:將XDP集成到我們的DDoS緩解通道中 (PDF)
在這個項(xiàng)目中,我們遇到了許多eBPF/XDP限制。其中之一是缺乏并發(fā)原語。實(shí)現(xiàn)無競爭令牌桶之類的東西非常困難。后來,我們發(fā)現(xiàn)Facebook工程師Julia Kartseva遇到了同樣的問題。在2月份,這個問題已經(jīng)通過引入bpf_spin_lock helper得到了解決。
雖然我們現(xiàn)代的容量DoS防御是在XDP層完成的,但我們?nèi)匀灰蕾噄ptables來減輕應(yīng)用層(第7層)的影響。在這里,更高級別防火墻的特性起著很大的作用:connlimit、hashlimit和ipset。我們還使用xt_bpf iptables模塊在iptables中運(yùn)行cBPF,從而匹配數(shù)據(jù)包有效負(fù)載。我們以前討論過這個問題:
抵御不可抗力的經(jīng)驗(yàn)教訓(xùn) (PPT)
介紹BPF工具
在XDP和iptables之后,我們有了最后一個內(nèi)核端DoS防御層。
考慮UDP緩解失敗的情況。在這種情況下,可能會有大量的數(shù)據(jù)包到達(dá)應(yīng)用程序UDP套接字。這可能會使套接字溢出,導(dǎo)致數(shù)據(jù)包丟失。這是有問題的——好的數(shù)據(jù)包和壞的數(shù)據(jù)包都會被隨意丟棄。對于DNS這樣的應(yīng)用程序來說,這一后果是災(zāi)難性的。在過去,為了減少危害,我們?yōu)槊總€IP地址運(yùn)行了一個UDP套接字。無法緩解的洪水攻擊是很糟糕的,但至少它沒有影響到其他服務(wù)器IP地址的流量。
如今,這種架構(gòu)已不再適用。我們正在運(yùn)行30,000多個DNS IP,并且運(yùn)行同樣數(shù)量的UDP套接字是不可行的。我們現(xiàn)代的解決方案是運(yùn)行一個帶有復(fù)雜eBPF套接字過濾器的UDP套接字——使用SO_ATTACH_BPFsocket選項(xiàng)。我們在以前的博客文章中討論過在網(wǎng)絡(luò)套接字上運(yùn)行eBPF:
eBPF,套接字,跳段距離,手動編寫eBPF程序集
SOCKMAP——未來的TCP粘合
上面提到的eBPF速率限制了數(shù)據(jù)包。它將狀態(tài)(數(shù)據(jù)包計(jì)數(shù))保存在eBPF映射中。我們可以確保一個被“淹沒”的IP不會影響其他流量。這種做法效果很好,盡管在進(jìn)行此項(xiàng)目期間,我們在eBPF驗(yàn)證程序中發(fā)現(xiàn)了一個令人擔(dān)憂的錯誤:
eBPF無法計(jì)數(shù)?!
我猜在UDP套接字上運(yùn)行eBPF并不如往常般簡單。
除了DoS,在XDP中我們還運(yùn)行了第4層負(fù)載均衡器層。這是一個新項(xiàng)目,我們還沒作過多的談?wù)?。無需深入探討:在某些情況下,我們需要從XDP執(zhí)行套接字查找。
這個問題相對簡單——我們的代碼需要查找從數(shù)據(jù)包中提取的5元組的“套接字”內(nèi)核結(jié)構(gòu)。這通常很簡單——可以借助bpf_sk_lookup helper。不出所料的是,這里出現(xiàn)了一些復(fù)雜情況。其中有個問題是,當(dāng)啟用SYN cookie時,無法驗(yàn)證接收到的ACK包是否是三方握手的有效部分。我的同事Lorenz Bauer正在為這個特殊情況提供額外支持。
經(jīng)過DoS和負(fù)載均衡層之后,數(shù)據(jù)包將被傳遞到通常的Linux TCP / UDP堆棧上。在這里,我們進(jìn)行套接字分配——例如,將進(jìn)入端口53的數(shù)據(jù)包傳遞到屬于我們的DNS服務(wù)器的套接字上。
我們盡力使用原始Linux功能,但是當(dāng)您在服務(wù)器上使用數(shù)千個IP地址時,事情會變得復(fù)雜。
使用“AnyIP”技巧使Linux能夠正確地路由數(shù)據(jù)包是相對比較容易的。但確保數(shù)據(jù)包被分發(fā)到正確的應(yīng)用程序則是另一回事。不幸的是,標(biāo)準(zhǔn)的Linux套接字分派邏輯不夠靈活,不能滿足我們的需要。對于TCP/80這樣的流行端口,我們希望在多個應(yīng)用程序之間共享端口,每個應(yīng)用程序在不同的IP范圍內(nèi)處理它。Linux不支持此功能。您可以在特定的IP或所有(使用0.0.0.0)的IP地址上調(diào)用bind()。
為了解決這個問題,我們開發(fā)了一個自定義內(nèi)核補(bǔ)丁,其中添加了一個SO_BINDTOPREFIX
socket選項(xiàng)。顧名思義——它允許我們在選定的IP前綴上調(diào)用bind()。這解決了多個應(yīng)用程序共享流行端口(如53或80)的問題。
然后我們遇到另一個問題。對于我們的Spectrum產(chǎn)品,我們需要監(jiān)聽所有總共65535個端口。運(yùn)行這么多監(jiān)聽套接字不是一個好主意(請參閱我們過去的戰(zhàn)爭故事博客),因此我們不得不尋找另一種方法。經(jīng)過一些實(shí)驗(yàn),我們學(xué)會了使用一個鮮有人知的iptables模塊——TPROXY——來達(dá)到這個目的。點(diǎn)擊這里進(jìn)行閱讀:
濫用Linux防火墻:允許我們構(gòu)建Spectrum的黑客行為
這個設(shè)置起到了作用,但我們不喜歡額外的防火墻規(guī)則。我們正在努力正確地解決這個問題——實(shí)際上我們擴(kuò)展了套接字調(diào)度邏輯。您猜對了——我們希望利用eBPF擴(kuò)展套接字分派邏輯。敬請期待我們的一些補(bǔ)丁。
然后還有一種使用eBPF改進(jìn)應(yīng)用程序的方法。最近,我們對使用SOCKMAP進(jìn)行TCP粘合很感興趣:
SOCKMAP——未來的TCP粘合
這項(xiàng)技術(shù)在改善我們許多軟件堆棧的尾部延遲方面具有巨大潛力。當(dāng)前的SOCKMAP實(shí)施尚未準(zhǔn)備好完全發(fā)揮其作用,但它的潛力是巨大的。
同樣,新的TCP-BPF又名BPF_SOCK_OPS掛載為檢查TCP流的性能參數(shù)提供了一種很好的方法。此功能對我們的性能表現(xiàn)團(tuán)隊(duì)非常有用。
一些Linux特性沒有很好地發(fā)展成熟,我們需要解決它們。例如,我們正在突破網(wǎng)絡(luò)指標(biāo)的限制。不要誤解我的意思——網(wǎng)絡(luò)指標(biāo)非常棒,但遺憾的是它們不夠精細(xì)。像TcpExtListenDrops和TcpExtListenOverflows之類的事物被稱為全局計(jì)數(shù)器,而我們需要在每個應(yīng)用程序的基礎(chǔ)上了解它。
我們的解決方案是使用eBPF探針直接從內(nèi)核中抽取數(shù)字。我的同事Ivan Babrou編寫了一個名為“ebpf_exporter”的Prometheus指標(biāo)導(dǎo)出器,以便進(jìn)行此操作。了解更多請繼續(xù)閱讀:
ebpf_exporter簡介
https://github.com/cloudflare/ebpf_exporter
使用“ebpf_exporter”,我們可以生成所有形式的詳細(xì)指標(biāo)。它非常強(qiáng)大,并在許多情況下極大地幫助了我們。
在本次演講中,我們討論了在邊緣服務(wù)器上運(yùn)行的6層BPF:
正在XDP eBPF上運(yùn)行的批量DoS緩解措施
用于應(yīng)用層攻擊的Iptables xt_bpf cBPF
用于UDP套接字速率限制的SO_ATTACH_BPF
在XDP上運(yùn)行的負(fù)載均衡器
用于運(yùn)行應(yīng)用幫助進(jìn)程的eBPF,例如用于TCP套接字粘合的SOCKMAP和用于TCP測量的TCP-BPF
用于獲取精細(xì)指標(biāo)的“ebpf_exporter”
我們才剛剛開始!很快,我們將在基于eBPF的套接字調(diào)度、在Linux TC(流量控制)層上運(yùn)行的eBPF以及與控制組eBPF掛載的更多集成上完成更多的工作。然后,我們的SRE團(tuán)隊(duì)將維護(hù)不斷增長的BCC腳本列表,這些腳本對于調(diào)試非常有用。
感覺就像Linux停止了開發(fā)新的API一樣,所有的新特性都要通過eBPF掛載和幫助進(jìn)程來實(shí)現(xiàn)。這很好,并且具有強(qiáng)大的優(yōu)勢。升級eBPF程序比重新編譯內(nèi)核模塊更容易、更安全。如果沒有eBPF,那么像TCP-BPF之類展現(xiàn)大量性能跟蹤數(shù)據(jù)的東西可能是無法實(shí)現(xiàn)的。
有的人說:“軟件正在占據(jù)世界”,但我想說:“BPF正在占據(jù)軟件”。