TCP頭時間戳選項與迴繞序列號
在說明TCP頭部中的時間戳選項與防迴繞有什麼關係之前,我們先來研究一下什麼叫做迴繞序列號。
在TCP中,兩端建立連線之前,會分別各自選擇一個初始序列號。初始序列號會隨著時間而改變,因此每一個連線都擁有不同的初始序列號。[RFC0793]指出,初始序列號可以視為一個32位的計數器,這個計數器的數值每4微妙加一。
這其中就會存在迴繞的問題。想象一下,如果序列號每次交換都會自增1,而連線又長時間存在且高速交換報文時,總有一個時刻序列號的長度會超過32位(2^32 - 1),此時序列號就會重置為0,此時的序列號就會從0重新開始自增1。這就是TCP序列號迴繞問題。
當然,序列號迴繞真正導致bug的問題概率很低。但是一旦因為序列號迴繞出現問題,那麼這樣的問題幾乎是所有網路測試工程師的噩夢:偶發、無規律並且無法穩定重現。
下面來研究一下序列號迴繞可能會導致什麼樣的問題。
傳送時間 |
傳送序列號 |
接收方 |
A |
N |
報文完好 |
B |
N+1 |
報文完好 |
。。。。。。 |
。。。。。。 |
報文完好 |
。。。。。。 |
。。。。。。 |
報文完好 |
C |
N |
報文完好 |
D |
N+1 |
報文完好 |
假設上表是兩個主機之前通訊的可能資料流。可以看到在B時刻和C時刻之間,序列號發生了迴繞。
如果資料包全部完好無損的順序到達,那麼這樣的資料流將沒有任何問題,可以正常維持服務。
但是我們來做一個假設,假設在B時刻,有一個報文段延遲。這時接收方由於遲遲收不到所需的報文段,要求傳送方重傳。這一次的重傳反而沒有問題,延遲的報文段被重新傳送了一份,正常由接收方接收。如果接下來沒有什麼問題,比如這個延遲的報文段自此丟失,那整個資料流也不會受到影響。
但是我們再做一個假設,如果這個B時刻延遲的報文段,在D時刻到達了。如果這個報文段丟失與重新出現的時間(也就是在網路中跋涉的時間)還很幸運的小於一個報文段在網路中存在的最大時間(MSL)的限制,那麼服務端就能正常接收到這個報文段。
這下接收端懵逼了,幸福來得太突然,一下子來了兩個報文段,還都拿著一樣的序列號,不知道要選誰好了。這就是迴繞序列號結合報文段延遲導致的重複報文段問題。這樣的問題只會發生在相對高速的連線中。
問題發生了就要解決,那麼如果解決這個問題呢?這就要用到TCP頭部中的額外選項——時間戳選項了。當使用時間戳選項時,傳送方將一個32位的數值填充到時間戳數值欄位(稱作TSV或TSval)作為時間戳選項的第一個部分;而接收方則將收到的時間戳數值原封不動的填充至第二部分的時間戳回顯重試欄位(稱作TSER或TSecr),如果這個報文屬於客戶端SYN握手包,那麼由於不知道服務端時間,第二部分的時間戳回顯重試欄位將被置為0。由於包含了時間戳選項,TCP頭部的長度將會增長10位元組(8個位元組用於儲存2個時間戳數值,而另兩個位元組用於指明選項的數值與長度)。
[RFC1323]推薦傳送方如果啟用時間戳選項,那麼每秒鐘至少將時間戳選項加1。
剛剛我們說到的報文段延遲並且恰好碰上序列號迴繞導致接收端同時收到兩個序列號一致的報文段的問題,就可以通過時間戳選項來解決。如果真的出現這樣序列號相同的報文段,那麼直接丟棄掉與最近接收到的報文段時間戳相差最遠的那個報文段即可。
時間戳選項廣義上可以解決所有由於序列號一致而造成的報文段重複問題,比如比迴繞序列號更容易出現的重置序列號問題。由於TCP實質上是通過一對端點,其中包括2個IP地址與2個埠號構成的四元組所唯一標識,因此即使是同一個四元組連線也會出現不同的例項。而不同的例項之間有更大的可能出現序列號重複的問題。
比如一個短連線總是由客戶端的80埠連結至服務端的443埠,這樣的短連線可能在一天之內斷開又重連上千次。那麼極有可能出現某個連線由於某個報文段的長時間延遲而被關閉,然後又以相同的四元組被重新開啟,延遲的報文段又會被視為有效資料重新進入新連線的資料流中。如果恰好此時報文段的序列號符合預期,那麼接收端又會陷入迷惑之中。開啟時間戳選項之後,這樣的問題將會得到避免。
當然,時間戳選項的真正作用並不是為了解決序列號重複問題而產生的,避免接收舊報文段與判斷報文段正確性只不過是時間戳選項帶來的額外好處。藉助時間戳選項,我們能夠獲得報文往返時間大量樣本,從而的相對精確的估算連線中報文的往返時間。
這,與重傳計時器的設定緊密相關。
相關文章
- javascript時間戳與php返回的時間戳統一JavaScript時間戳PHP
- 將時間戳轉換為時間例項程式碼時間戳
- MySQL時間戳、時間MySql時間戳
- 將時間戳轉換為時間日期程式碼例項時間戳
- 時間日期和時間戳相互轉換程式碼例項時間戳
- 時間戳與時間字串的多時區轉換時間戳字串
- JavaScript 時間戳JavaScript時間戳
- kafka時間戳Kafka時間戳
- tcp 選項TCP
- golang日期字串與時間戳轉換Golang字串時間戳
- linux與unix時間戳互轉Linux時間戳
- 時間型別和時間戳型別時間戳
- 時間轉換成時間戳時間戳
- C 時間轉換時間戳時間戳
- C# 時間戳轉時間C#時間戳
- js獲取某時間的當天0點時間戳 與某時間的當週週一0點時間戳JS時間戳
- java時間戳和PHP時間戳的轉換phptime()Java時間戳PHP
- 掌握時間與空間:深入探討Golang中的時間戳與時區轉換Golang時間戳
- C 時間戳轉換成時間時間戳
- 時間戳轉化為時間格式時間戳
- 兩個時間戳的時間差時間戳
- SqlServer時間戳與普通格式的轉換SQLServer時間戳
- 【時間序列分析】01. 時間序列·平穩序列
- javascript獲取date物件的時間戳程式碼例項JavaScript物件時間戳
- JavaScript獲取時間戳JavaScript時間戳
- Python-時間戳Python時間戳
- Excel中時間戳轉換時間Excel時間戳
- js時間戳與日期格式的相互轉換JS時間戳
- MYSQL中UNIX時間戳與日期的轉換MySql時間戳
- C++中UNIX時間戳與日期互轉C++時間戳
- c++ 獲取當前時間周初凌晨時間戳(獲取當前時間週一凌晨時間戳)C++時間戳
- javascript將時間物件轉換為時間戳JavaScript物件時間戳
- 如何生成分散式唯一時間戳識別符號 - vanillajava分散式時間戳符號Java
- 如何使用 System.Text.Json 序列化 DateTimeOffset 為 Unix 時間戳JSON時間戳
- Mongoose無法更新時間戳Go時間戳
- PostgreSQL自動更新時間戳SQL時間戳
- PHP日期格式轉時間戳PHP時間戳
- 設定oralce時間戳格式時間戳