ぼちぼち日記

おそらくプロトコルネタを書いていることが多いんじゃないかと思います。

Googleが仕掛ける新プロトコルQUICとは何か

まずは免責事項。

1.Disclaimer

本ブログの記載内容は、筆者が独自に QUIC に関する Chromium のソースを分析し、検証した結果です。 QUICに関するGoogle からの公式な技術資料は現状公開されていません。
今後、QUICの技術仕様の公表で本ブログの記述内容が不十分だったり、誤っている可能性があります。ご理解の上お読みください。
(注: 2013年6月27日に Google は正式に QUIC 仕様を公開しました。「Experimenting with QUIC 」 本ブログの内容は大筋では間違っていませんが、当時の解析漏れやその後の開発等により、細かいところで異なっていたり、説明が大きく不足している部分もあります。お読みになる際はご注意ください。)

2. はじめに、

Googleがまたまた新しいプロトコルの実装を始めました。Web表示の高速化を目指した SPDY に続いて QUIC と呼ばれるものです。(参考記事:Googleが改良版UDPとしてQUICというプロトコルを開発中(らしい) )
なにやらUDPに関連するもので技術情報は全くない。サーバ実装もまだ公開されていない。既に Chromium で実装され、ソースが公開されているらしい。ということで、実際に使いながらソースを追って早速調べてみました。

実は、先週末 QUIC のソースを読んでいるとなかなか面白いことがわかり、 Hello QUIC のサーバを作れないかと取り組んでいました。しかし、今朝になって急に SPDY over QUIC だけにする仕様変更が入るという事実が発覚。
Remove the --use-spdy-over-quic command line option. Always use SPDY over QUIC. (Closed)

今日なんとかQUIC初期認証のハンドシェイクの完了まではこぎつけましたが、まだ原因不明のRSTの発生が残り SPDY化も行ないといけなくなったので、一旦 Hello QUICサーバを断念。この辺で自分の頭の中を整理して、少し時間をかけて取り組もうと方針転換したわけです。

まだQUIC仕様や動作を完全に把握したわけではないですが、自分なりに大体の概要はつかめたので整理のため書いてみます。

3. QUIC を3行で説明すると

今はやりの手法で QUIC を説明すると、

  • QUICとは、UDP上でTCP+TLS相当の機能を実現するプロトコルである。(セッション確立、エラー訂正、フロー制御、輻輳制御、暗号化、認証等々。ただし現状、暗号化は対応しておらずハッシュによるメッセージ認証を行う)
  • QUIC上では、SPDY(SPDY over QUIC)のみ利用できる。(先述の通りHTTPの対応は削除済)よってUDPを使ったWebサービスの提供が利用用途として想定される。
  • ブラウザーから QUIC サーバを区別して接続する方法は現状2通り。サーバから通常のTCP上のHTTP接続で返されるAlternate-Protocol ヘッダ(ポート:quic/1)を見て、記載されている QUIC サーバに切り替える方法と QUIC 専用に接続するUDPポート番号をオプションで指定する方法。検証したところ前者の方法はうまく動作しなかった。(まだ完全じゃないみたい)

ということになります。QUICはユーザランドで動作しますので UDPプロトコルスタックを改変するものではありません。
なので、
QUICは UDPを改良するものではなく、既存のUDP上でTCP+TLS相当の機能を実現する新プロトコルスタックである。
下図のように捉えると良いかと思います。

4. QUICを使うには、

現状、 Chromium および Chrome Canary(27.0.1423.0) で QUICが実装されています。利用するには --enable-quic のコマンドラインオプションで起動することが必要です。 --origin-port-to-force-quic-on の引数で QUIC専用で接続するUDPポート番号の指定ができます。 昨日まではデフォルトで HTTP (SPDY利用にはオプション指定)だったのですが、現状 SPDYしかできないように変更されました。

QUICが実装されている Chrome/Chromium では、いつものごとく chome://net-internals/#quic で QUICの利用状況を見ることができます。

ブラウザから QUIC セッションが確立できた時のサンプル画面です。(レスポンスデータはまだ返していない状態ですが、3つほどアクティブセッションが確立されています。)

5. QUICの仕組み

QUICはUDP上でどうやって動作しているのでしょうか? その仕組みについて説明します。

5.1 QUICのデータ形式

QUICは、SPDYと同じくバイナリー形式のフレームデータ形式です。その大きな土台となるフレームの形式は下記のフォーマットで実装されています。

先頭は64bitのGUID。これは初期セッションIDに相当するものと思われます。publicフラグ(8bit)は、セッションの制御(RST)を行うためにあるのではないかと思われます(不明確)。 その後フレームのシーケンス番号(48bit)が続きます。(これはフレームを送信する度にインクリメントされています。)

続いてハッシュデータ(128bit)が入ります。このハッシュデータは、自身を除くフレームデータ全部に FNV1a-128 ハッシュ(Fowler–Noll–Vo Hash)をかけた値を入れています。データの受信の際は必ずハッシュチェックを行いデータ改ざんが検知できるようになっています。

その後は private flag(8bit) 。これは FEC(Forward Error Correction:前方誤り訂正)機能の有効化フラグだと思われます。(まだ試していません。) Public/Private の違いは、将来データの暗号化が実装されたらこのハッシュ以降のデータが対象になるからではないかと考えられます。

次に FECのグループ番号(8bit よくわかりません) そしてフレーム共通のヘッダとして最後にフレームタイプ(8bit)が続きます。

このヘッダを共通に持つQUICのフレームは、以下の6種類提供されています。

フレーム名 用途
PADDING_FRAME パディング用のNULLデータを送る
STREAM_FRAME 暗号化のハンドシェイクやSPDYデータをやり取りする時に利用
ACK_FRAME 受信してないフレーム数とシーケンス番号を通知します。
CONGESTION_FEEDBACK_FRAME 輻輳制御の種類(TCP/InterArrival/FixRate)によって通知するデータが違います。受信ウィンドウ消失したフレーム数、その時間間隔の情報などが送られます。)
RST_STREAM_FRAME あるストリームIDをリセットするフレーム
CONNECTION_CLOSE_FRAME QUICの接続を終了するフレーム

筆者はまだ STREAM_FRAME しか試していませんが、通常のサーバクライアント間のデータのやり取りは全て 次のSTREAM_FRAMEで行っているようです。

5.2 接続の仕方

ではフレーム形式がわかったところでどうやってやり取りをするのでしょうか?
簡単なやりとりのシーケンスを下図に書きます。(まだちゃんと検証できていないのであくまで想定です。)

QUICの接続の最初は(暗号化情報の交換のためのハンドシェイクが必要になります。これはタグ・バリュー形式のデータでやりとりされ STREAM_FRAME を使って行われます。この STREAM_FRAME は、サーバ・クライアントともにstream_id が必ず1番で、ペイロードデータの形式も決まっています。

これを CRYPTO_STREAM と名付け、プロトコル表にすると下記のようなデータ形式になります。

QUIC初期の暗号化ハンドシェイクでは、ペイロードにおいてクライアントからのハンドシェイクの CRYPTO_FRAME には CHLO 、サーバからは SHLO のメッセージタグが付与されています。

STREAM_FRAMEのペイロードデータの頭16ビットは、後に続くデータ長を表します。その後メッセージタグ名、タグエントリー数など暗号化や輻輳制御に必要な情報が続きます。オフセットを変えて、見やすく書き直すと次のようになります。

タグ名は4文字固定です。しかし可変長の値を持つものがあるため、各データ長を格納するフィールドが必要になっています。

CRYPTO_STREAM で交換されるタグ項目は下記の表になります。

ソースを見るとまだ実装されていないものも多いですが、各タグと値がどんな役割をはたすのかこれから細かく調べたいと思います。

実際のデータではどんな値が入っているのでしょうか?ブラウザーからサーバに一番最初に送られてくる CHLO の CRYPTO_STREAM を JSON 形式で例示するとこのように見えます。

{ public_flag: 0,
  seq: 1,
  guid: <Buffer 59 15 fb 40 7c e4 75 43>,
  frame_type: 1,
  hash: <Buffer ef b1 c7 98 ff ae d2 36 81 c1 09 dc f3 5f cf 9e>,
  private_flag: 0,
  fec_offset: 255,
  stream_id: 1,
  fin: 0,
  offset: <Buffer 00 00 00 00 00 00 00 00>,
  crypto:
   { data_length: 122,
     message_tag: 'CHLO',
     num_entries: 8,
     tag:
      [ 'SNI\u0000',
        'NONC',
        'AEAD',
        'ICSL',
        'KATO',
        'VERS',
        'KEXS',
        'CGST' ],
     value_length: [ 6, 32, 8, 4, 4, 2, 8, 4 ],
     value:
      [ 'unixjp',
        <Buffer af da 2d 51 44 50 31 aa b4 8a de d3 06 c1 76 71 9e 50 04 fb 61 40 45 d8 8a c2 f4 26 e4 7f 52 ad>,
        'AESGAESH',
        <Buffer 2c 01 00 00>,
        <Buffer 00 00 00 00>,
        <Buffer 00 00>,
        'C255P256',
        'QBIC' ] } }

6. まとめ

QUIC は UDP上で TCP+TLS相当の機能を実現するプロトコルであることはなんとなく理解できたでしょうか?

各機能の実現方法をまとめると、

  • GIDベースのストリームのFRAME形式を使いセッション確立(STREAM_FRAME,RST_FRAME,CONNECTION_CLOSE_FRAME)
  • ACK_FRAME, CONGESTION_FEEDBACK_FRAMEを使ってフロー制御や輻輳制御
  • FECを使ってエラー訂正
  • CRYPTO_FRAMEを使って暗号化・認証情報のハンドシェイク

となります。

QUIC実利用した場合の性能向上やメリットなどまだまだ未知数です。Googleは、これまでもTCPに対して Linux に新しい機能(TFO, ICW10)などを次々と一歩先を進めています。さらに UDP上でも新しいデータ通信の仕組みを実現しようとする Google の取り組みは、これからも目が離せません。