tcpip必須知道的十個問題(TCPIP詳解第14章超時與重傳)
2023-09-13 10:32:49
14.1 引言到目前為止,我們並沒有過多的討論效率和性能問題,我們主要關心的是運行的正確性。本章及接下來的兩章,我們不僅關注TCP執行的基本任務,還會關注TCP執行效率。TCP使用其下面的網絡層(IP層,可能會丟包、重複、包重排序)為兩個應用程式間提供了可靠的數據傳輸服務。為了提供無誤的數據交換,TCP重發它認為已經丟失的數據,為了確定哪數據需要重發,TCP依賴接收方到發送方的連續確認流,當數據段或確認丟失時,TCP重傳未確認的數據。TCP有兩種獨立的機制來完成重傳,一種基於時間,另一種基於確認信息的構成,第二種方法通常比第一種更高效。
TCP發送數據時會設置一個計時器,如果計時器到期時沒有收到確認,就會觸發超時重傳或基於計時器的重傳。超時發生在被稱為重傳超時(RTO)的時間間隔之後。還有另外一種重傳稱之為快速重傳,快速重傳不會有任何延遲。當TCP累積確認未能隨著不斷收到的ACK而增長,或者ACK攜帶選擇確認信息(SACK)表明接收方有亂序報文時,快速重傳基於上述規則推斷出現丟包。通常,當發送方認為接收方丟失數據時,需要在發送新數據(未發送過的)和重傳之間做出選擇。本章將仔細研究TCP如何判斷報文段丟失以及丟失時發送什麼數據,發送多少數據的問題我們推遲到第16章再討論,到時我們將討論懷疑有丟包時常調通常調用的擁塞控制過程。這裡,我們研究如何根據連接的往返時間(RTT)來設置RTO、基於計時器的重傳機制以及TCP的快速重傳機制如何工作的。我們還將討論如何使用SACK來幫助TCP確定接收方丟失了哪些數據、IP包重排序及重複對TCP行為的影響以及TCP重傳時改變包大小的方式。我們還將簡要介紹一些可以用來欺騙TCP,使TCP過分積極和被動的攻擊方法。
14.2 簡單的超時與重傳舉例我們已經見過了一些超時和重傳的例子。(1)第8章的ICMP目標不可達(埠不可達)示例中,採用UDP的TFTP客戶端使用一個簡單的(且低效)超時和重傳策略:TFTP認為5秒是一個合適的超時時間間隔,每5秒重傳一次。(2)第13章[h1] 中嘗試連接到一個不存在的主機時,TCP試圖建立連接時,它在每次重傳SYN報文段時使用越來越長的延遲。(3)第3章中,可以看到乙太網遇到衝突時會發生什麼。所有這些機制都是計時器到期引發的。
我們先看一下TCP使用的基於計時器的重傳策略。先建立一個連接,發送一些數據來驗證一切正常,然後斷開連接的一端,再發送一些數據並觀察TCP如何處理。這個案例中,我們使用Wireshark來查看連接的過程(如圖 14‑1所示)。
圖 14‑1 TCP超時和重傳機制的一個簡單示例。第1次重傳發生在42.954,緊接著在43.374、44.215、45.895和49.255發生重傳。連續重傳的間隔分別為206ms、420ms、841ms、1.68s和3.36s,這些時間表明同一報文段連續重傳的超時時間加倍。
報文段1、2、3為正常的TCP連接建立握手,當Web伺服器完成連接建立後,它靜靜的等待Web請求。在發起請求之前,我們隔離(斷開)伺服器主機。客戶端輸入如下:
Linux% telnet 10.0.0.10 80
Trying 10.0.0.10...
Connected to 10.0.0.10.
Escape character is '^]'.
GET / HTTP/1.0
Connection closed by foreign host.
這個請求無法發送到伺服器,所以它在客戶端的TCP隊列中保留了很長時間。在此期間,客戶端的netstat命令輸出表明隊列不是空的:
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 18 10.0.0.9:1043 10.0.0.10:www ESTABLISHED
可以看到發送隊列中有18個字節等待被發送到Web伺服器,這18個字節包括前面請求中顯示的字符以及兩組回車換行符。輸出的其它細節(包括地址和狀態信息)將在下面說明。
報文段4是客戶端第一次發送Web請求,時間為42.748s。緊接著的下一次嘗試在0.206s後即42.954s。然後0.420s後即43.374s又發起了一次嘗試。其它的重試(重傳)發生在44.215s、45.895s和49.255s,間隔分別為0.841s、1.680s和3.360s。
每次重傳間隔時間加倍稱為二進位指數退避(binary exponential backoff),我們在第13章TCP嘗試建立連接失敗時看到過,稍後我們會詳細討論它。如果我們計算初始請求到連接終止所用的時間,大約是15.5分鐘,然後,客戶端會顯示如下信息:
Connection closed by foreign host.
邏輯上講, TCP有兩個閾值來確定重傳同一個報文段持續多長時間,這2個閾值在主機需求RFC[RFC1122]中有描述,我們在第13章中簡要地提到了它們。閾值R1表示TCP向IP層傳遞「消極建議」前(如重新評估正在使用的IP路徑)嘗試的次數(或等待時間),閾值R2(大於R1)表示TCP應該放棄連接的時機,建議R1和R2應分別至少設置為3次重傳和100秒。對於連接建立(發送SYN段),閾值可能和數據報文段的設置不同,SYN報文段的R2值要求至少為3分鐘。
在Linux中,正常數據段的R1和R2值可以由應用程式更改,也可以分別使用系統級的配置變量net.ipv4.tcp_retries1和net.ipv4.tcp_retries2更改,單位為重傳次數,而不是以時間為單位。tcp_retries2的默認值是15,大約對應13-30分鐘,具體取決於連接的RTO。tcp_retries1的默認值是3。對於SYN報文段,net.ipv4.tcp_syn_retries和net.ipv4.tcp_synack_retries限制了SYN報文段的重傳次數,它們的默認值是5(大約180秒)。Windows也有許多影響TCP行為的變量,包括R1和R2的值,可以通過以下註冊表項[WINREG]修改這些值:
HKLM\System\CurrentControlSet\Services\Tcpip\Parameters
HKLM\System\CurrentControlSet\Services\Tcpip6\Parameters
TcpMaxDataRetransmissions的值對應Linux中tcp_retries2的值,默認值為5。即使是目前我們看到的簡單重傳的例子,也需要TCP為重傳計時器設置超時值,此值決定發送數據後等待ACK多長時間。若TCP只在靜態環境中使用,為超時值選擇一個合適的值是比較容易的。但TCP需要運行在各種各樣的環境下,且環境可能隨時時間的推移而變化,因此TCP需要根據當前的狀況來確定超時值。例如,如果一個網絡的鏈路故障,流量重新路由,RTT就會改變(可能變化很大),換句話說,TCP需要動態的確定RTO,我們接下來考慮這個問題。
14.3 設置RTOTCP超時和重傳過程的原理是如何根據給定連接的RTT測量值設置RTO。如果TCP早於RTT重傳報文段,可能會將不必要的重複數據注入網絡。相反,如果它延遲發送時間比RTT大得多,那麼當數據丟失時,網絡整體利用率(和單個連接的吞吐量)就會下降。RTT測量會使事情變得更加複雜,因為RTT會隨著時間的推移而改變,因為路由和網絡使用會發生變化,TCP必須追蹤這些變化並相應地修改其超時時間以保持良好的性能。
因為TCP在收到數據後發送確認,所以可以發送一個具有特定序號的單字節,並測量收到能覆蓋該序號的確認所需的時間,每一個這樣的測量被稱為RTT採樣。TCP所面臨的挑戰是,根據給定一組隨時間變化的樣本,建立RTT取值範圍的良好估計值,第二步是如何根據這些值設置RTO,得到這個「正確的RTO」對於TCP的性能非常重要。
每個TCP連接的RTT是獨立估算的,並且重傳計時器會為任何消耗序號(包括SYN和FIN報文段)的在途數據計時。多年來,人們一直在研究如何設置這個計時器,偶爾也會做出一些改進。本節將探討計算RTO方法演進過程中一些重要裡程碑。我們從第一個(「經典」)方法開始,詳細介紹見[RFC0793]。
14.3.1 經典方法最初的TCP規範[RFC0793]採用如下公式得到平滑的RTT估計(稱為SRTT):
SRTT基於現有的SRTT值和新的樣本值RTTs進行更新,常量 為平滑或比例因子,推薦值為0.8-0.9。每次得到新的樣本值時SRTT就會更新, 使用推薦值的情況下,新的估計值80%-90%來自前一次的估計值,10%-20%來自新的樣本值,這種類型的平均也稱為指數加權移動平均(EWMA)或低通濾波器。該方法容易實現,因為它只需要保存先前的SRTT就可得到新的估計值。
SRTT隨著RTT的變化而變化,[RFC0793]建議將RTO按如下方式設置:
其中 為延遲變化因子,推薦值為1.3-2.0。ubound是RTO的上邊界(建議,如1分鐘),lbound是RTO的下邊界(建議,如1秒)。我們將此方法稱為經典方法,它通常會將RTO設置為1秒或2倍的SRTT大小,對於相對穩定的RTT分布來說,此方法效果不錯。然而,當TCP運行在RTT變化較大的網絡(如早期的分組無線網絡)上時,性能就沒那麼好了。
14.3.2 標準方法在[J88]中,Jacobson進一步詳細說明了經典方法存在的問題——按[RFC0793]設置的計時器不能適應RTT大的波動(特別是當實際RTT比預期大得多時,會導致不必要的重傳),RTT樣本值增大表明網絡已經過載,不必要的重傳會進一步加重網絡負擔。
為了解決這個問題,需要對計算RTO的方法進行改進以適應RTT的大的波動,這可以通過追蹤RTT的變化和均值估計值來實現。與僅將RTO設置為均值的常數倍( )相比,基於均值估計值和RTT的變化得到的RTO能提供更好的超時響應。
[J88]中圖5和圖6展示了採用[RFC0793]方法得到的RTO值與考慮RTT變化得到的RTO的值的對比情況,如果將TCP的RTT採樣看作一個統計過程,同時對均值和方差(或標準差)進行估計能更好的預測未來值。對RTT取值範圍的良好預測有助於TCP在大多數情況下設置一個即不太大也不太小的RTO值。
如Jacobson所述,平均差(mean deviation,MD)是一種較好的對標準差的逼近,但平均差更容易、更快。計算標準差需要計算方差的平方根,對於快速TCP來說代價太大(這並不是全部,參見[G04]中關於「辯論」的有趣歷史)。因此我們需要對均值和平均差同時進行估計,對於每個RTT測試值M(前面稱為RTTs)採用如下公式計算均值和平均差[h2] :
srtt替代了前面的SRTT,rttvar為平均差的指數加權移動平均(EWMA),rttvar替代了 用於計算RTO。在計算機上實現時,這組方程可以寫成另外一種操作步驟較少的形式:
如前所述,srtt為均值的指數加權移動平均(EWMA),rttvar為絕對誤差 |Err| 的指數加權移動平均(EWMA),Err為測量值M和當前當前RTT估計值srtt的差值。srtt和rttvar都用於計算RTO,增益g為新的RTT樣本M在均值srtt中所佔的權重,設置為1/8。增益h為新的平均差樣本(新樣本M與當前均值srtt的絕對差)在平均差估計值rttvar中的權重,設置為1/4。偏差的增益越大,RTT變化時RTO上升的越快。g和h的值選擇為2的(負)次冪,允許在計算機上使用帶有移位和加法運算的定點整數算法來實現所有計算,而不用乘法和除法。
注意
[J88]在RTO計算中使用的2*rttvar,但經過進一步研究,[J90]將其改為了4*rttvar,BSD Net/1實現採用了該值並最終形成了標準[RFC6298]。
比較經典方法與Jacobson方法,RTT均值計算是類似的( )但使用了不同的增益。此外,Jacobson方法的RTO計算依賴平滑RTT和平滑偏差,而經典方法採用平滑RTT的倍數,這是今天許多TCP實現計算RTO的基礎。由於[RFC6298]採用了Jacobson方法作為基礎,我們稱之為標準方法,在[RFC6298]有一些細微改進,我們現在討論這些改進。
14.3.2.1 時鐘粒度與RTO邊界在RTT測量過程中,TCP的「時鐘」始終處於運行狀態。和初始序號一樣,實際的TCP連接的時鐘不是從0開始且精度有限。TCP時鐘通常是一個變量值並隨著系統時間的前進而更新,但不一定是一對一的同步更新。TCP時鐘「滴答」的長度稱為粒度,通常,該值比較大(約500ms),但最近的實現使用了更細粒度的時鐘(例如,Linux為1ms)。
時鐘粒度影響RTT測量細節和RTO的設置,在[RFC6298]中,時鐘粒度用於優化RTO的更新,並給RTO設置了一個下界,公式如下:
其中G為計時器粒度,1000ms為整個RTO的下界([RFC6298]中規則(2.4)的建議值),因此,RTO總是至少為1s。可以使用可選的上界,若使用上界則值至少為60s。
14.3.2.2 初始值我們已看到估計器如何隨著時間而更新的,但我們還需要知道如何設置初始值。第一次SYN交換前,TCP不知道RTO的值應設置為多少,也不知道估計器的初始值,除非是系統提供了此信息(有些系統在轉發表中緩存這個信息,見14.9節)。根據[RFC6298],雖然初始SYN報文段的超時時間為3s,但RTO的初始值應該為1s,當接收到第一個RTT測試量M時,估計器按如下方式初始化:
現在我們已經知道了估計器如何初始化及更新的細節了,這個過程依賴獲得RTT樣本,這個過程看起來很簡單,但並不總是這樣。
14.3.2.3 重傳二義性與Karn算法當數據包重傳時測量RTT樣本會出現問題,假設傳輸了一個數據包,出現超時,該數據包被重傳並收到了確認,這個ACK是第一次發送數據包的確認還是重傳的數據包的確認?這是重傳二義性的一個例子。除非使用了時間戳選項,否則ACK只提供確認號而無法指明對哪一個(第一次發送還是重傳的)報文的確認。
論文[KP87]指出,當發生超時和重傳時,最後收到重傳數據的確認時,不能更新RTT估計值,這是Karn算法的「第一部分」。它通過排除二義性的數據來解決確認二義性問題,這是[RFC6298]中的要求。
如果在設置RTO時完全忽略重傳段,很可能會將網絡提供的一些有用信息忽略(例如,網絡當前無法快速發送數據包)。在這種情況下,通過降低重傳速率來降低網絡負載是有益的,至少在數據包不再丟失之前是這樣,這個推論是我們在圖 14‑1中看到的指數退避行為的基礎。
TCP計算RTO時使用一個退避係數,每當重傳計時器超時時,退避係數翻倍,翻倍一直持續到收到未重傳的段的確認為止,此時,退避係數被設置為1(即,二進位指數退避被取消),重傳計時器回歸到正常值。重傳時退避係數翻倍是Karn算法的「第二部分」。注意,當TCP超時時,它還會調用擁塞控制過程改變其發送速率(擁塞控制將在第16章詳細討論)。Karn算法實際上包括兩部分,引用1987年的論文[KP87]如下:
已發送多次(即至少重傳一次)的數據包的確認到達時,忽略基於此數據包的任何RTT測量,從而避免重傳二義性問題。此外,當前數據包的RTO會應用於下一個包,只有當數據包(或隨後的數據包)被確認且中間沒有重傳時,才會根據SRTT重新計算RTO。
Karn算法作為TCP實現中必需的方法已經有一段時間了(自[RFC1122]起),但有一個例外,當使用TCP時間戳時(見第13章),可以避免確認二義性問題,此時Karn算法的第一部分不再適用。
14.3.2.4 使用時間戳選項進行RTT測量(RTTM)時間戳選項(TSOPT)除了作為我們在第13章中看到的PAWS算法基礎外,還可以用於RTT測量(RTTM)[RFC1323]。時間戳選項的基本格式已經在第13章做了描述,它允許發送方在TCP報文段中包含一個32位的數字,並在其對應的確認中返回。
初始SYN報文段的時間戳選項中攜帶時間戳值(TSV),並在SYN ACK報文段的時間戳選項TSER中返回此時間戳值,以此來設定srtt、rttvar和RTO的初始值。由於初始SYN報文段被認為是數據(即如果丟失會重傳且佔用一個序號),因此可以測量它的RTT,其它報文段中也攜帶有時間戳選項,所以可以持續估計連接的RTT。這看起來很簡單,但由於TCP並不是對它接收到的每一個報文段進行一次確認,所以會變得比較複雜。例如,當傳輸大量數據時,TCP通常每兩個報文段返回一個ACK(見第15章)。另外,當數據丟失、重排序或成功重傳時,TCP的累積確認機制意味著一個報文段和其ACK間並沒有固定的對應關係。為了處理這些問題,使用時間戳選項的TCP(目前大多數,包括Linux和Windows)使用如下算法獲取RTT樣本:
1. TCP發送方在它發送的每個TCP段的時間戳選項的TSV部分攜帶一個32位的時間戳,這個值是報文發送時的TCP時鐘值。
2. TCP接收方記錄(通常保存在TsRecent變量中)收到的TSV並在它生成的下一個ACK中發送,並記錄它發送的最後一個確認的確認號(通常在LastACK變量中)。回想一下,確認號表示接收方(即ACK的發送方)期望接收的下一個序號。
3. 當一個新報文段到達時,如果它包含的序號與LastACK相匹配(即此報文段是期望的報文段),則將新報文段的TSV值保存在TsRecent中。
4. 接收方無論什麼時候發送ACK都要包含時間戳選項,並將TsRecent變量值放入時間戳選項TSER部分。
5. 發送方收到使窗口移動的ACK時,使用當前TCP時鐘減去TSER的值,得到的差用來更新RTT估計值。
FreeBSD、Linux以及更高版本的Windows默認啟用時間戳選項。在Linux中,系統配置變量net.ipv4.tcp_timestamps標識是否使用時間戳(0代表不使用,1代表使用)。在Windows中,由註冊表Tcp1323Opts的值來控制,如果值為0,時間戳禁用;如果值為2,時間戳啟用,該鍵沒有默認值(默認註冊表中不存在),默認如果對方發起連接時使用時間戳則就使用時間戳選項。
14.3.3 Linux採用的方法Linux的RTT估計過程與標準方法略有不同,它採用的時鐘粒度為1ms,時間戳的精度也為1ms,與其它實現相比,其時間粒度更細。採用更頻繁的RTT測量及更細的時鐘粒度有助於更準確的估計RTT,但rttvar的值也會隨著時間的推移而變小[LS00],因為當累積了足夠多的均差樣本時,它們會相互抵消,這是設置與標準方法不同的RTO的一個考慮因素。另一個與標準方法在RTT樣本明顯小於現有RTT的估計srtt時增加rttvar的方式有關。
為了更好地理解第二個問題,回想一下RTO通常設置為srtt 4(rttvar),因此,無論新的RTT樣本是大於還是小於srtt,任何導致rttvar變大的因素都會導致RTO變大。這有點違反直覺——如果實際RTT顯著下降,則RTO就不應該增加。Linux通過限制顯著下降RTT對rttvar的影響來解決這一問題。現在,我們詳細介紹Linux設置RTO的方法,這個方法解決了剛才所說的兩個問題。
與標準方法一樣,Linux使用了srtt和rttvar變量,同時還使用了兩個新變量mdev和mdev_max。值mdev使用前面描述的rttvar的標準算法記錄平均差的運行估計,mdev_max記錄了最後一次測量的RTT中所看到的mdev的最大值且不允許小於50ms。此外,rttvar會定期更新以確保它至少於mdev_max一樣大,因此,RTO永遠不會低於200ms。
注意
可以修改RTO最小值,TCP_RTO_MIN是一個內核配置常量,可以在重新編譯和安裝內核前更改。一些Linux版本允許使用ip route命令更改此常量。在數據中心網絡使用TCP時,RTT可能只有幾微秒,若RTO最小為200ms會導致性能嚴重下降,因為本地交換機丟包後TCP恢復緩慢,這就是所謂的TCP incast問題。針對這個問題有多種解決方案,包括修改TCP計時器粒度和將RTO最小值設為微秒級[V09],不建議在全球網際網路上使用這樣小的RTO最小值。
當最大值mdev_max增加時,Linux使用mdev_max的值更新rttvar,它總是將RTO設置為srtt 4(rttvar),並且確保RTO的值不會超過TCP_RTO_MAX,後者的默認值為120秒,詳見[SK02]。可以在圖 14‑2中看到這一過程細節,該圖還展示了時間戳選項的使用方式。
圖 14‑2 TCP時間戳選項攜帶發送方時鐘副本,ACK會將此副本返回給發送方,發送方使用兩者差值(當前時鐘減去返回的時間戳)來更新它的srtt和rttvar的估計值。為了清晰起見,這裡只描述了一組時間戳。在Linux系統中,rttvar的值被限制為至少50ms,RTO的下界為200ms。
在圖 14‑2中,TCP連接在啟動時使用了時間戳選項。發送方系統為Linux 2.6,接收方系統為FreeBSD 5.4。為清晰起見,序號和時間戳取的相對值。此外,只顯示了發送方的時間戳,了為使數字易讀,圖中也沒有嚴格按照時間比例來畫。根據本例中的初始RTT測量值,Linux進行如下更新:
• srtt = 16ms
• mdev = (16/2)ms = 8ms
• rttvar = mdev_max = max(mdev, TCP_RTO_MIN) = max(8, 50) = 50ms
• RTO = srtt 4(rttvar) = 16 4(50) = 216ms
初始SYN報文段交換後,發送方對接收方的SYN返回了ACK,接收方用一個窗口更新作為響應。由於這些包不包含數據[h3] (SYN或FIN位被置位的包算作數據),它們不會被計時。當發送方收到窗口更新時也不會執行RTT估計更新。不包含數據的段不能被TCP可靠的傳輸,這意味著如果包丟失不會被重傳,這些類型的段不需要設置重傳計時器,因為它們永遠不會被重傳。
注意
值得一提的是,TCP選項本身不會被重傳或可靠傳輸,只有當選項專門放到數據段(包括SYN和FIN報文段)時丟失時才會重傳,這只是一個副作用。
應用程式第一次寫操作時,發送方發送了2個報文段,每個段的TSV值為127,因為這兩個段的發送間隔小於1ms(發送方TCP時鐘粒度),所以這兩個段的TSV值是相同的。當發送方以這種方式連續發送多個報文段時,很容易看到TCP時鐘不變或改變很小的情況。
接收方LastACK變量保存著接收方最後發送的ACK號,本例中,LastACK從1開始,因為最後發送的ACK是連接建立期間發送的SYN ACK包。當第一個完整的(full-size)段到達接收方時,它的序號與LastACK匹配,所以TsRecent變量值更新為收到的報文段的TSV值127,第二個報文段的序號與LastACK不匹配,所以不會更新TsRecent變量值。接收方對到達的包進行響應發送ACK,其時間戳選項TSER部分包含了TsRecent的值,並且ACK的發送也導致了接收方更新LastACK變量的值為確認號2801。
當這個ACK到達發送方時,TCP可以進行第二個RTT樣本測量,拿當前TCP時鐘減去到達包的TSER值得到測量值m:m=223-127=96,根據該測量值,TCP更新連接變量如下:
• mdev = mdev (3/4) |m-srtt|(1/4) = 8(3/4) |96-16|(1/4) = 26ms
• mdev_max = max(mdev_max, mdev) = max(50, 26) = 50ms
• srtt = srtt (7/8) m(1/8) = 16(7/8) 96(1/8) = 14 12 = 26ms
• rttvar = mdev_max = 50ms
• RTO = srtt 4(rttvar) = 26 4(50) = 226ms
如前所述,Linux TCP對經典的RTT估計算法有幾處改進。經典算法提出時,TCP時鐘的典型粒度是500ms並且時間戳也沒有廣泛使用,它只是每個窗口取一個RTT樣本並更新相應的估計值,如果時間戳不可用或未啟用則依然採用此方法。
如果每個窗口只採集一個RTT樣本,則rttvar項的變化相對較慢。利用時間戳和對每個包的測量,可以得到更多的樣本值。同一個窗口內的數據每個包的RTT通常都是不一樣的,在較短的時間內(例如,當窗口很大時)進行如此多的測量會導致均差估計變小(根據大數定律[F68]接近於0)。為了解決這個問題,Linux維護mdev變量作為平均差估計,但基於rttvar來設置RTO,rttvar為數據窗口期間mdev的最大值且至少為50ms,僅當進入下個窗口時,rttvar才允許減小一次。
標準方法中rttvar項使用了一個較大的權重(係數4),因此即使當RTT減小時,RTO也傾向於變大。時鐘粒度較粗時(例如500ms),這可能影響相對較小,因為RTO可以取的值非常少。然而,當時鐘粒度較細時,比如Linux使用的1毫秒,就可能會出現問題。為了解決這個問題,Linux會處理RTT下降的情況,如果新樣本小於估計的RTT的範圍的下界(srtt – mdev),那麼新樣本會被賦予更小的權重。完整的關係式如下:
if(m quit
通過tcpdump查看結果:
1 | 19:51:47.674418 IP 10.0.0.7.1029 > 169.229.62.97.6666: P 1:14(13)[h8] ack 1 win 5840 |
2 | 19:51:47.788992 IP 169.229.62.97.6666 > 10.0.0.7.1029: . ack 14 win 58254 |
3 | 19:52:35.130837 IP 10.0.0.7.1029 > 169.229.62.97.6666: FP 29:36(7)[h9] ack 1 win 5840 |
4 | 19:52:35.146358 IP 169.229.62.97.6666 > 10.0.0.7.1029: . ack 14 win 58254 |
5 | 19:52:39.414253 IP 10.0.0.7.1029 > 169.229.62.97.6666: FP 14:36(22)[h10] ack 1 win 5840 |
6 | 19:52:39.429228 IP 169.229.62.97.6666 > 10.0.0.7.1029: . ack 37 win 58248 |
7 | 19:52:39.429696 IP 169.229.62.97.6666 > 10.0.0.7.1029: F 1:1(0) ack 37 win 58254 |
8 | 19:52:39.430119 IP 10.0.0.7.1029 > 169.229.62.97.6666: . ack 2 win 5840 |
追蹤結果中刪除了初始的SYN交換,前兩個報文段為數據字符串hello there和它的確認。追蹤結果中的下一個包沒有按順序來:它以序號29開始並包含字符串and 3(7個字節),此包的確認包ack為14,並包含一個相對序號為{29,36}的SACK塊,中間序號的字符已經丟失。TCP重傳了這些字符,但使用了一個更大的包,此包序列為14:36。從此過程可以看到序號14包重傳如何重新分組形成一個更大的包(大小為22位元組)。有趣的是,這個包重複了SACK塊中的數據,FIN位欄位也已被置位,表示這是連接的最後一個數據。
14.11 與TCP重傳相關的攻擊有一類DoS攻擊被稱為低速率DoS攻擊[KK03]。在這種攻擊中,攻擊者向網關或主機發送大量流量,導致被攻擊系統重傳超時。若攻擊者能夠預測TCP何時會嘗試重傳,那麼在TCP每次重傳嘗試時發送大量流量。因此,被攻擊的TCP認為網絡擁塞,根據Karn算法TCP不斷退避其RTO,限制其發送速率直到接近於零,並且只能接收到很少的有效的網絡流量。解決這種類型攻擊的方案是添加RTO的隨機性,使攻擊者難以預測重傳發生的準確時間。
一種與DoS攻擊相關但不同的攻擊是放慢被攻擊者報文段的發送,從而造成過高估計RTT,這樣做會使被攻擊TCP在數據包丟失時不那麼積極地進行重傳。與此相反的攻擊也是可能的:當數據已經發送但實際上還未到達接收方時,攻擊者偽造ACK進行確認,在這種情況下,被攻擊TCP認為連接RTT比實際情況小得多,導致TCP過分積極生成大量不必要的重傳。
14.12 總結本章詳細介紹了TCP的超時和重傳策略。第一個示例展示了當TCP有數據包要發送時拔掉網線導致重傳計時器發起基於超時的重傳的情況,每次重傳等待的時間間隔是前一次的兩倍,這是Karn的算法第二部分二進位指數退避算法的結果。
TCP測量RTT,使用這些測量值跟蹤平滑RTT估計和平均差估計,並使用這兩個估計計算新的重傳超時值。如果沒有時間戳選項,TCP在每個數據窗口只能測量一個RTT。Karn的算法通過不使用已丟失報文段的RTT測量值來消除重傳二義性問題。今天,大多數TCP使用時間戳選項,它允許對每個報文段分別計時,即使是在包重排序或包重複的情況下時間戳選項也能正常工作。
我們研究了快速重傳算法,它可以在計時器未超時的情況下被觸發。對於TCP來說,這是填補接收方空洞(由於丟包造成)的最有效的方法(也是最常用的方法)。使用SACK可以改善快速重傳,這些ACK中攜帶的額外信息允許支持SACK的TCP發送方在每個RTT時間內填補多個空洞,這樣做在某些情況下可以提高性能。
如果RTT的估計值低於連接的實際值,就可能發生偽重傳。在這種情況下,如果TCP等待的時間稍微長一些,就不會發生(不必要的)重傳。已經提出了很多算法來檢測TCP的偽超時,DSACK方法要求重複的報文段到達接收方才可以使用。Eifel檢測算法依賴於TCP時間戳,但比DSACK響應更快,因為它根據超時前發送的報文段返回的ACK來檢測偽超時。F-RTO是另一種與Eifel類似的算法,但它不需要時間戳,它使得發送方檢測出偽超時後發送新數據。所有檢測算法都可以與響應算法相結合,Eifel響應算法是到目前為止描述的一個主要算法,如果延遲大幅增加,它可以重新設置RTT和RTT方差估計(或者「撤銷」TCP將在超時時執行的任何更改)。
我們還研究了如何緩存不同連接間TCP狀態、TCP如何對數據重新分組、以及一些欺騙TCP使其行為出現過分被動或過分積極的攻擊,在第16章(我們將研究擁塞控制過程)將看到更多關於這些攻擊造成的影響。
[h1]第13.2.5節
[h2]srtt為均值估計值,rttvar為平均差估計值
[h3]SYN交換後的包,第3個和第4個包
[h4][23801,25201),1400位元組
[h5][26601,28001) ,1400位元組
[h6]Wireshark中為天藍色
[h7]如第一個報文段中發送SACK塊為[a,b],其中[a,b]為最近收到的報文段序號範圍。第二個報文段中發送SACK塊為[c,d]、[a,b],其中[c,d] 為最近收到的報文段序號範圍。第三個報文段中發送SACK塊為[e,f]、[c,d]、[a,b],其中[e,f] 為最近收到的報文段序號範圍。
[h8]hello there\r\n
[h9]and 3\r\n
[h10]line number 2\r\nand 3\r\n
,