[轉帖]活久見,TCP連接互串了

https://plantegg.github.io/2020/11/18/TCP%E8%BF%9E%E6%8E%A5%E4%B8%BA%E5%95%A5%E4%BA%92%E4%B8%B2%E4%BA%86/

 

背景

應用每過一段時間總是會拋出幾個連接異常的錯誤,需要查明原因。

排查後發現是TCP連接互串了,這個案例實在是很珍惜,所以記錄一下。

抓包

業務結構: 應用->MySQL(10.112.61.163)

在 應用 機器上抓包這個異常連接如下(3269爲MySQL服務端口):

image.png

粗一看沒啥奇怪的,就是應用發查詢給3269,但是一直沒收到3269的ack,所以一直重傳。這裏唯一的解釋就是網絡不通。最後MySQL的3269還回復了一個rst,這個rst的id是42889,引起了我的好奇,跟前面的16439不連貫,正常應該是16440纔對。(請記住上圖中的綠框中的數字)

於是我過濾了一下端口61902上的所有包:

image.png

可以看到綠框中的查詢從61902端口發給3269後,很奇怪居然收到了一個來自別的IP+3306端口的reset,這個包對這個連接來說自然是不認識(這個連接只接受3269的回包),就扔掉了。但是也沒收到3269的ack,所以只能不停地重傳,然後每次都收到3306的reset,reset包的seq、id都能和上圖的綠框對應上。

明明他們應該是兩個連接:

61902->10.141.16.0:3306

61902->10.112.61.163:3269

他們雖然用的本地ip端口(61902)是一樣的, 但是根據四元組不一樣,還是不同的TCP連接,所以應該是不會互相干擾的。但是實際看起來seq、id都重複了,不會有這麼巧,非常像是TCP互串了。

分析原因

10.141.16.0 這個ip看起來像是lvs的ip,查了一下系統,果然是lvs,然後這個lvs 後面的rs就是10.112.61.163

那麼這個連結構就是10.141.16.0:3306:

應用 -> lvs(10.141.16.0:3306)-> 10.112.61.163:3269 跟應用直接連MySQL是一回事了

所以這裏的疑問就變成了:10.141.16.0 這個IP的3306端口爲啥能知道 10.112.61.163:3269端口的seq和id,也許是TCP連接串了

接着往下排查

先打個岔,分析下這裏的LVS的原理

這裏使用的是 full NAT模型(full NetWork Address Translation-全部網絡地址轉換)

基本流程(類似NAT):

  1. client發出請求(sip 200.200.200.2 dip 200.200.200.1)
  2. 請求包到達lvs,lvs修改請求包爲(sip 200.200.200.1, dip rip) 注意這裏sip/dip都被修改了
  3. 請求包到達rs, rs回覆(sip rip,dip 200.200.200.1)
  4. 這個回覆包的目的IP是VIP(不像NAT中是 cip),所以LVS和RS不在一個vlan通過IP路由也能到達lvs
  5. lvs修改sip爲vip, dip爲cip,修改後的回覆包(sip 200.200.200.1,dip 200.200.200.2)發給client

image.png

注意上圖中綠色的進包和紅色的出包他們的地址變化

本來這個模型下都是正常的,但是爲了Real Server能拿到client ip,也就是Real Server記錄來源ip的時候希望記錄的是client ip而不是LVS ip。這個時候LVS會將client ip放在tcp的options裏面,然後在RealServer機器的內核裏面將options中的client ip取出替換掉 lvs ip。所以Real Server上感知到的對端ip就是client ip。

回包的時候RealServer上的內核模塊同樣將目標地址從client ip改成lvs ip,同時將client ip放入options中。

回到問題

看完理論,再來分析這兩個連接的行爲

fulnat模式下連接經過lvs到達mysql後,mysql上看到的連接信息是,cip+port,也就是在MySQL上的連接

lvs-ip:port -> 10.112.61.163:3269 被修改成了 client-ip:61902 **-> 10.112.61.163:3269

那麼跟不走LVS的連接:

client-ip:61902 -> 10.112.61.163:3269 (直連) 完全重複了。

MySQL端看到的兩個連接四元組一模一樣了:

10.112.61.163:3269 -> client-ip:61902 (走LVS,本來應該是lvs ip的,但是被替換成了client ip)

10.112.61.163:3269 -> client-ip:61902 (直連)

這個時候應用端看到的還是兩個連接:

client-ip:61902 -> 10.141.16.0:3306 (走LVS)

client-ip:61902 -> 10.112.61.163:3269 (直連)

總結下,也就是這個連接經過LVS轉換後在服務端(MYSQL)跟直連MySQL的連接四元組完全重複了,也就是MySQL會認爲這兩個連接就是同一個連接,所以必然出問題了。

實際兩個連接建立的情況:

和mysqlserver的61902是04:22建起來的,和lvs的61902端口 是42:10建起來的,和lvs的61902建起來之後馬上就出問題了

問題出現的條件

  • fulnat模式的LVS,RS上裝有slb_toa內核模塊(RS上會將LVS ip還原成client ip)
  • client端正好重用一個相同的本地端口分別和RS以及LVS建立了兩個連接

這個時候這兩個連接在MySQL端就會變成一個,然後兩個連接的內容互串,必然導致rst

這個問題還挺有意思的,估計沒幾個程序員一輩子能碰上一次。推薦另外一個好玩的連接:如何創建一個自己連自己的TCP連接

參考資料

就是要你懂負載均衡–lvs和轉發模式

https://idea.popcount.org/2014-04-03-bind-before-connect/

no route to host

另一種形式的tcp連接互串,新連接重用了time_wait的port,導致命中lvs內核表中的維護的舊連接發給了老的realserver

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章