加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

大神如何不擇手段,最快最精準(zhǔn)打擊Linux網(wǎng)絡(luò)問題?

2021/06/01
243
閱讀需 12 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

內(nèi)容簡介

本文是大廠著名大神Dog250在調(diào)試一些網(wǎng)絡(luò)問題時候的實(shí)戰(zhàn),希望讀者通過閱讀本文,領(lǐng)悟大神們是如何“不擇手段,利用手頭一切的便利,最快的速度精準(zhǔn)打擊問題要害”,從而實(shí)現(xiàn)快速調(diào)試和解決問題的。

我們在工作中總是遇到一些需要快速解決的棘手問題,解決這類問題往往有一套可供遵循的常規(guī)思路,但是實(shí)際做起來往往非常耗時且依賴外部環(huán)境,更加棘手的是,為了按部就班地完成工作,你需要學(xué)習(xí)很多很多前置知識,比方說相關(guān)工具的使用。

我傾向于用最少的工作量來完成POC。

不會用crash/ebpf就不能debug內(nèi)核了嗎?不懂編程就不能優(yōu)化系統(tǒng)了嗎?并不是。

讓我來展示一下縣城擺攤修傘的二胡師傅和瑞士宮廷制表匠的區(qū)別吧。

本文我會舉三個實(shí)際的例子。用的都是low到爆的過時玩意兒。

### 示例1:排查TCP連接僵死
netstat顯示一條TCP連接的Send-Q堆積了很多數(shù)據(jù),對端相應(yīng)的Recv-Q卻是0,tcpdump顯示該連接持續(xù)無任何交互。

此時應(yīng)該怎么辦?

經(jīng)過ss -it確認(rèn)tcp_info信息,結(jié)論是該連接的RWND/CWND,RTT,RTO,MSS等數(shù)據(jù)均正常,網(wǎng)卡也無相關(guān)錯誤統(tǒng)計,但在事實(shí)上它就是僵住了,這是一個異?,F(xiàn)象,既然Send-Q中有數(shù)據(jù),它是無論如何都要 ***嘗試*** 發(fā)送出去的。

幾乎可以肯定,原因無外乎兩點(diǎn):
應(yīng)用程序進(jìn)入系統(tǒng)調(diào)用時lock住了socket并且阻塞了。
- 內(nèi)核存在問題。

如何來確認(rèn)?大多數(shù)人的思路傾向于使用crash工具去分析內(nèi)核數(shù)據(jù)結(jié)構(gòu),但是這是一個龐大的靜態(tài)分析工程。

我傾向于開著飛機(jī)修引擎,我不擅長分析死因,但我擅長做復(fù)蘇。我的方法是嘗試給該TCP做復(fù)蘇手術(shù)。

TCP的發(fā)送一直是靠ACK時鐘驅(qū)動的,事實(shí)上直到BBR開啟的基于pacing的新TCP時代,也依然沒有放棄ACK時鐘,雖然ACK在原教旨意義上不再需要,但BBR依然使用它來計算pacing rate,假如沒有ACK到來了,那么pacing rate便會逐漸跌到0,TCP也就僵住了。

***因此,TCP的復(fù)蘇手術(shù),主要是構(gòu)造一個ACK去擊打它!***

如果你對TCP足夠了解,那么你一定會大贊我的做法。

在TCP連接顯示Send-Q堆積的數(shù)據(jù)發(fā)送端構(gòu)造這個ACK,需要從本機(jī)的網(wǎng)卡注入,為了避開路由子系統(tǒng)的Martian報文校驗(yàn),需要另起一個net namespace來做這事。

接下來我們來構(gòu)造這個ACK:

 為了最快速定位問題,我往往不會遵守什么編碼規(guī)范,所以我會寫死地址和端口,哪怕需要改的時候再編輯一下代碼。

然后我們來注入:

 注意,代碼中的seq,ack字段我們并不知道,如何將這個ACK來精確注入這個僵死的TCP連接?

精確注入需要兩步,用一個bpftrace腳本配合上述python代碼獲取TCP的snd_una,rcv_nxt等字段:

 注意,我hook的是tcp_rcv_established,當(dāng)我實(shí)施第一步注入的時候,沒有進(jìn)入這個trace,那幾乎可以肯定是應(yīng)用程序lock住了該連接,進(jìn)而將該ACK排入了backlog以延后處理,這種情況就需要應(yīng)用程序開發(fā)人員來接鍋了。

如果順利進(jìn)入了該trace,那么我們便獲取了TCP連接的info信息,接下來我們可以用打印出來的snd_una,rcv_nxt信息來填充python代碼中的seq和ack了:

 ACK構(gòu)造配合bpftrace腳本,如此便可以一路跟蹤到數(shù)據(jù)的發(fā)送邏輯,進(jìn)而定位發(fā)送僵死的原因。核心的思路我已經(jīng)給出了,本文不是case by case分析,也就沒有繼續(xù)的必要了。

順便說一句,我不喜歡使用bpftrace,太麻煩且限制太多,還是systemtap順手,特別是-g選項(xiàng)。bpftrace無需編譯執(zhí)行快并非不可或缺的優(yōu)點(diǎn),大家都用bpftrace更多是因?yàn)樗鲁薄?/p>

### 示例2:實(shí)現(xiàn)tun網(wǎng)卡的readv
最近我雖然將golang實(shí)現(xiàn)的tun UDP隧道的總吞吐逼近了物理網(wǎng)卡極限25Gbps,但是對于單流吞吐而言,卻一直無法突破2~3Gbps,因此我想看看瓶頸到底在哪。

事實(shí)上,允許IP分片的情況下,我把tun的MTU設(shè)置成8000,單流吞吐可達(dá)8Gbps。然而在長傳有丟包的線路,IP分片(分片丟失會造成TCP時鐘卡頓)可能會使TCP的性能劣化,打亂BBR所依賴的pacing rate保真。

之前測量的結(jié)果,直連環(huán)境,通過tun UDP隧道的ping時延是物理網(wǎng)卡ping時延的10倍起步,那么tun和UDP socket處理的系統(tǒng)消耗大概要損耗10倍起步的吞吐,25Gbps下降到2~3Gbps是合理的。

因此我需要減少tun的read/write開銷。

批量讀寫是一個合理的思路,比方說io_uring,readv/writev等。可是tun并不支持這些,怎么辦?

io_uring直接拋棄,太復(fù)雜了。

如果要實(shí)現(xiàn)一個完備的讀寫數(shù)據(jù)包的readv/writev,我需要在內(nèi)核和用戶態(tài)均實(shí)現(xiàn)數(shù)據(jù)包邊界的拆包組包問題,我不得不處理各種協(xié)議,以在一塊整個的內(nèi)存中獲取數(shù)據(jù)包的長度并把它切下來,我不得不時刻當(dāng)心內(nèi)存的邊界,把不連續(xù)的內(nèi)存想辦法組合成一個看上去連續(xù)的內(nèi)存,以便后面的加密解密goroutine可以處理它們。

這看上去很復(fù)雜,需要對整個程序進(jìn)行修改(當(dāng)然了,這對于標(biāo)準(zhǔn)程序員根本不是事,但對于我,這很要命),至少也要花費(fèi)一整天的時間,可作為業(yè)余的事情,每天回家都很晚了,我哪有時間折騰這些。

下面是我一個小時完成事情全部的做法。我改變了readv的語義:
- 每一個iovec僅存放一個skb的數(shù)據(jù),下一個skb放在下一個iovec。
- 返回copy成功的skb的數(shù)量,而不是copy數(shù)據(jù)的總字節(jié)數(shù)。

下面是我對tun_do_read的改造:

 就這么幾行代碼。是不是很簡單。

下面是對應(yīng)的golang代碼:

下面是golang中的Readv:

 ...
### 示例3:實(shí)現(xiàn)松散TCP語義
來,最后一個例子,我簡單說。

我想為直播業(yè)務(wù)提供一個松散TCP傳輸協(xié)議,如何?

什么是松散TCP?很容易理解:
- 網(wǎng)絡(luò)狀態(tài)很好或者輕微丟包時,執(zhí)行完備TCP邏輯。
- 嚴(yán)重?fù)砣麜r不再重傳,直接發(fā)后面的數(shù)據(jù),能不能到達(dá),聽天由命。
- 接收端可以發(fā)送NAK指示發(fā)送端是否重傳。
- ...

這對于直播是有意義的,體現(xiàn)在三個方面:
- 直播防卡頓體驗(yàn)要比清晰度體驗(yàn)更核心,嚴(yán)重?fù)砣麜r用戶可以接受模糊但不能接受卡頓,因此可以丟幀,但不能卡住。
- 直播流量在嚴(yán)重?fù)砣麜r的松散非重傳處理可以降低帶寬成本。
- 大家都不拼命重傳了,或許網(wǎng)絡(luò)擁塞就過去了,可期待一種良性全局同步。

既能優(yōu)化體驗(yàn),又能降低成本,何樂而不為?那么怎么落地呢?

開會立項(xiàng),確定deadline,然后大改TCP協(xié)議的實(shí)現(xiàn)代碼嗎?Linux內(nèi)核中TCP的那一大脬代碼能把人看瘋。誰人改得動?然后可以期許的就是開會,延期,加班,哪來的快樂?

因此我用Netfilter:
- 發(fā)送端在IP層用Netfilter截獲出方向的TCP段,在嚴(yán)重?fù)砣麜r偽造ACK回復(fù)。
- 接收端在IP層用Netfilter截獲入方向的TCP段,在嚴(yán)重?fù)砣麜r用0填充丟包亂序造成的sequence空洞。

是不是不依賴TCP本身的實(shí)現(xiàn)了呢?而且實(shí)現(xiàn)起來很快,可以唱著歌寫。先把0.1版本推上去了,業(yè)務(wù)點(diǎn)了贊,然后慢慢再改那脬TCP代碼。

## 最后再寫點(diǎn)兒
其實(shí)還有很多類似的例子,但是時間有限,所以只能先寫幾個。將上面例子抽象一下,聊點(diǎn)形而上的。

不管做什么事情,什么最重要?是過程?是結(jié)果?還是別的什么?

過程和結(jié)果是經(jīng)理和用戶最關(guān)注的,工人則需要先把活干好。

工人在沒有一個理性,可行且快速,簡單的方案之前,過程和結(jié)果都是奢談。

很少有工人能給自己一個明確的定位,關(guān)注手頭最要緊的事情。所以很多工人在解決問題的時候顯得吃力,效率低。工人的信條就是, ***不擇手段,利用手頭一切的便利,最快的速度精準(zhǔn)打擊問題要害。*** 這至少是我的信條。

我擅長用火柴修雨傘,用筆帽修自行車鏈子,手工纏耳機(jī)實(shí)現(xiàn)重低音,用釘子和銅線做電容,自己纏電機(jī),升壓器,自制穩(wěn)壓器,用牛仔褲做提包,用椅子做桌子,自制分頻器,... 所有這些玩意兒,都不需要去店里采購材料,完全利用家里廢舊物什完成。

所有這些事,我關(guān)注的兩點(diǎn)核心是:
- ***快速-快速成功要么就失敗*** 
- ***簡單-不依賴其它工具*** 

這思路從小時候一直持續(xù)到后來做了工人,樸素,但管用啊...
***
浙江溫州皮鞋濕,下雨進(jìn)水不會胖。

 

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

專業(yè)的Linux技術(shù)社區(qū)和Linux操作系統(tǒng)學(xué)習(xí)平臺,內(nèi)容涉及Linux內(nèi)核,Linux內(nèi)存管理,Linux進(jìn)程管理,Linux文件系統(tǒng)和IO,Linux性能調(diào)優(yōu),Linux設(shè)備驅(qū)動以及Linux虛擬化和云計算等各方各面.