一、虛假重傳
在一些情況下,TCP可能會在沒有數據丟失的情況下初始化一個重傳,這種重傳就叫做虛假重傳(Spurious retransmission)。發生虛假重傳的原因可能是包傳輸中重排序、傳輸中發生包復制、ACK確認包傳輸中丟失等等。如果由於鏈路時延變化或者負載變化等因素導致RTT突然變大等原因,TCP的發送端可能還沒收到ACK確認包就已經RTO超時而觸發重傳,這種重傳就叫做虛假超時重傳(Spurious retransmission timeouts)。虛假超時重傳會降低網絡性能,其主要由兩方面的影響,一個是會導致重傳已經發出的但是還沒有收到ACK確認的數據,而這部分數據包的ACK報文可能只是延遲到達而已,因此對應的重傳是不必要的,另一方面是虛假超時后進入慢啟動階段,每當收到一個ACK確認包的時候可以發出兩個數據包,而原來初傳的數據包實際上並沒有丟失,因此增加了網絡負載,違背了包守恆原則。慢啟動和包守恆等內容在后續擁塞控制進行介紹。因此有必要進行檢測處理。對於虛假超時有幾種方法來處理,一般處理方法都是分為兩部分一個分是探測(detection)算法,另外一部分是響應(response)算法。當探測算法探測到一個虛假超時后,再調用響應算法撤銷之前RTO超時的影響。當前探測算法有FRTO、DSACK、Eifel探測算法等,我們這里主要介紹FRTO和DSACK,以對虛假重傳的探測有一定的簡單認識。Eifel探測算法則留到后面擁塞控制的時候進行介紹。響應算法一般主要有兩部分操作,一個是修正RTO值,第二個是撤銷擁塞控制的處理。linux中主要是撤銷擁塞控制並不會去修正RTO。擁塞控制相關內容我們后面內容在進行介紹。
二、FRTO簡介及linux實現介紹
其中FRTO(Forward RTO-Recovery ,也簡稱為F-RTO)就是一種發送端的無效RTO超時重傳檢測方法。在RTO超時重傳了第一個數據包之后,FRTO會檢測之后收到的ACK報文來判斷剛剛的超時重傳是否是虛假重傳(即對端實際上已經接收到了對應的TCP報文,但是發送端仍然RTO超時進行了重傳),然后根據判斷結果來決定接下來是接着進行重傳還是發送新的未發送數據。在傳輸鏈路RTT抖動比較大的場景下(例如無線網絡),FRTO可以有效的避免減少后續的虛假重傳從而提升TCP性能。相比於Eifel、DSACK探測算法,FRTO不需要任何額外的TCP選項支持,而且不需要修改接收端實現。協議中給出了兩種算法一種是基礎的FRTO算法,一種是SACK增強的FRTO算法,linux的FRTO代碼重構后只實現了SACK增強的FRTO算法(增強算法也可以用於普通場景),當/proc/sys/net/ipv4/tcp_frto設置為非0值的時候表示打開FRTO算法。
我們簡單介紹一下linux中FRTO的實現,想深入了解的話請參考RFC5682和linux中的相關代碼。這里涉及到大量擁塞控制的相關狀態(Open、Disorder、Recovery、Loss等等)可以看完擁塞控制再來看這塊。然后我們通過wireshark示例來進一步理解FRTO
先介紹一個概念
RecoveryPoint:在進行RTO超時重傳的時候,當前已發送的數據中的最高系列號,例如發送端發出P1(0-9)、P2(10-19)、P3(20-29)三個TCP報文后如果P1超時重傳,那么此時RecoveryPoint就是29,實際linux內部使用high_seq狀態變量維護記錄的是(RecoveryPoint+1),也就是記錄的30。
1、在RTO超時的時候,TCP發送端進入Loss狀態,判斷是否啟動FRTO過程,如果tcp_frto開啟,當前不是PMTU過程,且不是進入Loss狀態前不是Loss狀態或者Recovery狀態,那么對這次RTO超時開啟FRTO檢驗。接着發送RTO超時重傳報文。
2、如果上一步判斷對當前RTO超時啟動了FRTO過程,那么對於收到的ACK,判斷是否帶有SACK信息,並且SACK確認了RecoveryPoint之前的數據且SACK確認的數據是沒有在第一步重傳的數據。那么認為RTO重傳是虛假重傳,通過兩個操作嘗試撤銷Loss狀態的部分影響,一個是嘗試更改擁塞窗口cwnd,另外一個是標識先前的數據包為非丟失狀態,重傳完,進入Open狀態,並退出FRTO過程,不在執行下面的其他步驟。因為先前的數據包標記為非lost狀態了,接下來回到正常流程就會發送新的數據包。
3、如果上一步判斷中沒有認定虛假重傳,那么如果這個ACK確認包確認的數據超過RecoveryPoint,那么退回到普通的重傳恢復狀態,不再執行下面的步驟。
4、如果收到的ACK確認包是RTO后的第一個ACK確認包,且ack number確認了新數據,那么把RecoveryPoint更新為當前發送的最高系列號並嘗試發送新的未發送數據。如果沒有新數據發送,則退出FRTO過程,執行普通的RTO超時重傳處理,不在執行接下來的過程。
5、如果收到了第二個ACK確認包,如果這個確認包SACK了新的數據或者是dup ACK那么認為是真實重傳,退出FRTO過程執行普通的RTO超時重傳流程。
三、wireshark示例
1、設置tcp_frto=2,返回SACK確認部分未重傳的數據
首先通過一個RTO超時重傳(No7和No8)來縮減擁塞窗口,這樣server端在收到No9反饋包的時候就只能發出兩個數據包了,接着server端寫入500bytes的數據,server發出兩個數據包No10和No11(設置了MSS為62,減掉12bytp的TSOPT選項后,每個數據包大小最大為50bytes了)
client對No9回復一個ACK確認包,server端再次發出兩個數據包(No13和No14)
No13數據包RTO超時后進行重傳,對應No15數據包,此時判斷滿足啟動FRTO的條件
client回復No16數據包Ack=109確認了No11數據包,同時通過SACK選項告訴server端client收到了No14報文
server端收到No16確認包后發現當前FRTO為啟動狀態,並且ACK報文滿足上面FRTO流程中的第2步,即SACK確認了RecoveryPoint之前的數據且SACK確認的數據是沒有在第一步重傳的數據,因此認定這個RTO是虛假重傳,嘗試撤銷Loss狀態的部分影響,進入open狀態(實際上進入open狀態后又發現滿足了進入Disorder的條件,因此最終實際上進入了Disorder狀態),此時FRTO流程結束。
因為上一步已經對No13報文取消了lost標記,因此進入Disorder狀態后並不會重傳No13而是傳輸了新的未發送數據即No17報文,接着client回復ACK確認,整個傳輸過程結束
2、設置tcp_frto=0,返回SACK確認部分未重傳的數據
我們重復上面的業務模型,但是這次選擇關閉FRTO,如下圖所示No1到No14包的流程與前面相同,不同點如下
No15包為RTO超時重傳包,此時判斷開關關閉不對這次RTO超時重傳啟動FRTO探測
server在收到No16包的時候發現當前沒有啟動對FRTO探測,因此server端仍然處於Loss狀態,No16包觸發一個慢啟動重傳(SlowStartRetrans實際上走的就是快速重傳流程),接着沒有了待重傳的數據傳輸了一個新的未發送數據包No18
client端回復對應No17和No18的ACK確認包
3、設置tcp_frto=2,第一個ACK確認了新數據但是沒有SACK選項,第二個ACK帶有SACK選項。如下圖所示
No1-No15數據包與示例1相似不再解釋
client接收到到No15的重傳之后,回復一個不帶有SACK選項的partial ACK確認收到數據包No11,此時server端還有No13和No14兩個數據包沒有被ACK確認
server端的FRTO流程接收到No16確認包之后,發現滿足上面描述的第4點,即“如果收到的ACK確認包是RTO后的第一個ACK確認包,且ack number確認了新數據,那么嘗試發送新的未發送數據”,這時候緩存中正好還有一個數據包,因此FRTO流程發出了No17的新數據包。示例1中此處也是發出了新數據包,但是示例1是判斷為虛假重傳退出了Loss狀態和FRTO流程。而這里發出的No17新數據包是在FRTO流程中發出去的,server端仍然處於Loss狀態,FRTO流程還需要等待下一個ACK確認包
接着FRTO流程收到No18確認包,包含有確認了No17數據包的SACK信息,滿足上面描述的第5點:“如果收到了第二個ACK確認包,如果這個確認包SACK了新的數據或者是dup ACK那么認為是真實重傳,退出FRTO過程執行普通的RTO超時重傳流程。”FRTO判斷本次RTO超時為真實的RTO超時,退出FRTO流程執行普通的RTO超時慢啟動流程
接着慢啟動重傳了No19和No20兩個數據包,client回復對應的ACK報文。
補充說明:
1、細心的讀者會發現這幾個示例中client端的TSopt選項回復的TSECR是有問題的,正常的話實際上會觸發Eifel虛假重傳探測,后面擁塞控制部分會介紹Eifel探測相關內容。實際上所使用的client端TSopt的實現有兩種方式,一種是直接echo最近接收到的TSval,另外一種是按照協議要求實現的。具體選擇那種方式可以通過參數選擇來設置,注意第一種直接echo最近接收到的TSval是不符合協議的錯誤的實現。除了后面會介紹到的Eifel探測client端選用了協議標准的實現方式外,其余示例大部分都是使用的第一種方式。



