一、TFO背景
當前web和web-like應用中一般都是在三次握手后開始數據傳輸,相比於UDP,多了一個RTT的時延,即使當前很多應用使用長連接來處理這種情況,但是仍然由一定比例的短連接,這額外多出的一個RTT仍然對應用的時延有非常大的影響。TFO就是在這種背景下面提出來的。
TFO(TCP fast open)是TCP協議的experimental update,它允許服務器和客戶端在連接建立握手階段交換數據,從而使應用節省了一個RTT的時延。但是TFO會引起一些問題,因此協議要求TCP實現必須默認禁止TFO。需要在某個服務端口上啟用TFO功能的時候需要應用程序顯示啟用。
二、TFO過程
1.在使用TFO之前,client首先需要通過一個普通的三次握手連接獲取FOC(Fast Open Cookie)
1.client發送一個帶有Fast Open選項的SYN包,同時攜帶一個空的cookie域來請求一個cookie
2.server產生一個cookie,然后通過SYN-ACK包的Fast Open選項來返回給client
3.client緩存這個cookie以備將來使用TFO連接的時候使用
2.執行TFO
1.client發送一個帶有數據的SYN包,同時在Fast Open選項中攜帶之前通過正常連接獲取的cookie
2.server驗證這個cookie。如果這個cookie是有效的,server會返回SYN-ACK報文,然后這個server把接收到的數據傳遞給應用層。如果這個cookie是無效的,server會丟掉SYN包中的數據,同時返回一個SYN-ACK包來確認SYN包中的系列號
3.如果cookie有效,在連接完成之前server可以給client發送響應數據,攜帶的數據量受到TCP擁塞控制的限制(RFC5681,后面文章會介紹擁塞控制)。
4.client發送ACK包來確認server的SYN和數據,如果client端SYN包中的數據沒有被服務器確認,client會在這個ACK包中重傳對應的數據
4.剩下的連接處理就類似正常的TCP連接了,client一旦獲取到FOC,可以重復Fast Open直到cookie過期。
通過整個過程,我們可以看到TFO的核心是一個安全cookie,服務器使用這個cookie來給客戶端鑒權。一般來說這個cookie應該能鑒權SYN包中的源IP地址,並且不能被第三方偽造。為了保證安全,過一段時間后,server應該expire之前的cookie,並重新生成cookie。cookie驗證通過,server在發送SYN-ACK的時候,如果有待發送數據也同樣可以攜帶數據。
client在緩存cookie的時候,協議同樣建議緩存Maximum Segment Size(MSS),MSS代表了對端能接收的最大TCP段,這樣client在執行TFO的時候,SYN包可以攜帶的數據量大小就有了一個參考。即使緩存了MSS,也建議client SYN包中數據不要超過典型的MSS,即IPV4的1460bytes和IPV6的1440bytes。如果沒有緩存MSS,則SYN包中的數據大小限制在默認的MSS,IPV4為536bytes(RFC1122),IPV6為1220bytes(RFC2460)。
client在收到服務器SYN包但是沒有ACK之前自己發出的數據時候,或者ICMP錯誤或者根本沒有收到SYN-ACK響應的時候,client至少應該要在對應的連接路徑上臨時禁止TFO功能。
TFO場景下,client在超時重傳SYN包以及server超時重傳SYN-ACK報文的時候應該去掉Fast Open選項和對應的數據,以免因為不兼容TFO而導致連接建立失敗。
三、SYN包重復遞交數據
在TFO下隨着SYN發送的數據有可能重復遞交到應用層。例如在IP層不可靠傳輸的情況下,發送端的一個SYN包被傳輸成了兩個SYN包,而在接收端,接收到第一個SYN包后,接收端把隨SYN的數據傳遞到應用層,緊接着這個連接由發送端發起關閉TCP連接的操作,接收端不會進入TIME_WAIT保護狀態(后面在介紹TIME_WAIT),然后繼續收到第二個重復包則可能會將隨SYN傳輸的數據再次傳向應用層。因此如果應用層不能忍受這種包重復,則不能開啟TFO特性。
四、wireshark抓包
在下面的wireshark抓包中,限於篇幅,僅簡單介紹一下幾個相關的包,詳細的報文內容請下載wireshark的抓包文件打開后自行查看
No1:首先client發起普通連接,在SYN包中帶有一個FOC選項,向server請求cookie (No1表示wireshark截圖中序號為No1的報文,后續文章使用類似表示)
No2:server端回復SYN-ACK包,其中攜帶一個FOC選項,cookie域為0x1a39d8e2100b247e
緊接着client發送一個"hello"消息后關閉連接
No7:client重新發起一個連接,可以看到這個SYN包的Len=5,實際上我發送了"world",正好是5bytes,隨着syn包發送到了server,注意這次cilent的端口是57522和第一次連接的端口57520並不同,但是仍然可以使用第一次獲取的FOC發送數據,因此FOC是與client端口無關的。
補充說明
1.TFO介紹 https://www.ietf.org/proceedings/80/slides/tcpm-3.pdf
2.RFC7413 https://datatracker.ietf.org/doc/rfc7413/?include_text=1
3.原始的RFC793協議也是允許TCP在握手過程中傳遞數據的,但是在連接建立前不能遞交給應用層,linux實現上並不支持在非TFO場景下握手過程中攜帶數據。