淺析TCP(上)
用不可靠的設備搭建出一個可靠的網路是網際網路前身ARPANET的設計理念,TCP正是完美實現此理念的一種協議。TCP抽象自電話通信,是一種面向連接的傳輸協議,提供可靠的位元組流服務,用於實現網際網路上端到端的互聯互通,位於IP層之上。TCP協議之複雜,功能之豐富,從rfc的數量略見一斑。本文斗膽嘗試從協議頭欄位、協議特性與狀態圖結合實際開發場景對TCP進行一番淺析。
1. 協議頭
圖1.1 TCP頭
TCP頭由10個固定域與1個可選擴展域組成。固定域佔據20位元組,可選域不定長(最長不超過40位元組),因此TCP頭具有變長特性。然而內核程序對TCP報文進行解析時,如何分離頭部和數據呢?
1.1域釋義
- Source Port(16bits)
—發送報文的埠。一般由os隨機分配
- Destination Port(16bits)
— 接收報文的埠。著名埠
- Sequence Number(32 bits)
- Acknowledgment Number(32 bits)
- Offset(4 bits)
— 偏移量(取值5~15)。頭部大小為Offset * 4,因此頭部大小為20~60位元組
- Reserved(4 bits)
— rfc3540中,拿出了一位作為N標記
— 保留欄位,須全0填充
- TCP flags(8 bits)
— rfc3540中新增一個EXPERIMENTAL的N標記,flags拓展為9bits
— N:隨機和標誌。避免「顯示衝突通告」被泄露
— C:擁塞控制窗口收縮標誌
— E:ECN-Echo標記。syn階段表示對端是否具有擁塞控制能力
— U:緊急指針域標誌
— A:ack標誌
— P:push標記。告知對端趕快清空讀緩衝區
— R:reset標記。告知對端連接被重置
— S:同步標記
— F:斷開連接標記
- Window(16 bits)
— 滑動窗口大小。告知對端,當前能接收的最大位元組數,進行流量控制
- Checksum(16 bits)
— 校驗和。計算時需要ip中的源和目的地址作為偽頭部
- Urgent Pointer
— 如果U標記被設置,此域將表示一個偏移量,指示緊急數據
- Options
— 每個選項的第一個位元組都是kind欄位,用來說明選項類型
— kind = 0(1B):選項表結束
— kind = 1(1B):無操作
— kind = 2(4B):最大報文段長度(MSS),乙太網中一般為1460位元組
— kind = 3(3B):窗口縮放因子。頭部Window最大65535位元組,對於長肥網路中,此選項能提高傳輸效率
— kind = 4(2B):sack允許選項
— kind = 5(可變):sack,選擇重傳序號。用於提高重傳效率
— kind = 8(10B):timestamp。用於計算RTT、丟棄晚抵達的廢棄報文
— 由於Offset是4位元組對齊的,因此Options中可能出現利用kind=1來填充位元組的現象,如圖1.2
圖1.2Options利用NOP進行4位元組對齊
1.2 MSS與TSO
MSS
MSS(MaxSegment Size)是TCP定義的最大報文段長度。其實TCP作為一個位元組流協議,報文段長度是無限制的(由於IP最大64k,所以其實是有上限的),header中並無長度欄位便是直接的證據。TCP的Options中之所以引入MSS的概念,因為MTU(MaxTransmission Unit)。
MTU(電氣特性)決定了鏈路層一次能傳輸數據的最大位元組數。乙太網上此值為1500。考慮下面兩種場景:1)當數據塊大於MTU時,將在發送端IP層進行分片,接收端IP層進行重組。IP分組在網路中傳輸中出現丟包時,由於IP層沒有重傳機制,TCP將重傳整個報文段而不是丟失的IP分組(即使想重傳丟失的IP分組也有難度,路由器等設備有可能對IP分組進一步分片),鏈路傳輸效率降低;2)部分防火牆根據4到7層信息過濾和處理網路包,由於IP分片,IP分組抵達防火牆的順序可能錯亂,導致防火牆攔截非第一個分片(僅第一個分片中保留有埠號等信息)。因此,避免IP層分片是一個明智的選擇,這也是MSS在syn階段被設置成網卡MTU-40(IP頭+TCP頭)的緣故。
MSS的值通過在syn階段通告,收發雙方的MSS彼此獨立。如果一方不接收另一方的通告值,則採用默認值536。而第一次的發包大小為min{對方通告的MSS,出口MTU},當路徑中的MTU小於此值時,仍有可能出現重新分組(當IP設置了DF標記後,直接丟棄分組,返回ICMP報文,發送端調整分組大小為此鏈路的MTU然後重發)。乙太網中MSS通告值一般為1460。
有了MSS特性,TCP報文段將不會在IP層被分片,即單個報文段長度不超過1460。然而,在wireshark抓包時,圖1.2中31和38號報文分別為2027和4410個位元組,這到底是怎麼回事呢?因為TSO。
圖1.2wireshark抓包出現大於MTU
TSO
TSO(TCPSegment Offload)是一種利用網卡的處理能力,降低CPU發送數據包負載的技術。對於支持TSO的網卡,TCP協議棧在封包的時候會逐漸嘗試增大MSS,網卡接收到TCP向下遞交的數據後,按照MTU進行分片、複製TCP頭且重新計算校驗和,這樣在網卡上完成了對大塊數據的TCP分段,緩解了CPU的計算壓力。
TSO相關的兩條指令:
- 查看TSO開啟情況
$ sudo ethtool -k eth0Features for eth0:rx-checksumming: ontx-checksumming: onscatter-gather: ontcp-segmentation-offload: on
- 關閉和打開TSO
$ sudo ethtool -K eth0 tso off // 關閉tso$ sudo ethtool -K eth0 tso on // 開啟tso
TSO的存在解釋了TCP協議棧封包會存在大於MSS的數據包,但鏈路上傳輸的仍然是一個個不超過MSS的TCP報文段,圖1.2的現象難道不是更可疑么?其實這是抓包工具的問題。在支持TSO的主機上,抓取到的數據是網卡分片前的數據包,並不是真實鏈路上的數據幀,一切都是TSO造成的假象。
2. 可靠與無私性
2.1可靠與重傳
可靠
TCP報文依賴IP分組傳輸,IP分組的到達可能失序。TCP報文利用header的seq(SequenceNumber)域標記了數據的發送順序,在目的端對收到的TCP報文按seq進行重新排序,從而保證了遞交給上層應用的數據是有序的。
準確抵達依賴TCP重傳機製得以保證:1)當網路傳輸過程出現了數據篡改,導致Checksum校驗失敗,接收端丟棄此報文,發送端重傳;2)網路擁塞等,出現延遲和丟包,發送端重傳;3)延遲的網路報文抵達接收端,接收端丟棄。重傳
重傳的關鍵是發送端感知到網路出現了丟包,但如何感知?方法其實很簡單。舉個形象的例子:用戶在淘寶買了個商品,商家如何知曉商品是已經抵達用戶手中還是快遞途中丟失了?用戶主動確認+超時機制!TCP也是相似的機制:接收端通過ack來告知發送端前面的ack-1(相對值)個位元組都已經被接收,未被確認的報文在超時後進行重發。
重傳機制依賴於RTT(RoundTrip Time)的測量,從而計算RTO(RetransmissionTimeout)。演算法較多,不予討論,感興趣可以查閱相關rfc。目前通過TimestampTCP選項的測量方式算一種簡單而精準的方式。
單純的超時重試機制很容易退化,效率不高。如圖2.1所示,當數據包2丟失後,接收端對1,3~5的確認均是ack 2,當超時發生後,發送端可能重傳2~5報文!圖2.1超時重傳場景
避免盲目等待超時後重傳,便設計出了快速重傳的策略。
- 快速重傳
- 選擇重傳
接收端通過sack(Options域)告知發送端接收到的大於ack的連續分段,即ack~sack左邊界即為丟失的報文。linux內核2.4之後,默認開啟了選擇重傳。
- syn重傳
syn報文作為TCP連接的第一個報文,在傳輸中被丟棄或者被接收方主動丟棄(其實發送端是區分不出兩者差異的),發送方無法測量RTT,也就無法根據RTT計算RTO。因此syn重傳的機制相對簡單:指數退避的超時時間間隔(1,2,4,8s…不同的實現略不同),直至成功或達到重試次數上限,如圖2.2所示。
圖2.2tcpdump抓包syn重試
2.2 流控與擁塞控制
TCP利用流量控制解決接收端處理數據不及時導致接收緩衝區被填滿後丟失數據的問題;擁塞控制則用來解決網路擁堵時,過多的數據包進入網路進一步加劇擁堵引起網路風暴的問題。兩種方法都是作用於發送端的反饋機制。
- 流量控制
利用簡單的排隊理論很容易推斷,當快的發送端 遇到慢的接收端時,接收端的TCP內核緩衝區很容易被填滿。應對方法無非兩種:抑制發送端和增加接收端緩衝區。顯然增加緩衝區不可取,TCP採用了滑動窗口機制來抑制發送端的發包速率。
圖2.3滑動窗口協議
滑動窗口的初始值(位元組)是在syn階段協商的,其值隨著接收方接收數據而動態變化,且由接收方通知發送方。滑動窗口的大小由TCP頭部的Window欄位和Options中的窗口縮放因子選項共同表示。圖2.3是滑動窗協議示意圖,其中:
— 當前最大發送窗口大小為6位元組
— 13~15已經發送,暫未得到接收方確認;16~18是立馬能發送的數據
— 如果收到了13~15的ack,窗口左邊沿將向右滑動至16
— 窗口右邊沿向右張開將取決於接收端通知的新接收窗口大小
顯然,一旦滑動窗口大小縮小為0,發送端將停止發送數據,等待接收端新接收窗口值(大於0)的到來以移動滑動窗口的右邊沿。上述滑動窗口機制自然能抑制發送端的發包速率,但同時引入了糊塗窗口綜合症。
糊塗窗口綜合症(SillyWindow Syndrome)是指接收方通告一個較小的窗口,而發送方發送少量的數據的現象。最糟糕的情況:接收方每次通告一個位元組的窗口大小,發送方每次發送一個位元組的數據。意味著,使用40位元組的報文頭開銷,卻只傳輸了1個位元組的有效負荷,效率極低。
解決措施:
— 接收方通告大窗口(MSS大小的窗口或者能增加接收緩衝區一半大小)
— 發送較大報文(滿長度報文,或者至少接收方通告窗口大小一半的報文)
- 擁塞控制
TCP通過RTT的測量來感知網路的擁塞狀況。RTT大,意味著網路狀況不好;RTT小,意味著網路通暢。
RTT測量的經典方法有:Karn/Partridge、Jacobson/Karels(當前linux內核中採用)。
基於RTT的擁塞控制演算法有:慢啟動(SlowStart)、擁塞避免、快速重傳與快速恢復。
其實,慢啟動並不慢,擁塞窗口大小呈指數上升;快速恢復也不一定快,擁塞窗口呈線性上升。
發送方維護了兩個窗口:擁塞窗口和滑動窗口。兩者都是試圖對發送窗口大小進行控制的,自然發送窗口大小=min{滑動窗口大小,擁塞窗口大小}。當無網路擁塞發生時,滑動窗口大小一般小於擁塞窗口大小。
(後續還有5個章節,組成「中」和「下」兩篇。涉及的內容:TCP狀態機、協議棧參數、故障分析、fast open、tcp與udp的對比以及為什麼拋棄TCP)
RFC
- rfc675 - 1974年12月第一版,tcp詳細文檔
- rfc793 - TCP v4
- rfc1122 - 一些有關TCP的修正
- rfc1323 - TCP的高性能擴展(已被rfc7323淘汰)
- rfc1379 - TCP的事務概念擴展(T/TCP,被rfc6247淘汰)
- rfc1948 - 防序列號攻擊
- rfc2018 - TCP SACK選項
- rfc5681 - TCP擁塞控制
- rfc6247 - 歸檔一些未被使用的tcp擴展(相關rfc)
- rfc6298 - 計算TCP的重傳超時時間(RTO)
- rfc6824 - TCP關於多址的多徑操作擴展
- rfc7323 - TCP的高性能擴展
- rfc7414 - TCP規範文檔的索引
參考
- Richard S W. TCP/IP 詳解卷 1: 協議[J]. 2000
- https://en.wikipedia.org/wiki/Transmission_Control_Protocol
- https://nmap.org/book/tcpip-ref.html
- http://coolshell.cn/articles/11564.html
- http://www.cisco.com/support/zh/105/pmtud_ipfrag.shtml
- http://www.winyao.com/solution_show.asp?id=224
- http://www.vants.org/?post=36
- http://www.thegeekstuff.com/2013/10/tcp-sliding-window/?utm_source=tuicool
推薦閱讀: