QUICをゆっくり解説(17):QUICビットとトランスポート・パラメータ

2022年07月19日 火曜日


【この記事を書いた人】
山本 和彦

Haskellコミュニティでは、ネットワーク関連を担当。 4児の父であり、家庭では子供たちと、ジョギング、サッカー、スキー、釣り、クワガタ採集をして過ごす。

「QUICをゆっくり解説(17):QUICビットとトランスポート・パラメータ」のイメージ

お久しぶりです。一家の引っ越しでバタバタしておりました。ようやく落ち着いてきましたので、「硬直化」をテーマとしてQUICに関して3つほど記事を書いてみようと思います。

硬直化

硬直化とは、中間装置が想定外の動作をすることによって、新しい機能の普及が困難になることです。硬直化の例としては、TCP Fast Openが完全に普及できないことが挙げられます。

TCPでは、コネクションを確立するために、いわゆる3WAYハンドシェイクが実行されます。通常は、クライアントがSYNパケットを送信し、サーバがSYN/ACKパケットを返し、その後クライアントがACKを返す際に初めてデータを送信できます。

もし最初のSYNパケットにデータを乗せることができれば、最初の1往復(上図の赤丸部分)にかかる時間を短縮できます。これをTCPのオプションを使って実現するのが、TCP Fast Openです。

しかし、TCP Fast Openは実際には使えない場合が多いことが報告されています。理由は以下の2つです。

  • 知らないTCPオプションを削除する中間装置が存在する
  • SYNパケットにデータが乗っていると、そのパケットを落とす中間装置が存在する

このような経験から、QUICは中間装置が中身を見れないような仕組みを導入しています。

  • 多くの制御情報がペイロードに格納されており、ペイロードは暗号化されている(「QUICパケットの構造」、「ハンドシェイク」)
  • ヘッダには最小限の情報しかなく、最初のパケットを保存してない場合は、中身が見れないように保護されている(「ヘッダの保護」)

QUICの設計者たちは、これだけでは満足せずに、以下の2つを標準化しようとしています。

  • QUICビットの乱数化
  • QUICバージョン2

今回は、QUICビットの乱数化について説明します。

QUICビット

QUICビットとは、最初のバイトの内、左から2番目のビットのことです。QUICバージョン1では、このビットは必ず1となっています。

QUICバージョン1で使われるロングヘッダパケットとショートヘッダパケットを見比べてみましょう。ロングヘッダパケットの構造は以下のとおりです。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+
|1|1|T T|X X X X|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         Version (32)                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| DCID Len (8)  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               Destination Connection ID (0..160)            ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SCID Len (8)  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                 Source Connection ID (0..160)               ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

ショートヘッダパケットの構造は以下のとおりです。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+
|0|1|S|R|R|K|P P|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                Destination Connection ID (0..160)           ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Packet Number (8/16/24/32)              ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Protected Payload (*)                   ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

どちらも最初のバイトがフラグで、最も左のビットが1ならロングヘッダパケット、0ならショートヘッダパケットであることが分かります。また、左から2番目のビットは、つまりQUICビットはどちらも1になっています。

中間装置はUDPパケットに対して、このビットが立っていればQUICであると判断できそうです。実際、そういう中間装置はすでに存在するそうです。今回のテーマは、このビットに0という値を入れてもよいように仕様を更新することです。

トランスポート・パラメータ

QUICビットを乱数化するには、新しいトランスポート・パラメータを使います。トランスポート・パラメータとは、ハンドシェイクの際に交渉できる値のことです。「コネクションの終了」では、以下の2つの値がハンドシェイクの際に決まると説明しました。

  • コネクションのタイムアウトの時間
  • ステートレス・リセット・トークンの値

これらのトランスポート・パラメータには、具体的には以下のような名前がついています。

  • max_idle_timeout
  • stateless_reset_token

トランスポート・パラメータのやり取りには、この名前(実際は数値)と値の組が用いられます。それらの組は、なんとTLSの拡張として格納されます。

  • クライアントからサーバへは、ClientHelloの拡張として格納されます
  • サーバからクライアントへは、EncryptedExtensionsに格納されます

QUICのハンドシェイクの様子を以下に再掲します。

前者のClientHelloは、Initialパケット中のCRYPTOフレームに格納されます。これは一応暗号化されていますが、暗号の鍵は終点コネクションIDから作成できるので、誰でも復号できます。

後者のEncryptedExtensionsは、HandshakeパケットのCRYPTOフレームに格納されます。このフレームを含むペイロードは、クライアント(とサーバ)だけが復号できます。

QUICビットの乱数化

QUICビットの乱数化には、grease_quic_bitというトランスポート・パラメータを使います。受け取ったパケットのQUICビットが1であるか検査しない場合、ハンドシェイクの際にgrease_quic_bitを送って、相手にQUICビットを乱数化してもよいことを伝えます。

grease_quic_bitを受け取った側は、以降の送信パケットのQUICビットを乱数化してもよくなります。このように、grease_quic_bitはスイッチオンの役割を果たしますので、値には意味がなく、空の値を送ると定められています。

次回

次回は、QUIC バージョン2について説明する予定です。

こちらの記事もおすすめ

QUICを実装した経験を持つ IIJ技術研究所の 山本 和彦 が、経験者目線でQUICを解説する連載記事です

すべての記事をみる

山本 和彦

2022年07月19日 火曜日

Haskellコミュニティでは、ネットワーク関連を担当。 4児の父であり、家庭では子供たちと、ジョギング、サッカー、スキー、釣り、クワガタ採集をして過ごす。

Related
関連記事