Quic_Wire_layout_specification_翻譯

英文原文鏈接:QUIC wire specification

QUIC概述

  本節我們主要介紹QUIC的關鍵功能和優點。QUIC功能上等於TCP+TLS+HTTP/2,但是基於UDP傳輸的。QUIC優於TCP+TLS+HTTP/2的關鍵點有:

  • connect連接建立的低延時
  • 靈活的擁塞控制
  • 無頭部阻塞的多路複用(TCP是有頭部阻塞的)
  • 對頭部和負載進行認證和加密
  • 流和連接的流控
  • 連接遷移

connection連接低延時

  QUIC把加密和傳輸的握手合併,降低了安全連接建立的通信來回次數。QUIC的連接建立過程是0-RTT,也就是說大部分的QUIC連接,數據能立馬發送二不用等待服務器的返回,相比之下TCP+TLS的1-3次握手後才能通信。

  QUIC提供一個特定的流(streamid=1)來進行握手,本文不詳細描述握手協議。如果想要連接握手協議,可以訪問QUIC Crypto Handshake。當前的QUIC握手未來會被TLS1.3代替。

靈活的擁塞控制

  QUIC比TCP有可插拔的擁塞控制和豐富的信令,相對於TCP,這些新信令能爲QUIC提供很多的信息去做擁塞控制算法。當前,默認的擁塞控制是應用TCP Cubic;我們將會經歷更多的可選的擁塞控制方式。

  舉例,每個quic報文,無論是源報文還是重傳報文,都攜帶一個新的sequence號。不同的sequence號幫助發送端確認ACK信息是重傳包的還是原始包的,因此避免了TCP重傳模糊的問題。QUIC ACK也肯定產生包接收和ACK發送之間的延時,因爲有遞增的sequence,也就能準確計算出RTT。

  最後,QUIC的ACK報文支持256個ack,所以QUIC的伸縮性強於TCP(用的SACK),當亂序和丟失發生就能發送更多的字節。客戶端和服務端都有更精確的哪些報文已經收到。

數據流與連接的流控

  QUIC用的是流和連接級別的流控,類似HTTP/2’s流控。QUIC流級別的流控工作如下。QUIC接受者發送字節偏移量,也就是接受者針對每條流能接受的字節數。當在某條流上的數據的收和發,接受者都會發送WINDOW_UPDATE報文增加流的字節偏移量,來允許對端發送更多的數據。

  除了基於流的流控外,QUIC也提供連接級別的流控來限制聚合bffer,其控制QUIC接受者分配一個連接。連接流控的工作方式同流的流控方式一樣,只是字節的發送和接收偏移量是針對所有流的。

  同TCP的接收窗口自動調節機制一樣,QUIC對流和連接的流控應用信用自動調節的機制。當接收應用比較慢時,如果需要限制發送者的速率,QUIC自動調節每個WINDOW_UPDATE報文的信用size。

多路複用

  HTTP/2在TCP上有頭部阻塞的問題。應爲HTTP/2是多流複用的會造成頭部阻塞的問題,一小片TCP報文的丟失會阻塞住後續所有的分片,直到這小片的重傳能收到,完全不在乎後面的HTTP/2分片。
  因爲QUIC設計初衷就是爲了多路複用,對於某一路流的丟包應該隻影響該路流。每路流能馬上被調度當收到報文,哪些沒有報文丟失的流應該能被包重組和正常繼續其應用。

認證和加密頭和數據負載

對認證和加密不太熟悉,本節跳過。

連接遷移

  TCP的連接由4元組定義: 源IP,源port,目的IP,目的port。TCP最著名的問題就是連接無法容忍IP地址變化(舉例,WIFI遷移到移動網絡)或者端口的變化(如當客戶端的NAT綁定超時造成端口的變化)。當MPTCP導致TCP連接遷移,有個很大的困擾就是缺少中間件支持和缺少OS操作系統級別的支持。

  QUIC連接由64bits的connectID定義,有客戶端生成個隨機數。QUIC能繼續連接,即使IP變化或NAT重綁定發生,只要在遷移過程中connectID保持不變。QUIC也提供了自動的加密認證的客戶端變化方式,因爲遷移的客戶端會繼續用同一個會話key來進行加密和認證。

  在某些特定場景中,如果連接可以被IP4元組唯一定義,且該4元組不會變化,可以選擇不包含connectID進行連接。

包類型和格式

  QUIC有特殊包(Special Packets)和常規包(Regular Packets)。

  有兩種特殊包(Special Packets):

  • 版本協商報文(Version Negotiation Packets)
  • public重置報文(Public Reset Packets)

  常規包(Regular Packets)只包括數據報文。

  所有的QUIC報文都應該適配傳輸路徑的MTU大小,以避免IP分片。路徑MTU發現還在研究中,當前推薦IPV6最大的MTU是1350字節,IPv4是1370字節。這裏說的字節數是不包括IP頭和UDP頭的。

QUIC的公共頭(Public Packet Header)

  所有QUIC報文的公共頭都是1~51字節,格式如下:

--- src
     0        1        2        3        4            8
+--------+--------+--------+--------+--------+---    ---+
| Public |    Connection ID (64)    ...                 | ->
|Flags(8)|      (optional)                              |
+--------+--------+--------+--------+--------+---    ---+

     9       10       11        12   
+--------+--------+--------+--------+
|      QUIC Version (32)            | ->
|         (optional)                |                           
+--------+--------+--------+--------+


    13       14       15        16      17       18       19       20
+--------+--------+--------+--------+--------+--------+--------+--------+
|                        Diversification Nonce                          | ->
|                              (optional)                               |
+--------+--------+--------+--------+--------+--------+--------+--------+

    21       22       23        24      25       26       27       28
+--------+--------+--------+--------+--------+--------+--------+--------+
|                   Diversification Nonce Continued                     | ->
|                              (optional)                               |
+--------+--------+--------+--------+--------+--------+--------+--------+

    29       30       31        32      33       34       35       36
+--------+--------+--------+--------+--------+--------+--------+--------+
|                   Diversification Nonce Continued                     | ->
|                              (optional)                               |
+--------+--------+--------+--------+--------+--------+--------+--------+

    37       38       39        40      41       42       43       44
+--------+--------+--------+--------+--------+--------+--------+--------+
|                   Diversification Nonce Continued                     | ->
|                              (optional)                               |
+--------+--------+--------+--------+--------+--------+--------+--------+


    45      46       47        48       49       50
+--------+--------+--------+--------+--------+--------+
|           Packet Number (8, 16, 32, or 48)          |
|                  (variable length)                  |
+--------+--------+--------+--------+--------+--------+

  負載會包含類型獨立的頭部字節,描述如下。

  公共頭字段如下:

  • Public Flags

      * 0x01 = PUBLIC_FLAG_VERSION. 這個flag的含義在於報文由服務器還是客戶端發出。當報文由客戶端發出,設置改bit意味着頭部包含有QUIC version(如下)。客戶端必須設置該bit,直到服務端返回運行的version。服務端同意客戶端的version,但服務端發送的報文中並不設置該標誌位。如果服務端發送的報文設置該bit,意味該報文是version協商報文。version的協商將在後面進行討論。

      * 0x02 = PUBLIC_FLAG_RESET. 該bit位表示Public Reset packet報文。

      * 0x04 表示在頭部有32字節的多元化標誌。

      * 0x08 表示報文有全8字節的connect ID。該bit必須在所有報文中設置,直到有不同的值產生(舉例,客戶端可能需要connect id更少的字節)

      * 0x30 這兩個字節的佔位表示packet number需要字節的數量。這兩個bit僅僅正對數據報文。對於public reset和version negotiation報文(服務端發送的),這兩個字節的佔位設置爲0。

        * 0x30 表示packet number字段有6個字節的長度

        * 0x20 表示packet number字段有4個字節的長度

        * 0x10 表示packet number字段有2個字節的長度

        * 0x00 表示packet number字段有1個字節的長度

      * 0x40 保留爲多路徑用途

      * 0x80 未使用,必須設置爲0

  • Connection ID:

      這個是客戶端生成的64位bit的隨機數,標識連接的唯一性。因爲QUIC的連接設計初衷是即使客戶端IP遷移,連接也不中斷,IP4元組(源IP,源port,目的IP,目的port)並不需要去確定連接的唯一性。如果對於某個傳輸的方向,IP4元組能代表連接的唯一性(其實就是不可能發生IP遷移等),connect ID字段也就不需要了。

  • QUIC Version:

      32位表示QUIC協議的版本。該字段僅僅當public flag設置了FLAG_VERSION後纔有(i.e public_flags & FLAG_VERSION !=0)。客戶端設置這個flag後,且必須包含一個客戶端推薦的quic version,包含任意數據(符合這個版本的)。服務器設置這個flag,僅當客戶端推薦的quic version不支持,服務端返回一個列表包含可接受的quic version,但是不必後續帶有數據。版本字段例子,"Q025"版本,"Q"在第9個字節,"0"在第10個字節,依次類推。(文檔後有版本列表)

  • Packet Number:

      packet number的長度基於FLAG_BYTE_SEQUENCE_NUMBER的flag設置在public flag。每一個常規報文regular packet(也就是非public reset和version negotiation報文)都需要被髮送方設置packet number。第一個被髮送的報文的packet number應該設置成1,後續的報文的packet number應該+1遞增。

      packet number的64位被放在加密的內容中;因此,QUIC的一方不能發送報文,其packet number不在64bits內。如果QUIC的一方發送的packet number是2^64-1,報文產生CONNECTION_CLOSE報文,錯誤碼是QUIC_SEQUENCE_NUMBER_LIMIT_REACHED,並且不會再發送其他的報文。

      大部分情況packet number的48bits長度的傳輸,爲了接收端能清晰的對packet number進行組包,QUIC發送端不應該發送packet number大於2^(bitlength-2)。因此48bits長度的packet number不應該大於(2^46)。

      任何被截斷的packet number都應該被推斷爲最接近已經收到最大packet number,其包含這個截斷的packet number。這個packet number的傳輸比例與推斷中的地位bits對應。

      Public Flag的處理流程如下:

--- src
Check the public flags in public header
                 |
                 |
                 V
           +--------------+
           | Public Reset |    YES
           | flag set?    |---------------> Public Reset Packet
           +--------------+
                 |
                 | NO
                 V
           +------------+          +-------------+
           | Version    |   YES    | Packet sent |  YES
           | flag set?  |--------->| by server?  |--------> Version Negotiation
           +------------+          +-------------+               Packet
                 |                        |
                 | NO                     | NO
                 V                        V
           Regular Packet         Regular Packet with 
                              QUIC Version present in header
---

Special Packets

Version Negotiation Packet

  version協商報文僅僅由服務端發送。version協商報文由8bit的public flag和64bit的connect ID。public flag必須設置PUBLIC_FLAG_VERSION,和64位bit的connect ID。報文後續是一個服務器支持version的信息列表,列表每項是4byte的version字段:

--- src
     0        1        2        3        4        5        6        7       8
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
| Public |    Connection ID (64)                                                 | ->
|Flags(8)|                                                                       |
+--------+--------+--------+--------+--------+--------+--------+--------+--------+

     9       10       11        12       13      14       15       16       17
+--------+--------+--------+--------+--------+--------+--------+--------+---...--+
|      1st QUIC version supported   |     2nd QUIC version supported    |   ...
|      by server (32)               |     by server (32)                |             
+--------+--------+--------+--------+--------+--------+--------+--------+---...--+

---

Public Reset Packet

  Public Reset報文由8bit的public flag和64bits的connect ID。public flag必須設置PUBLIC_FLAG_RESET,和64bit的connect ID。如果這是一個帶tag PRST加密的握手消息,報文的剩餘部分是被加密的(見 [QUIC-CRYPTO]):

--- src
     0        1        2        3        4         8
+--------+--------+--------+--------+--------+--   --+
| Public |    Connection ID (64)                ...  | ->
|Flags(8)|                                           |
+--------+--------+--------+--------+--------+--   --+

     9       10       11        12       13      14       
+--------+--------+--------+--------+--------+--------+---
|      Quic Tag (32)                |  Tag value map      ... ->
|         (PRST)                    |  (variable length)                         
+--------+--------+--------+--------+--------+--------+---
---

Tag value map: 這個Tag value map有一下tar-values信息:

  • RNON (public reset nonce proof) - a 64-bit unsigned integer. Mandatory.
  • RSEQ (rejected packet number) - a 64-bit packet number. Mandatory.
  • CADR (client address) - the observed client IP address and port number. 這當前只是用於調試目的,所以是可選的。

常規報文(Regular Packets)

  常規報文加上認證和加密的。Public header是加了認證信息,但是並未加密,常規報文的剩餘部分是被加密的。在public header後面,常規報文包含AEAD(authenticated encryption and associated data,認證和被加密的數據)數據。這些數據應該按順序被解密。解密後,明文應該由按順序的frame組成。

數據報文(Frame Packet)

  Frame報文的負載由一系列的type前綴的frames組成。報文type的格式後面會描述,總體的格式如下:

--- src
+--------+---...---+--------+---...---+
| Type   | Payload | Type   | Payload |
+--------+---...---+--------+---...---+
---

QUIC連接的生命週期(Life of a QUIC Connection)

連接建立(Connection Establishment)

  QUIC客戶端是一方發起連接的。QUIC的連接由version協商和加密、傳輸握手混合進行,以此降低連接的延時。我們下面先介紹version協商。

  每個客戶端發向服務端的初始化報文必須設置version flag,必須定義將要使用version。每個客戶端發送的報文都不許帶version flag,直到收到服務端返回一個不帶version flag的報文。在服務端收到客戶端第一個不帶version flag的報文後,服務端就必須丟棄所有再收到version flag的報文。

  當服務端收到一個新的connect ID,它將比較客戶端的版本自己是否支持。如果客戶端的版本自己自持,服務端將在整個連接週期內用該版本。然後,所有服務端的發送報文都應該清除version flag該標誌位。

  如果客戶端的版本不被服務器接收,1個RTT的延時就會觸發。服務端將發送Version協商報文給客戶端。這個報文的version flag會被設置,並且會包含服務端支持的version列表。

  當客戶端收到version協商報文,會選擇其中一個version並用這個version重發所有報文。這些報文必須也設置version flag和包含該version。最終,客戶端接收到從服務器來的第一個常規報文開始,表示version協商的結束,客戶端之後發送的所有報文都應該去使能version flag。

  爲了避免downgrade攻擊,客戶端定義在第一個報文的version和服務器支持的version列表都必須包含在加密的handleshake數據中。客戶端需要確認在handshake中的version列表和version協商列表進行對比,得到相同一致的。服務端需要確認客戶端發來的handshake中的version是否實際支持。

  連接建立的後續部分將在handshake文檔中介紹[QUIC-CRYPTO]。加密的handshake被分配固定的stream ID 1。

  在連接建立過程中,handshake必須協商各種傳輸參數。當前已經定義的傳輸參數再本文後面有介紹。

數據傳輸(Data Transfer)

  QUIC應用連接可靠性,擁塞控制和流控。QUIC流控基本上同HTTP/2的流控一樣。QUIC可靠性和擁塞控制在相關的文檔中描述。QUIC連接用唯一的packet sequence數字字段,對整個連接中的擁塞空着和丟包重傳都一致。

  在QUIC連接中傳輸的所有數據,包括加密的handshake,都是在stream中作爲數據傳輸,ACK返回QUIC報文除外。

  本節概念上對一個QUIC連接中數據傳輸中流的使用進行介紹。各個各樣的報文會在Frame Type and Formats節進行介紹。

QUIC流的生命週期(Life of a QUIC Stream)

  QUIC流是雙向發送的數據被分配到流分配包中的很多獨立序列。stream能被客戶單或服務器創建,能與其他的流一起併發發送數據,並且能停止發送。QUIC流的生命週期模型與HTTP/2的非常相似。[RFC7540]
(QUIC流的HTTP/2用法在本文檔後面進行詳細描述)

  針對指定流發送一個流報文,就隱性的創建一個stream。爲了避免stream ID衝突,如果是服務端發起stream的話,stream-ID必須是偶數;客戶端發起stream的話,stream-ID必須是單數。0不是一個有效的stream-ID。Stream 1給加密的handshake作爲第一個客戶端端發起stream使用。當應用HTTP/2 over QUIC時,Stream 3爲發送所有其他流的壓縮頭使用,從而確保可靠有序的發送和頭部處理。

  當新流被創建時,連接雙方的stream ID應該連續的增長。舉例,Stream2應該在Stream 3後創建(stream 3是客戶端,stream2是服務端),但是stream 7肯定不能再stream 9後才創建。對端可能接受的流是無序的。舉例,如果在服務端接受packet9包含stream7前,接受到packet10包含stream9,服務器必須能從容處理這樣的亂序情況。

  如果一方收到一個stream包但並不想接收它,它可以立即返回一個RST_STREAM報文(下面會介紹)。注意,雖然發起方已經在該stream中發送數據,但這些數據會被丟棄。

  一旦流被創建,它就能發送和接收數據。也就是說直到流在某方向結束前,這條流上的報文都能持續的被髮送。

  每個QUIC端都能正常終結stream。有3種終結stream的方法:

  • 正常終結(Normal termination): 因爲流是雙向的,所以流能是單方向關閉或全關閉。當一方發送的報文帶有FIN標誌位,就代表單方向關閉。FIN標誌着發送FIN的這一方不會再有數據要發送。當QUIC的一方發送並接受了FIN,這方也就被認爲完全關閉了。FIN應該放在最後一個用戶數據的報文中,但是FIT也能在最後一個用戶數據報文後作爲空報文發送(有點浪費)
  • 突然結束(Abrupt termination):客戶端和服務器能發送RST_STREAM在任何時候。RST_STREAM報文包含error錯誤碼解釋失敗的原因(錯誤碼列表在本文最後)。當RST_STREAM是流發起方發送,表明有錯誤發生且不會有更多的數據在該流發送。當RST_STREAM是接受者發送,流的發送方在接收到RST_STREAM報文後,應該立即停止任何數據在該流上的發送。流的接收方也應該意識到有個時間間隔在發送方已經發送的數據,和發送方接收到接收方發來的RST_STREAM報文。爲了保證連接級別的流控能正確的被計數,即使RST_STREAM報文已經收到,發送方也需要確認:在該流上的FIN和所有數據字節對端已經收到;或對端收到RST_STREAM。也就是說,RST_STREAM的發送端需要繼續用正確的WINDOW_UPDATEs響應這條流上的數據,保證發送方不會有流控阻塞,保證其完成FIN的發送。
  • 當連接斷開,流肯定也斷開,在下面一節會詳細介紹連接斷開。

連接斷開(Connection Termination)

  連接保持打開狀態直到變成空閒狀態一段設定的時間。當服務端要斷開一個空閒連接,它不需要通知客戶端,這回導致移動設備的喚醒信號。QUIC連接一段建立,有兩種方式可以結束:

  • 顯式關閉(Explicit Shutdown): 一方發送CONNECTION_CLOSE報文給另外一方表明連接開始中斷。一方也可以發送GOAWAY報文給另外一方,而不是用CONNECTION_CLOSE,GOAWAY表明連接很快將關閉。GOAWAY發送到對端後,對端繼續對所有活躍的報文進行處理,但是GOAWAY的發送方不再發送新的報文,也不在接收任何新的數據報文。對活躍流的結束,也可以發送CONNECTION_CLOSE。如果當爲結束的流是活躍的(沒有FIN或RST_STREAM報文被髮送或接收),一方發送CONNECTION_CLOSE報文,那麼對端就認爲流未完成,已經被非正常結束。
  • 隱式關閉(Implicit Shutdown):默認的QUIC連接的空閒超時是30秒,在連接協商中有個參數"ICSL"定義。最大值是10分鐘。如果在空閒超時時間內沒有任何網絡活躍,連接會關閉。默認情況下CONNECTION_CLOSE將發送。當發送顯示關閉太浪費,如移動網絡會喚醒手機信號,"靜音"關閉的選項被使能。

      QUIC的一方在任何連接獲取的時候,也能通過發送PUBLIC_RESET來終結連接。PUBLIC_RESET的PUBLIC_RESET是等價於TCP的RST。

報文類型和格式(Frame Types and Formats)

  QUIC流是以報文方式存在,報文都有報文類型(frame type),類型有完全獨立的解釋,後面跟隨fream header字段。所有的frame都被包含在QUIC報文中,沒有哪個frame會越過QUIC報文的邊界。

報文類型(Frame Types)

  對於報文類型有兩種解釋,也就由此定義兩種報文類型:

  • 特殊報文(Special Frame Types)

    特殊報文包含frame type和flag信息在frame type的字段中
  • 常規報文(Regular Frame Types)

    常規報文只包含frame type在frame type的字段中

當前定義的特殊報文類型(Special Frame Types):

--- src
   +------------------+-----------------------------+
   | Type-field value |     Control Frame-type      |
   +------------------+-----------------------------+
   |     1fdooossB    |  STREAM                     |
   |     01ntllmmB    |  ACK                        |
   |     001xxxxxB    |  CONGESTION_FEEDBACK        |
   +------------------+-----------------------------+
---

當前定義的常規報文類型(Regular Frame Types):

--- src
   +------------------+-----------------------------+
   | Type-field value |     Control Frame-type      |
   +------------------+-----------------------------+
   | 00000000B (0x00) |  PADDING                    |
   | 00000001B (0x01) |  RST_STREAM                 |
   | 00000010B (0x02) |  CONNECTION_CLOSE           |
   | 00000011B (0x03) |  GOAWAY                     |
   | 00000100B (0x04) |  WINDOW_UPDATE              |
   | 00000101B (0x05) |  BLOCKED                    |
   | 00000110B (0x06) |  STOP_WAITING               |
   | 00000111B (0x07) |  PING                       |
   +------------------+-----------------------------+
---

流報文(STREAM Frame)

  流報文被隱式的創建流併發送報文,格式如下:

--- src
     0        1       …               SLEN
+--------+--------+--------+--------+--------+
|Type (8)| Stream ID (8, 16, 24, or 32 bits) |
|        |    (Variable length SLEN bytes)   |
+--------+--------+--------+--------+--------+

  SLEN+1  SLEN+2     …                                         SLEN+OLEN   
+--------+--------+--------+--------+--------+--------+--------+--------+
|   Offset (0, 16, 24, 32, 40, 48, 56, or 64 bits) (variable length)    |
|                    (Variable length: OLEN  bytes)                     |
+--------+--------+--------+--------+--------+--------+--------+--------+

  SLEN+OLEN+1   SLEN+OLEN+2
+-------------+-------------+
| Data length (0 or 16 bits)|
|  Optional(maybe 0 bytes)  |
+------------+--------------+
---

流報文頭部的各個字段描述如下:

  • Frame Type: 報文類型是8bit大小,包含各種flag信息(1fdooossB)

       * 最左邊的bit設置1,表示這是個流報文。The leftmost bit must be set to 1 indicating that this is a STREAM frame.

       * f標誌位是FIN表示,當設置爲1,表示發送端完成該流的發送,並希望半雙工關閉(後續詳細介紹)

       * d表示Data length會在STREAM頭部存在,如果設置爲0,表示流報文的長度會是一直到報文末尾。

       * ooo表示offset字段的長度,000–111分別表示0, 16, 24, 32, 40, 48, 56, or 64 bits長度。

       * ss表示Stream ID的長度,00–11分別表示8, 16, 24, or 32 bits長度。
  • Stream ID: 可變長度的無符號整型ID,標識唯一的流。
  • Offset: 可變長度的無符號整型,表示流數據塊開始的偏移位置。
  • Data length: (可選)16bit長的無符號整型,標識報文中的數據長度。如果不需要此字段,表示offset後到末尾的所有字節都是數據,後面沒有padding數據。

    一個流報文肯定要麼有非0的數據長度,要麼數據長度爲0但是FIN標誌位被設置1。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章