一、RACK概述
RACK(Recent ACKnowledgment)是一種新的基於時間的丟包探測算法,RACK的目的是取代傳統的基於dupthresh門限的各種快速重傳及其變種。前面介紹的各種基於dup ACK的快速重傳算法及其變種通過修改dupthresh門限等手段,有些可以迅速的探測到丟包,有些可以精確的探測丟包,但是沒有能同時達到迅速和精確兩個目標的算法。
RACK基本思想:如果發送端收到的確認包中的SACK選項確認收到了一個數據包,那么在這個數據包之前發送的數據包要么是在傳輸過程中發生了亂序,要么是發生了丟包。RACK使用最近投遞成功的數據包的發送時刻來推測在這個數據包之前傳輸的數據包是否已經過期(expired),RACK把這些過期的數據包標記為lost。RACK可以修復丟包而不用等一個比較長的RTO超時,RACK可以用於快速恢復也可以用於超時恢復(快速恢復和超時恢復我們會在擁塞控制階段詳細講解),既可以利用初傳的數據包探測丟包也可以利用重傳的數據包探測丟包,而且可以探測初傳丟包也可以探測重傳丟包,因此RACK是一個適應多種場景的丟包恢復機制。
在現今的網絡環境中亂序傳輸是一個比較常見的場景,使用dupthresh和dup ACK來做丟包探測的可靠性越來越低。同時因為傳統的基於系列號空間的亂序度來探測丟包時,如果發生報文重傳,初傳報文和重傳報文在系列號空間就會重疊。而RACK基於時間的亂序來探測丟包的時候,重傳報文和初傳報文在時間線上是不重疊的,因此RACK可以同時利用初傳報文和重傳報文來探測丟包。
RACK使用的需要三個條件:
1、TCP連接必須使用SACK選項
2、對於每個發送的數據包,發送端必須存儲這個數據包的發送時間,時間精度至少要達到毫秒精度。如果連接的RTT小於1ms,那么微秒精度將會更有利於RACK探測丟包。
3、對於每個發送出去的數據包,發送端必須存儲這個數據包是否已經重傳過。
二、RACK算法描述
RACK目前還是一個實驗算法,RACK需要使用到的幾個狀態變量:
Packet.xmit_ts:數據包上次傳輸所對應的時間,如果是重傳也需要記錄這個時間。發送端需要對每個數據包都記錄這個時間,且時間精度至少是毫秒
RACK.xmit_ts:在所有被ack number或者SACK確認的數據包中,最近發送的數據包的Packet.xmit_ts
RACK.end_seq:上面用於記錄RACK.xmit_ts的數據包的終止系列號
RACK.RTT:上面用於記錄RACK.xmit_ts的數據包對應的RTT
RACK.reo_wnd:表示這個TCP連接的時間亂序度,這個變量的單位是時間。RACK使用這個變量來推測丟包
RACK.min_RTT:估計的這個連接的最小RTT
注意這些變量的粒度,每個數據包都有一個Packet.xmit_ts變量,每個TCP連接維護一組RACK.xmit_ts, RACK.RTT, RACK.reo_wnd和RACK.min_RTT變量
算法實現:
1、當傳輸一個新的數據包或者重傳一個舊的數據包的時候,把當前時間記錄在與這個數據包對應的Packet.xmit_ts變量中。
2、當接收到一個ACK的時候
Step2.1:根據測量到的RTT更新RACK.min_RTT。
發送端可以使用這個連接的全局最小RTT來維護RACK.min_RTT,也可以使用一個每發送窗口最小RTT的濾波值來維護RACK.min_RTT。
Step2.2:更新RACK.reo_wnd。RACK.reo_wnd默認值為1ms,當探測到包亂序的時候可以設置RACK.reo_wnd=RACK.min_RTT/4
Step2.3:更新RACK.xmit_ts、RACK.RTT 和 RACK.end_seq。
首先在這個ACK新確認的數據包(包括通過ack number和SACK確認的數據包)中排除下面兩類重傳數據包
1、如果這個數據包中的TSecr指示這個確認包並不是確認的重傳數據包。這個實際上是Eifel探測算法,后面我們會進行詳細介紹。
2、這個數據包上次重傳時間距離當前時間小於RACK.min_rtt。這個也意味着這個數據包多半是虛假重傳。
接着在剩余的新確認的數據包中找出最近發送的數據包的Packet.xmit_ts,如果Packet.xmit_ts比RACK.xmit_ts在時間上更靠后,那么更新RACK.xmit_ts = Packet.xmit_ts。如果RACK.xmit_ts發生了更新,那么更新RACK.RTT = Now() - RACK.xmit_ts,RACK.end_seq = Packet.end_seq。如果RACK.xmit_ts沒有更新,那么退出針對這個確認包的RACK處理流程,不再執行下面的丟包探測過程。
Step2.4:丟包探測
對於每個還沒有被SACK完全確認的數據包,如果在時間上RACK.xmit_ts比Packet.xmit_ts + RACK.reo_wnd更靠后,說明這個數據包已經超過預計的時間亂序度,標記這個數據包為lost狀態。另外對於未被標記為lost的數據包,發送端可以等待下次收到ACK確認包的時候再次進行RACK標記處理,也可以設置一個"reordering settling"定時器,以待定時器超時的時候把這個數據包標記為lost。設置定時器的方法可以防止大量丟包或者應用層發送數據受限而造成RTO超時。定時器的超時時間協議給出的是timeout = Packet.xmit_ts + RACK.RTT + RACK.reo_wnd + 1。
這里值得一提的是,RACK功能可以很好的與TLP功能配合,因為RACK可以使用重傳包來探測丟包,因此TLP其實可以發送第一個未被確認的數據包來進行丟包探測,這樣就可以應用層傳輸時延。
三、linux實現簡介
目前linux中RACK功能受到/proc/sys/net/ipv4/tcp_recovery參數控制,tcp_recovery是一個比特開關,其中0x1比特位控制是否使能RACK,該比特位有效的時候,則打開RACK功能,默認值為0x1,即linux中默認打開RACK功能。
另外就是linux並沒有實現"reordering settling"定時器功能。linux中RACK其余處理基於與上面描述的一致了。
目前RFC草案中只說RACK可以在快速恢復和RTO超時恢復階段使用,我們這里重點關注RACK丟包探測的機制,關於快速恢復和超時恢復,我們后面介紹擁塞控制的時候會在進行詳細介紹,同時會再次回顧RACK的相關內容。
四、wireshark示例
我們先簡單的看一個示例來理解RACK重傳,后面介紹完擁塞控制的時候,會再次引入其他的RACK示例
1、RACK重傳基礎示例
在進行測試之前我們設置tcp_timestamps=0以關閉TCP的時間戳選項,用來說明RACK雖然是基於時間的丟包探測,但是並不依賴TSopt選項。當然這里打開這個TSopt這個選項也是可以的,只不過為了場景正交,因此這里演示關閉TSopt時候RACK一樣可以正常處理。
業務場景:client與server端建立連接后,server端連續發送8個數據包,即No4-No11。其中只有No4和No11成功傳輸到接收端,中間的6個數據包(我手動設置了高亮顯示)都丟失了。其中client與server端的RTT大約穩定在50ms左右。
No1-No3:通過三次握手建立連接,其中TSopt選項未協商成功
No4-No11:server端大約以3ms為間隔,連續發送了8個數據包,其中No5-No10高亮的這6個數據包丟失
No12:client回復對應No4的ACK確認包
No13:client回復對應No12的ACK確認包,通過SACK確認了No11,server端收到這個數據包的時候,更新fackets_out=7,此時dupthresh=3, fackets_out-dupthresh=4, 因此server端把No5、No6、No7、No8數據包標記為lost。
No14:server端先把No5數據包重傳出去,對應No14,受限於擁塞控制,其余被標記為lost的數據包暫時還不能進行重傳。
No15:No15通過ack number確認了No14這個重傳包。server端更新 RACK.xmit_ts=No14的Packet.xmit_ts≈0.072604us,因為目前linux還沒有檢測到亂序傳輸(即dupthresh還沒有更新),因此RACK.reo_wnd=1000us,此時server端會把RACK.xmit_ts-RACK.reo_wnd≈0.071604us時間點之前發送的數據包都標記為lost。即No9和No10也會被server端標記為lost。
No16-No29:接着在擁塞控制的限制下,對標記為lost的數據包進行重傳操作,並在最終數據包傳輸完畢后關閉這個TCP連接。
補充說明:
1、https://datatracker.ietf.org/doc/draft-ietf-tcpm-rack/?include_text=1
2、https://www.ietf.org/proceedings/94/slides/slides-94-tcpm-6.pdf

