(譯)Cloudflare: 通往 QUIC 之路(The Road to QUIC)

做者: Alessandro Ghedini,原文地址: https://blog.cloudflare.com/the-road-to-quic/

QUIC (Quick UDP Internet Connections) 是一種新的默認加密Internet傳輸協議,它提供了許多改進,旨在加速 HTTP 通訊並使其更加安全,其目標是最終取代 web 上 的 TCP 和 TLS。在這篇博文中,咱們將概述 QUIC 的一些關鍵特性,它們如何爲 web 帶來好處,以及支持這個新協議所面臨的一些挑戰。html

事實上,有兩個協議同名:「Google QUIC」(簡稱 gQUIC),是Google工程師幾年前設計的原始協議,通過多年的試驗,如今已經被 IETF(互聯網工程任務組)採用進行標準化。git

「IETF-QUIC」(從如今起就是「QUIC」)已經與 gQUIC 有至關大的不一樣,所以能夠將其視爲一個單獨的協議。從數據包的格式,到握手(handshake)和 HTTP 映射,QUIC 改進了最初的 gQUIC 設計,這得益於許多組織和我的的開放協做,共同的目標是使互聯網更快、更安全。github

那麼,QUIC提供了哪些改進?web

內置安全性(和性能)

QUIC 與如今受人尊敬的 TCP 的一個更根本的差異是,它的設計目標是提供一個默認安全的傳輸協議。QUIC 經過提供安全特性來實現這一點,好比身份驗證和加密,這些特性一般由比傳輸協議自己更高層的協議(如TLS)處理。算法

QUIC 初始握手合併了 TCP 的典型三次握手和 TLS 1.3 握手,提供了端點的身份驗證和密碼參數的協商。對於那些熟悉 TLS 協議的人,QUIC 用本身的幀格式替換 TLS 記錄層,同時保持相同的TLS握手消息。瀏覽器

這不只能夠確保鏈接始終通過身份驗證和加密,並且還能夠加快初始鏈接的創建:與 TCP 和 TLS 1.3 握手組合起來所需的 2次 RTT(round-trip) 相比,典型的 QUIC 握手只需在客戶端和服務器之間進行1 次 RTT。安全

 

但 QUIC 更進一步,它還加密了額外的鏈接元數據(metadata),這些元數據可能被中間設備(middle-boxes)濫用來干擾鏈接。例如,當使用鏈接遷移(connection migration)時,被動路徑攻擊者可以使用數據包編號關聯多個網絡路徑上的用戶活動(見下文)。經過加密數據包編號,QUIC 確保除了鏈接中的端點以外,任何人都不能使用它們來關聯用戶的活動。服務器

加密也能夠是對僵化(ossification)的一種有效的補救措施,它使得協議中內置的靈活性(例如可以協商(negotiate)該協議的不一樣版本)。因爲實現中的錯誤假設而沒法在實踐中使用(僵化(ossification)是致使 TLS 1.3 長時間沒法部署的緣由,這隻有在幾回修改後纔可能實現,這些修改旨在防止僵化的中間設備(middle-boxes)錯誤地阻塞 TLS 協議的新修訂)。網絡

隊頭阻塞(Head-of-line blocking)

HTTP/2 的主要改進之一是可以將不一樣的 HTTP 請求多路複用(multiplexing)到同一個TCP鏈接上。這容許 HTTP/2 應用程序併發地處理請求,並更好地利用可用的網絡帶寬。併發

與當時的現狀相比,這是一個很大的改進,即若是應用程序但願同時處理多個 HTTP/1.1 請求,則須要啓動多個 TCP+TLS 鏈接(例如,當瀏覽器須要同時獲取 CSS 和 Javascript 資源來呈現網頁時)。初始的握手會減慢初始頁面的傳輸速度,這意味着須要屢次刷新新頁面。多路複用(Multiplexing)HTTP 避免了全部這些問題。

然而,這也有一個缺點:因爲多個請求/響應是經過同一個 TCP 鏈接傳輸的,所以它們都會受到數據包丟失(例如,因爲網絡擁塞)的影響,即便丟失的數據只涉及單個請求。這被稱爲「隊頭阻塞」(head-of-line blocking)。

QUIC 更深刻一點,爲多路複用提供一流的支持,這樣不一樣的 HTTP 流能夠映射到不一樣的 QUIC 傳輸流,可是它們仍然共享相同的 QUIC 鏈接,所以不須要額外的握手和共享擁塞狀態。可是 QUIC流是獨立的,這樣在大多數狀況下,一個流的包丟失不會影響其餘流。

例如,這能夠顯著減小呈現完整網頁(使用CSS、Javascript、圖片和其餘類型的資源)所需的時間,尤爲是在穿越高度擁擠的網絡、具備高數據包丟失率的網絡時。

這麼簡單,呃?

爲了實現它的承諾,QUIC 協議須要打破許多網絡應用程序認爲理所固然的一些假設,這可能會使QUIC 的實現和部署更加困難。

QUIC 被設計在 UDP 數據報之上傳輸,以便於部署,並避免網絡設備丟棄未知協議的數據包,由於大多數設備已經支持UDP。這也容許 QUIC 實如今用戶態(user-space)中生存,所以,例如瀏覽器將可以實現新的協議特性並將其發送給用戶,而沒必要等待操做系統的更新。(而 TCP 協議的更新受到網絡設備和操做系統內容的約束,這也是 QUIC 設計在 UDP 之上的緣由之一)

然而,儘管預期的目標是避免破壞,但它也使得防止濫用和正確地將數據包路由到正確的端點更具挑戰性。

一個 NAT 把他們都帶來,在黑暗中捆綁他們(One NAT to bring them all and in the darkness bind them)

典型的 NAT 路由器可使用傳統的4元組(源 IP 地址和端口、目的 IP 地址和端口)來跟蹤通過它們的TCP鏈接,並經過觀察在網絡上傳輸的 TCP SYN、ACK 和 FIN 包,來檢測新鏈接什麼時候創建和什麼時候終止。這使它們可以精確地管理 NAT 綁定的生命週期(lifetime),內部 IP 地址和端口之間的關聯,以及外部 IP 地址和端口之間的關聯。

對於QUIC,這還不可能,由於如今普遍部署的 NAT 路由器還不瞭解 QUIC,因此它們一般會退回(fallback)到默認和不太精確的處理 UDP 流,這一般涉及使用任意的,有時很是短的超時,這可能會影響長時間運行的鏈接。

當 NAT 從新綁定發生時(例如因爲超時),NAT 外圍外部的端點將看到來自不一樣源端口的數據包,而不是最初創建鏈接時觀察到的源端口,這使得僅使用4元組沒法跟蹤鏈接。

不只僅是 NAT!QUIC 打算提供的特性之一稱爲「鏈接遷移」(connection migration),它容許 QUIC 端點隨意將鏈接遷移到不一樣的IP地址和網絡路徑。例如,當已知的 WiFi 網絡可用時(好比當用戶進入他們最喜歡的咖啡店時),移動客戶端將可以在蜂窩數據網絡和 WiFi 之間遷移 QUIC 鏈接。

QUIC 試圖經過引入鏈接ID(connection ID)的概念來解決這個問題:一個由 QUIC 包攜帶的可變長度的不透明blob,能夠用來標識鏈接。端點可使用這個 ID 來跟蹤它們負責的鏈接,而不須要檢查4元組(實際上,同一個鏈接可能有多個 ID 標識,例如,在使用鏈接遷移(connection migration)時,爲了不連接不一樣的路徑,可是這種行爲由端點控制而不是中間設備控制)。

然而,這也給使用選播(anycast)尋址和 ECMP 路由的網絡運營商帶來了一個問題,在這些網絡運營商中,一個目的地IP地址可能識別數百甚至數千個服務器。因爲這些網絡使用的邊緣路由器還不知道如何處理 QUIC 流量,所以可能會發生屬於同一 QUIC 鏈接(即具備相同鏈接ID)但具備不一樣4元組(因爲 NAT 從新綁定或鏈接遷移)的UDP數據包可能最終被路由到不一樣的服務器,從而中斷了鏈接。

爲了解決這一問題,網絡運營商可能須要採用更智能的第4層負載平衡解決方案,這種解決方案能夠在軟件中實現,而且無需接觸邊緣路由器便可部署(例如,Facebook 的 Katran 項目)。

QPACK

HTTP/2 引入的另外一個好處是頭部壓縮(或HPACK),它容許 HTTP/2 端點經過刪除 HTTP 請求和響應中的冗餘來減小經過網絡傳輸的數據量。

特別是,在其餘技術中,HPACK 使用動態表填充了從之前的 HTTP 請求(或響應)發送(或接收)的頭,容許端點在新的請求(或響應)中引用之前遇到的頭,而沒必要從新傳輸它們。

HPACK 的動態表須要在編碼器 encoder(發送HTTP請求或響應的一方)和解碼器 decoder(接收它們的一方)之間同步,不然解碼器 decoder 將沒法解碼它接收到的內容。

對於 TCP 之上的 HTTP/2,這種同步是透明的,由於傳輸層(TCP)負責以相同順序發送 HTTP 請求和響應,更新表的指令能夠由編碼器 encoder 做爲請求(或響應)自己的一部分發送,從而使編碼很是簡單。但對 QUIC 來講,這更復雜。

QUIC 能夠在不一樣的流上獨立地傳遞多個 HTTP 請求(或響應),這意味着,儘管它負責按單流的順序傳遞數據,但在多個流之間不能保證排序。

例如,若是客戶端經過 QUIC stream A 發送 HTTP 請求 A,而經過 stream B 發送請求 B,則因爲網絡中的數據包從新排序或丟失,服務器可能會在請求A 以前接收到請求 B,而且若是請求 B 被用來自請求 A 的報頭編碼,服務器將沒法解碼,由於它尚未看到請求 A。

在 gQUIC 協議中,這個問題只需在同一個 gQUIC 流上序列化全部 HTTP 請求和響應頭(而不是主體),這意味着不管發生什麼,消息頭都將按順序傳遞。這是一個很是簡單的方案,它容許實現(implementations)重用大量現有的 HTTP/2 代碼,但另外一方面,它增長了QUIC 本來設計減小的隊頭阻塞( head-of-line blocking )。IETF QUIC 工做組所以設計了 HTTP 和 QUIC 之間的新映射(「HTTP/QUIC」),以及一種稱爲「QPACK」的頭壓縮新方案。

在 HTTP/QUIC 映射和 QPACK 規範的最新草案中,每一個 HTTP 請求/響應交換都使用本身的雙向 QUIC 流,所以沒有隊頭阻塞(head-of-line blocking)。另外,爲了支持 QPACK,每一個 peer 方建立兩個e額外的單向 QUIC 流,一個用於向另外一個 peer 方發送 QPACK 表更新,另外一個用於確認另外一方接收到的更新。這樣,QPACK 編碼器 encoder 只有在解碼器 decoder 顯式地確認了動態表引用以後才能使用它。

偏轉反射(Deflecting Reflection)

基於UDP的協議中的一個常見問題是它們容易受到反射攻擊(reflection attacks),即攻擊者經過欺騙數據包的源 IP 地址,使其看起來像是來自受害者,從而欺騙本來無辜的服務器向第三方受害者發送大量數據。


當服務器發送的響應剛好大於它接收到的請求時,這種攻擊很是有效,在這種狀況下,咱們稱之爲「放大」。

TCP 一般不用於此類攻擊,由於在其握手 (SYN, SYN+ACK, …) 過程當中傳輸的初始數據包具備相同的長度,所以它們不會提供任何潛在放大攻擊的風險。

另外一方面,QUIC 的握手是很是不對稱的:就像 TLS 同樣,在它的第一次傳輸中,QUIC 服務器一般發送本身的證書鏈(certificate chain),它可能很是大,而客戶端只需發送幾個字節(TLS ClientHello消息嵌入 QUIC 包中)。所以,客戶端發送的初始 QUIC 包必須填充到特定的最小長度(即便包的實際內容要小得多)。然而,這種緩解仍然不夠,由於典型的服務器響應跨越多個數據包,所以仍然能夠遠遠大於填充的客戶端數據包。

QUIC協議還定義了一個顯式的源地址驗證(source-address verification)機制,在該機制中,服務器不發送長響應,只發送一個很小的「重試(retry)」包,其中包含一個惟一的加密令牌,而後客戶端必須在新的初始包中向服務器回顯。這樣,服務器就有了更高的信心,即客戶機不會欺騙本身的源IP地址(由於它收到了重試數據包(retry packet)),而且能夠完成握手。這種緩解的缺點是,它將初始握手持續時間從 1 次 RTT 增長到 2 次。

另外一種解決方案包括減小服務器對反射攻擊(reflection attack)變得不那麼有效的響應,例如使用 ECDSA 證書(一般比 RSA 證書小得多)。咱們還一直在嘗試使用現成的壓縮算法(如 zlib 和 brotli)壓縮 TLS 證書的機制,這是 gQUIC 最初引入的一個特性,但目前在 TLS 中尚未。

UDP 性能

QUIC 常常出現的一個問題是如今普遍部署的硬件和軟件沒法理解它。咱們已經在另外一個C端的路由器上測試了數據的傳輸性能。咱們已經瞭解了 QUIC 如未嘗試處理像路由器這樣的網絡中間設備,可是另外一個潛在的問題是在 QUIC 端點上經過 UDP 發送和接收數據的性能。多年來,人們作了大量的工做來儘量地優化 TCP 的實現,包括在軟件(如操做系統)和硬件(如網絡接口)中構建卸載功能,但目前尚未一種方法可用於UDP。

然而,等QUIC實現時也能利用這些功能,這只是時間問題。例如,最近在Linux上實現UDP通用分段卸載的工做,它容許應用程序以單個UDP段(或足夠接近)的代價在用戶態(user-space)和內核態(kernel-space)網絡堆棧之間捆綁和傳輸多個 UDP 段,除了在Linux上添加零拷貝套接字支持以外,還容許應用程序避免將用戶態(user-space )內存複製到內核空間的成本。

結論

與 HTTP/2 和 TLS 1.3同樣,QUIC 將提供許多新特性,這些特性旨在提升web站點的性能和安全性,以及其餘基於Internet的資產。IETF 工做組目前正準備在今年年末發佈QUIC規範的初版,Cloudflare的工程師們已經在努力工做,爲咱們全部的客戶提供 QUIC 的好處。

相關文章
相關標籤/搜索