この記事は Kyash Advent Calendar 2019 21日目の記事です。
現在IETFによって HTTP/3 という HTTP/2 に続くHTTPの新しいバージョンの仕様策定が進められています。HTTP/3 は QUIC というプロトコル上で動作するものです。今回は特に QUIC について紹介します。
QUICとは
QUIC は現在IETFによって標準化が進められているプロトコルです。簡単に言うと UDP 上に再実装された TCP+TLS のようなものです。冗談で TCP/2 と呼ぶ人もいる*1ようです。QUIC は TLS 1.3 の仕組みを使った暗号化が必須となっており、暗号化せずに QUIC を使うことはできません。
なぜ QUIC という新しいプロトコルが開発されているのか
QUIC は Google によって発案*2されました。そもそも Google からはウェブ高速化のために TCP を改善するための提案*3が幾度もされてきています。しかし TCP は世界中のネットワーク機器のファームウェアや、カーネルに実装されているため、必要なアップグレードの量を考えると TCP に大きな変更を加えてすぐに恩恵を受けることはほとんど不可能といえます。
その点、QUIC の場合はユーザランドで UDP 上に実装されるため、ファームウェアやカーネルの制約を受けずに変更を行うことができます。またほとんどのデータが暗号化されていることにより、ルーターなど途中のネットワーク機器による介入が難しく、プロトコルの進化が阻害されにくいようになっています。
また QUIC の用途は HTTP だけが想定されているわけではなく、DNS over QUIC や、RTP over QUIC といったプロトコルも提案されています。
QUIC の特徴
QUIC には以下のような目標*4があります。
- インターネットでの広範な展開可能性(カーネルの変更や権限昇格をせずに一般的なユーザクライアントでされる)。
- パケット損失による Head-of-line blocking の低減 (1つのパケット損失により他の多重化ストリームを損なわない)。
- 低遅延 (セットアップや再開時およびパケット損失の応答時のラウンドトリップコストの最小化)。
- 遅延や効率においてモバイルのサポートの改善 (無線の切断により壊れてしまうTCP接続とは対照的です)。
- TCP に匹敵し使いやすい輻輳回避のサポート。
- TLS に匹敵するプライバシー保証 (順番通りに転送することや複合することを必要としない)。
- サーバサイドとクライアントサイドで信頼性が高く安全なリソース要件のスケーリング (合理的なバッファー管理とアンプ攻撃の回避支援も含む)。
- 帯域の消費を減らし、チャネルステータスの応答性を向上 (すべての多重化ストリームにわたるチャネルステータスの統一的なシグナリングによる)。
- 他の目標と矛盾していない場合、パケット数を削減。
- 多重化ストリームのための信頼できるトランスポートをサポート (多重化ストリーム上で TCP をシミュレートできます)。
- 他の目標と矛盾していない場合、プロキシのための効率的なdemux-mux(多重分離化-多重化)特性。
述べた目標を犠牲にせず、可能な限り任意の時点で既存のプロトコルを再利用または進化させます (例えばuTP(Ledbat)、DCCP、TCP など)
の展開可能性については前節で述べました。他の項目についてもいくつか抜粋して説明をします。
Head-of-line blocking の削減について
TCP は順番にパケットを受け取って処理するプロトコルであるため、先行するパケットが欠損した場合、後続のパケットの受け取りがブロックされる Head-of-line blocking という問題があります。例えば HTTP/2 のように TCP 上でストリームが多重化されている場合、あるストリームでパケット欠損が起こると、他のストリームをブロックしてしまいます。
QUIC は TCP を使わず、受信する順序に依存しない UDP を用い、パケット単位で暗号を復号できるようにすることでこの問題を削減します。QUIC のストリームは独立しているため、あるストリームのパケット損失が他のストリームに影響することはほとんどありません。
ラウンドトリップコストの最小化について
TCP 上で TLS を使って接続する場合は、TCP のハンドシェイク分も含めて、3-RTT を必要とします。
QUIC は、UDP を用いて TLS1.3 も統合的に実装されたことにより、新規接続の場合で 1-RTT、再接続の場合は 0-RTT で接続することができます。
モバイルのサポートの改善 (ローミング)
モバイルでよくあるようにキャリア通信とWi-Fi通信が切り替わったときなどは、通信に使うIPとポート番号が変わります。TCP ではこのような場合、コネクションを作り直さなければなりません。
QUIC では、コネクションごとにコネクションIDというものを割り当ててコネクションを識別するので、IPとポート番号が変わった場合でもコネクションを継続することができ、ネットワークエラーになる場合を減らすことができます。
既存の実装
まだ仕様はドラフトの段階ですが、すでに実装はいくつもあり相互接続テスト*5が行われています。 試すのであれば HTTP/3: the past, the present, and the future を参考にして、Quiche や Google Chrome を使うのがお手軽です。keylogファイルを出力してドラフトの対応バージョンに気をつければ、Wireshark でパケットの内容を詳細に観察することもできます。
とても勉強になるプロトコル
QUIC はユーザランドで実装されるプロトコルなので、TCP と比べてかなりいじりやすいプロトコルです。さらに QUIC は TCP や TLS のアルゴリズムを煮詰めて再実装しているプロトコルでもあるので、かなり学びがいがあります。
というわけで調べていると、実装してみたいモチベーションが出てきたので、自分で QUIC の実装をしてみています。まだまだ理解が追いついていない部分も多いのですが、これからがんばっていきます。
参考
- RFCのドラフト
- draft-ietf-quic-transport-24 - QUIC: A UDP-Based Multiplexed and Secure Transport
- draft-ietf-quic-recovery-24 - QUIC Loss Detection and Congestion Control
- draft-ietf-quic-tls-24 - Using TLS to Secure QUIC
- draft-ietf-quic-http-24 - Hypertext Transfer Protocol Version 3 (HTTP/3)
- draft-ietf-quic-qpack-11 - QPACK: Header Compression for HTTP/3
- QUICの紹介
*1:https://ma.ttias.be/googles-quic-protocol-moving-web-tcp-udp/
*2:https://blog.chromium.org/2013/06/experimenting-with-quic.html
*3:BBR: Congestion-Based Congestion Control、RFC 7413 - TCP Fast Open、RFC 6937 - Proportional Rate Reduction for TCP、RFC6928 - Increasing TCP's Initial Window など
*4:QUIC: Design Document and Specification Rationaleの GOALS を雑に訳しました
*5:https://docs.google.com/spreadsheets/d/1D0tW89vOoaScs3IY9RGC0UesWGAwE6xyLk0l4JtvTVg/edit#gid=117825384