QUIC——快速 UDP 網絡連接協議

Quick, UDP, Internet, Connections

誕生背景

使用 HTTP/2 所提供的多路複用功能在鏈路出現丟包時,TCP 的按序確認機制使得丟失的數據包需要等待重新發送和確認,滑動窗口停滯,其後的所有數據包都被阻塞,這樣一來 HTTP/2 在這種情形下的表現反而不如 HTTP/1。

此外,HTTP/2 在建立 TCP 連接的時,需要和服務器進行三次握手來確認連接成功,會消耗 1.5 個 RTT,如果使用 HTTPS 的話,還需要使用 TLS 協議進行加密,而 TLS 也根據版本需要 1~2 個 RTT(TLS1.2 需要 1RTT),也就是說,使用 HTTP/2 在信息得到傳輸前就需要消耗 3~4 個 RTT(至少 2.5RTT)的時間。

TCP 的短板問題

  1. TCP +TSL 握手佔用時間(至少 2.5RTT)

  2. TCP 巨大的頭部浪費帶寬(20~60 字節)

  3. TCP 頭部擁塞

TCP 的按序確認除了導致頭部擁塞外,還導致了另一個重傳包數量問題:TCP 接收方可以將未按序到達的數據包 37、38、40 先行緩存(並且引入快速重傳機制,回送缺失數據包 34 的 ack 不斷提醒發送方,如果發送方連續收到 3 次相同的 ack,就會重傳,防止超時引發窗口縮小),但是由於 ack 序列號只能確認連續的數據包,所以無法通知發送方 37、38、40 已經先行到達,只能發送數據包 34 的 ack,而發送方在接收到重傳請求後不確定從 35~41 這些已經發送的數據包要不要同樣重傳,因爲後續的包可能被接收,也可能丟失。如果全部重傳,那麼會浪費帶寬,如果不重傳,那麼如果這些包丟失,就會浪費時間。(後續又引入了 SACK 機制:https://caoziye.top/2019/10/TCP-Options/,但是 SACK 又繼續增大了 TCP 的頭部)

  1. TCP 連接無法遷移(源 IP + 源 Port + 目標 IP + 目標 Port + 傳輸層協議)

除了傳輸層協議是 TCP 不變以外,剩下的四元組其中任一發生變化,TCP 連接就會斷開,需要重新和新的 ip:port 重新握手建立連接。比如移動設備 wifi 和 5g 網絡的切換,或者是行車過程中導致的移動網絡節點的切換都會讓 TCP 的連接斷開。

傳輸層協議帶來的問題無法在應用層協議上得到解決,並且 TCP 因爲已經存在了 40 多年,基於 TCP 協議的更新非常難以推進(因爲被大量內置於操作系統內核、中間件固件以及硬件實現中),因此 Google 基於 UDP 協議推出了 QUIC 協議。

UDP 協議相較於 TCP,擁有更小的頭部,簡單而高效,但是不保證可靠交付,因此使用 UDP 協議同時爲了確保數據傳輸的可靠性,需要自己維護丟包檢測、數據確認、擁塞控制、重傳等等一系列基礎設施。

QUIC 主要特性

多路複用

HTTP/2.0 使得一個 TCP 連接能夠順序傳輸多個文件,再通過 SPDY 協議實現請求的併發以及優先級控制,但是終歸會受到頭部擁塞的限制。

而 QUIC 是基於 UDP 的,在傳輸層層面並沒有固定的連接,可以根據需要開闢任意邏輯鏈路。QUIC 一次建立一個 Connection,一個 Connection 下包含多個 Stream 流(每個 stream 獨自維護一個邏輯連接,因爲 UDP 層面上是無連接的),每個流對應一個文件傳輸,並將不同的 Stream 中的數據交付給不同的上層應用。QUIC 的一個 Connection 對應多個 Stream,Stream 之間相互獨立,因此任意一條鏈路斷開都不會導致其他數據阻塞。

協議頭部

QUIC 是基於 UDP 的,所以最外層是 UDP 頭部(單位爲 Bit)

內部是 QUIC Connection 頭部和每個 Stream 的 Frame 頭部(單位爲 Bit)

具體每個頭部字段含義和標誌位過於機械和繁雜,有興趣可以直接查看原文 https://datatracker.ietf.org/doc/html/rfc9000#section-12

數據流控制

QUIC 提供了兩種層面上的數據流控制方案:

Stream 控制

💡 其實 QUIC 的亂序確認設計思想並不新鮮,大量網絡視頻流就是通過類似的基於 UDP 的 RUDP、RTP、UDT 等協議來實現快速可靠傳輸的。他們同樣支持亂序確認,所以就會導致這樣的觀看體驗:明明進度條顯示還有一段緩存,但是畫面就是卡着不動了,如果跳過的話視頻又能夠播放了。

  1. 如圖所示,當前緩衝區大小爲 8,QUIC 按序(offset 順序)發送 29-36 的數據包:

  1. 31、32、34 數據包先到達,基於 offset 被優先亂序確認,但 30 數據包沒有確認,所以當前已提交的字節偏移量不變,緩存區不變。

  1. 30 到達並確認,緩存區收縮到閾值,接收方發送 MAX_STREAM_DATA frame(協商緩存大小的特定幀)給發送方,請求增長最大絕對字節偏移量。

  1. 協商完畢後最大絕對字節偏移量右移,緩存區變大,同時發送方發現數據包 33 超時

  1. 發送方將超時數據包重新編號爲 42 繼續發送

以上就是最基本的數據包發送 - 接收過程,控制數據發送的唯一限制就是最大絕對字節偏移量,該值是接收方基於當前已經提交的偏移量(連續已確認並向上層應用提交的數據包 offset)和發送方協商得出。

Connection 控制

除了 Stream 層面的數據流控制之外,QUIC 還提供了 Connection 層面的總體緩存大小控制,Connection 具有總體的緩衝區大小限制,並且可以爲其中的各個 stream 動態分配緩衝區大小,在總體緩衝區大小不變的情況下優先向速度更快的 stream 傾斜(並不是平均分配)。

如圖所示,Connection 具有傳輸字節上限,即 Stream1、2、3 的 Maximum Offset 之和不得超過該上限,QUIC 會根據網絡情況爲各個 Stream 分配不同的偏移量,並且隨着傳輸的進行,接收方會發送 MAX_DATA frame 通知發送方提高 Connection 總體傳輸字節分配上限,並在 Stream 連接中通過 MAX_STREAM_DATA frame 爲各個 Stream 分配更多的緩存。

快速握手與加密傳輸

QUIC 在握手過程中使用 Diffie-Hellman 算法協商初始密鑰,初始情況下服務器存儲的配置參數如下:

  1. Server Config:一個服務器配置文件,包括服務器端的 Diffie-Hellman 算法的長期公鑰 A 以及兩個固定質數 g 和 p

  2. Certificate Chain:用來對服務器進行認證的信任鏈證書

  3. Signature of the Server Config:Server Config 的簽名並用信任鏈的葉子證書的私鑰加密

  4. Source-Address Token:一個經過身份驗證的加密塊,包含客戶端可見的 IP 地址和服務器的時間戳。

這些參數會週期性的更新。

Diffie-Hellman 算法的基本原理

Diffie-Hellman 並不是加密算法,而是密鑰的一種交換技術,可以通過該算法在雙方互不知情的情況下建立加密通訊

假設 Alice 爲服務器,Bob 爲客戶端

於是,雙方都有了一個共享密鑰 (初始密鑰)K。簡單理解,a、b 就相當於密鑰,A、B 就相當於公鑰。

隨後再利用這個初始密鑰商定會話密鑰,之後就一直用會話密鑰溝通了。

密鑰交換過程

QUIC 首次連接需要 1RTT,具體過程如下:

step1: 客戶端發送 Inchoate Client Hello 消息(CHLO)請求建立連接。

step2: 服務器根據一組質數 p 以及其原根 g 和 a(長期私鑰)算出 A(長期公鑰),將 Apg(通過 CA 證書私鑰加密後)放在 serverConfig 裏面,發到 Rejection 消息(REJ)到客戶端;

服務器一開始不直接使用隨機生成的短期密鑰的原因就是因爲客戶端可以緩存下服務端的長期公鑰,這樣在下一次連接的時候客戶端就可以直接使用這個長期公鑰實現 0-RTT 握手並直接發送加密數據

setp3&4: 客戶端在接收到 REJ 消息後,會隨機選擇一個數 b(短期密鑰),並用 CA 證書獲取的公鑰解密出 serverConfig 裏面的 p、A 和 b 就可以算出初始密鑰 K,並將 B(Complete client hello 消息)和用初始密鑰 K 加密的 Data 數據發到服務器。

step5: 服務器收到客戶端發來的公開數 B,再利用 p、g 計算得到同樣的初始祕鑰 K,來解密客戶端發來的數據。這時會利用其他加密算法隨機生成此次會話密鑰 K' ,再通過初始密鑰 K 加密 K'發送給客戶端 (SHLO)(每次會話都是用隨機密鑰,並且服務器會定期更新 a 和 A,實際上這就是爲了保證前向安全性)

在密碼學中,前向保密(Forward Secrecy)是密碼學中通訊協議的安全屬性,指的是當前使用的主密鑰泄漏不會導致過去的會話密鑰泄漏。

step6: 客戶端收到 SHLO 後利用初始密鑰 K 解出會話密鑰 K',二者後續的會話都使用 K'加密。

連接遷移

TCP 的連接標識是通過 “源 IP + 源 Port + 目標 IP + 目標 Port + 傳輸層協議(TCP)” 組成的唯一五元組,一旦其中一個參數發生變化,則需要重新創建新的 TCP 連接。

都會造成 TCP 斷線,需要客戶端上層應用重新發送請求建立連接(又一次進行握手)

QUIC 連接不再以 IP 及端口四元組標識,而是以一個服務端產生的 64 位的隨機數作爲 ID 來標識,這樣就算 IP 或者端口發生變化時,只要 ID 不變,這條連接依然維持着,上層業務邏輯感知不到變化,不會中斷,也就不需要重連。(當然如果 UDP 和 IP 協議所包含的源 IP + 源 Port + 目標 IP + 目標 Port 四元組已經能夠標識鏈接的唯一性的話,connection 頭部是可忽略的)

連接遷移的簡化流程(實際情況更爲複雜):

  1. 連接遷移之前,客戶端的 IP 1,使用非探測包(Non-probing Packet)和服務端進行通信。

  2. 客戶端的 IP 變成 2,它繼續發送非探測包維持通信,將連接遷移到新的地址。

  3. 服務端收到包後在新路徑啓動路徑驗證 [1],驗證新路徑的可達性,以及客戶端對其新 IP 地址的所有權。

  4. 服務端發送包含PATH_CHALLENGE幀的探測包(Probing Packet),PATH_CHALLENGE幀裏面包含一個不可預測的隨機值。

  5. 客戶端在PATH_RESPONSE幀裏面包含前一步PATH_CHALLENGE接收到的隨機值,響應探測包(Probing Packet)。

  6. 服務端接收到客戶端發送的的PATH_RESPONSE ,驗證 payload 裏面的值是否正確。

  7. 隨後客戶端也會對服務端進行路徑驗證保證雙向通信。

丟包檢測

TCP 傳輸的數據只包括校驗碼,並沒有增加糾錯碼等冗餘數據,如果出現部分數據丟失或損壞,只能重新發送該數據包。

QUIC 引入了前向冗餘糾錯碼(FEC: Fowrard Error Correcting),如果接收端出現少量(不超過 FEC 的糾錯能力)的丟包或錯包,可以藉助冗餘糾錯碼恢復丟失或損壞的數據包,這就不需要再重傳該數據包了,降低了丟包重傳概率,自然就減少了擁塞控制機制的觸發次數,可以維持較高的網絡利用效率。因此需要根據當前網絡狀況設置一定比率的冗餘數據,就可以帶來網絡利用率的提升。

此外由於 QUIC 採用單向遞增的 Packet Number 來標識數據包,所以不像 TCP 會因爲超時重傳的同樣序列的數據包而和原數據包重疊,造成 RTT 測量的不準確,進而導致 RTO(Retransmission Time Out:重傳超時時間)的不準確。

TCP 的 RTT 計算

TCP 對於此問題也是非常頭疼,於是也不斷進行改進,比如

QUIC 的包號不會重複,重傳的包採用了新的 Packet Number,因此不會產生 RTT 歧義問題

因此 QUIC 對於 RTT 的計算更爲準確,預估的超時時間能夠有效防止更多的重傳請求被錯誤地發送回發送端。同時也給予了 QUIC 網絡更爲快速的反應時間,及時通知發送方重傳數據包。

自定義擁塞控制

QUIC 的傳輸控制不再依賴內核的擁塞控制算法,而是實現在應用層上,這意味着我們根據不同的業務場景,實現和配置不同的擁塞控制算法以及參數。比如 BRR 或者 Cubic,如果有興趣可以自行查閱相關算法資料。

在 HTTP/3 上的應用

  1. wifi 和移動網絡無縫切換

  2. 更強的網絡安全性(前向安全 + 全載荷加密)

  3. 在慢網情況下更高的傳輸速率

QUIC 離我們並不遙遠

QUIC 早在 2012 年就已經開始試驗性部署,關於其詳細草案在 2015 年向 IETF 提出,終於在 2021 年五月被接受並於 RFC9000 中標準化。

chrome://flags/#enable-quic 在 chrome 瀏覽器中可以選擇是否開啓 QUIC 實驗性功能,如果服務端支持 QUIC 協議,就會啓用該協議(大部分都是 Google 的服務器)。

推薦一個插件可以查看當前網頁支持的連接類型:HTTP/2 and SPDY indicator:

https://chrome.google.com/webstore/detail/http2-and-spdy-indicator/mpbpobfflnpcgagjijhmgnchggcjblin

性能參考(數據來源:騰訊 PCG 研發部)

https://mp.weixin.qq.com/s/DHvvp6EUR5tDffJqzVir0A

60kb 主頁面資源加載速度(單位:毫秒)

弱網環境下的表現

不同丟包率下的下載耗時

從總體上來看,QUIC 在網絡環境良好的情況下對於當前 HTTP2 的提升有限,尤其是首次 1-RTT 握手的總體時間消耗提升只有 15% 左右,但是在後續有緩存的情況下建立連接的速度就會快很多,首次響應時間將會大大縮小。

此外在弱網環境下,尤其是丟包率高的情況下 QUIC 對於性能提升十分驚人,良好的 RTO 估算機制使得超時重發的估算變得更爲精確。同時多個邏輯連接使得文件與文件之間的傳輸互不干擾阻塞,加上更加輕量的頭部和簡單高效的握手方式,因此能夠在弱網環境下取得更爲強大的表現。

總結

隨着網絡基礎設施的提升,UDP 的傳輸準確率也得到了很大的提升,而 TCP 卻因爲 20~60 字節的頭部以及可能的頭部擁塞導致一定的效率降低,但是 TCP 協議已經被大量內置於操作系統內核中,因此只能利用 UDP 進行定製化。雖然 QUIC 可能會在小頁面的性能不如 TCP,但隨着前端日益複雜化,資源量不斷增大的情況下,使用 QUIC 替換 TCP 將能夠顯著提升傳輸速率。

放棄 TCP 而使用基於 UDP 的 QUIC,有點類似早期 x86cpu 內置的 tss 硬件切換不好用,linux 系統內核直接使用軟件控制進程上下文切換。

參考文獻

【HTTP/2 與 HTTP/3 的新特性】https://blog.csdn.net/howgod/article/details/102597450

【QUIC 協議原理淺解】https://www.163.com/dy/article/G5D1ETVH0518R7MO.html

【QUIC 加密握手中共享密鑰算法】https://blog.csdn.net/chuanglan/article/details/85106706

【QUIC 流量控制】:https://zhuanlan.zhihu.com/p/337175711

【QUIC 加密傳輸和握手】https://zhuanlan.zhihu.com/p/301505712

【TCP 亂序緩存和重傳的改進方式】https://blog.csdn.net/cws1214/article/details/52430554

【科普:QUIC 協議原理分析】https://zhuanlan.zhihu.com/p/32553477

【rfc9000】https://datatracker.ietf.org/doc/html/rfc9000

參考資料

[1]

路徑驗證: https://zhuanlan.zhihu.com/p/290694322

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/W0ExnM3bU6VOwelJVABR5w