HTTP3 のパケットの中身を復号して覗いてみる

目次

はじめに

前回、Envoy を使って HTTP/3 のサーバを立てて、Wireshark や tcpdump で QUIC のパケットが流れているところまで確認しました。

Envoy を使って既存の Web アプリケーションを HTTP3 対応してみる

今回はもう少し踏み込んで、QUIC パケットの中身である HTTP/3 のメッセージも確認してみます。

試した環境

HTTP/3 サーバとして Envoy v1.21.1 を利用し、設定は envoy.yaml としました。 direct_response を使って数バイトのテキストを返すようにしています。

HTTP/3 クライアントは次の3種類で試してみました。

  1. Firefox v98.0
  2. curl + ngtcp2
  3. curl + quiche

パケットキャプチャは Envoy 側で tcpdump で行いました。 また、各クライアントでは環境変数 SSLKEYLOGFILE を用いて TLS 通信のキーをファイルに保存し、Wireshark で復号してパケットの中身を確認しました。

パケットの中身

各クライアントから QUIC コネクションの確立および HTTP/3 で1リクエスト送信したときのパケットの中身は次のようになっていました。

Firefox v98.0 の場合

No 送信者 QUIC Packet QUIC Frame HTTP/3 Frame / Stream
1 クライアント Initial CRYPTO
2 サーバ Initial ACK, CRYPTO, PADDING
Handshake CRYPTO
3 サーバ 1-RTT STREAM (3) SETTINGS
4 クライアント Initial ACK
Handshake ACK
5 クライアント Handshake CRYPTO
1-RTT ACK
6 クライアント 1-RTT STREAM (2) SETTINGS
STREAM (6) QPACK Encoder Stream
STREAM (10) QPACK Decoder Stream
7 サーバ 1-RTT CRYPTO
8 サーバ 1-RTT HANDSHAKE_DONE, NEW_TOKEN
9 クライアント 1-RTT ACK
10 サーバ 1-RTT ACK
11 クライアント 1-RTT STREAM (0) HEADERS
STREAM (6) QPACK Encoder Stream
12 サーバ 1-RTT ACK
STREAM (7) QPACK Decoder Stream
STREAM (11) QPACK Encoder Stream
STREAM (0) HEADERS, DATA
13 クライアント 1-RTT STREAM (10) QPACK Decoder Stream
14 クライアント 1-RTT ACK
15 サーバ 1-RTT ACK

※表の1つの "No" は 1つの UDP パケットを表しています。 また、No 2 の UDP パケットには2つの QUIC パケットが含まれており、さらに Initial パケットには3つの QUIC フレームが含まれていることを表しています。 QUIC Frame の括弧書きの数字は、Stream ID です。

curl + ngtcp2 の場合

No 送信者 QUIC Packet QUIC Frame HTTP/3 Frame / Stream
1 クライアント Initial CRYPTO, PADDING
2 サーバ Initial ACK, CRYPTO, PADDING
3 クライアント Initial ACK, CRYPTO, PADDING
4 サーバ Initial ACK, CRYPTO
Handshake CRYPTO
5 サーバ Handshake CRYPTO
1-RTT STREAM (3) SETTINGS
6 クライアント Initial ACK
Handshake ACK, CRYPTO
1-RTT ACK, NEW_CONNECTION_ID
STREAM (2) SETTINGS
STREAM (10) QPACK Decoder Stream
STREAM (6) QPACK Encoder Stream
PADDING
7 クライアント 1-RTT STREAM (6) QPACK Encoder Stream
STREAM (0) HEADERS
8 サーバ 1-RTT CRYPTO
9 サーバ 1-RTT HANDSHAKE_DONE, NEW_TOKEN
10 サーバ 1-RTT ACK
STREAM (7) QPACK Decoder Stream
STREAM (11) QPACK Encoder Stream
STREAM (0) HEADERS, DATA
11 クライアント 1-RTT ACK
12 クライアント 1-RTT CONNECTION_CLOSE

curl + quiche の場合

No 送信者 QUIC Packet QUIC Frame HTTP/3 Frame / Stream
1 クライアント Initial CRYPTO
2 サーバ Initial ACK, CRYPTO
Handshake CRYPTO
3 サーバ Handshake CRYPTO
1-RTT STREAM (3) SETTINGS
4 クライアント Initial ACK
Handshake ACK, CRYPTO
1-RTT ACK, PADDING
5 クライアント 1-RTT STREAM (2) SETTINGS
6 クライアント 1-RTT STREAM (6) QPACK Encoder Stream
7 クライアント 1-RTT STREAM (10) QPACK Decoder Stream
8 クライアント 1-RTT STREAM (0) HEADERS
9 クライアント 1-RTT STREAM (14) 不明な Type の Stream
10 サーバ 1-RTT CRYPTO
11 サーバ 1-RTT HANDSHAKE_DONE, NEW_TOKEN
12 サーバ 1-RTT ACK, PADDING
13 クライアント 1-RTT ACK
14 サーバ 1-RTT ACK
STREAM (0) HEADERS, DATA
15 サーバ 1-RTT ACK
STOP_SENDING (14)
16 クライアント 1-RTT ACK
RESET_STREAM (14)
17 クライアント 1-RTT CONNECTION_CLOSE, ACK

所感

HTTP/3 のメッセージは全て QUIC の STREAM フレームとして送受信されています。

HTTP/3 の SETTINGS, HEADERS, DATA フレームをやり取りするというのは、HTTP/2 と同じような感じですね。 ただ、クライアントから QUIC の Initial & Handshake の ACK を返す前に、サーバから先に SETTINGS フレームを送るというのは、HTTP/3 ならではのような気がします。(未確認)

curl + quiche 版は、サーバからレスポンスを受信できているものの、少し様子がおかしいです。 No 9 のパケットでクライアントは謎の Type の Stream を開いており、そのせいかサーバから QPACK Encoder Stream / Decoder Stream が開かれず、No 15 のパケットでは STOP_SENDING によって Stream が中止されてしまっています。 実装の問題なのでしょうけど、今の時点で気にしてもしょうがないですね。 (Stream のデータ部分が "GREASE is the word" という文字列だったので、これは気になりますけど…。)

ひとまず、QUIC のコネクション確立から HTTP/3 メッセージのやり取りまで見ることができたので、理解が捗りそうです。