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