Linux中國

TCP 窗口縮放、時間戳和 SACK

Linux TCP 協議棧具有無數個可以更改其行為的 sysctl 旋鈕。 這包括可用於接收或發送操作的內存量、套接字的最大數量、可選的特性和協議擴展。

有很多文章出於各種「性能調優」或「安全性」原因,建議禁用 TCP 擴展,比如時間戳或 選擇性確認 Selective ACKnowledgments (SACK)。

本文提供了這些擴展功能的背景,為什麼會默認啟用,它們之間是如何關聯的,以及為什麼通常情況下將它們關閉是個壞主意。

TCP 窗口縮放

TCP 可以承受的數據傳輸速率受到幾個因素的限制。其中包括:

  • 往返時間 Round trip time (RTT)。

這是數據包到達目的地並返回回復所花費的時間。越低越好。

  • 所涉及的網路路徑的最低鏈路速度。
  • 丟包頻率。
  • 新數據可用於傳輸的速度。

例如,CPU 需要能夠以足夠快的速度將數據傳遞到網路適配器。如果 CPU 需要首先加密數據,則適配器可能必須等待新數據。同樣地,如果磁碟存儲不能足夠快地讀取數據,則磁碟存儲可能會成為瓶頸。

  • TCP 接收窗口的最大可能大小。

接收窗口決定了 TCP 在必須等待接收方報告接收到該數據之前可以傳輸多少數據(以位元組為單位)。這是由接收方宣布的。接收方將在讀取並確認接收到傳入數據時不斷更新此值。接收窗口的當前值包含在 TCP 報頭 中,它是 TCP 發送的每個數據段的一部分。因此,只要發送方接收到來自對等方的確認,它就知道當前的接收窗口。這意味著往返時間(RTT)越長,發送方獲得接收窗口更新所需的時間就越長。

TCP 的未確認(正在傳輸)數據被限制為最多 64KB。在大多數網路場景中,這甚至還不足以維持一個像樣的數據速率。讓我們看看一些例子。

理論數據速率

在往返時間(RTT)為 100 毫秒的情況下,TCP 每秒最多可以傳輸 640KB。在延遲為 1 秒的情況下,最大理論數據速率降至只有 64KB/s。

這是因為接收窗口的原因。一旦發送了 64KB 的數據,接收窗口就已經滿了。發送方必須等待,直到對等方通知它應用程序已經讀取了至少一部分數據。

發送的第一個段會把 TCP 窗口縮減去該段的大小。在接收窗口值的更新信息可用之前,需要往返一次。當更新以 1 秒的延遲到達時,即使鏈路有足夠的可用帶寬,也會導致 64KB 的限制。

為了充分利用一個具有幾毫秒延遲的快速網路,必須有一個比傳統 TCP 支持的窗口更大的窗口。「64KB 限制」是協議規範的產物:TCP 頭只為接收窗口大小保留了 16 個位。這允許接收窗口最大為 64KB。在 TCP 協議最初設計時,這個大小並沒有被視為一個限制。

不幸的是,想通過僅僅更改 TCP 頭來支持更大的最大窗口值是不可能的。如果這樣做就意味著 TCP 的所有實現都必須同時更新,否則它們將無法相互理解。為了解決這個問題,我們改變了對接收窗口值的解釋。

「窗口縮放選項」允許你改變這個解釋,同時保持與現有實現的兼容性。

TCP 選項:向後兼容的協議擴展

TCP 支持可選擴展。這允許使用新特性增強協議,而無需立即更新所有實現。當 TCP 發起方連接到對等方時,它還會發送一個支持的擴展列表。所有擴展都遵循相同的格式:一個唯一的選項號,後跟選項的長度以及選項數據本身。

TCP 響應方檢查連接請求中包含的所有選項號。如果它遇到一個不能理解的選項號,則會跳過 該選項號附帶的「長度」位元組的數據,並檢查下一個選項號。響應方忽略了從答覆中無法理解的內容。這使發送方和接收方都夠理解所支持的公共選項集。

使用窗口縮放時,選項數據總是由單個數字組成。

窗口縮放選項

Window Scale option (WSopt): Kind: 3, Length: 3
    +---------+---------+---------+
    | Kind=3  |Length=3 |shift.cnt|
    +---------+---------+---------+
         1         1         1

窗口縮放 選項告訴對等方,應該使用給定的數字縮放 TCP 標頭中的接收窗口值,以獲取實際大小。

例如,一個宣告窗口縮放因子為 7 的 TCP 發起方試圖指示響應方,任何將來攜帶接收窗口值為 512 的數據包實際上都會宣告 65536 位元組的窗口。增加了 128 倍(2^7)。這將允許最大為 8MB 的 TCP 窗口。

不能理解此選項的 TCP 響應方將會忽略它,為響應連接請求而發送的 TCP 數據包(SYN-ACK)不會包含該窗口縮放選項。在這種情況下,雙方只能使用 64k 的窗口大小。幸運的是,默認情況下,幾乎每個 TCP 棧都支持並默認啟用了此選項,包括 Linux。

響應方包括了它自己所需的縮放因子。兩個對等方可以使用不同的因子。宣布縮放因子為 0 也是合法的。這意味著對等方應該如實處理它接收到的接收窗口值,但它允許應答方向上的縮放值,然後接收方可以使用更大的接收窗口。

與 SACK 或 TCP 時間戳不同,窗口縮放選項僅出現在 TCP 連接的前兩個數據包中,之後無法更改。也不可能通過查看不包含初始連接三次握手的連接的數據包捕獲來確定縮放因子。

支持的最大縮放因子為 14。這將允許 TCP 窗口的大小高達 1GB。

窗口縮放的缺點

在非常特殊的情況下,它可能導致數據損壞。但在你禁用該選項之前,要知道通常情況下是不可能損壞的。還有一種解決方案可以防止這種情況。不幸的是,有些人在沒有意識到它與窗口縮放的關係的情況下禁用了該解決方案。首先,讓我們看一下需要解決的實際問題。想像以下事件序列:

  1. 發送方發送段:s_1、s_2、s_3、... s_n。
  2. 接收方看到:s_1、s_3、... s_n,並發送對 s_1 的確認。
  3. 發送方認為 s_2 丟失,然後再次發送。它還發送了段 s_n+1 中包含的新數據。
  4. 接收方然後看到:s_2、s_n+1,s_2:數據包 s_2 被接收兩次。

當發送方過早觸發重新傳輸時,可能會發生這種情況。在正常情況下,即使使用窗口縮放,這種錯誤的重傳也絕不會成為問題。接收方將只丟棄重複項。

從舊數據到新數據

TCP 序列號最多可以為 4GB。如果它變得大於此值,則該序列會迴繞到 0,然後再次增加。這本身不是問題,但是如果這種問題發生得足夠快,則上述情況可能會造成歧義。

如果在正確的時刻發生迴繞,則序列號 s_2(重新發送的數據包)可能已經大於 s_n+1。因此,在最後的步驟(4)中,接收方可以將其解釋為:s_2、s_n+1、s_n+m,即它可以將 「舊」 數據包 s_2 視為包含新數據。

通常,這不會發生,因為即使在高帶寬鏈接上,「迴繞」也只會每隔幾秒鐘或幾分鐘發生一次。原始數據包和不需要的重傳的數據包之間的間隔將小得多。

例如,對於 50MB/s 的傳輸速度,重複項要遲到一分鐘以上才會成為問題。序列號的迴繞速度沒有快到讓小的延遲會導致這個問題。

一旦 TCP 達到 「GB/s」 的吞吐率,序列號的迴繞速度就會非常快,以至於即使只有幾毫秒的延遲也可能會造成 TCP 無法檢測出的重複項。通過解決接收窗口太小的問題,TCP 現在可以用於以前無法實現的網路速度,這會產生一個新的,儘管很少見的問題。為了在 RTT 非常低的環境中安全使用 GB/s 的速度,接收方必須能夠檢測到這些舊的重複項,而不必僅依賴序列號。

TCP 時間戳

最佳截止日期

用最簡單的術語來說,TCP 時間戳只是在數據包上添加時間戳,以解決由非常快速的序列號迴繞引起的歧義。如果一個段看起來包含新數據,但其時間戳早於上一個在接收窗口內的數據包,則該序列號已被重新迴繞,而「新」數據包實際上是一個較舊的重複項。這解決了即使在極端情況下重傳的歧義。

但是,該擴展不僅僅是檢測舊數據包。TCP 時間戳的另一個主要功能是更精確的往返時間測量(RTTm)。

需要準確的 RTT 估算

當兩個對等方都支持時間戳時,每個 TCP 段都攜帶兩個附加數字:時間戳值和回顯時間戳。

TCP Timestamp option (TSopt): Kind: 8, Length: 10
+-------+----+----------------+-----------------+
|Kind=8 | 10 |TS Value (TSval)|EchoReply (TSecr)|
+-------+----+----------------+-----------------+
    1      1         4                4

準確的 RTT 估算對於 TCP 性能至關重要。TCP 會自動重新發送未確認的數據。重傳由計時器觸發:如果超時,則 TCP 會將尚未收到確認的一個或多個數據包視為丟失。然後再發送一次。

但是,「尚未得到確認」 並不意味著該段已丟失。也有可能是接收方到目前為止沒有發送確認,或者確認仍在傳輸中。這就造成了一個兩難的困境:TCP 必須等待足夠長的時間,才能讓這種輕微的延遲變得無關緊要,但它也不能等待太久。

低網路延遲 VS 高網路延遲

在延遲較高的網路中,如果計時器觸發過快,TCP 經常會將時間和帶寬浪費在不必要的重發上。

然而,在延遲較低的網路中,等待太長時間會導致真正發生數據包丟失時吞吐量降低。因此,在低延遲網路中,計時器應該比高延遲網路中更早到期。所以,TCP 重傳超時不能使用固定常量值作為超時。它需要根據其在網路中所經歷的延遲來調整該值。

往返時間的測量

TCP 選擇基於預期的往返時間(RTT)的重傳超時。RTT 事先是未知的。它是通過測量發送的段與 TCP 接收到該段所承載數據的確認之間的增量來估算的。

由於多種因素使其而變得複雜。

  • 出於性能原因,TCP 不會為收到的每個數據包生成新的確認。它等待的時間非常短:如果有更多的數據段到達,則可以通過單個 ACK 數據包確認其接收。這稱為 「累積確認」 cumulative ACK
  • 往返時間並不恆定。這是有多種因素造成的。例如,客戶端可能是一部行動電話,隨其移動而切換到不同的基站。也可能是當鏈路或 CPU 的利用率提高時,數據包交換花費了更長的時間。
  • 必須重新發送的數據包在計算過程中必須被忽略。這是因為發送方無法判斷重傳數據段的 ACK 是在確認原來的傳輸數據(畢竟已到達)還是在確認重傳數據。

最後一點很重要:當 TCP 忙於從丟失中恢復時,它可能僅接收到重傳段的 ACK。這樣,它就無法在此恢復階段測量(更新)RTT。所以,它無法調整重傳超時,然後超時將以指數級增長。那是一種非常具體的情況(它假設其他機制,如快速重傳或 SACK 不起作用)。但是,使用 TCP 時間戳,即使在這種情況下也會進行 RTT 評估。

如果使用了擴展,則對等方將從 TCP 段的擴展空間中讀取時間戳值並將其存儲在本地。然後,它將該值作為 「回顯時間戳」 放入發回的所有數據段中。

因此,該選項帶有兩個時間戳:它的發送方自己的時間戳和它從對等方收到的最新時間戳。原始發送方使用 「回顯時間戳」 來計算 RTT。它是當前時間戳時鐘與 「回顯時間戳」 中所反映的值之間的增量。

時間戳的其他用途

TCP 時間戳甚至還有除 PAWS( 防止序列號迴繞 Protection Against Wrapped Sequences ) 和 RTT 測量以外的其他用途。例如,可以檢測是否不需要重發。如果該確認攜帶較舊的回顯時間戳,則該確認針對的是初始數據包,而不是重新發送的數據包。

TCP 時間戳的另一個更晦澀的用例與 TCP syn cookie 功能有關。

在伺服器端建立 TCP 連接

當連接請求到達的速度快於伺服器應用程序可以接受新的傳入連接的速度時,連接積壓最終將達到其極限。這可能是由於系統配置錯誤或應用程序中的錯誤引起的。當一個或多個客戶端發送連接請求而不對 「SYN ACK」 響應做出反應時,也會發生這種情況。這將用不完整的連接填充連接隊列。這些條目需要幾秒鐘才會超時。這被稱為 「同步泛洪攻擊」 syn flood attack

TCP 時間戳和 TCP Syn Cookie

即使隊列已滿,某些 TCP 協議棧也允許繼續接受新連接。發生這種情況時,Linux 內核將在系統日誌中列印一條突出的消息:

埠 P 上可能發生 SYN 泛洪。正在發送 Cookie。檢查 SNMP 計數器。

此機制將完全繞過連接隊列。通常存儲在連接隊列中的信息被編碼到 SYN/ACK 響應 TCP 序列號中。當 ACK 返回時,可以根據序列號重建隊列條目。

序列號只有有限的空間來存儲信息。因此,使用 「TCP Syn Cookie」 機制建立的連接不能支持 TCP 選項。

但是,對兩個對等點都通用的 TCP 選項可以存儲在時間戳中。ACK 數據包在回顯時間戳欄位中反映了該值,這也允許恢復已達成共識的 TCP 選項。否則,cookie 連接受標準的 64KB 接收窗口限制。

常見誤區 —— 時間戳不利於性能

不幸的是,一些指南建議禁用 TCP 時間戳,以減少內核訪問時間戳時鐘來獲取當前時間所需的次數。這是不正確的。如前所述,RTT 估算是 TCP 的必要部分。因此,內核在接收/發送數據包時總是採用微秒級的時間戳。

在包處理步驟的其餘部分中,Linux 會重用 RTT 估算所需的時鐘時間戳。這還避免了將時間戳添加到傳出 TCP 數據包的額外時鐘訪問。

整個時間戳選項在每個數據包中僅需要 10 個位元組的 TCP 選項空間,這不會顯著減少可用於數據包有效負載的空間。

常見誤區 —— 時間戳是個安全問題

一些安全審計工具和(較舊的)博客文章建議禁用 TCP 時間戳,因為據稱它們泄露了系統正常運行時間:這樣一來,便可以估算系統/內核的補丁級別。這在過去是正確的:時間戳時鐘基於不斷增加的值,該值在每次系統引導時都以固定值開始。時間戳值可以估計機器已經運行了多長時間(正常運行時間 uptime)。

從 Linux 4.12 開始,TCP 時間戳不再顯示正常運行時間。發送的所有時間戳值都使用對等設備特定的偏移量。時間戳值也每 49 天迴繞一次。

換句話說,從地址 「A」 出發,或者終到地址 「A」 的連接看到的時間戳與到遠程地址 「B」 的連接看到的時間戳不同。

運行 sysctl net.ipv4.tcp_timeamp=2 以禁用隨機化偏移。這使得分析由諸如 wiresharktcpdump 之類的工具記錄的數據包跟蹤變得更容易 —— 從主機發送的數據包在其 TCP 選項時間戳中都具有相同的時鐘基準。因此,對於正常操作,默認設置應保持不變。

選擇性確認

如果同一數據窗口中的多個數據包丟失了,TCP 將會出現問題。這是因為 TCP 確認是累積的,但僅適用於按順序到達的數據包。例如:

  • 發送方發送段 s_1、s_2、s_3、... s_n
  • 發送方收到 s_2 的 ACK
  • 這意味著 s_1 和 s_2 都已收到,並且發送方不再需要保留這些段。
  • s_3 是否應該重新發送? s_4 呢? s_n?

發送方等待 「重傳超時」 或 「重複 ACK」 以使 s_2 到達。如果發生重傳超時或到達了 s_2 的多個重複 ACK,則發送方再次發送 s_3。

如果發送方收到對 s_n 的確認,則 s_3 是唯一丟失的數據包。這是理想的情況。僅發送單個丟失的數據包。

如果發送方收到的確認段小於 s_n,例如 s_4,則意味著丟失了多個數據包。發送方也需要重傳下一個數據段。

重傳策略

可能只是重複相同的序列:重新發送下一個數據包,直到接收方指示它已處理了直至 s_n 的所有數據包為止。這種方法的問題在於,它需要一個 RTT,直到發送方知道接下來必須重新發送的數據包為止。儘管這種策略可以避免不必要的重傳,但要等到 TCP 重新發送整個數據窗口後,它可能要花幾秒鐘甚至更長的時間。

另一種方法是一次重新發送幾個數據包。當丟失了幾個數據包時,此方法可使 TCP 恢復更快。在上面的示例中,TCP 重新發送了 s_3、s_4、s_5、...,但是只能確保已丟失 s_3。

從延遲的角度來看,這兩種策略都不是最佳的。如果只有一個數據包需要重新發送,第一種策略是快速的,但是當多個數據包丟失時,它花費的時間太長。

即使必須重新發送多個數據包,第二個也是快速的,但是以浪費帶寬為代價。此外,這樣的 TCP 發送方在進行不必要的重傳時可能已經發送了新數據。

通過可用信息,TCP 無法知道丟失了哪些數據包。這就是 TCP 選擇性確認(SACK)的用武之地了。就像窗口縮放和時間戳一樣,它是另一個可選的但非常有用的 TCP 特性。

SACK 選項

   TCP Sack-Permitted Option: Kind: 4, Length 2
   +---------+---------+
   | Kind=4  | Length=2|
   +---------+---------+

支持此擴展的發送方在連接請求中包括 「允許 SACK」 選項。如果兩個端點都支持該擴展,則檢測到數據流中丟失數據包的對等方可以將此信息通知發送方。

   TCP SACK Option: Kind: 5, Length: Variable
                     +--------+--------+
                     | Kind=5 | Length |
   +--------+--------+--------+--------+
   |      Left Edge of 1st Block       |
   +--------+--------+--------+--------+
   |      Right Edge of 1st Block      |
   +--------+--------+--------+--------+
   |                                   |
   /            . . .                  /
   |                                   |
   +--------+--------+--------+--------+
   |      Left Edge of nth Block       |
   +--------+--------+--------+--------+
   |      Right Edge of nth Block      |
   +--------+--------+--------+--------+

接收方遇到 s_2 後跟 s_5 ... s_n,則在發送對 s_2 的確認時將包括一個 SACK 塊:

                +--------+-------+
                | Kind=5 |   10  |
+--------+------+--------+-------+
| Left edge: s_5                 |
+--------+--------+-------+------+
| Right edge: s_n                |
+--------+-------+-------+-------+

這告訴發送方到 s_2 的段都是按順序到達的,但也讓發送方知道段 s_5 至 s_n 也已收到。然後,發送方可以重新發送那兩個數據包(s_3、s_4),並繼續發送新數據。

神話般的無損網路

從理論上講,如果連接不會丟包,那麼 SACK 就沒有任何優勢。或者連接具有如此低的延遲,甚至等待一個完整的 RTT 都無關緊要。

在實踐中,無損行為幾乎是不可能保證的。即使網路及其所有交換機和路由器具有足夠的帶寬和緩衝區空間,數據包仍然可能丟失:

  • 主機操作系統可能面臨內存壓力並丟棄數據包。請記住,一台主機可能同時處理數萬個數據包流。
  • CPU 可能無法足夠快地消耗掉來自網路介面的傳入數據包。這會導致網路適配器本身中的數據包丟失。
  • 如果 TCP 時間戳不可用,即使一個非常小的 RTT 的連接也可能在丟失恢復期間暫時停止。

使用 SACK 不會增加 TCP 數據包的大小,除非連接遇到數據包丟失。因此,幾乎沒有理由禁用此功能。幾乎所有的 TCP 協議棧都支持 SACK —— 它通常只在不進行 TCP 批量數據傳輸的低功耗 IOT 類的設備上才不存在。

當 Linux 系統接受來自此類設備的連接時,TCP 會自動為受影響的連接禁用 SACK。

總結

本文中研究的三個 TCP 擴展都與 TCP 性能有關,最好都保留其默認設置:啟用。

TCP 握手可確保僅使用雙方都可以理解的擴展,因此,永遠不需因為對等方可能不支持而全局禁用該擴展。

關閉這些擴展會導致嚴重的性能損失,尤其是 TCP 窗口縮放和 SACK。可以禁用 TCP 時間戳而不會立即造成不利影響,但是現在沒有令人信服的理由這樣做了。啟用它們還可以支持 TCP 選項,即使在 SYN cookie 生效時也是如此。

via: https://fedoramagazine.org/tcp-window-scaling-timestamps-and-sack/

作者:Florian Westphal 選題:lujun9972 譯者:gxlct008 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國