當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > CPP開(kāi)發(fā)者
[導(dǎo)讀]近期遇到一個(gè)問(wèn)題,簡(jiǎn)單點(diǎn)說(shuō),主機(jī)A上顯示一條ESTABLISHED狀態(tài)的TCP連接到主機(jī)B,而主機(jī)B上卻沒(méi)有任何關(guān)于主機(jī)A的連接信息,經(jīng)查明,這是由于主機(jī)A和主機(jī)B的發(fā)送/接收緩沖區(qū)差異巨大,導(dǎo)致主機(jī)B進(jìn)程退出后,主機(jī)A暫時(shí)憋住,主機(jī)B頻繁發(fā)送零窗口探測(cè),F(xiàn)IN_WAIT1狀態(tài)超...



近期遇到一個(gè)問(wèn)題,簡(jiǎn)單點(diǎn)說(shuō),主機(jī)A上顯示一條ESTABLISHED狀態(tài)的TCP連接到主機(jī)B,而主機(jī)B上卻沒(méi)有任何關(guān)于主機(jī)A的連接信息,經(jīng)查明,這是由于主機(jī)A和主機(jī)B的發(fā)送/接收緩沖區(qū)差異巨大,導(dǎo)致主機(jī)B進(jìn)程退出后,主機(jī)A暫時(shí)憋住,主機(jī)B頻繁發(fā)送零窗口探測(cè),FIN_WAIT1狀態(tài)超時(shí),進(jìn)而連接被銷毀,然而主機(jī)A并不知情導(dǎo)致。

正好昨天也有人咨詢另外一個(gè)類似的問(wèn)題,那么就抽昨晚和今天早上的時(shí)間,寫(xiě)一篇總結(jié)吧。

TCP處處是坑!

不要覺(jué)得你對(duì)TCP的實(shí)現(xiàn)的代碼爛熟于心了就能把控它的所有行為!不知道你有沒(méi)有發(fā)現(xiàn),目前市面上新上市的關(guān)于Linux內(nèi)核協(xié)議棧的書(shū)可謂是汗牛充棟,然而無(wú)論作者是國(guó)內(nèi)的還是國(guó)外,幾乎都是碰到TCP就草草略過(guò),反而對(duì)IP,ARP,DNS這些大書(shū)特書(shū),Why?因?yàn)長(zhǎng)inux內(nèi)核里TCP的代碼太亂太復(fù)雜了,很少有人能看明白80%以上的,即便真的有看過(guò)的,其中還包括只懂代碼而不懂網(wǎng)絡(luò)技術(shù)的,我就發(fā)現(xiàn)很多聲稱自己精通Linux內(nèi)核TCP/IP源碼,結(jié)果竟然不知道什么是默認(rèn)路由…


所以我打算寫(xiě)一篇文章,趁著這個(gè)FIN_WAIT1問(wèn)題,順便表達(dá)一下我是如何學(xué)習(xí)網(wǎng)絡(luò)技術(shù),我是如何解決網(wǎng)絡(luò)問(wèn)題的方法論觀點(diǎn),都是形而上,個(gè)人看法:

  • 設(shè)計(jì)覆蓋全面的復(fù)現(xiàn)實(shí)驗(yàn)

  • 通讀協(xié)議標(biāo)準(zhǔn)文檔,理解實(shí)現(xiàn)建議

  • 再次實(shí)驗(yàn),預(yù)測(cè)并確認(rèn)問(wèn)題以外的現(xiàn)象

  • 核對(duì)代碼實(shí)現(xiàn),跟蹤代碼的Changelog

  • 寫(xiě)一個(gè)自己的實(shí)現(xiàn)或者亂改代碼


本文聊聊TCP的FIN_WAIT1以及TCP假連接(死連接)問(wèn)題。先看FIN_WAIT1。

首先還是從狀態(tài)機(jī)入手,看看和FIN_WAIT1相關(guān)的狀態(tài)機(jī)轉(zhuǎn)換圖:

我們只考慮常規(guī)的從ESTABLISHED狀態(tài)的轉(zhuǎn)換,很簡(jiǎn)單的一個(gè)單一狀態(tài)轉(zhuǎn)換:

  • ESTAB狀態(tài)發(fā)送FIN即切換到FIN_WAIT1狀態(tài);

  • FIN_WAIT1狀態(tài)下收到針對(duì)FIN的ACK即可離開(kāi)FIN_WAIT1到達(dá)FIN_WAIT2.

看一下和上述狀態(tài)機(jī)轉(zhuǎn)換相關(guān)的簡(jiǎn)單時(shí)序圖:

從狀態(tài)圖和時(shí)序圖上,我們很明確地可以看到,FIN_WAIT1持續(xù)1個(gè)RTT左右的時(shí)間!這個(gè)時(shí)間段幾乎不會(huì)被肉眼觀察到,轉(zhuǎn)瞬而即逝。


然而,這是真的嗎?


我們之所以得到FIN_WAIT1持續(xù)1個(gè)RTT這個(gè)結(jié)論,基于兩個(gè)假設(shè),即:
TCP的對(duì)端是一個(gè)正常的TCP端;兩端TCP之間的鏈路是正常的,可達(dá)的。OK,接下來(lái)我們來(lái)設(shè)計(jì)一個(gè)實(shí)驗(yàn)?zāi)M異常的情況。準(zhǔn)備實(shí)驗(yàn)拓?fù)淙缦拢?br>


host1和host2的系統(tǒng)內(nèi)核版本(uname -r獲取):

3.10.0-862.2.3.el7.x86_64
首先,我們看一下如果對(duì)端TCP針對(duì)FIN發(fā)送的ACK丟失,會(huì)發(fā)生什么。按照上述的時(shí)序圖,正常應(yīng)該是FIN_WAIT1將會(huì)永久持續(xù)。我們來(lái)驗(yàn)證一下。
實(shí)驗(yàn)1:模擬ACK丟失在host1上做以下命令:

nc -l -p 1234
host2上完成以下命令:

cat /dev/zero|nc 1.1.1.1 1234
以上保證了host1和host2之間的TCP建立并且連接之間有持續(xù)的數(shù)據(jù)傳輸。接下來(lái),在host2上執(zhí)行下列動(dòng)作:

iptables -A INPUT -p tcp --tcp-flags ACK,FIN ACK
killall nc
此時(shí)在host2上:

[root@localhost?~]#?netstat??-antp|grep?1234
tcp????????0???1229?1.1.1.2:39318???????????????1.1.1.1:1234????????????????FIN_WAIT1???-
連續(xù)上翻命令,這個(gè)FIN_WAIT1均不會(huì)消失,暫時(shí)符合我們的預(yù)期…出去抽根煙,刷會(huì)兒微博…回來(lái)后,發(fā)現(xiàn)這個(gè)FIN_WAIT1消失了!


它是如何消失的呢?這個(gè)時(shí)候,我們提取netstat數(shù)據(jù),執(zhí)行“ netstat -st”,會(huì)發(fā)現(xiàn):

TcpExt:
...
1 connections aborted due to timeout

多了一條timeout連接!

我這里直接說(shuō)答案吧。
雖然說(shuō)在協(xié)議上規(guī)范上看,TCP沒(méi)有必要為鏈路或者說(shuō)對(duì)端的不合常規(guī)的行為而買單,但是從現(xiàn)實(shí)角度,TCP的實(shí)現(xiàn)必須處理異常情況,TCP的實(shí)現(xiàn)必然要有所限制!

我們知道,計(jì)算機(jī)是無(wú)法處理無(wú)限,無(wú)窮這種抽線的數(shù)學(xué)概念的,所有如果針對(duì)FIN的ACK遲遲不來(lái),那么必然要有一個(gè)等待的極限,這個(gè)極限在Linux內(nèi)核協(xié)議棧中由以下參數(shù)控制:

net.ipv4.tcp_orphan_retries?#?默認(rèn)值是0!這里有坑...
這個(gè)參數(shù)表示如果一直都收不到針對(duì)FIN的ACK,那么在徹底銷毀這個(gè)FIN_WAIT1的連接前,等待幾輪RTO退避。


所謂的orphan tcp connection,意思就是說(shuō),在Linux進(jìn)程層面,創(chuàng)建該連接的進(jìn)程已經(jīng)退出銷毀了,然而在TCP協(xié)議層面,它依然在遵循TCP狀態(tài)機(jī)的轉(zhuǎn)換規(guī)則存在著。


注意,這個(gè)參數(shù)不是一個(gè)時(shí)間量,而是一個(gè)次數(shù)量。我們知道,TCP每一次超時(shí),都會(huì)對(duì)下一次超時(shí)時(shí)間進(jìn)行指數(shù)退避,這里的次數(shù)量就是要經(jīng)過(guò)幾次退避的時(shí)間。舉一個(gè)例子,如果RTO是2ms,而tcp_orphan_retries 的值是4,那么所計(jì)算出的FIN_WAIT1容忍時(shí)間就是:T=21 22 23 24T=21 22 23 24還是看看Linux內(nèi)核文檔怎么說(shuō)的吧:

tcp_orphan_retries?-?INTEGER
??This?value?influences?the?timeout?of?a?locally?closed?TCP?connec??tion,?when?RTO?retransmissions?remain?unacknowledged.
??See?tcp_retries2?for?more?details.
??The?default?value?is?8.
??If?your?machine?is?a?loaded?WEB?server,
??you?should?think?about?lowering?this?value,?such?sockets
??may?consume?significant?resources.?Cf.?tcp_max_orphans.
讓我們看看tcp_retries2,以獲取數(shù)值的含義:

tcp_retries2?-?INTEGER
??This?value?influences?the?timeout?of?an?alive?TCP?connection,
??when?RTO?retransmissions?remain?unacknowledged.
??Given?a?value?of?N,?a?hypothetical?TCP?connection?following
??exponential?backoff?with?an?initial?RTO?of?TCP_RTO_MIN?would
??retransmit?N?times?before?killing?the?connection?at?the?(N 1)th?RTO.
??The?default?value?of?15?yields?a?hypothetical?timeout?of?924.6
??seconds?and?is?a?lower?bound?for?the?effective?timeout.
??TCP?will?effectively?time?out?at?the?first?RTO?which?exceeds?the?hypothetical?timeout.
??RFC?1122?recommends?at?least?100?seconds?for?the?timeout,
??which?corresponds?to?a?value?of?at?least?8.
雖然說(shuō)文檔上默認(rèn)值的建議是8,但是大多數(shù)的Linux發(fā)行版上其默認(rèn)值都是0。更多詳情,就自己看RFC和Linux源碼吧。


有了這個(gè)參數(shù)保底,我們知道,即便是ACK永遠(yuǎn)不來(lái),F(xiàn)IN_WAIT1狀態(tài)也不會(huì)一直持續(xù)下去的,這有效避免了有針對(duì)性截獲ACK或者不發(fā)送ACK而導(dǎo)致的DDoS,退一萬(wàn)步講,即便是沒(méi)有DDoS,這種做法也具有資源利用率的容錯(cuò)性,使得資源使用更加高效。


實(shí)驗(yàn)1的結(jié)論如下:

  • 如果主動(dòng)斷開(kāi)端調(diào)用了close關(guān)掉了進(jìn)程,它會(huì)進(jìn)入FIN_WAIT1狀態(tài),此時(shí)如果它再也收不到ACK,無(wú)論是針對(duì)pending在發(fā)送緩沖的數(shù)據(jù)還是FIN,它都會(huì)嘗試重新發(fā)送,在收到ACK前會(huì)嘗試N次退避,該N由tcp_orphan_retries參數(shù)控制。
接下來(lái),我們來(lái)看一個(gè)更加復(fù)雜一點(diǎn)的問(wèn)題,還是先從實(shí)驗(yàn)說(shuō)起。
實(shí)驗(yàn)2:模擬對(duì)端TCP不收數(shù)據(jù),接收窗口憋死在host1上做以下命令:# 模擬小接收緩存,使得憋住接收窗口更加容易

sysctl -w net.ipv4.tcp_rmem="16 32 32"
nc?-l?-p?1234
host2上完成以下命令:

cat /dev/zero|nc 1.1.1.1 1234
sleep 5 # 稍微等一下
killall nc
此時(shí),我們發(fā)現(xiàn)host2的TCP連接進(jìn)入了FIN_WAIT1狀態(tài)。然而抓包看的話,數(shù)據(jù)傳輸依然在進(jìn)行:

05:15:51.674630?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[P.],?seq?305:321,?ack?1,?win?5840,?options?[nop,nop,TS?val?1210945?ecr?238593370],?length?16
05:15:51.674690?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?321,?win?0,?options?[nop,nop,TS?val?238593471?ecr?1210945],?length?0
05:15:51.674759?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?321,?win?16,?options?[nop,nop,TS?val?238593471?ecr?1210945],?length?0
05:15:51.777774?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[P.],?seq?321:325,?ack?1,?win?5840,?options?[nop,nop,TS?val?1211048?ecr?238593471],?length?4
05:15:51.777874?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?325,?win?16,?options?[nop,nop,TS?val?238593497?ecr?1211048],?length?0
05:15:52.182918?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[P.],?seq?325:341,?ack?1,?win?5840,?options?[nop,nop,TS?val?1211453?ecr?238593497],?length?16
05:15:52.182970?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?341,?win?0,?options?[nop,nop,TS?val?238593599?ecr?1211453],?length?0
05:15:52.183055?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?341,?win?16,?options?[nop,nop,TS?val?238593599?ecr?1211453],?length?0
05:15:52.592759?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[P.],?seq?341:357,?ack?1,?win?5840,?options?[nop,nop,TS?val?1211863?ecr?238593599],?length?16
05:15:52.592813?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?357,?win?0,?options?[nop,nop,TS?val?238593701?ecr?1211863],?length?0
05:15:52.592871?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?357,?win?16,?options?[nop,nop,TS?val?238593701?ecr?1211863],?length?0
05:15:52.695160?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[P.],?seq?357:361,?ack?1,?win?5840,?options?[nop,nop,TS?val?1211965?ecr?238593701],?length?4
05:15:52.695276?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?361,?win?16,?options?[nop,nop,TS?val?238593727?ecr?1211965],?length?0
05:15:53.099612?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[P.],?seq?361:377,?ack?1,?win?5840,?options?[nop,nop,TS?val?1212370?ecr?238593727],?length?16
05:15:53.099641?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?377,?win?0,?options?[nop,nop,TS?val?238593828?ecr?1212370],?length?0
05:15:53.099671?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?377,?win?16,?options?[nop,nop,TS?val?238593828?ecr?1212370],?length?0
05:15:53.505028?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[P.],?seq?377:393,?ack?1,?win?5840,?options?[nop,nop,TS?val?1212775?ecr?238593828],?length?16
05:15:53.505081?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?393,?win?0,?options?[nop,nop,TS?val?238593929?ecr?1212775],?length?0
05:15:53.505138?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?393,?win?16,?options?[nop,nop,TS?val?238593929?ecr?1212775],?length?0
05:15:53.605923?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[P.],?seq?393:397,?ack?1,?win?5840,?options?[nop,nop,TS?val?1212876?ecr?238593929],?length?4
這是顯然的,這是因?yàn)槭瞻l(fā)兩端巨大的緩存大小差異造成的,即便是host2發(fā)送端進(jìn)程退出了,在退出前已經(jīng)有大量數(shù)據(jù)pending到了TCP的發(fā)送緩沖區(qū)里面而脫離已經(jīng)被銷毀的進(jìn)程了,F(xiàn)IN包當(dāng)然是排在了緩沖區(qū)的末尾了。


TCP的狀態(tài)機(jī)運(yùn)行在緩存的上層,即只要把FIN包pending排隊(duì),就切換到了FIN_WAIT1,而不是說(shuō)實(shí)際發(fā)送了FIN包才切換。


因此,我們可有的等了,數(shù)據(jù)傳輸依然在正常有序進(jìn)行,針對(duì)小包的ACK源源不斷從host1回來(lái),這進(jìn)一步促進(jìn)host2發(fā)送未竟的數(shù)據(jù)包,直到所有緩沖區(qū)的數(shù)據(jù)全部發(fā)送完畢…


不管怎樣,總是有個(gè)頭兒,只要有結(jié)束,就不需要擔(dān)心。我們可以簡(jiǎn)單得出一個(gè)結(jié)論:

  • 如果主動(dòng)斷開(kāi)端調(diào)用了close關(guān)掉了進(jìn)程,它會(huì)進(jìn)入FIN_WAIT1狀態(tài),如果接收端的接收窗口呈現(xiàn)打開(kāi)狀態(tài),此時(shí)它的TCP發(fā)送隊(duì)列中的數(shù)據(jù)包還是會(huì)像正常一樣發(fā)往接收端,直到發(fā)送完,最后發(fā)送FIN包,收到FIN包ACK后進(jìn)入FIN_WAIT2。
現(xiàn)在,我們進(jìn)行實(shí)驗(yàn)的下一步,把host1上的接收進(jìn)程nc的接收邏輯徹底憋死。很簡(jiǎn)單,host1上執(zhí)行下面的命令即可:

killall -STOP nc
進(jìn)程并沒(méi)有退出,只是暫停了,nc進(jìn)程上下文的recv不再執(zhí)行,然而軟中斷上下文的TCP協(xié)議的處理依然在進(jìn)行。


這個(gè)時(shí)候,抓包就會(huì)發(fā)現(xiàn)只剩下指數(shù)時(shí)間退避的零窗口探測(cè)包了:

#?注意觀察探測(cè)包發(fā)送時(shí)間的間隔
05:15:56.444570?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[.],?ack?1,?win?5840,?options?[nop,nop,TS?val?1215715?ecr?238594487],?length?0
05:15:56.444602?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?465,?win?0,?options?[nop,nop,TS?val?238594664?ecr?1214601],?length?0
05:15:57.757217?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[.],?ack?1,?win?5840,?options?[nop,nop,TS?val?1217027?ecr?238594664],?length?0
05:15:57.757248?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?465,?win?0,?options?[nop,nop,TS?val?238594992?ecr?1214601],?length?0
05:16:00.283259?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[.],?ack?1,?win?5840,?options?[nop,nop,TS?val?1219552?ecr?238594992],?length?0
05:16:00.283483?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?465,?win?0,?options?[nop,nop,TS?val?238595624?ecr?1214601],?length?0
05:16:05.234277?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[.],?ack?1,?win?5840,?options?[nop,nop,TS?val?1224503?ecr?238595624],?length?0
05:16:05.234305?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?465,?win?0,?options?[nop,nop,TS?val?238596861?ecr?1214601],?length?0
05:16:15.032486?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[.],?ack?1,?win?5840,?options?[nop,nop,TS?val?1234301?ecr?238596861],?length?0
05:16:15.032532?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?465,?win?0,?options?[nop,nop,TS?val?238599311?ecr?1214601],?length?0
05:16:34.629137?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[.],?ack?1,?win?5840,?options?[nop,nop,TS?val?1253794?ecr?238599311],?length?0
05:16:34.629164?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?465,?win?0,?options?[nop,nop,TS?val?238604210?ecr?1214601],?length?0
05:17:13.757815?IP?1.1.1.2.39318?>?1.1.1.1.1234:?Flags?[.],?ack?1,?win?5840,?options?[nop,nop,TS?val?1292784?ecr?238604210],?length?0
05:17:13.757863?IP?1.1.1.1.1234?>?1.1.1.2.39318:?Flags?[.],?ack?465,?win?0,?options?[nop,nop,TS?val?238613992?ecr?1214601],?length?0
這個(gè)實(shí)驗(yàn)的現(xiàn)象和實(shí)驗(yàn)1的現(xiàn)象,僅有一個(gè)區(qū)別,那就是實(shí)驗(yàn)1是阻塞了ACK,而本實(shí)驗(yàn)則是FIN根本就還沒(méi)有發(fā)送出去就進(jìn)入了FIN_WAIT1,且針對(duì)RTO指數(shù)時(shí)間退避發(fā)送的零窗口探測(cè)的ACK持續(xù)到來(lái),簡(jiǎn)單總結(jié)就是:實(shí)驗(yàn)1沒(méi)有ACK到來(lái),實(shí)驗(yàn)2有ACK到來(lái)。


在實(shí)驗(yàn)結(jié)果之前,我們來(lái)看一段摘錄,來(lái)自RFC1122:https://tools.ietf.org/html/rfc1122#page-92

4.2.2.17?Probing?Zero?Windows:?RFC-793?Section?3.7,?page?42
.
??Probing?of?zero?(offered)?windows?MUST?be?supported.
.
??A?TCP?MAY?keep?its?offered?receive?window?closed
???indefinitely.?As?long?as?the?receiving?TCP?continues?to
??send?acknowledgments?in?response?to?the?probe?segments,?the
??sending?TCP?MUST?allow?the?connection?to?stay?open.
緊接著后面是一段注解:

DISCUSSION:
??It?is?extremely?important?to?remember?that?ACK
??(acknowledgment)?segments?that?contain?no?data?are?not
??reliably?transmitted?by?TCP.?If?zero?window?probing?is
??not?supported,?a?connection?may?hang?forever?when?an
??ACK?segment?that?re-opens?the?window?is?lost.
.
??The?delay?in?opening?a?zero?window?generally?occurs
??when?the?receiving?application?stops?taking?data?from
??its?TCP.?For?example,?consider?a?printer?daemon
??application,?stopped?because?the?printer?ran?out?of
??paper.
只要有ACK到來(lái),連接就要保持,這會(huì)帶來(lái)什么問(wèn)題呢?確實(shí)會(huì)帶來(lái)問(wèn)題,但是在正視這些問(wèn)題之前,Linux內(nèi)核協(xié)議棧的實(shí)現(xiàn)者,也保持了緘默,我們來(lái)看一段實(shí)驗(yàn)主機(jī)host1和host2所用的標(biāo)準(zhǔn)內(nèi)核主線版本3.10的內(nèi)核源碼,來(lái)自tcp_probe_timer函數(shù)內(nèi)部的注釋以及一小段代碼:

/*?*WARNING*?RFC?1122?forbids?this
?????*
?????*?It?doesn't?AFAIK,?because?we?kill?the?retransmit?timer?-AK
?????*
?????*?FIXME:?We?ought?not?to?do?it,?Solaris?2.5?actually?has?fixing
?????*?this?behaviour?in?Solaris?down?as?a?bug?fix.?[AC]
?????*
?????*?Let?me?to?explain.?icsk_probes_out?is?zeroed?by?incoming?ACKs
?????*?even?if?they?advertise?zero?window.?Hence,?connection?is?killed?only
?????*?if?we?received?no?ACKs?for?normal?connection?timeout.?It?is?not?killed
?????*?only?because?window?stays?zero?for?some?time,?window?may?be?zero
?????*?until?armageddon?and?even?later.?We?are?in?full?accordance
?????*?with?RFCs,?only?probe?timer?combines?both?retransmission?timeout
?????*?and?probe?timeout?in?one?bottle.?????????????--ANK
?????*/

?????...
????????max_probes?=?sysctl_tcp_retries2;


if?(sock_flag(sk,?SOCK_DEAD))?{?//?如果是orphan連接的話
const?int?alive?=?((icsk->icsk_rto?<icsk_backoff)?//?即獲取tcp_orphan_retries參數(shù),有微調(diào),請(qǐng)?jiān)攲?。本?shí)驗(yàn)參數(shù)默認(rèn)值取0!
????????max_probes?=?tcp_orphan_retries(sk,?alive);


if?(tcp_out_of_resources(sk,?alive?||?icsk->icsk_probes_out?<=?max_probes))
return;
????}
//?只有在icsk_probes_out,即未應(yīng)答的probe次數(shù)超過(guò)探測(cè)最大容忍次數(shù)后,才會(huì)出錯(cuò)清理連接。
if?(icsk->icsk_probes_out?>?max_probes)?{
????????tcp_write_err(sk);
????}?else?{
/*?Only?send?another?probe?if?we?didn't?close?things?up.?*/
????????tcp_send_probe0(sk);
????}
是的,從上面那一段注釋,我們看出了抱怨,一個(gè)FIN_WAIT1的連接可能會(huì)等到世界終結(jié)日之后,然而我們卻只能“in full accordance with RFCs”!


這也許暗示了某種魔咒般的結(jié)果,即FIN_WAIT1將會(huì)一直持續(xù)到終結(jié)世界的大決戰(zhàn)之日。然而非也,你會(huì)發(fā)現(xiàn)大概在發(fā)送了9個(gè)零窗口探測(cè)包之后,連接就消失了。netstat -st的結(jié)果中,呈現(xiàn):

connections aborted due to timeout
看來(lái)想制造點(diǎn)事端,并非想象般容易!


如上所述,我展示了標(biāo)準(zhǔn)主線的Linux 3.10內(nèi)核的tcp_probe_timer函數(shù),現(xiàn)在的問(wèn)題是,為什么下面的條件被滿足了呢?

if?(icsk->icsk_probes_out?>?max_probes)
只有當(dāng)這個(gè)條件被滿足,tcp_write_err才會(huì)被調(diào)用,進(jìn)而:

tcp_done(sk);
//?遞增計(jì)數(shù),即netstat?-st中的那條“1?connections?aborted?due?to?timeout”
NET_INC_STATS_BH(sock_net(sk),?LINUX_MIB_TCPABORTONTIMEOUT);
按照注釋和代碼的確認(rèn),只要收到ACK,icsk_probes_out 字段就將被清零,這是很明確的啊,我們?cè)趖cp_ack函數(shù)中便可看到無(wú)條件清零icsk_probes_out的動(dòng)作:

static?int?tcp_ack(struct?sock?*sk,?const?struct?sk_buff?*skb,?int?flag)
{
????...
????sk->sk_err_soft?=?0;
????icsk->icsk_probes_out?=?0;
????tp->rcv_tstamp?=?tcp_time_stamp;
????...
}
從代碼上看,只要零窗口探測(cè)持續(xù)發(fā)送,不管退避到多久(最大TCP_RTO_MAX),只要對(duì)端會(huì)有ACK回來(lái),icsk_probes_out 就會(huì)被清零,上述的條件就不會(huì)被滿足,連接就會(huì)一直在FIN_WAIT1狀態(tài),而從我們抓包看,確實(shí)是零窗口探測(cè)有去必有回的!

預(yù)期會(huì)永遠(yuǎn)僵在FIN_WAIT1狀態(tài)的連接在一段時(shí)間后竟然銷毀了。沒(méi)有符合預(yù)期,到底發(fā)生了呢?


如果我們看高版本4.14版的Linux內(nèi)核,同樣是tcp_probe_timer函數(shù),我們會(huì)看到一些不一樣的代碼和注釋:

static?void?tcp_probe_timer(struct?sock?*sk)
{
...
/* RFC 1122 4.2.2.17 requires the sender to stay open indefinitely as
* long as the receiver continues to respond probes. We support this by
* default and reset icsk_probes_out with incoming ACKs. But if the
* socket is orphaned or the user specifies TCP_USER_TIMEOUT, we
* kill the socket when the retry count and the time exceeds the
* corresponding system limit. We also implement similar policy when
* we use RTO to probe window in tcp_retransmit_timer().
*/
start_ts = tcp_skb_timestamp(tcp_send_head(sk));
if (!start_ts)
tcp_send_head(sk)->skb_mstamp = tp->tcp_mstamp;
else if (icsk->icsk_user_timeout
本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開(kāi)發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來(lái)越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來(lái)越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開(kāi)幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉