1. 程式人生 > >TCP-超時與重傳

TCP-超時與重傳

主要內容:

  1. 超時與重傳簡單例項(見TCP/IP詳解)
  2. 設定超時重傳方法
  3. 基於計時器重傳
  4. 快速重傳
  5. 帶選擇確認的重傳
  6. 偽超時與重傳
  7. 包失序與包重複
  8. 目的度量
  9. 重新組包
  10. 與TCP重傳相關的攻擊

TCP重傳機制

由於下層IP網路層或則路由器可能出現丟失、重複、失序包的情況、TCP要提供可靠資料傳輸服務必須cover住這些丟包重包異常情況並能夠做出正常的處理。

注意,接收端給傳送端的Ack確認只會確認最後一個連續的包,比如,傳送端發了1,2,3,4,5一共五份資料,接收端收到了1,2,於是回ack 3,然後收到了4(注意此時3沒收到),此時的TCP會怎麼辦?我們要知道,因為正如前面所說的,SeqNum和Ack是以位元組數為單位,所以ack的時候,不能跳著確認,只能確認最大的連續收到的包,不然,傳送端就以為之前的都收到了。

TCP的重傳指:重傳尚未確認的資料,TCP有兩套獨立的機制來完成重傳:一是基於時間的,二是基於確認資訊的構成。第二種比第一種更加高效。

(1)基於時間重傳:TCP在傳送資料的時候回設定一個計時器,若計時器超時任未收到資料的確認資訊,則會引發相應的超時或則計時器的重傳操作,計時器超時稱為重傳超時RTO(Retransmission Timeout)

(2)基於確認資訊的構成重傳稱為快速重傳:在沒有發生延時的情況下,若TCP積累確認無法返回新的ACK或則當ACK包含的選擇確認資訊SACK表明出現失序報文段時,快速重傳會推斷出現丟失包。

1.設定超時重傳RTO

TCP超時和重傳的基礎是:根據給定連線的資料傳輸往返延時RTT(Round-Trip Time)來設定RTO。

設定一個合理的RTO對於網路整體利用率至關重要。TCP會在傳輸過程中取樣一些資料傳輸該確認資訊所需要的時間的一些樣本。每個此類的測量結果稱為RTT樣本。TCP首先根據一段時間內的樣本值建立好的估計值,第二步是基於估計值設定RTO。RTO設定得當是保證TCP效能的關鍵。

下面主要介紹一些計算RTO的方法,只做概述,具體演算法可檢視TCP/IP詳解。

經典方法

1)通過下面的公式計算得到平滑的RTT估計值(SRTT):

SRTT <- α(SRTT) + (1-α)RTTs

最新的SRTT是根據現存的SRTT和新的樣本值RTTs計算出來的。 常量α為平滑因子,推薦值是0.8~0.9。這種計算方法叫做指數加權移動平均法或則低通過濾器。

根據前面求的SRTT計算出RTO:

RTO = min(ubound, max(lbound, (SRTT)β))

β是時延離散因子,推薦值1.3~2.0。 ubound是RTO上邊界,lbound是下邊界。這種計算方法就是經典方法。

這種方法缺點就是沒法適應大規模的變動(網路不穩定情況)

標準方法:

具體公式就不給出了,主要說說大致方法論:基於RTT測量值的變化方差、平均值來得到較為精確的估計值RTO。這樣更加能適應RTT變化幅度大的情況。

2. 基於計時器的重傳

TCP傳送端得到了基於時間變化的RTT測量值,就能據此設定RTO,傳送報文段時應確保重傳計時器設定合理。設定計時器前要記錄報文序列號,若及時收到了報文的ACK,就取消計時器。TCP的連線傳送端不斷的設定和取消重傳計時器,如果沒有資料丟失就不會出現計時器超時。

TCP將超時重傳視為相當重要的事件,當發生這種情況時候,通過降低當前資料傳送率來對此進行快速響應。實現有兩種方法:一是基於擁塞控制機制減小發送視窗,二是當一個報文段被再次重傳時候,增大RTO的退避因子。 退避因子正常情況下是1,隨著多次重傳會加倍成2,4,8等等,直到上限。

大多數情況下計時器超時並觸發重傳是不必要的,因為RTO的設定一般大於RTT的2倍,因此基於計時器的重傳會導致網路利用率下降。幸運的是,TCP有另外一種方法檢測和修復丟包,因為該方法不需要觸發計時器超時,所以稱為快速重傳。

3. 快速重傳-Fast Retransmit

快速重傳機制:基於接收端的反饋資訊(ACK)來引發重傳,而非重傳計時器的超時。不以時間驅動,而以資料驅動重傳。也就是說,如果,包沒有連續到達,就ack最後那個可能被丟了的包,如果傳送方連續收到3次相同的ack,就重傳。Fast Retransmit的好處是不用等timeout了再重傳。

比如:如果傳送方發出了1,2,3,4,5份資料,第一份先到送了,於是就ack回2,結果2因為某些原因沒收到,3到達了,於是還是ack回2,後面的4和5都到了,但是還是ack回2,因為2還是沒有收到,於是傳送端收到了三個ack=2的確認,知道了2還沒有到,於是就馬上重轉2。然後,接收端收到了2,此時因為3,4,5都收到了,於是ack回6。示意圖如下:
這裡寫圖片描述

Fast Retransmit只解決了一個問題,就是timeout的問題,它依然面臨一個艱難的選擇,就是,是重傳之前的一個還是重傳所有的問題。對於上面的示例來說,是重傳#2呢還是重傳#2,#3,#4,#5呢?因為傳送端並不清楚這連續的3個ack(2)是誰傳回來的?也許傳送端發了20份資料,是#6,#10,#20傳來的呢。這樣,傳送端很有可能要重傳從2到20的這堆資料(這就是某些TCP的實際的實現)。可見,這是一把雙刃劍。

SACK 方法

另外一種更好的方式叫:Selective Acknowledgment (SACK)(參看RFC 2018),這種方式需要在TCP頭裡加一個SACK的東西,ACK還是Fast Retransmit的ACK,SACK則是彙報收到的資料碎版。參看下圖:

這裡寫圖片描述

這樣,在傳送端就可以根據回傳的SACK來知道哪些資料到了,哪些沒有收到。於是就優化了Fast Retransmit的演算法。當然,這個協議需要兩邊都支援。在 Linux下,可以通過tcp_sack引數開啟這個功能(Linux 2.4後預設開啟)。

這裡還需要注意一個問題——接收方Reneging,所謂Reneging的意思就是接收方有權把已經報給傳送端SACK裡的資料給丟了。這樣幹是不被鼓勵的,因為這個事會把問題複雜化了,但是,接收方這麼做可能會有些極端情況,比如要把記憶體給別的更重要的東西。所以,傳送方也不能完全依賴SACK,還是要依賴ACK,並維護Time-Out,如果後續的ACK沒有增長,那麼還是要把SACK的東西重傳,另外,接收端這邊永遠不能把SACK的包標記為Ack。

注意:SACK會消費傳送方的資源,試想,如果一個攻擊者給資料傳送方發一堆SACK的選項,這會導致傳送方開始要重傳甚至遍歷已經發出的資料,這會消耗很多傳送端的資源。詳細的東西請參看《TCP SACK的效能權衡》

Duplicate SACK – 重複收到資料的問題

Duplicate SACK又稱D-SACK,其主要使用了SACK來告訴傳送方有哪些資料被重複接收了。RFC-2883 裡有詳細描述和示例。下面舉幾個例子(來源於RFC-2883)

D-SACK使用了SACK的第一個段來做標誌,

  • 如果SACK的第一個段的範圍被ACK所覆蓋,那麼就是D-SACK
  • 如果SACK的第一個段的範圍被SACK的第二個段覆蓋,那麼就是D-SACK

示例一:ACK丟包
下面的示例中,丟了兩個ACK,所以,傳送端重傳了第一個資料包(3000-3499),於是接收端發現重複收到,於是回了一個SACK=3000-3500,因為ACK都到了4000意味著收到了4000之前的所有資料,所以這個SACK就是D-SACK——旨在告訴傳送端我收到了重複的資料,而且我們的傳送端還知道,資料包沒有丟,丟的是ACK包。

Transmitted  Received    ACK Sent
Segment      Segment     (Including SACK Blocks)

3000-3499    3000-3499   3500 (ACK dropped)
3500-3999    3500-3999   4000 (ACK dropped)
3000-3499    3000-3499   4000, SACK=3000-3500

示例二,網路延誤

下面的示例中,網路包(1000-1499)被網路給延誤了,導致傳送方沒有收到ACK,而後面到達的三個包觸發了“Fast Retransmit演算法”,所以重傳,但重傳時,被延誤的包又到了,所以,回了一個SACK=1000-1500,因為ACK已到了3000,所以,這個SACK是D-SACK——標識收到了重複的包。

這個案例下,傳送端知道之前因為“Fast Retransmit演算法”觸發的重傳不是因為發出去的包丟了,也不是因為迴應的ACK包丟了,而是因為網路延時了。

Transmitted    Received    ACK Sent
Segment        Segment     (Including SACK Blocks)

500-999        500-999     1000
1000-1499      (delayed)
1500-1999      1500-1999   1000, SACK=1500-2000
2000-2499      2000-2499   1000, SACK=1500-2500
2500-2999      2500-2999   1000, SACK=1500-3000
1000-1499      1000-1499   3000
               1000-1499   3000, SACK=1000-1500

可見,引入了D-SACK,有這麼幾個好處:

1)可以讓傳送方知道,是發出去的包丟了,還是回來的ACK包丟了。

2)是不是自己的timeout太小了,導致重傳。

3)網路上出現了先發的包後到的情況(又稱reordering)

4)網路上是不是把我的資料包給複製了。

知道這些東西可以很好得幫助TCP瞭解網路情況,從而可以更好的做網路上的流控。

Linux下的tcp_dsack引數用於開啟這個功能(Linux 2.4後預設開啟)