QUICをゆっくり解説(17):QUICビットとトランスポート・パラメータ
2022年07月19日 火曜日
CONTENTS
お久しぶりです。一家の引っ越しでバタバタしておりました。ようやく落ち着いてきましたので、「硬直化」をテーマとして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について説明する予定です。