一、DSACK介紹
RFC2883通過指定使用SACK來指示接收端的重復包(duplicate packet)擴展了RFC2018對SACK選項的定義(SACK選項的介紹和示例參考前面內容)。RFC2883建議在收到重復報文的時候,SACK選項的第一個塊(這個塊也叫做DSACK塊)可以用來傳遞觸發這個ACK確認包的系列號,這個就是DSACK(duplicate-SACK)功能。這樣允許TCP發送端根據SACK選項來推測不必要的重傳。進而利用這些信息在亂序傳輸的環境中執行更健壯的操作。這個DSACK擴展是與原有的SACK選項的實現相互兼容的。DSACK的使用也不需要TCP連接的雙方額外協商(只要之前協商了SACK選項即可)。當TCP的發送方不理解DSACK擴展的時候會簡單的丟棄DSACK塊並繼續處理SACK選項中的其他塊。
當DSACK使能的時候,總結起來如下
一個DSACK塊只用來傳遞一個接收端最近接收到的重復報文的系列號,每個SACK選項中最多有一個DSACK塊
接收端每個重復包最多在一個DSACK塊中上報一次。如果接收端依次發送了兩個帶有相同DSACK塊信息的ACK報文,則表示接收端接收了兩次重復包,因此帶有DSACK塊信息的ACK確認包傳輸丟失的時候重復包信息也會丟失。
和普通的SACK塊一樣,DSACK塊左邊指定重復包的第一個字節的系列號,右邊指定重復包最后一個字節的下一個系列號
如果收到重復報文,第一個SACK塊應該應該指定觸發這個ACK確認包的系列號(這個SACK塊也叫做DSACK塊)。如果這個重復報文是一個大的不連續塊的一部分,那么接下來的這個SACK塊應該指定這個大的不連續塊,額外的SACK塊應該按照RFC2018指定的順序排列。
另外還有一種部分重復段(partial duplicate segment)的上報場景,我們會在示例中展示說明。注意按照上面的描述,DSACK塊有可能處於ack number之前。接收端在接收到SACK報文的時候,應該把第一個SACK塊與這個ACK報文的ack number比較(而不是和當前已經接收到的最大的ack number比較),如果小於等於ack number則說明是DSACK塊,如果大於ack number則應該與第二個SACK塊比較,如果第二個SACK塊包含第一個SACK塊,則說明第一個SACK塊為DSACK塊,如果上面兩個條件都不滿足說明第一個SACK塊是普通的SACK塊。
在linux中/proc/sys/net/ipv4/tcp_dsack控制發出的報文中是否攜帶DSACK信息,但是不管該參數設置為何值,對於接收的TCP報文,linux總是會執行DSACK塊的檢測處理。當linux檢測到DSACK塊信息的時候會嘗試撤銷擁塞控制對於擁塞窗口的作用。另外在TLP丟包探測中也可以用來做loss probe的丟包探測。
二、wireshark示例
1、tcp_dsack=1,接收到ack number之前的數據包
如下圖所示,client和server三次握手后,依次發送No4(1-6)、No6(7-13)、No8(7-13)、No10(14-20)四個數據包,server端接收到No8時候發現是一個重復包,因此回復一個帶有DSACK的ACK確認包(No9),其中DSACK塊信息為(7-14),表示收到了一個系列號為(7-13)的重復包。
2、tcp_dsack=1,接收到ack number之后的數據包
client端分別發送No4(1-6)和No6(7-13),server端正常回復不帶有SACK選項的ACK確認包
接着client發送No8(21-27),模擬系列號(14-20)的報文在由client向server傳輸的過程中丟包
server端回復一個帶有SACK選項的ACK確認包(No9),其中包含一個SACK塊(21-28)
client端繼續發送No10(24-34),server端回復No11的ACK確認包,包含一個SACK塊(21-35)
接着client發送No12(42-48),模擬系列號為(35-41)的報文傳輸過程中發生丟失
server端回復No13的ACK報文,帶有兩個SACK塊,依次為(42-49)和(21-35)
client端發送No14(28-34),這個數據包與No10數據包系列號相同
server端收到No14的重復包之后,回復No15確認包,帶有一個DSACK塊(28-35)和兩個額外的SACK塊,這兩個SACK塊依次為(21-35)(42-49)
接着client發送No16(14-20),server端回復No17確認包,帶有一個SACK塊(42-49)。
client發送No18(35-41),server端回復No19確認包,不帶有SACK信息,整個傳輸過程結束
3、tcp_dsack=1,接收到ack number之后的部分重傳段
此處不在羅嗦敘述過程,此處僅把SACK的信息文字補充描述一下,其他信息請自行下載對應的wireshark文件
其中No9、No11、No13、No15包含SACK信息,No9包含一個SACK塊(28-35),No11包含一個SACK塊(28-42),No13包含兩個SACK塊信息(49-56)、(28-42),No15包含一個DSACK塊(28-42)和一個額外的SACK塊(21-56),注意此處No14(21-55)與之前的兩個SACK塊(49-56)、(28-42)都是重復包,着一個TCP包與前面的三個包內容重復還發送了新的數據,這種同時帶有新數據和重復數據的tcp報文就叫做部分重傳段,當部分重傳段中同時帶有多個重復段的時候,協議規定DSACK塊值反饋第一個重復端的系列號范圍。可以看到此時linux的處理是符合協議的。
4、tcp_dsack=1,接收到ack number之前的部分重傳段
此處同樣僅用文字描述一下SACK信息,No9帶有一個SACK塊(21-28),No11帶有一個SACK塊(21-35),No13帶有兩個SACK塊(42-49)和(21-35),No15數據包帶有一個DSACK塊(21-56)。注意這里與上面同樣是帶有兩個重復段大的部分重傳段,但是linux反饋的DSACK信息卻把兩個重復段都包含了,而且還擴展到了最新發送的55系列號(DSACK塊中的56表示55系列號的后一個系列號)。RFC2883協議中的對這種場景的描述是:
When the SACK option is used for reporting partial duplicate segments, the first D-SACK block reports the first duplicate sub-segment. If the data packet being acknowledged contains multiple partial duplicate sub-segments, then only the first such duplicate sub-segment is reported in the SACK option.
而linux的處理則是,如果當前接收到的數據包的系列號與待接收的連續系列號相同,那么回復的DSACK塊的起始系列號則為亂序隊列中的最小系列號,終止系列號則為新收到的數據包的最大系列號加1。顯然linux的處理與協議要求不符合,而且與DSACK擴展功能的預期不相符,具體原因我也沒查到(不過並不是所有不符合協議的實現都是bug,有些實現可能會在協議的基礎上做一些擴展)。
補充信息:
1、示例4場景中描述的linux的DSACK處理邏輯位於函數tcp_ofo_queue中