2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

QUIC event definitions for qlog の紹介

Last updated at Posted at 2021-07-18

本記事ではIETFで標準化が行われている、QUICなどのロギングを行うフォーマットであるqlogの、QUICに関するイベントを定義している「QUIC event definitions for qlog」について紹介します。

QUIC event definitions for qlog とは

qlogには発生したイベントを記録するeventという情報をロギングする仕組みがあります。「QUIC event definitions for qlog」では、qlogにQUICの情報をログとして記録するための、イベント定義が行われています。

具体手的には、eventに対する、イベントの種類を特定するためのname (category + event)と定義されたイベントをロギングするときに含む具体的なデータの定義が行われています。

それぞれのイベントには、重要性、dataに含まれるデータの定義、そして、想定されているイベントを記録するトリガーが説明されています。

なお、qlogの提案自体は2019年ころから行われていましたが、「QUIC event definitions for qlog」まだWG Draftになって間もない状態です。RFC-9000ではなくおそらくQUIC draft-23を参照しています。

そのため今後の議論によって内容が大きく変わる可能性があります。

レポジトリや、Issueの状況は下記から参照可能です。

レポジトリ
https://github.com/quicwg/qlog

議論
https://github.com/quicwg/qlog/issues

qlog main schemaのおさらい

最初に理解を深めるために、Main logging schema for qlogを見て、eventがどのように格納されているのかおさらいをします。

qlogのトップレベルの構成は、以下のようになっています。traces の中に、クライアントやサーバーなどで収集したログを格納します。traces はクライアントとサーバーのログを単一のファイルに格納可能にするため、配列として定義されています。

   JSON serialization:

   {
       "qlog_version": "draft-03-WIP",
       "qlog_format": "JSON",
       "title": "Name of this particular qlog file (short)",
       "description": "Description for this group of traces (long)",
       "summary": {
           ...
       },
       "traces": [...]
   }

traces には、クライアントやサーバーなどが個別に収集したログが含まれます。
例として、以下のようなフォーマットで格納されています。

   {
       "title": "Name of this particular trace (short)",
       "description": "Description for this trace (long)",
       "configuration": {
           "time_offset": 150
       },
       "common_fields": {
           "ODCID": "abcde1234",
           "time_format": "absolute"
       },
       "vantage_point": {
           "name": "backend-67",
           "type": "server"
       },
       "events": [...]
   }

この中の、events という配列に、実際のQUICクライアント・サーバー実行時に起きたイベントをログとして保存します。

events には、個別のイベントが含まれます。例として、以下のようなフォーマットで格納されます。

   {
       time: 1553986553572,

       name: "transport:packet_sent",
       data: { ... }

       protocol_type:  ["QUIC","HTTP3"],
       group_id: "127ecc830d98f9d54a42c4f0842aa87e181a",

       time_format: "absolute",

       ODCID: "127ecc830d98f9d54a42c4f0842aa87e181a", // QUIC specific
   }

「QUIC event definitions for qlog」では、このイベントに対するname フィールドと、data フィールドに記録する情報が定義されています。

nameについて

name フィールドは、data フィールドに含まれているものを識別するために使用されます。

nameは、categorytypeを結合したものとして扱われます。

categorytypeは別々にロギングすることで高レベルでのフィルタリングとイベントタイプの再利用が可能になるようです。ただし、ドラフトではログのサイズが大きく増えるので別々にロギングする方法はあまり使われていないと言及されています。

nameと、categorytypeによる記述はそれぞれ以下のようになります。

name による記述

   {
       name: "transport:packet_sent"
   }

categorytypeによる記述

   {
       category: "transport",
       type: "packet_sent"
   }

なお、namecategorytype に分ける方法については著者がissueを上げているので今後変わるかもしれません。

QUICに対するname の定義

現時点では、categoryは、connectivitysecuritytransportrecovery の4つが定義されています。

現時点のWDでは 「3.1. connectivity」、「3.2. security」、「3.3. transport」、「3.4. recovery」という構成で、それぞれのカテゴリが定義されています。

そして、「3.1.1. server_listening」のようにカテゴリ毎にイベントタイプが定義されています。

イベントのImportanceについて

イベントのImportanceはmain schemaのEvent importance indicatorsで説明されています。

イベントの設計によって、似たような情報や重複がある情報が別々のイベントに含まれるかもしれません。
例えば、QUICのconnection_startedというイベントは、より一般的なconnection_state_updatedと被ります。

このような場合、もし両方のイベントに重複する様な情報が含まれていたとすると、どのイベントを優先するべきかが常に明確とは限りません。(訳注: connection_statedconnection_state_updated自体はログされるデータ自体は現時点では被っていないように思います。)

そのような場合に判断を助けるために、イベントを定義するときには "Importance Indicator" を定義するべきだと書かれています。

現状では、以下の3つの重要度があります。並び順は重要なものから順番に並んでいます。

  • Core
  • Base
  • Extra

Core

Coreは、あるプロトコルのログを含む場合はすべてのqlogファイルに含まれるべきイベントです。
典型的には、基本的なパケットやフレームの解析と生成や、基本的な内部状態のメトリクスに紐づいた情報です。

QUICについて、現時点のドラフトでCoreとしているものは、transport:version_informationtransport:alpn_informationtransport:parameters_settransport:packet_receivedrecovery:metric_updatedrecovery:packet_lost の7つです。

Base

"Base"は追加のデバッグのオプションです。
"Base"に含まれるものは多くの場合、"Core"にすべての情報が含まれていれば推測可能ですが、明示的にログを出すことで実装の振る舞いを明確にすることが可能になります。

これらのイベントは、データのバッファへの渡し方、どのように内部の状態が変化したか、実際に受信したデータによってどのような判断が行われたか、などが含まれます。

現状では、14個のイベントが"Base"として定義されています。

実装者が”Core”のイベントをパフォーマンスの問題ですべてロギングしたくないような場合は、代替となるような"Base"のイベントをロギングするべきとドラフトでは書かれています。

Extra

"Extra"は、プロトコルのデバッグよりは実装の低レベルのデバッグに役立つものとして定義されています。

"Extra"として定義されているものにより細かいレベルでの内部の振る舞いが追跡可能になります。

現状では、8個のイベントが"Extra"として定義されています。

qlog main schemaとのつながり

このドキュメントでは、main schemaで定義されたフィールド(例えば、name, category, type, data, group_id, protocol_type, 時間に関連するフィールド、importance, RawInfoなど)が再利用されます。

このドキュメントでQUICのために定義されているイベントをeventsに含める場合、protocol_typeの配列には"QUIC"を含める必要があります。

もし、group_id を使うのであれば、QUICのOriginal Destination Connection ID (ODCID, クライアントが最初に選んだCID) の使用が推奨されています。これは接続全体で変わらないIDとなるからです。

Raw packet and frame information

main schemaで定義された、RawInfo も再利用されます。 RawInfoは、低レベルのバイト長とバイトデータ自身を保存するためのデータ構造です。

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
}

Events not belonging to a single connection

特定のQUIC接続に紐づけられないイベントもあります。例えばヘッダに未知のconnection_idがある場合などが考えられます。典型的にはqlogは単一の接続を紐づけるため、それらのイベントをどのようにログするかは明確ではありません。

理想的には特定の接続に紐づけられないエンドポイントレベルのトレースファイルを作り、接続に紐づかないイベントはそこに保存するべきです。しかし、実装によっては現実的でないようです。これらのイベントのほとんどのセマンティクスはプロトコルで十分に定義されており、また、コネクションに属するものとして誤認することは困難であるため、実装者は、たとえ単一のコネクションに強く関連するイベントであっても、特定のコネクションに属さないイベントを他のトレースに記録することを選択してもよいからです。

これによって複数のvantage pointからなるログをマッチさせることが難しくなります。
クライアント側では、version negotiaionやretryを同じトレースに入れるのは簡単ですが、サーバーでは異なるファイルに記録されます。サーバーは、これらのイベントを1つのトレースに入れるために追加の処理が必要になります。

イベントの定義

繰り返しになりますが、「QUIC event definitions for qlog」ではconnectivitysecuritytransportrecoveryの4つのcategoryが定義されています。それぞれのcategory に対して event typeが定義されています。

Connectivity

connectivityには、接続の開始、終了、接続状態の変更などの接続に関するイベントタイプが定義されています。
現状では、検討中のものを含めて7個のイベントタイプが定義されています。

event type Importance サマリー
server_listening Extra サーバーが接続をlistenし始めた時にIPアドレスやポート番号を記録します
connection_started Base クライアントが新しい接続を開始しようとした時と、サーバーが新しい接続をacceptしたときに使われます
onnection_closed Base 接続が閉じたことを記録します
connection_id_updated Base サーバーかクライアントのどちらかが接続IDを更新したことを記録します
spin_bit_updated Base spin bit の更新を記録します
connection_retried 検討中 検討中
connection_state_updated Base TLSのハンドシェイクや、接続を閉じるときなどの状態を表します

マイグレーションに関わるイベントも現在検討中のようです。

server_listening

Importance: Extra

server_listening はサーバーが接続をacceptしたときに記録されます。server_listeningには、IPアドレスとポート番号を記録できます。

また、サーバーが1-RTT接続を行わない設定の場合に、サーバーが常にretryを送信する設定かを記録するretry_required? というフィールドがあるようです。

   Data:
{
    ip_v4?: IPAddress,
    ip_v6?: IPAddress,
    port_v4?: uint32,
    port_v6?: uint32,

    retry_required?:boolean // the server will always answer client initials with a retry (no 1-RTT connection setups by choice)
}

connection_started

Importance: Base

connecion_started は、クライアントが新しい接続を開始しようとした時と、サーバーが新しい接続をacceptした時の両方に使われます。

このイベントは、connection_state_updatedと被ります。connection_state_updatedに対して追加でログするべき情報があるのでconnection_startedconnection_state_updatedとは別のイベントとして定義されています。

   Data:
   {
       ip_version?: "v4" | "v6",
       src_ip?: IPAddress,
       dst_ip?: IPAddress,

       protocol?: string, // transport layer protocol (default "QUIC")
       src_port?: uint32,
       dst_port?: uint32,

       src_cid?: bytes,
       dst_cid?: bytes,
   }

connection_closed

Importance: Base

coinnection_closedは、典型的にはエラーやタイムアウトが起きて接続が閉じたことを記録するために使われます。

このイベントは、connectivity_connection_state_updatedCONNECTION_CLOSEフレームと被ります。しかし、運用的には接続が閉じたことを表すイベントがあったほうが便利なため定義されているようです。

このイベントには上記のイベントと異なり理由を定義するフィールドがあります。例えばタイムアウトが起きて接続を閉じる場合、タイムアウトが起きたということを記録するのがほかのイベントでは難しいためこのイベントが役に立ちます。

このイベントはQUICで定義されている、接続エラーやアプリケーションエラーだけでなく、実装に特有の内部エラーコードにも対応できるようになっています。

   {
       owner?:"local"|"remote", // which side closed the connection

       connection_code?:TransportError | CryptoError | uint32,
       application_code?:ApplicationError | uint32,
       internal_code?:uint32,

       reason?:string
   }

このイベントには、以下のようなトリガーが想定されています。

connection_id_updated

Importance: Base

このイベントは、サーバーかクライアントのどちらかが接続IDを更新したことを記録します。

典型的には接続の中で散発的にしか起こらないので、このイベントタイプを使うことでCIDをすべてのpacket_sentpacket_receivedのイベントに出力するよりも効率的に記録できます。

もし、新しいconnection idをピアから受け取ったら、dst_ fieldsがセットされます。
もし、自身のconnectin_id をNEW_CONNECTION_ID によってセットする場合は、src_ fieldsがセットされます。

訳注: 自身が更新した場合は、ownerを"local"に、ピアが更新した場合はownerを"remote"に設定するか、あるいは、src_ciddst_cidを設定するかのどちらかになるのではないかと思います。

   Data:

   {
       owner: "local" | "remote",

       old?:bytes,
       new?:bytes,
   }

spin_bit_updated

Importance: Base

このイベントは、spin bitの値が変わったときだけ記録されます。spin bitの値が変わらないときに記録しようとするとすべての1-RTTパケットを記録することになるため、値が変わったときだけ記録します。

   Data:

   {
       state: boolean
   }

connection_retried

このイベントはまだ定義されていないようです。

connection_state_updated

Importance: Base

このイベントは、ハンドシェイクの状態と、接続を閉じるときの状態が記録できます。
それぞれの状態を独立に記録できるようにConnectionStateという詳細なオプションが提供されています。

一方で、より簡単でシンプルな実装として、SimpleConnectionStateというものも提供されています。

Data: 
{
    old?: ConnectionState | SimpleConnectionState,
    new: ConnectionState | SimpleConnectionState
}

enum ConnectionState {
    attempted, // initial sent/received
    peer_validated, // peer address validated by: client sent Handshake packet OR client used CONNID chosen by the server. transport-draft-32, section-8.1
    handshake_started,
    early_write, // 1 RTT can be sent, but handshake isn't done yet
    handshake_complete, // TLS handshake complete: Finished received and sent. tls-draft-32, section-4.1.1
    handshake_confirmed, // HANDSHAKE_DONE sent/received (connection is now "active", 1RTT can be sent). tls-draft-32, section-4.1.2
    closing,
    draining, // connection_close sent/received
    closed // draining period done, connection state discarded
}

enum SimpleConnectionState {
    attempted,
    handshake_started,
    handshake_confirmed,
    closed
}

これらの状態は、クライアントとサーバーの以下の状態にマッピングされます。

Client

状態名 状態
send inital attempted
get initial peer_validated
get first handshake packet handskae_statrted
get Handshake packet containing Server Finished handshake_complete
send ClientFinished early_write(1RTT が送信可能)
get HANDSHAKE_DONE handshake_confirmed

Server

状態名 状態
get initial attempted
send intial 検討中 他のフレームと一緒に送信されるから必要ないかもしれない。
send handshake EE, Cert, CV handshake_started
send ServerFinished early_write (1RTT が送信可能)
get first handshake packet / something using a server-issued CID of min length validated
get handshake packet containing ClientFinished handshake_complete
state handshake_coinfirmed

attemptedは、概念的には、クライアント観点のconnection startedと同じです。
また、closing or drainingは、connection closed イベントと同じです。

MIGRATION-related events

マイグレーションに関わるイベントはまだ検討中のようです。

security

sercurityには鍵の更新のイベントと鍵のリトライのイベントの二つが現状では定義されています。

event type Importance サマリー
key_updated Base 鍵の更新を記録します
key_retried Base

key_updated

Importance: Base

このイベントは鍵の更新を記録します。secret_updatedのほうがより正確ですが、現状のドラフトでは、KEY_UPDATEとの一貫性のためこの名前にしているようです。

Key Updateについては、QUIC-TLSの6. Key Update を参照ください。

   Data:
   {
       key_type:KeyType,
       old?:bytes,
       new:bytes,
       generation?:uint32 // needed for 1RTT key updates
   }

トリガーは以下の項目が考えられています。

  • "tls": 例えば、TLSによるinitial, handshake, 0-RTTのキーの生成が例としてあります。
  • "remote_update"
  • "local_update"

key_retired

Importance: Base

現状では説明は特に書かれていませんが、
Retry Packetと関連するのではないかと思います。

   Data:
   {
       key_type:KeyType,
       key?:bytes,
       generation?:uint32 // needed for 1RTT key updates
   }

トリガーは以下の項目が考えられます。

  • "tls" // (e.g., initial, handshake and 0-RTT keys are dropped implicitly)
  • "remote_update"
  • "local_update"

transport

transportにはQUICのバージョンやALPNの結果、trasnport parameter、パケットの送受信のタイミングやそれにかかわるデータなどの情報が含まれます。

event type Importance サマリー
version_information Core QUICのバージョン情報を記録します
alpn_information Core ALPN Negotiationの情報を記録します
parameters_set Core transport parameter を記録します
parameters_restored Base 0-RTT接続の場合に、前の接続から復元して使うtransport parameterを記録します
packet_sent Core 送信するQUICパケットの情報を記録します
packet_received Core 受信したQUICパケットの情報を記録します
packet_dropped Base QUICパケットが、解析に失敗などしてドロップした場合に使われます
packet_buffered Base パケットがバッファリングされて処理されていないときに使います
packets_acked Extra QUICパケットがAckされた場合に使用します
datagrams_sent Extra UDPのデータグラムをソケットにパスしたときにログに書き込みます(DATAGRAM拡張ではありません)
datagrams_received Extra UDPのデータグラムをOS側から受信したときに使います
datagram_dropped Extra UDPのデータグラムがドロップしたときに使われます
stream_state_updated Base ストリームの状態が更新されたときに発行されます
frames_processed Extra QUICのフレームが処理されたことを表すときに使われます
data_moved Base データが異なるレイヤを移動したことを記録するときに使われます

version_information

Importance: Core

QUICのエンドポイントはサーバー・クライアントともにサポートしているQUICのversionを保持しています。クライアントは最もそれらしいバージョンを最初のinitialに使います。

サーバーはそのバージョンをサポートしていない場合、サーバーがサポートしているバージョンが含まれているversion_negotiation パケットを送信します。

クライアントはその中からバージョンを選択します。

このイベントはそれらの情報を一つのイベントにまとめます。このイベントは、エンドポイントがサポートしているバージョンを、バージョンネゴシエーションが実際に行われたタイミングじゃなくてもログに記録することができます。

参考: 6. Version Negotiation15. Versions

   Data:
   {
       server_versions?:Array<bytes>,
       client_versions?:Array<bytes>,
       chosen_version?:bytes
   }

このイベントは以下のような使い方が想定されています。

  • クライアントがinitialを送ったときに、client_versionchosen_version をセットしてログを記録します。
  • サーバーがクライアントからのサポートしているバージョンを含むinitialを受け取ったら、このイベントにをserver_versionchosen_versionをセットしてログを書き込みます。
  • サーバーがサポートしていないバージョンをクライアントから受け取ったら、sever_versionにサーバーがサポートしているバージョンを、client_versionにクライアントが使おうとしたバージョンをログに記録します。chosen_versionを空にすることで、クライアントとサーバーのバージョンにマッチするものがなかったことを現わせます。
  • クライアントは、version negotiation packetをサーバーから受け取ったら、client_versionの集合と、 server_versionsの集合とchosen_version(次のinitial packetで使う)をログに書き込みます。

alpn_information

Importance: Core

QUICの実装はそれぞれアプリケーションレベルの実装の一覧とサポートしているバージョンを持っています。

TLSのAPLN拡張として、クライアントは自身がサポートしている一覧をinitialに入れて送ります。

もし共通のものがあればサーバーは最適なものを選んでクライアントに返します。

共通のものがない場合はコネクションを閉じます。

   Data:
   {
       server_alpns?:Array<string>,
       client_alpns?:Array<string>,
       chosen_alpn?:string
   }

以下のような使い方が想定されています。

  • initialを送るときにクライアントはclient_alpnsの集合をログに記録する。
  • サーバーはサポートしているalpnを受信した時に、サーバーのalpn、クライアントのalpn、選んでクライアントに送ったalpnをサーバーのログに含めます。
  • クライアントは、alpnをinitialで受信したらchosen_alpnをログに記録します。
  • クライアントは最初のイベントをログせずに、サーバーからintialを受信したときにclient_alpnchosen_alpnを一緒に保存することもできます。

parameters_set

Importance: Core

parameters_setは、transport parameter、 TLC ciphersなどいくつかの異なる情報を一つのイベントとして定義します。このように定義しているのは、イベントの量を最小限に抑え、概念的な設定の影響をその基礎となるメカニズムから切り離すことで、高レベルの推論を容易にするためです。

典型的にはこれらの設定は一度設定されたら変更されません。しかし、典型的には接続の異なるタイミングで使用されます。そのため、parameter_setのイベントは異なるフィールドを持ったインスタンスが存在することになります。

設定には、ローカルでセットしたものとリモートのピアからリクエストされたものの2種類が存在します。これは"owner"フィールドに反映されます。そのため、このフィールドは、1つのイベントインスタンスに含まれるすべての設定に対して正しくなければなりません。 2つの側からの設定を記録する必要がある場合は 2つの別々のイベントインスタンスを発行しなければなりません。

接続の再開と0-RTTの場合、サーバーのいくつかのパラメータはクライアントに保存され、接続の開始時に使用されます。
それらは後程サーバーからのリプライによって更新されます。この場合、"parameters_restored"を活用し初期値と、更新された値を表すことになります。

Data:
{
    owner?:"local" | "remote",

    resumption_allowed?:boolean, // valid session ticket was received
    early_data_enabled?:boolean, // early data extension was enabled on the TLS layer
    tls_cipher?:string, // (e.g., "AES_128_GCM_SHA256")
    aead_tag_length?:uint8, // depends on the TLS cipher, but it's easier to be explicit. Default value is 16

    // transport parameters from the TLS layer:
    original_destination_connection_id?:bytes,
    initial_source_connection_id?:bytes,
    retry_source_connection_id?:bytes,
    stateless_reset_token?:Token,
    disable_active_migration?:boolean,

    max_idle_timeout?:uint64,
    max_udp_payload_size?:uint32,
    ack_delay_exponent?:uint16,
    max_ack_delay?:uint16,
    active_connection_id_limit?:uint32,

    initial_max_data?:uint64,
    initial_max_stream_data_bidi_local?:uint64,
    initial_max_stream_data_bidi_remote?:uint64,
    initial_max_stream_data_uni?:uint64,
    initial_max_streams_bidi?:uint64,
    initial_max_streams_uni?:uint64,

    preferred_address?:PreferredAddress
}

interface PreferredAddress {
    ip_v4:IPAddress,
    ip_v6:IPAddress,

    port_v4:uint16,
    port_v6:uint16,

    connection_id:bytes,
    stateless_reset_token:Token
}

このイベントは、unknown (greased)なトランスポートパラメータや、独自仕様のトランスポートパラメータを含むことが可能です。

parameters_restored

Importance: Base

0-RTTを使う時、クライアントは以前の接続のサーバーのトランスポートパラメータを記憶して保存していることが想定されます。このイベントは、どのパラメータが復元されたのかを記録できます。

再利用が禁止されているものもあるため、すべてのトランスポートパラメータ―が復元されるべきではありません。ここに定義されているものは0-RTTを正しく使うために役立つと期待されるものです。

   Data:

   {
       disable_active_migration?:boolean,

       max_idle_timeout?:uint64,
       max_udp_payload_size?:uint32,
       active_connection_id_limit?:uint32,

       initial_max_data?:uint64,
       initial_max_stream_data_bidi_local?:uint64,
       initial_max_stream_data_bidi_remote?:uint64,
       initial_max_stream_data_uni?:uint64,
       initial_max_streams_bidi?:uint64,
       initial_max_streams_uni?:uint64,
   }

parameter_set と同じように、このイベントには仕様書では記述されていない追加のフィールドやカスタムパラメータを持つことが可能です。

packet_sent

Importance: Core

このイベントには、送信されるQUIC Packetの情報が含まれます。具体的には、PacketHeaderの情報、そのQUIC Packetに含まれるQUIC Frame などがあります。

Data:
{
    header:PacketHeader,

    frames?:Array<QuicFrame>, // see appendix for the definitions

    is_coalesced?:boolean, // default value is false

    retry_token?:Token, // only if header.packet_type === retry

    stateless_reset_token?:bytes, // only if header.packet_type === stateless_reset. Is always 128 bits in length.

    supported_versions:Array<bytes>, // only if header.packet_type === version_negotiation

    raw?:RawInfo,
    datagram_id?:uint32
}

このイベントには以下のようなトリガーが想定されています。

  • "retransmit_reordered" // draft-23 5.1.1
  • "retransmit_timeout" // draft-23 5.1.2
  • "pto_probe" // draft-23 5.3.1
  • "retransmit_crypto" // draft-19 6.2
  • "cc_bandwidth_probe" // needed for some CCs to figure out
    bandwidth allocations when there are no normal sends

packet_received

Importance: Core

このイベントには、送信されるQUIC Packetの情報が含まれます。
具体的には、PacketHeaderの情報、そのQUIC Packetに含まれるQUIC Frame などがあります。

   Data:
{
    header:PacketHeader,

    frames?:Array<QuicFrame>, // see appendix for the definitions

    is_coalesced?:boolean,

    retry_token?:Token, // only if header.packet_type === retry

    stateless_reset_token?:bytes, // only if header.packet_type === stateless_reset. Is always 128 bits in length.

    supported_versions:Array<bytes>, // only if header.packet_type === version_negotiation

    raw?:RawInfo,
    datagram_id?:uint32
}

以下のようなトリガーが想定されています。

  • "keys_available": パケットがバッファされたタイミング。そのタイミングで復号可能になるため。

packet_dropped

Importance: Base

このイベントはQUICレベルのパケットのドロップが、部分的に解析した後、あるいは、解析無しで見つかったことを表します。

   Data:

{
    header?:PacketHeader, // primarily packet_type should be filled here, as other fields might not be parseable

    raw?:RawInfo,
    datagram_id?:uint32
}

このイベントに対しては、デバッグの助けになるので、trigger フィールドはセットされるべきです。

trigger フィールドにセットされる値は、以下のようなものが想定されています。

  • "key_unavailable"
  • "unknown_connection_id"
  • "header_parse_error"
  • "payload_decrypt_error"
  • "protocol_violation"
  • "dos_prevention"
  • "unsupported_version"
  • "unexpected_packet"
  • "unexpected_source_connection_id"
  • "unexpected_version"
  • "duplicate"
  • "invalid_initial"

packet_buffered

Importance: Base

このイベントは、パケットがバッファリングされている時に、処理されていないことを記録するために使います。典型的には、パケットの解析が終わっていないので、生の情報をログする形になります。

   Data:
{
    header?:PacketHeader, // primarily packet_type and possible packet_number should be filled here, as other elements might not be available yet

    raw?:RawInfo,
    datagram_id?:uint32
}

以下のようなトリガーが想定されています。

  • "backpressure": パーサーの処理が追いつかないことを示し、後の処理のためにパケットを一時的にバッファします。
  • "keys_unavailable": 適切な鍵がないため複合ができない場合

packets_acked

Importance: Extra

このイベントは送信した(グループの)QUICパケット(複数)がリモートのピアによってackされた場合にログに記録します。

この情報は、ACKフレームの中身から推測ができます。しかし、ACKフレームによって、パケットがACKされたかを確認しようとした場合、繰り返し同じ範囲が含まれるため、あるパケットが初めてACKされたのがそのACKフレームなのかを判断するロジックが必要になります。

packet_acked イベントによってそれを回避することが可能になります。

   Data:
 { 
   packet_number_space?:PacketNumberSpace,
   packet_numbers?:Array<uint64> } 

もし、packet_number_spaceが捨てられた場合、PacketNumberSpace.application_dataのデフォルトの値を仮定します。なぜなら、それが一般的に最もよく使用されるQUICのパケットだからです。

datagrams_sent

Importance: Extra

UDPレベルのデータグラムをソケットにパスしたときにログに書き込みます。(Datagram拡張ではありません)
これにより、QUICのパケットバッファがどのようにOSに渡されているかが分かります。

   Data:
{
    count?:uint16, // to support passing multiple at once
    raw?:Array<RawInfo>, // RawInfo:length field indicates total length of the datagrams, including UDP header length

    datagram_ids?:Array<uint32>
}

QUIC自体にはdatagram_idのコンセプトはありません。

このフィールドは、qlogレベルで、複数のQUICパケットがUDPのデータグラムでどのように融合されたのかトラッキングするための、qlog特有のものになります。

これは、QUICハンドシェイクにとって重要なオプションになります。実装では、ユニークIDをそれぞれのデータグラムに割り当て、どのパケットが同じデータグラムに融合されたのかを記録します。

パケットの融合は典型的にはハンドシェイクの時に起きるため(少なくとも一つlong header packetを必要とする)、このイベントはほとんどオーバーヘッドなしで扱えるようです。

datagrams_received

Importance: Extra

1または複数のUDPレベルのデータグラムを受信したときに使います。
どのようにデータグラムがOSからユーザースペースに渡されているかを判断するのに役に立ちます。

   Data:

{
    count?:uint16, // to support passing multiple at once
    raw?:Array<RawInfo>, // RawInfo:length field indicates total length of the datagrams, including UDP header length

    datagram_ids?:Array<uint32>
}

datagram_dropped

Importance: Extra

UDP-levelのデータグラムがドロップしたときに、使われます。
これは典型的には、validなQUICパケットを含んでいない場合に使われます。(そのような場合はpacket_droppedが使われます。)

   Data:
   {
       raw?:RawInfo
   }

stream_state_updated

Importance: Base

このイベントは、内部のストリームの状態が更新された時に発行されます。

draft-23のセクション3に書かれているように、多くの場合はいくつかのタイプのフレームの情報を元に判断します。
しかし、明示的なシグナルがあることでこの判断が行いやすくなります。

   Data:
{
    stream_id:uint64,
    stream_type?:"unidirectional"|"bidirectional", // mainly useful when opening the stream

    old?:StreamState,
    new:StreamState,

    stream_side?:"sending"|"receiving"
}

enum StreamState {
    // bidirectional stream states, draft-23 3.4.
    idle,
    open,
    half_closed_local,
    half_closed_remote,
    closed,

    // sending-side stream states, draft-23 3.1.
    ready,
    send,
    data_sent,
    reset_sent,
    reset_received,

    // receive-side stream states, draft-23 3.2.
    receive,
    size_known,
    data_read,
    reset_read,

    // both-side states
    data_received,

    // qlog-defined
    destroyed // memory actually freed
}

QUICの実装では、より細かいストリームの状態(例:data_sent、reset_received)ではなく、簡略化された双方向(HTTP/2-alike)のストリームの状態(例:idle、open、closed)を主に記録すべきです。 後者は主により詳細なデバッグのためのものです。 ツールは両方のタイプを同等に扱うことができるべきです。

frames_processed

Importance: Extra

packet_receivedは受信したタイミングですが、このイベントは、QUICフレームを処理して適用した結果を記録できるようです。例えば、

このイベントの目的は、特定の目的のために大量にイベントが定義されるのを防ぐためにあります。
例えば、packet_acknowledged, flow_control_updated, stream_data_received といったものを個別に定義する場合が考えられます。

packet_receivedのようにパケットレベルの詳細を記録することなく、このタイプの情報を(選択的に)記録する機会を実装に与えたいという意図があります。

ほとんどの場合、実装の内部状態にフレームを適用した効果はそのフレームの内容から推測できるため、これらのイベントを「frames_processed」イベントに集約しています。

このイベントは、パースの直接の結果ではなく内部状態が変わったことシグナルに使うことができます。例えば、フレームがパースされ、データがバッファに入れられ、そののちに処理されたあとで、このイベントを記録します。

packet_receivedにはすべてのパケットの構成要素を含まれているので、それを実装する場合はframe_processedはログされることを予期しません。むしろ、すべてのパケットをログしたくない、あるいは、フレームが処理された場合追加の情報を明示的に出したいにこのイベントが使えます。

いくつかのイベントに対して、このアプローチは情報を失います(例えば、暗号レベルのパケットのAck?)。もしそういった情報が重要ならpacket_receivedを使うべきです。

いくつかの実装では、packet_sentpacket_receivedイベントを使ってもフレームを直接ログするのは難しいです。
その場合、このイベントが、packet_numberフィールドを直接含み、明示的にpacket_sentpacket_receivedとリンクされます。

   Data:

   {
       frames:Array<QuicFrame>, // see appendix for the definitions

       packet_number?:uint64
   }

data_moved

Importance: Base

例えば、HTTPなどのアプリケーションプロトコルからQUICのストリームバッファへの受け渡しや、アプリケーションプロトコル間(HTTPとユーザーのプロトコルの間)のデータの移動などのデータが異なるレイヤを移動しているときに使われます。

これによりデータのフローが明らかになり、どのくらいの間バッファにデータが滞留しているかや、それによるそれぞれのレイヤのオーバーヘッドが分かります。

例えば、QUICのストリームに渡されたデータがアプリケーションプロトコルに即座に渡されたか(例えば受信したパケット毎に渡されているか)のか、巨大なバッチ(すべてのクイックパケットが最初に処理されてそれからアプリケーションレイヤが新しく利用可能になったストリームのデータを読み込む)として扱われたのかが明確になります。

これにより、ボトルネックやスケジューリングの問題が明らかにできます。

   Data:

{
    stream_id?:uint64,
    offset?:uint64,
    length?:uint64, // byte length of the moved data

    from?:string, // typically: use either of "user","application","transport","network"
    to?:string, // typically: use either of "user","application","transport","network"

    data?:bytes // raw bytes that were transferred
}

"direction"フィールド("up"/"down") はデータフローを記述するためには使われません。これは、いくつかの最適化で、データが独立したレイヤをスキップするからです。

さらに、"from"、"to"によって、柔軟な、概念的なレイヤ間のデータの渡し方を定義できます。
例えば、QUIC CRYPTフレームがTLSレイヤに渡される、あるいは、HTTP/3からQPACKに渡される、といったことが記述できます。

このイベントタイプは、transportのカテゴリにありますが、実際に、他の例やでも適用することができます。
これによって、いくつかの抽象化の漏れが起きます(例えば、ストリームIDあるいはストリームオフセットがロギングポイントで使えない、あるいは、rawデータがバイトアレイに含まれない)。
このような場合、実装は新しい、in-contextフィールドを手動のデバッグのために定義することが可能です。

recovery

recoveryのカテゴリには、ロス検出や輻輳制御に関連するイベントが定義されています。
おそらく、QUIC Loss Detection and Congestion Control に関連するものをまとめているのではないかと思います。

event type Importance サマリー
parameters_set Base ロス検出や輻輳制御に関するパラメータを記録します
metrics_updated Core 1または複数のリカバリ用のメトリクスに変更があったことを記録します
congestion_state_updated Base 輻輳制御が何らかの新しい状態に入ったことや振る舞いが変わったことを記録します
loss_timer_updated Extra リカバリロスタイマーが変更したときに発行されます
packet_lost Core パケットロスが検出されたときに発行されます
marked_for_retransmit Extra どのデータがパケットロスにより再走されたものかをマークします

このカテゴリのほとんどのイベントは、異なるリカバリと多くの輻輳制御のアルゴリズムの方法をサポートするために汎用的に作られています。

したがって、ツールを作る場合はこれらのイベントに不明なフィールドがあったとしてもサポートと可視化する努力をするべきと書かれています(例えば、可視化の時に、未知の輻輳制御の状態名をタイムライン上にプロットできるようにする)。

parameters_set

Importance: Base

このイベントは、ロス検出と輻輳制御に関する初期のパラメータをひとまとめにしています。

これらの設定は、典型的に一度設定されると変わりませんが、実装で何らかの理由で更新されることがあります。その場合、parameter_setのログを2回書き込むことがあるかもしれません。

   Data:

{
    // Loss detection, see recovery draft-23, Appendix A.2
    reordering_threshold?:uint16, // in amount of packets
    time_threshold?:float, // as RTT multiplier
    timer_granularity?:uint16, // in ms
    initial_rtt?:float, // in ms

    // congestion control, Appendix B.1.
    max_datagram_size?:uint32, // in bytes // Note: this could be updated after pmtud
    initial_congestion_window?:uint64, // in bytes
    minimum_congestion_window?:uint32, // in bytes // Note: this could change when max_datagram_size changes
    loss_reduction_factor?:float,
    persistent_congestion_threshold?:uint16 // as PTO multiplier
}

このイベントは異なるリカバリの方法をサポートするためにここでは記述されていないフィールドを持つかもしれません。

metrics_updated

Importance: Core

このイベントは、1または複数のリカバリ用のメトリクスの変更があった場合に発行されます。

このイベントグループは、一度に起きたあるいは同時に起きたすべてのメトリクスの更新をグループにするべきです。(例えばmin_rttとsmoothed_rttが同時に変更されたら、一つのmetrics_updated エントリに入れます)。

結果、metric_updated イベントは、リストされたメトリクスから少なくとも一つのものが含まれていることが保証されます。

   Data:
{
    // Loss detection, see recovery draft-23, Appendix A.3
    min_rtt?:float, // in ms or us, depending on the overarching qlog's configuration
    smoothed_rtt?:float, // in ms or us, depending on the overarching qlog's configuration
    latest_rtt?:float, // in ms or us, depending on the overarching qlog's configuration
    rtt_variance?:float, // in ms or us, depending on the overarching qlog's configuration

    pto_count?:uint16,

    // Congestion control, Appendix B.2.
    congestion_window?:uint64, // in bytes
    bytes_in_flight?:uint64,

    ssthresh?:uint64, // in bytes

    // qlog defined
    packets_in_flight?:uint64, // sum of all packet number spaces

    pacing_rate?:uint64 // in bps
}

ロギングを簡単にするためには値の更新がなかったパラメータについてもロギングしたほうがよいかもしれないです。しかし、アプリケーションは1回、実際に更新されたものだけをログするべきです。

また、このイベントは未定義のフィールドを含むかもしれません。

congestion_state_updated

Importance: Base

このイベントは、輻輳制御が何らかの新しい状態に入ったことや振る舞いが変わったことを表します。

このイベントの定義は、異なる輻輳制御アルゴリズムをサポートするために汎用的に定義されています。

例えば、Recovery に定義されたアルゴリズム ("enhanced" New Reno) は以下のような状態が定義されます。

  • slow_start
  • congestion_avoidance
  • application_limited
  • recovery
   Data:

   {
       old?:string,
       new:string
   }

トリガーフィールドは、状態変化を起こすものが複数ある場合は保存するべきです。しかし、単一のイベントのみによって起きる場合無視することができます。例えば、slow startはssthreshが超えた場合のみ抜け出します。

"enhanced" New Reno の場合は以下のようなトリガーが考えられます。

  • persistent_congestion
  • ECN

loss_timer_updated

Importance: Extra

このイベントは、リカバリロスタイマーが変更したときに発行されます。

おもに3つのタイミングがあります。

  • set: タイマーを次にイベントが発生するdelta timeoutにセットします。
  • expired: delta taimeoutが切れた場合
  • canceld: タイマーがキャンセルされた場合(すべてのパケットがackされて、アイドル状態になる)

アクティブなタイマーのタイムアウトを示すために、新しい"set"イベントが使われる。

   Data:
{
    timer_type?:"ack"|"pto", // called "mode" in draft-23 A.9.
    packet_number_space?: PacketNumberSpace,

    event_type:"set"|"expired"|"cancelled",

    delta?:float // if event_type === "set": delta time in ms or us (see configuration) from this event's timestamp until when the timer will trigger
}

複数のタイマーを使うような場合は現在検討中のようです。

packet_lost

Importance: Core

このイベントは、パケットがロス検出によってロスされたと判断されたときに発行されます。

   Data:
{
    header?:PacketHeader, // should include at least the packet_type and packet_number

    // not all implementations will keep track of full packets, so these are optional
    frames?:Array<QuicFrame> // see appendix for the definitions
}

トリガーは、以下のようなものが想定されます。

  • "reordering_threshold",
  • "time_threshold"
  • "pto_expired" // draft-23 section 5.3.1, MAY

marked_for_retransmit

Importance: Extra

このイベントは、どのデータがパケットロスによる再送されたものをマークします。

frame_processedと同様の理由で、異なるイベントを少なくするために、すべてのタイプの再走されるデータを単一のイベントで定義します。

フルパケットあるいはフレームを直接再送するような実装は、このログをロスしたパケット全体を記録するために使えます。
(あるいは、このイベントは使わずに、packet_lostイベントを使います。)

より複雑な実装(例えば、送信中のストリームの範囲を記録している)場合や、フレーム全体を記録していない(例えばストリームのオフセットと長さを記録している)ような場合、それらの内部の振る舞いを適切にフレームに変換する必要があります。

   Data:
   {
       frames:Array<QuicFrame>, // see appendix for the definitions
   }

QUIC data field definitions

enumなどの再利用可能なものはドキュメントの最後のAppendix A に定義されています。

IPAddress

class IPAddress : string | bytes;

IPアドレスは、人間が読める形式か生のバイトデータのどちらも可能です。

PacketType

   enum PacketType {
       initial,
       handshake,
       zerortt = "0RTT",
       onertt = "1RTT",
       retry,
       version_negotiation,
       stateless_reset,
       unknown
   }

PacketNumberSpace

   enum PacketNumberSpace {
       initial,
       handshake,
       application_data
   }

PacketHeader

class PacketHeader {
    // Note: short vs long header is implicit through PacketType

    packet_type: PacketType;
    packet_number: uint64;

    flags?: uint8; // the bit flags of the packet headers (spin bit, key update bit, etc. up to and including the packet number length bits if present) interpreted as a single 8-bit integer

    token?:Token; // only if packet_type == initial

    length?: uint16, // only if packet_type == initial || handshake || 0RTT. Signifies length of the packet_number plus the payload.

    // only if present in the header
    // if correctly using transport:connection_id_updated events,
    // dcid can be skipped for 1RTT packets
    version?: bytes; // e.g., "ff00001d" for draft-29
    scil?: uint8;
    dcil?: uint8;
    scid?: bytes;
    dcid?: bytes;
}

Token

class Token {
    type?:"retry"|"resumption"|"stateless_reset";

    length?:uint32; // byte length of the token
    data?:bytes; // raw byte value of the token

    details?:any; // decoded fields included in the token (typically: peer's IP address, creation time)
}

initial packetで運ばれるトークンは、
①Retryパケットのretry token
②Stateless reset packetからのstateless reset token
③接続を再開するための、サーバーから提供されるNewTokenフレーム(アドレス検証プロセス)
のどれかです。

Retryとresumptionトークンは典型的にはメタデータにエンコードされ、妥当性を検証されます。しかし、このメタデータとフォーマットは実装依存です。なので、このフィールドは汎用的なdetailsというフィールドにされています。

KeyType

   enum KeyType {
       server_initial_secret,
       client_initial_secret,

       server_handshake_secret,
       client_handshake_secret,

       server_0rtt_secret,
       client_0rtt_secret,

       server_1rtt_secret,
       client_1rtt_secret
   }

QUIC Frames

type QuicFrame = PaddingFrame | PingFrame | AckFrame | ResetStreamFrame | StopSendingFrame | CryptoFrame | NewTokenFrame | StreamFrame | MaxDataFrame | MaxStreamDataFrame | MaxStreamsFrame | DataBlockedFrame | StreamDataBlockedFrame | StreamsBlockedFrame | NewConnectionIDFrame | RetireConnectionIDFrame | PathChallengeFrame | PathResponseFrame | ConnectionCloseFrame | HandshakeDoneFrame | UnknownFrame;

PaddingFrame

QUICでは、PADDINGフレームは1つの0バイトを表しています。論理的には、各パディングバイトがパディングフレームとして記録されます。

しかし、そうするとログのオーバーヘッドが大きくなってしまうため、実装は一つのPaddingFrameだけを発行し、paload_lengthプロパティでパケットに含まれるPADDINGバイトの量を表すことができます。

   class PaddingFrame{
       frame_type:string = "padding";

       length?:uint32; // total frame length, including frame header
       payload_length?:uint32;
   }

PingFrame

   class PingFrame{
       frame_type:string = "ping";

       length?:uint32; // total frame length, including frame header
       payload_length?:uint32;
   }

AckFrame

class AckFrame{
    frame_type:string = "ack";

    ack_delay?:float; // in ms

    // first number is "from": lowest packet number in interval
    // second number is "to": up to and including // highest packet number in interval
    // e.g., looks like [[1,2],[4,5]]
    acked_ranges?:Array<[uint64, uint64]|[uint64]>;

    // ECN (explicit congestion notification) related fields (not always present)
    ect1?:uint64;
    ect0?:uint64;
    ce?:uint64;

    length?:uint32; // total frame length, including frame header
    payload_length?:uint32;
}

AckFrame.acked_rangesは、順番になっていなくてもよいです。ログを出す側は[120,120]を[120]と出すべきです。ただし、ツールは両方対応できないといけません。

ResetStreamFrame

   class ResetStreamFrame{
       frame_type:string = "reset_stream";

       stream_id:uint64;
       error_code:ApplicationError | uint32;
       final_size:uint64; // in bytes

       length?:uint32; // total frame length, including frame header
       payload_length?:uint32;
   }

StopSendingFrame

   class StopSendingFrame{
       frame_type:string = "stop_sending";

       stream_id:uint64;
       error_code:ApplicationError | uint32;

       length?:uint32; // total frame length, including frame header
       payload_length?:uint32;
   }

CryptoFrame

   class CryptoFrame{
       frame_type:string = "crypto";

       offset:uint64;
       length:uint64;

       payload_length?:uint32;
   }

NewTokenFrame

   class NewTokenFrame{
     frame_type:string = "new_token";

     token:Token
   }

StreamFrame

class StreamFrame{
    frame_type:string = "stream";

    stream_id:uint64;

    // These two MUST always be set
    // If not present in the Frame type, log their default values
    offset:uint64;
    length:uint64;

    // this MAY be set any time, but MUST only be set if the value is "true"
    // if absent, the value MUST be assumed to be "false"
    fin?:boolean;

    raw?:bytes;
}

MaxDataFrame

   class MaxDataFrame{
     frame_type:string = "max_data";

     maximum:uint64;
   }

MaxStreamDataFrame

   class MaxStreamDataFrame{
     frame_type:string = "max_stream_data";

     stream_id:uint64;
     maximum:uint64;
   }

MaxStreamsFrame

   class MaxStreamsFrame{
     frame_type:string = "max_streams";

     stream_type:string = "bidirectional" | "unidirectional";
     maximum:uint64;
   }

DataBlockedFrame

   class DataBlockedFrame{
     frame_type:string = "data_blocked";

     limit:uint64;
   }

StreamDataBlockedFrame

   class StreamDataBlockedFrame{
     frame_type:string = "stream_data_blocked";

     stream_id:uint64;
     limit:uint64;
   }

StreamsBlockedFrame

   class StreamsBlockedFrame{
     frame_type:string = "streams_blocked";

     stream_type:string = "bidirectional" | "unidirectional";
     limit:uint64;
   }

NewConnectionIDFrame

   class NewConnectionIDFrame{
     frame_type:string = "new_connection_id";

     sequence_number:uint32;
     retire_prior_to:uint32;

     connection_id_length?:uint8;
     connection_id:bytes;

     stateless_reset_token?:Token;
   }

RetireConnectionIDFrame

   class RetireConnectionIDFrame{
     frame_type:string = "retire_connection_id";

     sequence_number:uint32;
   }

PathChallengeFrame

   class PathChallengeFrame{
     frame_type:string = "path_challenge";

     data?:bytes; // always 64-bit
   }

PathResponseFrame

   class PathResponseFrame{
     frame_type:string = "path_response";

     data?:bytes; // always 64-bit
   }

ConnectionCloseFrame

raw_error_codeは、実際には数値のコードです。これは、一部のエラータイプが複数のコードにまたがっている場合に便利です。

type ErrorSpace = "transport" | "application";

class ConnectionCloseFrame{
    frame_type:string = "connection_close";

    error_space?:ErrorSpace;
    error_code?:TransportError | ApplicationError | uint32;
    raw_error_code?:uint32;
    reason?:string;

    trigger_frame_type?:uint64 | string; // For known frame types, the appropriate "frame_type" string. For unknown frame types, the hex encoded identifier value
}

HandshakeDoneFrame

   class HandshakeDoneFrame{
     frame_type:string = "handshake_done";
   }

UnknownFrame

   class UnknownFrame{
       frame_type:string = "unknown";
       raw_frame_type:uint64;

       raw_length?:uint32;
       raw?:bytes;
   }

TransportError

   enum TransportError {
       no_error,
       internal_error,
       connection_refused,
       flow_control_error,
       stream_limit_error,
       stream_state_error,
       final_size_error,
       frame_encoding_error,
       transport_parameter_error,
       connection_id_limit_error,
       protocol_violation,
       invalid_token,
       application_error,
       crypto_buffer_exceeded
   }

CryptoError

このエラーは、TLSのドキュメントに「TLSアラートは、1バイトのエラーコードをQUICエラーコードに変換することでQUIC接続エラーに変換される。このアラートは、0x100からQUICのエラーコードのために予約される」と記されています。

このアプローチは、事前に定義されたenumです。crypto_error文字列は、hex-encodedされたTLSアラートを表すダイナミックコンポーネントとして定義します。

   enum CryptoError {
       crypto_error_{TLS_ALERT}
   }

終わりに

長くなりましたが、「QUIC event definitions for qlog」でこれから定義しようとしている、qlogにQUICのイベントを記録するための情報の定義を紹介しました。

個人的には、ログされるイベントから規格の理解を深めることもできるのではないかと思いました。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?