手艺人舍bpftrace而取systemtap的代价和思考
上個(gè)禮拜我就想噴eBPF了,由于周末時(shí)間實(shí)在太緊,就準(zhǔn)備拖延一周,但還是立了個(gè)flag,先發(fā)了個(gè)朋友圈:
ebpf就像牛皮蘚一樣,已經(jīng)遍布在linux內(nèi)核的各個(gè)角落,每個(gè)調(diào)用點(diǎn)都看上去很隨意,毫無(wú)規(guī)劃,讓人覺得好像自己覺得哪里需要這么一個(gè)調(diào)用點(diǎn)并不很難…
但實(shí)際上如果你真的去嘗試在某處加一個(gè)ebpf調(diào)用點(diǎn)時(shí),就會(huì)覺得這件事和清除牛皮蘚的過程非常類似,修改散落在各個(gè)目錄的多個(gè)文件,還得重新編譯,大概率失敗,還要重新做一次,很難一次做干凈,當(dāng)你好不容易成功了,會(huì)有一種“不過如此”的嗟嘆…
我曾將ebpf比做擴(kuò)散的癌細(xì)胞,這個(gè)比喻沒有給人密集恐懼的效果,所以我換成了牛皮癬。該存在ebpf調(diào)用點(diǎn)的地方一個(gè)也沒有,沒必要ebpf的地方到處都是,這些點(diǎn)還在持續(xù)增加,迄至5.11內(nèi)核,ebpf已經(jīng)有大三十個(gè)點(diǎn)了,依然在毫無(wú)規(guī)劃地瘋長(zhǎng)著…
eBPF是個(gè)創(chuàng)新,但人們明顯狂熱過度了,ebpf增加調(diào)用點(diǎn)也過于隨意,太業(yè)務(wù)導(dǎo)向了,損壞了內(nèi)核的內(nèi)聚性,遠(yuǎn)遠(yuǎn)比不上當(dāng)初netfilter的五個(gè)hook點(diǎn)以及qdisc這種經(jīng)過良好設(shè)計(jì)的機(jī)制,另外還有一個(gè)問題,netfilter的五個(gè)hook點(diǎn)上如果部署了ebpf點(diǎn),其實(shí)就能解決大部分性能問題,然而直到現(xiàn)在都沒有,感覺是社區(qū)矯枉過正了,真的徹底把netfilter當(dāng)成了舊時(shí)代的象征,把馬殺掉的同時(shí),輪子也不要了
人們都像蟲子一樣在這里你爭(zhēng)我搶,吃的都是良心,拉的全是思想。
終于到了周末,我終于還是不能說(shuō)話,我甚至已經(jīng)不知道該說(shuō)些什么了。
昨天,我把自己血祭了,這種自我犧牲在古羅馬共和國(guó)是一種美德,執(zhí)政官會(huì)把自己獻(xiàn)給神,以換取戰(zhàn)爭(zhēng)的勝利。
為了噴eBPF,在平時(shí)的工作和學(xué)習(xí)中,我積累了很多素材,eBPF的領(lǐng)地分為兩個(gè)部分:
- 網(wǎng)絡(luò)協(xié)議棧功能
- trace跟蹤
在網(wǎng)絡(luò)方面,我用牛皮癬的比喻來(lái)說(shuō)明eBPF不斷瘋長(zhǎng)的畫面是多么糟糕。而在trace跟蹤方面,我想拿性能和功能說(shuō)事。
我本希望用這些素材來(lái)佐證自己的一些怪異的觀點(diǎn),昨天,我準(zhǔn)備用我的實(shí)際工作來(lái)作為我最后一個(gè)素材,結(jié)果它恰恰驗(yàn)證了我的認(rèn)知錯(cuò)誤,它恰恰說(shuō)明了eBPF作為trace跟蹤工具是多么的好用!
我的故事是這樣的。
在大流量的背景下,特別是如果你的代碼使用類似Bonding,tun/tap,GRE,IPIP等虛擬網(wǎng)卡,排查skb在哪里被drop一直都是一個(gè)很麻煩的事情。即便是你已經(jīng)知道了一個(gè)特定的五元組,這件事也不會(huì)因此變得簡(jiǎn)單。
抓包?抓包永遠(yuǎn)是第一步的操作,但也只是第一步,它只能告訴你skb收到了或者沒有收到,如果沒有收到,進(jìn)一步就需要確認(rèn)skb到底在哪里丟了。當(dāng)然了,如果最后實(shí)在是定位不了,一般會(huì)把鍋甩給運(yùn)營(yíng)商這個(gè)黑洞。
?
類似wireguard這種,其所有復(fù)雜的操作,包括加密,分發(fā)等邏輯在內(nèi)均在wireguard虛擬網(wǎng)卡的xmit函數(shù)中完成,跟蹤wg_xmit的細(xì)節(jié)除了要求你對(duì)wireguard的代碼非常熟悉之外,還需要各種奇技淫巧的手藝。
?
以下面這個(gè)場(chǎng)景為例:
我要實(shí)錘skb在從1到11的哪一步被drop的,要怎么做?
?
這不正是stap的地盤嗎?哦,我不能說(shuō)stap,在有了bpftrace可供選擇的時(shí)候,再說(shuō)stap有點(diǎn)不正確,如果我堅(jiān)持要用stap,會(huì)有一群人建議我用bpftrace,并說(shuō)stap是多么的糟糕,過時(shí),不友好。
那好,那就用bpftrace,下面的腳本可以對(duì)skb進(jìn)行完美的全程trace:
#!/usr/local/bin/bpftrace ? #include <linux/skbuff.h> #include <linux/udp.h> #include <net/sock.h> ? k:encrypt_packet {$skb = (struct sk_buff *)arg0;// 這個(gè)skb的mark需要iptables來(lái)為特定的五元組標(biāo)記上,但是encrypt_packet這里是可以使用mark的最后的地方。// 在encrypt_packet成功返回后,skb的幾乎所有附屬標(biāo)記都會(huì)被reset,包括skb->mark。// 因此這里必須用另一個(gè)標(biāo)記,以確保在encrypt_packet之后還能用此特征跟蹤到特定的skb。// 由于bpftrace只能讀不能寫,這里我選擇直接用skb的地址!if ($skb->mark == 1234) {printf("encrypt got %p\n", $skb);@addr = $skb;} } ? //k:send4 k:udp_tunnel_xmit_skb {$daddr = arg4;$saddr = arg3;$skb = (struct sk_buff *)arg2;// 這里除了match地址之外,是不是也要match一下其它字段呢?畢竟slub中的skb是可以重用的。// 如果mark 1234的skb在這個(gè)之前被drop & free了,它被重新alloc后依然會(huì)到這里,這就錯(cuò)了!// 然而由于流量可控,且我是一個(gè)函數(shù)一個(gè)函數(shù)trace,上述概率極低。手藝人不求完美!if ($skb == @addr) {printf("---- skb:%p daddr:%08x saddr:%08x \n", @addr, $daddr, $saddr);} } ? k:iptunnel_xmit //k:dev_queue_xmit //k:dev_hard_start_xmit //k:dev_queue_xmit_nit {$skb = (struct sk_buff *)arg2;// 從裸包中取外層協(xié)議頭的內(nèi)容。$udph = (struct udphdr *)($skb->head + $skb->transport_header);$sport = $udph->source;$dport = $udph->dest;if ($skb == @addr) {$port = (($sport & 0xff00) >>8) | (($sport & 0xff) << 8);$port2 = (($dport & 0xff00) >>8) | (($dport & 0xff) << 8);printf("sport:%d dport:%d\n", $port, $port2);// trace結(jié)束,重置全局變量。@addr = (struct sk_buff *)0;} }啊哈,我覺得這是一個(gè)讓人感覺很順暢的腳本,skb在進(jìn)入wg_xmit前打上mark,在wg_xmit的過程中清除skb的mark之前將其地址保存,此后跟蹤該地址的skb。然而悲哀的是,skb順利發(fā)送出去了,我一無(wú)所獲,然而悲哀的是,內(nèi)層的報(bào)文在對(duì)端wireguard的wg網(wǎng)卡上沒有抓到。
?
去對(duì)端反著來(lái)一遍且OK?思路是一回事,落地是另一回事。
?
怎么才能在對(duì)端繼續(xù)trace這個(gè)skb呢?
如果沒有辦法trace這個(gè)skb,你怎么區(qū)分這個(gè)報(bào)文是被中間網(wǎng)絡(luò)設(shè)備drop了還是被對(duì)端wireguard接收過程drop了呢?由于發(fā)送端已經(jīng)可以獲取內(nèi)層和外層的特定五元組,在接收端用外層五元組去match外層協(xié)議頭當(dāng)然是一個(gè)正確的思路,問題是如果外層隧道的五元組被大流量復(fù)用,你又將如何在skb解密前去匹配內(nèi)層五元組,流量實(shí)在太大了,就像很多抓包由于流量大無(wú)法進(jìn)行一樣,你想要的信息幾乎會(huì)被瞬間淹沒!
?
我想知道的是,bpftrace怎么來(lái)做這件事。如果不能方便快捷地解決這個(gè)問題,我就有充分的理由使用舊時(shí)代的舊事物了。
?
問題是bpftrace不允許我修改skb啊!現(xiàn)在,我決定扔掉bpftrace,用stap來(lái)做正確的事。
?
我需要做的僅僅是,為特定的數(shù)據(jù)包打上一個(gè)標(biāo)記,該標(biāo)記必須在對(duì)端可以被識(shí)別。我決定使用無(wú)傷大雅的IP頭TTL字段,使用stap完成這件事非常簡(jiǎn)單。順便地,我將為skb打mark這件事也用stap來(lái)做,于是我也刪掉了iptables規(guī)則:
如此一個(gè)腳本,在對(duì)端直接match外層五元組和TTL值就行了,我們只需要在匹配外層五元組的同時(shí),匹配TTL值大于70的skb即可。bpftrace只能讀不能寫,為了讓這件事成為可能,我只能用stap。
?
以上就是我的態(tài)度,我不是不接受bpftrace,我更不是不接受新事物,我只是想說(shuō)不能在接受新事物的時(shí)候把舊事物一棍子打死!不能因?yàn)閑BPF的流行就選擇bpftrace而把stap丟進(jìn)垃圾桶。
?
…
?
我正準(zhǔn)備吐槽,然而我正準(zhǔn)備噴bpftrace做不到某某事情的時(shí)候,我血祭了!
?
我明明可以在wireguard的數(shù)據(jù)接收端像數(shù)據(jù)發(fā)送端一樣用bpftrace來(lái)trace這個(gè)特定的skb,畢竟我只是想知道它在哪里被drop了,這完全沒有寫操作的必要。然而我為了一種假裝成格調(diào)的態(tài)度,死活非要用stap來(lái)完成這件事而放棄bpftrace。
?
雖然我自以為自己是stap的熟練工,但我卻幾乎都是在用-g的guru模式,不是因?yàn)槲易孕?#xff0c;而是因?yàn)槲腋悴磺宄钟洸蛔tap的語(yǔ)法。我?guī)缀踔粫?huì)C和匯編,我?guī)缀蹩偸怯洸蛔∪魏纹渌Z(yǔ)言的語(yǔ)法,包括Bash在內(nèi)…
?
我在使用stap來(lái)trace內(nèi)核或者模塊的函數(shù)前,我總會(huì)看下它的參數(shù)解析情況:
很不幸,無(wú)法使用任何參數(shù),原因未知。因此當(dāng)我希望使用它的參數(shù)的時(shí)候,我只能裸取寄存器了,像下面這樣:
... probe module("wireguard").function("wg_allowedips_lookup_src") {// 由于stap -L無(wú)法解析參數(shù),只能用x86_64的調(diào)用規(guī)則直接取寄存器if (cmpskb(register("rsi"))) {a = 1;} }probe module("wireguard").function("wg_allowedips_lookup_src").return {if (a == 1) {printf("peer returned::%p\n", register("rax"));a = 0;} }? ...既然都用stap了,為何不讓事情簡(jiǎn)單一些呢?于是我就開始了自信滿滿的寫操作。改skb內(nèi)存,手工修改skb的data,以期望能bypass掉很多不必要的流程。
?
…
?
在我持續(xù)這么玩了大概幾個(gè)小時(shí)后,大概就是開著飛機(jī)修引擎的感覺,我有些疲勞,大概在某個(gè)精確的時(shí)間點(diǎn),crash or soft lockup,完美的完成了血祭!
這是一次線上作業(yè),鍋顯然是我的。
?
這不能說(shuō)明我的手藝不精湛,在這種硬著陸之前,我畢竟還放飛了幾個(gè)小時(shí)呢,但這說(shuō)明一個(gè)問題,bpftrace就是比stap好,至少安全。而這恰恰是我要反駁的觀點(diǎn),卻被我證明了。
在穩(wěn)定性方面,eBPF的兩個(gè)不允許就夠了:
- eBPF不允許你寫任何有潛在風(fēng)險(xiǎn)的代碼。
- eBPF不允許你寫任何復(fù)雜的代碼。
stap里你可以隨便一個(gè)while(true)而把系統(tǒng)鎖死,bpftrace中卻不行。
…
處理類似的問題時(shí),其實(shí)我是帶有偏見的,我不喜歡使用工具特別是不喜歡使用新工具的原因背后更多的是因?yàn)槲冶容^懶,我不喜歡面對(duì)和駕馭一大坨不相關(guān)的東西,比方說(shuō)我明明知道有個(gè)dropwatch卻沒有使用,就是因?yàn)樗珡?fù)雜了,還要去了解那么復(fù)雜的命令行,與其這樣,還不如我直接stap probe kfree_skb然后dump_stack呢。
一開始我對(duì)stap也是抵觸的,因?yàn)樗彩亲銐驈?fù)雜,我寧愿裸寫ftrace函數(shù),比方說(shuō)手工把一個(gè)函數(shù)的頭5個(gè)字節(jié)替換成call stub_handler這種。如今即便我對(duì)stap已經(jīng)輕車熟路了,我依然還是堅(jiān)持只寫guru模式的腳本,我依然還是懶得去學(xué)習(xí)stap的語(yǔ)法。
使用工具提高效率那是針對(duì)熟悉這種工具的人來(lái)講的,對(duì)于不熟悉該工具的來(lái)講,比如我,花在學(xué)習(xí)這種工具的使用方法上的時(shí)間將讓我延遲對(duì)真正問題的處理。
同樣,磨刀不誤砍柴工,工欲善其事必先利其器,我并不贊同這是普適的,這種話術(shù)是針對(duì)頻繁解決同類問題的人來(lái)講的,他們需要的是總結(jié)出一種范式,錘子能釘釘子,如果你需要頻繁釘釘子,你當(dāng)然需要買一把錘子,但如果你只需要釘一次釘子,隨手拿起邊上的一塊磚或許比去買一把錘子更方便。
如果你每次玩的都是新花樣,當(dāng)然不需要工具了。
此外 “在路上” 的價(jià)值觀在我針對(duì)工具的觀點(diǎn)也起到了推波助瀾的作用。我一向覺得自己是在路上的人,因此我討厭任何累贅,我不回去攜帶,背負(fù),記憶那些隨處可以得到的東西。杭州往上海搬家,既然上海可以買到被子,我何必要把被子寄回來(lái),直接扔掉不是更好嗎?
在石器時(shí)代,人們就已經(jīng)擁有了類別豐富的工具,但對(duì)于一個(gè)母系時(shí)代凈身出戶的男人而言,唯一可以帶走并且他們自愿選擇的工具就是弓箭,彈弓這種類似的遠(yuǎn)程攻擊工具,可能就連刀,斧之類的,都屬于累贅,抓住問題的本質(zhì)本身,這就足夠了。
浙江溫州皮鞋濕,下雨進(jìn)水不會(huì)胖!
總結(jié)
以上是生活随笔為你收集整理的手艺人舍bpftrace而取systemtap的代价和思考的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python图像加密模块_使用Pycry
- 下一篇: java 压缩 乱码_如何解决java压