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種類で試してみました。
- Firefox v98.0
- curl + ngtcp2
- ngtcp2 版のビルド手順 を参考に、Dockerfile でビルドした curl コマンドを利用。
- 実際に参照した Git リポジトリのコミットは次の通り。
- qictls/openssl : 62d4de00ab
- ngtcp2/nghttp3 : 6faaea03b0
- ngtcp2/ngtcp2 : 12db75d38a
- curl/curl : 3b4a353025
- curl + quiche
- quiche 版のビルド手順 を参考に、Dockerfile でビルドした curl コマンドを利用。
- 実際に参照した Git リポジトリのコミットは次の通り。
- cloudflare/quiche : 1705d05485
- curl/curl : 3b4a353025
パケットキャプチャは 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 メッセージのやり取りまで見ることができたので、理解が捗りそうです。