知乎千贊的 TCP 文章,我寫錯了一個點。。。
- 實驗一:模擬 TCP 第一次握手的 SYN 丟包;
- 實驗二:模擬 TCP 第二次握手的 SYN、ACK 丟包;
- 實驗三:模擬 TCP 第三次握手的 ACK 包丟;
我也把 TCP 三次握手模擬異常情況的實驗過程整理了下,大家有興趣可以模仿我這篇文章的實驗步驟來做實驗。5000字的車,坐穩(wěn)了!
TCP 三次握手異常情況實戰(zhàn)分析
TCP 三次握手的過程相信大家都背的滾瓜爛熟,那么你有沒有想過這三個異常情況:- TCP 第一次握手的 SYN 丟包了,會發(fā)生了什么?
- TCP 第二次握手的 SYN、ACK 丟包了,會發(fā)生什么?
- TCP 第三次握手的 ACK 包丟了,會發(fā)生什么?
- 那會重傳幾次?
- 超時重傳的時間 RTO 會如何變化?
- 在 Linux 下如何設置重傳次數(shù)?
- ….
實驗場景
本次實驗用了兩臺虛擬機,一臺作為服務端,一臺作為客戶端,它們的關(guān)系如下:- 客戶端和服務端都是 CentOs 6.5 Linux,Linux 內(nèi)核版本 2.6.32
- 服務端 192.168.12.36,apache web 服務
- 客戶端 192.168.12.37
實驗一:TCP 第一次握手 SYN 丟包
為了模擬 TCP 第一次握手 SYN 丟包的情況,我是在拔掉服務器的網(wǎng)線后,立刻在客戶端執(zhí)行 curl 命令:其間 tcpdump 抓包的命令如下:過了一會, curl 返回了超時連接的錯誤:從date
返回的時間,可以發(fā)現(xiàn)在超時接近 1 分鐘的時間后,curl 返回了錯誤。接著,把 tcp_sys_timeout.pcap 文件用 Wireshark 打開分析,顯示如下圖:從上圖可以發(fā)現(xiàn), 客戶端發(fā)起了 SYN 包后,一直沒有收到服務端的 ACK ,所以一直超時重傳了 5 次,并且每次 RTO 超時時間是不同的:- 第一次是在 1 秒超時重傳
- 第二次是在 3 秒超時重傳
- 第三次是在 7 秒超時重傳
- 第四次是在 15 秒超時重傳
- 第五次是在 31 秒超時重傳
SYN
超時重傳次數(shù),是如下內(nèi)核參數(shù)指定的:$?cat?/proc/sys/net/ipv4/tcp_syn_retries5
tcp_syn_retries
默認值為 5,也就是 SYN 最大重傳次數(shù)是 5 次。接下來,我們繼續(xù)做實驗,把 tcp_syn_retries
設置為 2 次:$?echo?2?>?/proc/sys/net/ipv4/tcp_syn_retries重傳抓包后,用 Wireshark 打開分析,顯示如下圖:
實驗一的實驗小結(jié)通過實驗一的實驗結(jié)果,我們可以得知,當客戶端發(fā)起的 TCP 第一次握手 SYN 包,在超時時間內(nèi)沒收到服務端的 ACK,就會在超時重傳 SYN 數(shù)據(jù)包,每次超時重傳的 RTO 是翻倍上漲的,直到 SYN 包的重傳次數(shù)到達
tcp_syn_retries
值后,客戶端不再發(fā)送 SYN 包。實驗二:TCP 第二次握手 SYN、ACK 丟包
為了模擬客戶端收不到服務端第二次握手 SYN、ACK 包,我的做法是在客戶端加上防火墻限制,直接粗暴的把來自服務端的數(shù)據(jù)都丟棄,防火墻的配置如下:接著,在客戶端執(zhí)行 curl 命令:從date
返回的時間前后,可以算出大概 1 分鐘后,curl 報錯退出了??蛻舳嗽谶@其間抓取的數(shù)據(jù)包,用 Wireshark 打開分析,顯示的時序圖如下:從圖中可以發(fā)現(xiàn):- 客戶端發(fā)起 SYN 后,由于防火墻屏蔽了服務端的所有數(shù)據(jù)包,所以 curl 是無法收到服務端的 SYN、ACK 包,當發(fā)生超時后,就會重傳 SYN 包
- 服務端收到客戶的 SYN 包后,就會回 SYN、ACK 包,但是客戶端一直沒有回 ACK,服務端在超時后,重傳了 SYN、ACK 包,接著一會,客戶端超時重傳的 SYN 包又抵達了服務端,服務端收到后,然后回了 SYN、ACK 包,但是SYN、ACK包的重傳定時器并沒有重置,還持續(xù)在重傳,因為第二次握手在沒收到第三次握手的 ACK 確認報文時,就會重傳到最大次數(shù)。
- 最后,客戶端 SYN 超時重傳次數(shù)達到了 5 次(tcp_syn_retries 默認值 5 次),就不再繼續(xù)發(fā)送 SYN 包了。
咦?客戶端設置了防火墻,屏蔽了服務端的網(wǎng)絡包,為什么 tcpdump 還能抓到服務端的網(wǎng)絡包?添加 iptables 限制后, tcpdump 是否能抓到包 ,這要看添加的 iptables 限制條件:
- 如果添加的是
INPUT
規(guī)則,則可以抓得到包 - 如果添加的是
OUTPUT
規(guī)則,則抓不到包
- 進來的順序 Wire -> NIC -> tcpdump -> netfilter/iptables
- 出去的順序 iptables -> tcpdump -> NIC -> Wire
tcp_syn_retries 是限制 SYN 重傳次數(shù),那第二次握手 SYN、ACK 限制最大重傳次數(shù)是多少?TCP 第二次握手 SYN、ACK 包的最大重傳次數(shù)是通過
tcp_synack_retries
內(nèi)核參數(shù)限制的,其默認值如下:$?cat?/proc/sys/net/ipv4/tcp_synack_retries5
是的,TCP 第二次握手 SYN、ACK 包的最大重傳次數(shù)默認值是
5
次。為了驗證 SYN、ACK 包最大重傳次數(shù)是 5 次,我們繼續(xù)做下實驗,我們先把客戶端的 tcp_syn_retries
設置為 1,表示客戶端 SYN 最大超時次數(shù)是 1 次,目的是為了防止多次重傳 SYN,把服務端 SYN、ACK 超時定時器重置。接著,還是如上面的步驟:- 客戶端配置防火墻屏蔽服務端的數(shù)據(jù)包
- 客戶端 tcpdump 抓取 curl 執(zhí)行時的數(shù)據(jù)包
- 客戶端的 SYN 只超時重傳了 1 次,因為
tcp_syn_retries
值為 1 - 服務端應答了客戶端超時重傳的 SYN 包后,由于一直收不到客戶端的 ACK 包,所以服務端一直在超時重傳 SYN、ACK 包,每次的 RTO 也是指數(shù)上漲的,一共超時重傳了 5 次,因為
tcp_synack_retries
值為 5
tcp_syn_retries
依然設置為 1:$?echo?2?>?/proc/sys/net/ipv4/tcp_synack_retries$?echo?1?>?/proc/sys/net/ipv4/tcp_syn_retries
依然保持一樣的實驗步驟進行操作,接著把抓取的數(shù)據(jù)包,用 Wireshark 打開分析,顯示的時序圖如下:可見:
- 客戶端的 SYN 包只超時重傳了 1 次,符合 tcp_syn_retries 設置的值;
- 服務端的 SYN、ACK 超時重傳了 2 次,符合 tcp_synack_retries 設置的值
實驗二的實驗小結(jié)通過實驗二的實驗結(jié)果,我們可以得知,當 TCP 第二次握手 SYN、ACK 包丟了后,客戶端 SYN 包會發(fā)生超時重傳,服務端 SYN、ACK 也會發(fā)生超時重傳??蛻舳?SYN 包超時重傳的最大次數(shù),是由 tcp_syn_retries 決定的,默認值是 5 次;服務端 SYN、ACK 包時重傳的最大次數(shù),是由 tcp_synack_retries 決定的,默認值是 5 次。
實驗三:TCP 第三次握手 ACK 丟包
為了模擬 TCP 第三次握手 ACK 包丟,我的實驗方法是在服務端配置防火墻,屏蔽客戶端 TCP 報文中標志位是 ACK 的包,也就是當服務端收到客戶端的 TCP ACK 的報文時就會丟棄,iptables 配置命令如下:接著,在客戶端執(zhí)行如下 tcpdump 命令:然后,客戶端向服務端發(fā)起 telnet,因為 telnet 命令是會發(fā)起 TCP 連接,所以用此命令做測試:此時,由于服務端收不到第三次握手的 ACK 包,所以一直處于SYN_RECV
狀態(tài):而客戶端是已完成 TCP 連接建立,處于 ESTABLISHED
狀態(tài):過了 1 分鐘后,觀察發(fā)現(xiàn)服務端的 TCP 連接不見了:過了 30 分別,客戶端依然還是處于 ESTABLISHED
狀態(tài):接著,在剛才客戶端建立的 telnet 會話,輸入 123456 字符,進行發(fā)送:持續(xù)「好長」一段時間,客戶端的 telnet 才斷開連接:以上就是本次的實現(xiàn)三的現(xiàn)象,這里存在兩個疑點:- 為什么服務端原本處于
SYN_RECV
狀態(tài)的連接,過 1 分鐘后就消失了? - 為什么客戶端 telnet 輸入 123456 字符后,過了好長一段時間,telnet 才斷開連接?
- 客戶端發(fā)送 SYN 包給服務端,服務端收到后,回了個 SYN、ACK 包給客戶端,此時服務端的 TCP 連接處于
SYN_RECV
狀態(tài); - 客戶端收到服務端的 ?SYN、ACK 包后,給服務端回了個 ACK 包,此時客戶端的 TCP 連接處于
ESTABLISHED
狀態(tài); - 由于服務端配置了防火墻,屏蔽了客戶端的 ACK 包,所以服務端一直處于
SYN_RECV
狀態(tài),沒有進入 ?ESTABLISHED
狀態(tài),tcpdump 之所以能抓到客戶端的 ACK 包,是因為數(shù)據(jù)包進入系統(tǒng)的順序是先進入 tcpudmp,后經(jīng)過 iptables; - 接著,服務端超時重傳了 SYN、ACK 包,重傳了 5 次后,也就是超過 tcp_synack_retries 的值(默認值是 5),然后就沒有繼續(xù)重傳了,此時服務端的 TCP 連接主動中止了,所以剛才處于 SYN_RECV 狀態(tài)的 TCP 連接斷開了,而客戶端依然處于
ESTABLISHED
狀態(tài); - 雖然服務端 TCP 斷開了,但過了一段時間,發(fā)現(xiàn)客戶端依然處于
ESTABLISHED
狀態(tài),于是就在客戶端的 telnet 會話輸入了 123456 字符; - 此時由于服務端已經(jīng)斷開連接,客戶端發(fā)送的數(shù)據(jù)報文,一直在超時重傳,每一次重傳,RTO 的值是指數(shù)增長的,所以持續(xù)了好長一段時間,客戶端的 telnet 才報錯退出了,此時共重傳了 15 次。
- 服務端在重傳 SYN、ACK 包時,超過了最大重傳次數(shù)
tcp_synack_retries
,于是服務端的 TCP 連接主動斷開了。 - 客戶端向服務端發(fā)送數(shù)據(jù)包時,由于服務端的 TCP 連接已經(jīng)退出了,所以數(shù)據(jù)包一直在超時重傳,共重傳了 15 次, telnet 就斷開了連接。
TCP 第一次握手的 SYN 包超時重傳最大次數(shù)是由 tcp_syn_retries 指定,TCP 第二次握手的 SYN、ACK 包超時重傳最大次數(shù)是由 tcp_synack_retries 指定,那 TCP 建立連接后的數(shù)據(jù)包最大超時重傳次數(shù)是由什么參數(shù)指定呢?TCP 建立連接后的數(shù)據(jù)包傳輸,最大超時重傳次數(shù)是由
tcp_retries2
指定,默認值是 15 次,如下:$?cat?/proc/sys/net/ipv4/tcp_retries215
如果 15 次重傳都做完了,TCP 就會告訴應用層說:“搞不定了,包怎么都傳不過去!”
那如果客戶端不發(fā)送數(shù)據(jù),什么時候才會斷開處于 ESTABLISHED 狀態(tài)的連接?這里就需要提到 TCP 的 ?;顧C制。這個機制的原理是這樣的:定義一個時間段,在這個時間段內(nèi),如果沒有任何連接相關(guān)的活動,TCP 保活機制會開始作用,每隔一個時間間隔,發(fā)送一個「探測報文」,該探測報文包含的數(shù)據(jù)非常少,如果連續(xù)幾個探測報文都沒有得到響應,則認為當前的 TCP 連接已經(jīng)死亡,系統(tǒng)內(nèi)核將錯誤信息通知給上層應用程序。在 Linux 內(nèi)核可以有對應的參數(shù)可以設置保活時間、保活探測的次數(shù)、?;钐綔y的時間間隔,以下都為默認值:net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75??
net.ipv4.tcp_keepalive_probes=9
- tcp_keepalive_time=7200:表示?;顣r間是 7200 秒(2小時),也就 2 小時內(nèi)如果沒有任何連接相關(guān)的活動,則會啟動?;顧C制
- tcp_keepalive_intvl=75:表示每次檢測間隔 75 秒;
- tcp_keepalive_probes=9:表示檢測 9 次無響應,認為對方是不可達的,從而中斷本次的連接。
實驗三的實驗小結(jié)在建立 TCP 連接時,如果第三次握手的 ACK,服務端無法收到,則服務端就會短暫處于
SYN_RECV
狀態(tài),而客戶端會處于 ESTABLISHED
狀態(tài)。由于服務端一直收不到 TCP 第三次握手的 ACK,則會一直重傳 SYN、ACK 包,直到重傳次數(shù)超過 tcp_synack_retries
值(默認值 5 次)后,服務端就會斷開 TCP 連接。而客戶端則會有兩種情況:- 如果客戶端沒發(fā)送數(shù)據(jù)包,一直處于
ESTABLISHED
狀態(tài),然后經(jīng)過 2 小時 11 分 15 秒才可以發(fā)現(xiàn)一個「死亡」連接,于是客戶端連接就會斷開連接。 - 如果客戶端發(fā)送了數(shù)據(jù)包,一直沒有收到服務端對該數(shù)據(jù)包的確認報文,則會一直重傳該數(shù)據(jù)包,直到重傳次數(shù)超過
tcp_retries2
值(默認值 15 次)后,客戶端就會斷開 TCP 連接。