背景:總是出現周期性地調用openstack接口connect timed out報錯,調用方和接收方都說不是自己的問題,懷疑是對方的問題,爭執不休,只能抓包看了。
抓包小技巧
抓包命令很簡單,tcpdump -i eth0 -w openstack.cap。
由于問題是周期性出現的,不知道抓包要多久,抓到的包可能非常大,所以指定50M大小的包,查了官方文檔來抓包,tcpdump -i eth0 -s0 -C 50 -w openstack.cap,分別在調用openstack的客戶端以及和opensatck服務端上相應網口抓包。
如果磁盤空間有限制,如果抓包只能是10個G的空間,按照50M大小的包來算,只能抓204個包,tcpdump -i eth0 -s0 -C 50 -c 204? -w? openstack.cap。
如果ssh客戶端老是退出,可以啟動在后臺工作,nohup tcpdump -i eth0 -s0 -C 50 -c 204? -w? openstack.cap &。
如果需要抓某個時間段內的包,可以定時抓包,使用crontab。crontab? -e像vim一樣編輯和保存定時任務列表。比如在每天的凌晨2點半開始抓包,抓1個50M大小的包如果需要30分鐘,又想在9點時間之前將抓包命令停掉。
30? 2? *? *? *? nohup tcpdump -i eth0 -s0 -C 50 -c 204? -w? openstack.cap &
0? 9? *? *? *? pid=` ps -ef | grep tcpdunp `;for i in $pid; done echo $pid ; kill -9 $pid
抓包結果
抓到的包有200個,怎么找相應的報錯信息呢?整體上從抓包中Ctrl+F查找package detail中相應時間點或報錯字符,告訴你兩個方法:
1) 從業務角度出發,看下業務日志中是否有相應的報錯信息,然后在合適的抓包文件(每50M大小的包是有最后寫文件的時間)中找出相應的時間點和接口URL。
2) 在合適的抓包文件(每50M大小的包是有最后寫文件的時間)中找出相應報錯信息,比如timed out。
找到報錯信息后,可以右擊某條記錄,選擇Fellow TCP flow,幸運的話,可以看到一次TCP從建接到結束的整個過程,會看到TCP3次握手和TCP四次揮手過程。
看了以上抓包信息后,TCP第3次握手失敗,這是為什么呢?(可以自行腦補下什么是TCP三次握手呢?)
從上面可以看出,當客戶端收到SYN+ACK后,會發出ACK給服務器,客戶端就認為連接上了服務器。 一般情況下,服務器會收到客戶端的ACK,服務器認為自己連接上了客戶端。不過,也有可能服務器并沒有收到ACK。這時候,客戶端認為自己連上了服務器,而服務器認為自己并沒有連上客戶端。客戶端的動作,接下來可能會直接發送數據給服務器,就像上圖中的PSH+ACK。服務器可能會收到PSH+ACK,也可能沒有收到。但不管如何,服務器此時的狀態還是SYN_RECV。服務器不會進行任何動作,除了催促客戶端”我還需要一個ACK"。因此,3秒鐘后,服務器會重新發送SYN+ACK給客戶端。若6秒后還沒有得到ACK,服務器會再次發送....上面的客戶端發送了PSH+ACK,有可能會得到服務器的回應ACK+SYN,也有可能沒有(比較ACK+SYN需要3秒的超時時間)。不管如何,客戶端希望服務器回復的是指定序號的ACK。客戶端也會設定一個超時定時器,它會再次發送PSH+ACK來提醒服務器“我需要你的ACK來確認你是否收到數據”
現在問題清楚了,由于第三次握手失敗,導致服務器的狀態不對。客戶端雖然認為自己已經建立了連接,但是數據發送不到服務器。 可是為什么會這樣?
客戶的網絡結構和鏈接提到的只是有點不一樣(請求和響應的路不一樣):
對于服務器來說,它的路由ICMP的重定向包修改。因此,每次發往客戶端的數據包都不會經過路由器。而客戶端發給服務器的數據包每次都會經過路由器。
路由器上的防火墻再轉發第3次握手的SYN包時,檢查到服務器并沒有將第2次握手SYN+ACK包,認為客戶端的第3次握手的SYN是無效的,從而并沒有轉發這個數據包。導致服務器收不到第3次握手無法建立連接。
解決辦法是路由器禁止掉ICMP重定向,或者服務器忽略掉路由器的ICMP重定向請求。
本案例來源于:https://blog.csdn.net/king523103/article/details/47776933
腦補下TCP三次握手和TCP四次揮手
TCP三次握手建立連接
第1次握手:客戶端發送syn包(seq=x)到服務器,并進入SYN_SEND狀態,等待服務器確認;
第2次握手:服務器收到syn包,必須確認客戶端的SYN(ack=x+1),同時自己也發送一個SYN包(seq=y),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
第3次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=y+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成3次握手。
你說建立個連接為啥要發3次請求呢?我想是這樣的,比如你要給大山那頭喊話,你一喊話,山那頭就能收到你的話嘛?假設山那頭碰巧收到你的話,也回了話,說我在線等你哈。假設你收到山那頭的話,肯定也會回話,我也在線了。這就是整個過程,哪一環出了問題,連接就建立不起來:1) 服務端沒收到第1次握手,建立不起來;2) 即使服務端收到了第1次握手但客戶端又沒有收到第2次握手,建立不起來;3) 即使服務端沒收到第3次握手,連接也建立不起來。當然了,每次握手如果失敗了,發送方會不斷重試的,見抓包中的TCP Retransmission,每次默認30秒的重試時長。
理想狀態下,TCP連接一旦建立,在通信雙方中的任何一方主動關閉連接之前,TCP 連接都將被一直保持下去。
TCP四次揮手斷開連接
第1次揮手:主動關閉方發送一個FIN,用來關閉主動方到被動關閉方的數據傳送,也就是主動關閉方告訴被動關閉方:我已經不會再給你發數據了(當然,在fin包之前發送出去的數據,如果沒有收到對應的ack確認報文,主動關閉方依然會重發這些數據),但此時主動關閉方還可以接收數據。
第2次揮手:被動關閉方收到FIN包后,發送一個ACK給對方,確認序號為收到序號+1(與SYN相同,一個FIN占用一個序號)。
第3次揮手:被動關閉方發送一個FIN,用來關閉被動關閉方到主動關閉方的數據傳送,也就是告訴主動關閉方,我的數據也發送完了,不會再給你發數據了。
第4次揮手:主動關閉方收到FIN后,發送一個ACK給被動關閉方,確認序號為收到序號+1,至此,完成4次揮手。
你說建立個連接要3次,斷開連接為何要4次呢?雙方建立連接后,如果有任意方要斷開連接,這個時候不傳輸數據,雙方協商下斷開也就是了。想斷開的一方其實是不想再發數據給對方了,但不代表不收數據了,當然這個時候如果之前發送的數據失敗了,還是會重新發出去的,這也是第1次揮手的情形。當對端收到斷開連接的請求時可能會接著發生數據,發送結束后也會給對方說斷開連接吧,這個時候不代表不收數據了,前發送的數據失敗了還是會重新發出去的,這也是第2次揮手的情形。當想斷開一方收到對端也要斷開連接了,自己發送的所有數據也發送成功了,發送確認我再也不給你發消息了,但還可以收。當對端收到想斷開一方不再發送消息了,自己發送的數據也確認發生完了,那就徹底斷開連接了,byebye您那。
參考資料
http://man.he.net/?topic=tcpdump§ion=all
http://www.lxweimin.com/p/347634e92f92
https://baijiahao.baidu.com/s?id=1618114723935605183&wfr=spider&for=pc