本記事ではIETFで標準化が行われている、QUICなどのロギングを行うフォーマットであるqlogの、HTTP/3とQPACKに関するイベントを定義している「HTTP/3 and QPACK event definitions for qlog」について紹介します。
HTTP/3 and QPACK event definitions for qlog とは
qlogには発生したイベントをロギングする仕組みがあります。「HTTP/3 and QPACK event definitions for qlog」は、qlogにHTTP/3とQPACKのイベントを記録するための情報を定義しています。
具体手的には、イベントの種類を特定するためのname
(category
+ event
)とそのイベントをロギングするときに含むデータが定義されています。また、それぞれのイベントにはImportance という情報も定義されています。
なお、qlogの提案自体は2019年ころから行われていましたが、「HTTP/3 and QPACK event definitions for qlog」まだWG Draftになって間もない状態です。また、HTTP/3はDraft-34、QPACKはDraft-21を参照しています。
本記事は、Draft-00を参照しています。そのため今後の議論によって内容が大きく変わる可能性があります。
ドラフトのベースとなっているドキュメントがあるレポジトリや、議論されているIssueの状況は下記から参照可能です。
レポジトリ
https://github.com/quicwg/qlog
議論
https://github.com/quicwg/qlog/issues
qlog main schemaのおさらい
「HTTP/3 and QPACK event definitions for qlog 」を読む上で必要になるqlogの基礎知識は別記事にまとめてあります。
QUICとHTTP/3・QPACKのログを同時に出力したい場合
qlogには、HTTP/3・QPACKのログとQUICのログを同時に出力することもできます。逆に、HTTP/3・QPACKのみ出力することやQUICのみ出力することもできます。
QUICと一緒に使う場合は、推奨されるファイル名やサーバーやクライアントのトレースの分割方法は、QUICのイベント定義のドキュメントを(Destination Connection IDの使用を推奨しています)優先します。
QUICと一緒に使わない場合、HTTP/3の接続にグローバルで一意なIDを設定することが推奨されます。そのIDは、qlogのgroup_idや、ファイル名、ファイル識別子、vantagepoint typeのサフィックスに使います。例えば、abcd1234_server.qlogは、接続のGUIDがabcd1234のサーバーサイドのトレースを含みます。
qlog main schemaとのつながり
このドキュメントでは、main schemaで定義されたフィールド(例えば、name, category, type, data, group_id, protocol_type, 時間に関連するフィールド、importance, RawInfoなど)が再利用されます。
このドキュメントでHTTP/3やQPACKのために定義されているイベントをトレースファイルに含める場合、protocol_typeの配列には"HTTP/3"を含める必要があります。
Raw packet and frame information
qlogのメインスキーマで定義されている、RawInfo
は再利用されます。
RawInfo
は生のバイト長やバイトを保存しておくことで、パケタイゼーションの振る舞いの調査やチューニング、エンコードやフレーミングのオーバーヘッドの確認などに使うために定義されています。
HTTP/3の場合、ヘッダ長 header_length
は
header_length = RawIngo:length - RawInfo:payload_length
で計算されるため定義はされていません。
class RawInfo {
length?:uint64; // the full byte length of the entity (e.g., packet or frame) including headers and trailers
payload_length?:uint64; // the byte length of the entity's payload, without headers or trailers
data?:bytes; // the contents of the full entity, including headers and trailers
}
いくつかの場合で、lengthフィールドは、明示的にフレームのヘッダに反映されます。例えば、すべてのHTTP/3フレームは、明示的のペイロード長をヘッダの中に持ちます。
参考: DATAフレームの定義
DATA Frame {
Type (i) = 0x0,
Length (i),
Data (..),
}
HTTP/3 and QPACK event definitions
現時点では、HTTP/3とQPACKそれぞれにhttp
、qpack
というカテゴリが割り当てられています。
HTTP/3に対するイベントは、http:parameters_set
のような形式で定義されます。
QPACKに対するイベントは、qpack:state_updated
のような形式で定義されています。
http
HTTP/3 には以下のようなイベントが定義されています。
event type | Importance | サマリー |
---|---|---|
parameters_set | Base | SETTINGSフレームで設定されるHTTP/3やQPACKの設定が含まれます |
parameters_restored | Base | 0-RTT接続するときに、再利用するパラメータを記録します |
stream_type_set | Base | ストリームの種類を記録します。 |
frame_created | Core | HTTP/3のフレームが生成されたことを記録します |
frame_parsed | Core | HTTP/3のフレームを解析したことを記録します |
push_resolved | Extra | プッシュされたリソースが上位から要求されたことを記録します |
parameters_set
Importance: Base
parameters_set
はHTTP/3とQPACKの設定を含みます。
多くの場合これらの設定はHTTP/3のSETTINGSフレームによって受信されます。
HTTP/3の設定は、SETTINGS_MAX_FIELD_SECTION_SIZE があります。SETTINGS_MAX_FIELD_SECTION_SIZEは1つのHTTPメッセージに含まれるヘッダのサイズを制限します。
QPACKの設定は、5. Configuration に書かれているSETTINGS_QPACK_MAX_TABLE_CAPACITYとSETTINGS_QPACK_BLOCKED_STREAMSが含まれます。
SETTINGS_QPACK_MAX_TABLE_CAPACITY は、デコーダー側が決めるQPACKで使用する動的テーブルのサイズの最大値になります。
SETTINGS_QPACK_BLOCKED_STREAMSはブロックされるストリームの上限を設定します。QUICはストリームの到着順序を保証していないため、ダイナミックテーブルのエントリーが参照しても即座に処理できない場合があります。そしてそのような場合ストリームはブロックされます。この値によってそのストリームの数の上限が決まります。
これらのパラメータは一度設定されたら変更されない場合が多いです。しかし、典型的には接続の異なるタイミングで設定されます。なので、このイベントは異なるフィールドを持った複数のインスタンスが記録される可能性があります。
設定には、ローカルで設定されるものとリモートのピアから設定されるものがあります。どちらが設定したかはowner
フィールドに反映されます。
owner
フィールドは必ず含まれなければいけない設定ではないですが、もし含めるのであれば、単一のイベントインスタンスに含まれるすべての設定に対してowner
が正しい必要があります。したがって、もしクライアントとサーバーの二つの側からの設定を明示的にログに残したい場合は、二つの別々のイベントインスタンスを発行する必要があります。
このイベントは仕様書には記されないフィールドを持つことが可能です。これは、例えば、unknown (greased)な設定や、パラメータの拡張を含むことが可能です。
Data:
{
owner?:"local" | "remote",
max_header_list_size?:uint64, // from SETTINGS_MAX_HEADER_LIST_SIZE
max_table_capacity?:uint64, // from SETTINGS_QPACK_MAX_TABLE_CAPACITY
blocked_streams_count?:uint64, // from SETTINGS_QPACK_BLOCKED_STREAMS
// qlog-defined
waits_for_settings?:boolean // indicates whether this implementation waits for a SETTINGS frame before processing requests
}
サーバープッシュの設定について
サーバープッシュは、HTTP/3の設定やパラメータによって使用可能になるわけではありません。
その代わりに、MAX_PUSH_IDフレームのやり取りによって決まります。
クライアントが、送信したMAX_PUSH_IDよりサイズの大きいPushIDを持ったストリームは使えません。
参考: https://datatracker.ietf.org/doc/html/draft-ietf-quic-http-34#section-7.2.7
A client MUST treat receipt of a push stream as a connection error of type H3_ID_ERROR (Section 8) when no MAX_PUSH_ID frame has been sent or when the stream references a Push ID that is greater than the maximum Push ID.
なので、サーバープッシュの設定はframe_created
とframe_parsed
イベントによるMAX_PUSH_IDフレームの生成・解析によって記録されます。
parameters_restored
Importance: Base
QUICの0-RTTを使うときに、HTTP/3のクライアントは以前の接続のサーバーの設定を保持して再利用することが可能です。
参考: https://datatracker.ietf.org/doc/html/draft-ietf-quic-http-34#section-7.2.4.2
When a 0-RTT QUIC connection is being used, the initial value of each server setting is the value used in the previous session. Clients SHOULD store the settings the server provided in the HTTP/3 connection where resumption information was provided, but MAY opt not to store settings in certain cases (e.g., if the session ticket is received before the SETTINGS frame). A client MUST comply with stored settings -- or default values, if no values are stored -- when attempting 0-RTT. Once a server has provided new settings, clients MUST comply with those values.
このイベントは、HTTP/3の設定を復元して0-RTT接続で利用されることを示します。
Data:
{
max_header_list_size?:uint64,
max_table_capacity?:uint64,
blocked_streams_count?:uint64
}
このイベントはparameters_set
と同様に、このドキュメントで定義されていないフィールドやカスタムの設定を含むことができます。
stream_type_set
Importance: Base
このイベントはストリームタイプが既知になったときに記録されます。典型的には、ストリームが開かれて、ストリームタイプを表す情報が送信または受信されたときにログを発行します。
Bidirectional Streamは、data
ストリームになります。
Unidirectional Streamはいくつかの種類があります。
HTTP/3 で規定されているものは以下の二つです。
また、"0x1f * N + 0x21"のIDを使うReserved Stream Typesというのもあります。
QPACKでは、以下の二つが定義されています。
- Encoder Stream
- Decoder Stream
参考: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qpack-21#section-4.2
ストリームIDはQUICのレベルで分割されているため、多くの情報はストリームIDを見ることで推測できるようです。
そうだとしても、 この情報が明確に示されているとデバッグに大いに役立つため、このイベントには"Base"を割り当てられています。
{
stream_id:uint64,
owner?:"local"|"remote"
old?:StreamType,
new:StreamType,
associated_push_id?:uint64 // only when new == "push"
}
enum StreamType {
data, // bidirectional request-response streams
control,
push,
reserved,
qpack_encode,
qpack_decode
}
frame_created
Importance: Core
このイベントは、HTTP/3のフレームを作成したときに発行されます。そのタイミングはHTTP/3のデータがQUICのレイヤに渡されたのと同じタイミングである必要はありません。
QUICレイヤに渡されたタイミングに関するイベントはdata_moved
イベントで定義されています。
Data:
{
stream_id:uint64,
length?:uint64, // payload byte length of the frame
frame:HTTP3Frame, // see appendix for the definitions,
raw?:RawInfo
}
HTTP/3では、ヘッダのオーバーヘッドを減らすためDATAフレームは任意の長さを持つことが可能です。そのような場合、DATAフレームは、多くのQUICパケットにまたがりストリーミングされる形になります。
この場合は、frame_created
イベントはフレームヘッダのために1回だけ発行されます。そして、それ以降にストリームされるデータは、data_moved
イベントでログに記録されます。
frame_parsed
Importance: Core
このイベントはHTTP/3のフレームがパースされたタイミングで発行されます。
これは、QUICレイヤでHTTP/3のデータが実際に受信されたタイミングと同じである必要はありません。
QUICレイヤからデータを受け取るタイミングに関するイベントはdata_moved
イベントで定義されています。
Data:
{
stream_id:uint64,
length?:uint64, // payload byte length of the frame
frame:HTTP3Frame, // see appendix for the definitions,
raw?:RawInfo
}
HTTP/3では、DATAフレームはフレームヘッダのオーバーヘッドを減らすために任意の長さを設定できます。frame_created
と同様に、DATAフレームは複数のQUICパケットにまたがります。そして、ストリーミングされる形になります。
この場合、frame_parsed
イベントはフレームヘッダに対して1回だけ発行され、ストリームされたデータは data_moved
で記録をします。
push_resolved
Importance: Extra
このイベントは、プッシュされたリソースがウェブブラウザなどのHTTP/3の上位のアプリケーションから要求される(使用される)場合か、捨てられる場合に発行されます。
このイベントは、予期されないPushの振る舞いをデバッグするのに役立ちます。
{
push_id?:uint64,
stream_id?:uint64, // in case this is logged from a place that does not have access to the push_id
decision:"claimed"|"abandoned"
}
qpack
QPACKに関するイベントは、主に低レベルのQPACKの問題のデバッグに役に立ちます。
高レベルでは、プレインテキストのヘッダの値はHTTPのframe_created
とframe_parsed
に記録されるべきです。
qpack
のカテゴリには以下のようなイベントがあります。
event type | Importance | サマリー |
---|---|---|
state_updated | Base | QPACKの内部変数が変わったことを記録します |
stream_state_updated | Core | ストリームがブロック・アンブロックされたことを記録します |
dynamic_table_updated | Extra | ダイナミックテーブルへのエントリーの挿入・削除を記録します |
headers_encoded | Base | ヘッダブロックがエンコードされたことを記録します |
headers_decoded | Base | ヘッダブロックがデコードされたことを記録します |
instruction_created | Base | QPACKのinstructionが生成されてEncoder/Decoderストリームに追加されたことを記録します |
instruction_parsed | Base | QPACKのinstructionがEncoder/Decoderストリームから読み込まれたことを記録します |
qpack
に対する parameter_set
は定義されていません。
QPACKの仕組みをHTTP/3の拡張としてとらえています(HTTP/3の規格に載っていないという意味ととらえるとよさそうです)。そのため、自身ではparamsets_set
のイベントは持たず、httpのparameters_set
に統合されています。
他のHTTP/3の拡張は、SETTINGSフレームにhttp.parameters_set に記録されるかもしれませんし、独自のイベントとして記録されるかもしれません。
state_updated
Importance: Base
このイベントは、一つまたは複数のQPACK内部の変数が変わったときに発行されます。
現状で記述されているのは、ダイナミックテーブルのキャパシティ、サイズ、Known Received Count、Insert Count(ダイナミックテーブルに挿入されたエントリーの合計数)などがあります。
設定には、ローカルで設定されるものとリモートのピアから設定されるものがあります。どちらが設定したかはowner
フィールドに反映されます。
owner
フィールドは必ず含まれなければいけない設定ではないですが、もし含めるのであれば、単一のイベントインスタンスに含まれるすべてのセッティングに対してowner
が正しい必要があります。したがって、もしクライアントとサーバーの二つの側からの設定を明示的にログに残したい場合は二つの別々のイベントインスタンスを発行する必要があります。
Data:
{
owner:"local" | "remote",
dynamic_table_capacity?:uint64,
dynamic_table_size?:uint64, // effective current size, sum of all the entries
known_received_count?:uint64,
current_insert_count?:uint64
}
stream_state_updated
Importance: Core
このイベントは、ヘッダーデコーディングリクエストまたはQPACK instructionによって、ストリームがブロック・アンブロックされた場合に発行されます。
このイベントは、HTTP/3の性能を観測する場合に影響が大きいためImportanceはCoreにされています。
Data:
{
stream_id:uint64,
state:"blocked"|"unblocked" // streams are assumed to start "unblocked" until they become "blocked"
}
dynamic_table_updated
Importance: Extra
このイベントは、QPACKのダイナミックテーブルの一つまたは複数のエントリーが挿入あるいは削除されたときに発行されます。
Data:
{
owner:"local" | "remote", // local = the encoder's dynamic table. remote = the decoder's dynamic table
update_type:"inserted"|"evicted",
entries:Array<DynamicTableEntry>
}
class DynamicTableEntry {
index:uint64;
name?:string | bytes;
value?:string | bytes;
}
headers_encoded
Importance: Base
このイベントは、圧縮されていないヘッダブロックがエンコードされたときに発行されます。
このイベントのheaders
フィールドに含まれるHeadersFrameはhttp.frame_craeted
と重複します。実装者は、両方のイベントを発行するときにheaders
フィールドをこのイベントから排除するかもしれません。
Data:
{
stream_id?:uint64,
headers?:Array<HTTPHeader>,
block_prefix:QPackHeaderBlockPrefix,
header_block:Array<QPackHeaderBlockRepresentation>,
length?:uint32,
raw?:bytes
}
headers_decoded
Importance: Base
このイベントは、圧縮されたヘッダブロックがデコードされたときに発行されます。
このイベントのheaders
フィールドに含まれるHeadersFrameはframe_parsed
と重複します。実装者は、両方のイベントを発行するときにheaders
フィールドをこのイベントから排除するかもしれません
Data:
{
stream_id?:uint64,
headers?:Array<HTTPHeader>,
block_prefix:QPackHeaderBlockPrefix,
header_block:Array<QPackHeaderBlockRepresentation>,
length?:uint32,
raw?:bytes
}
instruction_created
Importance: Base
このイベントは、QPACKのinstructionが生成されてencoder/decoderストリームに追加されたときに発行します
Instructionには、4.3. Encoder Instructions と 4.4. Decoder Instructions があります。
Data:
{
instruction:QPackInstruction // see appendix for the definitions,
length?:uint32,
raw?:bytes
}
instruction_parsed
Importance: Base
このイベントは、QPACKのinstruction (decoderとencoderの両方)が、encoder/decoderストリームから読まれたときに発行されます。
Data:
{
instruction:QPackInstruction // see appendix for the definitions,
length?:uint32,
raw?:bytes
}
HTTP/3 data field definitions
ここでは、HTTP/3のフレームと、エラーコードの記録方法を定義しています。
HTTP/3 Frames
HTTP/3で定義されているフレームに対応する構造がログ用に定義されます。
type HTTP3Frame = DataFrame | HeadersFrame | PriorityFrame | CancelPushFrame | SettingsFrame | PushPromiseFrame | GoAwayFrame | MaxPushIDFrame | DuplicatePushFrame | ReservedFrame | UnknownFrame;
DataFrame
DATA フレ―ムの定義です
class DataFrame{
frame_type:string = "data";
raw?:bytes;
}
HeadersFrame
HEADERS フレームの定義です。
HeadersFrame
には、QPACKの圧縮が適用されていない状態のplaintext のHTTP ヘッダを格納します。
headers: [{"name":":path","value":"/"},{"name":":method","value":"GET"},{"name":":authority","value":"127.0.0.1:4433"},{"name":":scheme","value":"https"}]
class HeadersFrame{
frame_type:string = "header";
headers:Array<HTTPHeader>;
}
class HTTPHeader {
name:string;
value:string;
}
CancelPushFrame
CANCEL_PUSH フレームの定義です。
class CancelPushFrame{
frame_type:string = "cancel_push";
push_id:uint64;
}
SettingsFrame
SETTINGS フレームの定義です。
class SettingsFrame{
frame_type:string = "settings";
settings:Array<Setting>;
}
class Setting{
name:string;
value:string;
}
PushPromiseFrame
PUSH_PROMISE フレームの定義です。
class PushPromiseFrame{
frame_type:string = "push_promise";
push_id:uint64;
headers:Array<HTTPHeader>;
}
GoAwayFrame
GOAWAY フレームの定義です。
class GoAwayFrame{
frame_type:string = "goaway";
stream_id:uint64;
}
MaxPushIDFrame
MAX_PUSH_ID の定義です。
class MaxPushIDFrame{
frame_type:string = "max_push_id";
push_id:uint64;
}
DuplicatePushFrame
DUPLICATE_PUSH フレームはドラフト26以降で削除されたようです。
class DuplicatePushFrame{
frame_type:string = "duplicate_push";
push_id:uint64;
}
ReservedFrame
Reserved Frame Types の定義です。
class ReservedFrame{
frame_type:string = "reserved";
}
UnknownFrame
QUICのUnknownFrameと値や使い方が被るので定義を再利用します。
class UnknownFrame{
frame_type:string = "unknown";
raw_frame_type:uint64;
raw_length?:uint32;
raw?:bytes;
}
ApplicationError
8.1. HTTP/3 Error Codes で定義されているエラーコードに対応しています。
enum ApplicationError{
http_no_error,
http_general_protocol_error,
http_internal_error,
http_stream_creation_error,
http_closed_critical_stream,
http_frame_unexpected,
http_frame_error,
http_excessive_load,
http_id_error,
http_settings_error,
http_missing_settings,
http_request_rejected,
http_request_cancelled,
http_request_incomplete,
http_early_response,
http_connect_error,
http_version_fallback
}
QPACK DATA type definitions
ここでは、QPACKのinstructionsとField Line Representations の記録方法が定義されています。
QPACK Instructions
QPACKのEncoder InstructionとDecoder Instruction に対応しています。
instructionは、名前や機能からEncoderのinstructionか、Decoderのinstructionかが一意に定まるので、Encoder/Decoder instructionを一つのtypeに含めているようです。
type QPackInstruction = SetDynamicTableCapacityInstruction | InsertWithNameReferenceInstruction | InsertWithoutNameReferenceInstruction | DuplicateInstruction | HeaderAcknowledgementInstruction | StreamCancellationInstruction | InsertCountIncrementInstruction;
SetDynamicTableCapacityInstruction
Set Dynamic Table Capacity の定義です。
class SetDynamicTableCapacityInstruction {
instruction_type:string = "set_dynamic_table_capacity";
capacity:uint32;
}
InsertWithNameReferenceInstruction
Insert With Name Reference の定義です。
class InsertWithNameReferenceInstruction {
instruction_type:string = "insert_with_name_reference";
table_type:"static"|"dynamic";
name_index:uint32;
huffman_encoded_value:boolean;
value_length?:uint32;
value?:string;
}
InsertWithoutNameReferenceInstruction
Insert With Literal Name に該当します。
class InsertWithoutNameReferenceInstruction {
instruction_type:string = "insert_without_name_reference";
huffman_encoded_name:boolean;
name_length?:uint32;
name?:string;
huffman_encoded_value:boolean;
value_length?:uint32;
value?:string;
}
DuplicateInstruction
Duplicate に関する定義です。
class DuplicateInstruction {
instruction_type:string = "duplicate";
index:uint32;
}
HeaderAcknowledgementInstruction
Section Acknowledgment に該当していそうです。
class HeaderAcknowledgementInstruction {
instruction_type:string = "header_acknowledgement";
stream_id:uint64;
}
StreamCancellationInstruction
Stream Cancellation に該当します。
class StreamCancellationInstruction {
instruction_type:string = "stream_cancellation";
stream_id:uint64;
}
InsertCountIncrementInstruction
Insert Count Increment に該当します。
class InsertCountIncrementInstruction {
instruction_type:string = "insert_count_increment";
increment:uint32;
}
QPACK Header compression
おそらく、Field Line Representations に該当します。
type QPackHeaderBlockRepresentation = IndexedHeaderField | LiteralHeaderFieldWithName | LiteralHeaderFieldWithoutName;
IndexedHeaderField
Indexed Field Line と、baseの値から動的テーブルのインデックスを識別する Indexed Field Line が含まれます。
class IndexedHeaderField {
header_field_type:string = "indexed_header";
table_type:"static"|"dynamic"; // MUST be "dynamic" if is_post_base is true
index:uint32;
is_post_base:boolean = false; // to represent the "indexed header field with post-base index" header field type
}
LiteralHeaderFieldWithName
Literal Field Line With Name Referenceと、Literal Field Line With Post-Base Name Reference が含まれます。
class LiteralHeaderFieldWithName {
header_field_type:string = "literal_with_name";
preserve_literal:boolean; // the 3rd "N" bit
table_type:"static"|"dynamic"; // MUST be "dynamic" if is_post_base is true
name_index:uint32;
huffman_encoded_value:boolean;
value_length?:uint32;
value?:string;
is_post_base:boolean = false; // to represent the "Literal header field with post-base name reference" header field type
}
LiteralHeaderFieldWithoutName
Literal Field Line With Literal Name のことではないかと思います。
class LiteralHeaderFieldWithoutName {
header_field_type:string = "literal_without_name";
preserve_literal:boolean; // the 3rd "N" bit
huffman_encoded_name:boolean;
name_length?:uint32;
name?:string;
huffman_encoded_value:boolean;
value_length?:uint32;
value?:string;
}
QPackHeaderBlockPrefix
Encoded Field Section Prefix ではないかと思います。
class QPackHeaderBlockPrefix {
required_insert_count:uint32;
sign_bit:boolean;
delta_base:uint32;
}
終わりに
「HTTP/3 and QPACK event definitions for qlog」でこれから定義しようとしている、qlogにHTTP/3やQPACKのイベントを記録するための定義について紹介しました。
これから標準化が進んでいくと思われるので、今後も注目です。