(RFC8740) HTTP/2においてTLS1.3のpost-handshake authenticationの禁止

2020/02/23 追記
下記提案はRFC8740として標準化されました
https://tools.ietf.org/html/rfc8740


GoogleのDavid Benjamin氏より「Using TLS 1.3 with HTTP/2」という提案仕様が出されています。

この仕様自体は、単純にHTTP/2においてTLS1.3のpost-handshake authenticationを禁止するものです。

HTTP/2でTLS1.2を使用している場合はrenegotiatoinが禁止されており、同様の課題をもつTLS1.3のpost-handshake authenticationも明示的に禁止するというのがこの提案です。

背景

Webサービスを利用している際、特定のURL(管理用ページなど)だけセキュリティーレベルが高く、クライアント証明書を用いた認証を行いたい場合があります。

一般的にTLS1.2ではコネクションを確立したあとにクライアント証明書を要求する場合はrenegotiationという手順をとっていました。

しかし、HTTP/2では一つのコネクション上でHTTPリクエストが並列的に送信されます。このTLS1.2のrenegotiationでは、どのHTTPリクエストによってrenegotiationがトリガされたかクライアントは分からないという課題がありました。

(TLS1.2のrenegotiationが脆弱だという話もありつつ。)

TLS1.3では、post-handshake authenticationという形でコネクションを確立したあとにクライアント証明書の要求/送信が行えるようになりました。

しかしHTTP/2を利用しているとき、多重化されたHTTPリクエストのどれがpost-handshake authenticationのトリガになったかという課題は同じです。ですので、改めてHTTP/2でTLS1.3を利用している場合、post-handshake authenticationの禁止を明示します。

Secondary Certificate Authentication in HTTP/2

それでは、追加でクライアント証明書を要求する場合どうするのかというと、別の提案仕様が提出されています。

詳細は以前書いたとおりです。
asnokaze.hatenablog.com

この仕様では、どのストリームが証明書を要求しているかを関連付けることが出来、HTTP/2レイヤで証明書の要求及び送信を行うことで先の課題を解決しています。

もともとはこちらの提案仕様が先に出されていたわけですが、議論の結果、post-handshake authenticationの禁止は別提案として明文化するようになりました。

その話が書かれたメールは下記です
lists.w3.org

「Address-bound Token for QUIC」が面白い

FastlyのKazuhoさんが「Address-bound Token for QUIC」という提案仕様を出している、この中で出てくる "Sharing the Congestion Controller" という仕組みが非常に面白かったので、簡単に書いてみようと思う。

address-bound token

この提案では、同一エンドポイント間で複数のQUICコネクションをはる際に、それらのコネクションが同一エンドポイント間でやりとりされていることを確かにする「address-bound token」というものを定義します。

最初のコネクションでサーバからトークンを払い出し、クライアントが続けて同じホスト名か、IPとポートが同じサーバにコネクションを確立する際にこのトークンを提出します。
f:id:ASnoKaze:20190411014412p:plain
(transport parameterでaddress_bound_tokenaパラメータを設定した場合のみ、NEW_TOKENがaddress-bound tokenになります)

QUICはUDPを使用しますが、もちろん輻輳制御が行われます。この「address-bound token」を使うことで、同一エンドポイント間における複数のQUICコネクションで輻輳コントローラの状態を共有できるようになります(Sharing the Congestion Controller)。

OSではなくユーザランドで動作するため、このようなコネクションと輻輳コントローラを分けて関連付けられるようになる。

Sharing the Congestion Controller

QUICではアンプ攻撃の対策として、そのパケットが確かにIPの所持者から送信されている事が確認できるまで、データの送信量が制限されています。address-bound tokenは払い出し先のIPがわかっているので、そのIPからaddress-bound tokenが提出されれば、確かにIPの所持者であるため、この制限は必要なくなります。(払い出したIPとは別の場合は、path validationを行うべきです。)

Sharing the Congestion Controllerによって、新しいコネクションはスロースタートする必要がありません。

送信ウィンドウサイズを共用しますが、それらをどう分配するかは送信者が決めます。

感想

Sharing the Congestion Controller すごい面白い。CDNのように複数のホスト名をホストしているとより有用そう。

Initial + 0RTTの場合はaddress-bound token使えるのかな。リプレイ(正しいIP)された場合に該当コネクションに不要なデータがより送られそうな気もするが大量にはならないか、ハンドシェイク周りの部分自信がない。

HTTP/3のヘッダ圧縮仕様QPACKについて

この記事では、HTTP/3で導入されるHTTPヘッダ圧縮の仕組みである「QPACK 」について説明します。(執筆時 draft 07)

2020/06/01追記
まるっと解説記事を書き直しました
asnokaze.hatenablog.com

HTTP/2の場合

簡単に、HTTP/2で導入されたヘッダ圧縮の仕組みである「RFC7541 HPACK」について補足します。飛ばしていただいても大丈夫です。

HTTP/2の元となったSPDYというプロトコルではHTTPヘッダを単純にDeflateで圧縮していましたが、CRIME攻撃の危険があったため、HTTP/2では新しいヘッダ圧縮の仕組みHPACKが考えられました。

HPACKでは以下の2つの方法を用いてHTTPヘッダを少ないデータ量で表現します。

  • ハフマン符号
  • 静的テーブル、動的テーブル
ハフマン符号

各ヘッダ名と値のテキストをハフマン符号を用いて表現します。

ハフマン符号は各文字の出現頻度に合わせてbit表現を変更します。例えば出現頻度の多い'a'は0b00011を割り当て 5bitで表現できますが、出現頻度の低い'q'は0b1110110 が割り当てられており 7bit での表現となります。

このハフマン符号を用いてヘッダ名やヘッダ値の文字列(リテラル)を短く表現できます。

ハフマン符号の対応表は仕様中に定義されており、通信中に変更されるものではありません。

静的テーブル、動的テーブル

テーブルとは、ヘッダ名と値が格納された辞書データのようなものです(値は空の場合もあります)。このテーブルの何番目に格納されたヘッダなのかを指し示すことで、そのHTTPヘッダを表現できます。

たとえば、「accept-encoding: gzip, deflate」というヘッダは静的テーブルの16番に格納されているので、index:16 (1byte) で表現できるようになります。格納されているひとつのヘッダ名・値のペアをエントリと呼びます。

f:id:ASnoKaze:20190408083505p:plain

静的テーブルは一般に使用されるヘッダのリストが事前に定義されているテーブル領域であり、仕様で定義されています。

動的テーブルはクライアントとサーバが通信してる中で更新していくテーブルです。例えば、一度「user-agent: hoge」というヘッダを文字列(リテラル)表現で送信し、合わせて動的テーブルにそのヘッダを格納します。次から同じヘッダを送信する場合は静的ヘッダを参照する場合と同様に index: 62と送るだけで「user-agent: hoge」を表現できるようになります。

もちろん送りたいヘッダがテーブルにない場合もあります。その場合は以下のどちらかでヘッダを表現します。ヘッダ名は既存のテーブルから、ヘッダ名は文字列で表現する。もしくはヘッダ名と値とも文字列で表現する。のどちらかになります(文字列での表現にはハフマン符号が使用できる)。その際に合わせて動的テーブルにエントリを追加できます。

HPACKでは、動的テーブルのインデックスは静的デーブルに続いて62番から始まります。

動的テーブルに新しいエントリを追加する際は常にindex62から追加され、古いものは後ろにずれていきます。テーブルサイズを超えた場合、古いエントリから削除されていきます。

f:id:ASnoKaze:20190408084228p:plain

動的テーブルは、エンコードコンテキストとデコードコンテキスト用でそれぞれ管理されます。クライアントがHTTPリクエストを送信する場合はエンコーダとして振る舞いますが、HTTPレスポンスを受信する場合はデコーダとして振る舞います。逆にサーバはHTTPリクエストを受信する場合はデコーダで、HTTPレスポンスを送信する場合はエンコーダとなります。

動的テーブルは、ヘッダの送信者(エンコーダ)とヘッダの受診者(デコーダ)でテーブルの状態が同期されます。もちろん、一度コネクションが切れれば動的テーブルは引き継がれるようなことはありません。

HPACKでは、実際に解釈されるHTTPヘッダを格納したHEADERSフレーム内でヘッダを表現するのとテーブルに追加を指示するのをあわせて行っていました。ですので、各ストリーム上で動的テーブルの変更と参照が行われていました。

HTTP/3とQPACKの導入背景

HTTP/3ではトランスポートプロトコルとしてQUICを使用しています。HTTP/2ではパケットがドロップした場合は、OSレイヤで欠損したデータを回復した後アプリケーションにデータが渡されていたましたが、HTTP/3ではUDPとなるので後続の届いたパケットが処理可能であれば処理を進めることが出来ます。

こうすることで、ヘッドオブラインブロッキングを回避することが出来ます。しかし、HPACKをそのままHTTP/3で使おうとすると動的テーブルの更新と廃棄が、ヘッダの送信者(エンコーダ)とヘッダの受診者(デコーダ)で同期的に行われる必要があり、エンコーダが送信した順番にデコーダも処理する必要があります。これでは、後続のパケットが届いていても、ドロップしたり順番の入れ替わったパケットを回復するまで処理を進めることが出来ません。

そこで、HPACKの基本コンセプトとなるハフマン符号とテーブルの概念はそのまま、パケットの順番が入れ替わっても処理が進められるようにQPACKというヘッダ圧縮の標準化が議論されています。

動的ヘッダテーブル及びインデックスの示し方の変更がメインです、その他の部分については

  • ハフマン符号については変更なし(エンコーダ側がヘッダ名と値にハフマン符号を使用するか選択する
  • 静的ヘッダテーブルについては、中身が変更されており、現在多く使用されているヘッダが追加されました。(また、動的テーブルとインデックスが共有されなくなりました。参照する際に、どちらのテーブルを参照しているかフラグで示します。)

静的テーブルの具体的な中身については仕様を参照 (Appendix A)

QPACKの概要

QPACKにおける動的テーブルの更新と参照の概要を以下にします。
(クライアントがエンコーダの場合のみの図ですが、クライアント側がデコーダの場合は逆の図になります。)

f:id:ASnoKaze:20190407204726p:plain
overview

  • Encoderストリーム: 動的テーブルへのエントリ追加を指示するEncoder Instructionsを転送する単方向ストリーム (Type=0x02)
  • Decoderストリーム: デコーダからエンコーダへのフィードバックであるDecoder Instructionsを転送するための単方向ストリーム (Type=0x03)
  • Requestストリーム: HTTP/3のリクエスト・レスポンスのペア毎に使用される双方向ストリーム。実際のリクエストヘッダやレスポンスヘッダのやりとりがされる。QPACKでは、HEADERSフレームやPUSH_PROMISEフレームでは動的テーブルを変更しません。Header Block Instructionsで動的テーブルを参照するだけです。(Requestストリームの定義自体はHTTP/3側)

QUICでは、同一ストリーム内の整合性(データの順番)は保証されます。一つのEncoderストリーム上で動的テーブルの更新を行いますので、エンコーダが送信した順番通りデコーダも動的テーブルを更新します。

一方で、ストリームをまたがって順番は保証されません、Encoderストリームのデータを含むパケットが欠損した場合でも、Requestストリームはブロックされません。もし、HEADERSの処理が可能であれば処理が進められます。ただし必要な動的テーブルがまだ追加されていなければ、追加されるまでHEADERSフレームの処理は待たされます。

逆に、特定のRequestストリームのパケットが欠損した場合、動的テーブルに追加したエントリが使用されたかどうかわからないため、動的テーブルから削除することが出来ません。そのために、デコーダはDecoderストリームで、Requestストリームの処理が完了したことをエンコーダ側にフィードバックを送ります。こうすることで、エントリを動的テーブルから安全にevictionすることが出来ます。エンコーダはこのようにevictionされるエントリを考慮して動的テーブルにエントリを追加する必要があります。

もう一つの問題として、Requestストリームのやりとりとは独立して動的テーブルが変更される点です。エンコーダ側の動的テーブルがどういう状態で送信されたHEADERSフレームかがわからないと正しくデコードすることが出来ません。そこでQPACKでは、HPACKのような絶対インデックスではなく相対インデックスを用いてテーブルの各エントリを指し示します。また、ワンパスエンコーディングを実現するために、Post-Baseインデックスという指し示し方も行います。

Encoder Instructions

エンコーダ側からEncoderストリームで、動的テーブルが更新するEncoder Instructionsが送信されます。命令の種類は4つです

  • Insert With Name Reference: 動的テーブルにエントリを追加します。ヘッダ名についてはテーブルを参照し、値については文字列で指定します
  • Insert Without Name Reference: 動的テーブルにエントリを追加します。ヘッダ名と値両方共文字列で指定します。
  • Duplicate: 動的テーブルにエントリを追加します。既存のエントリを複製します。
  • Set Dynamic Table Capacity: テーブルのキャパシティを変更します。

(文字列を使う場合はハフマン符号を使用できます)

最初に追加されたエントリは絶対インデックス0であり、次に追加されるエントリは絶対インデックス1へと、1つずつ増えていきます。新しいエントリを追加する際に、設定したテーブルのキャパシティを超える場合はキャパシティに収まるように古いエントリからevictionされます。なお、デコーダが使用する可能性があるエントリがevictionされないようにエンコーダが注意する必要があります。

f:id:ASnoKaze:20190407205500p:plain

もし、エントリの追加が出来ない場合は、HEADERSフレームではヘッダ名とヘッダ値の両方とも文字列で表現してHTTPリクエスト・レスポンスのやり取りをすることになります。

追加するエントリは、evictionされるエントリを参照できるので、evictionされるタイミングでDuplicateすることが出来ます。

Decoder Instructions

デコーダ側からDecoderストリームで、動的テーブルの一貫性を保証するためエンコーダにフィードバックが送信されます。命令の種類は3つです。

  • Insert Count Increment: 以前に送信してから、動的テーブルに追加されたエントリの増分を示す。
  • Header Acknowledgement: 動的テーブルを参照するストリームを処理した際に、そのストリームIDを示す。
  • Stream Cancellation: ストリームをキャンセルした(動的テーブルを参照せず、不使用となる)

これらの情報をエンコーダにフィードバックすることで、エンコーダはどのエントリが参照可能か、eviction可能か判別できるようになります。

Header Block Instructions

Header Block Instructionsは、HEADERSフレームやPUSH_PROMISEフレームで実際にHTTPリクエストやHTTPレスポンスのヘッダを示すのに使用されます。静的・動的テーブルを参照することでヘッダ名・値を表現することが出来ます。また、テーブルは使用せず文字列(リテラル)で直接ヘッダを表現することも出来ます。

圧縮されたヘッダのリストをヘッダブロックと呼び、HEADERSフレームで送信されます。

具体的にはプレフィックスである2つの値と、ヘッダブロックであるCompressed Headersが送信されます。
f:id:ASnoKaze:20190408010307p:plain

  • Required Insert Count: このヘッダブロックをデコードするのに必要な、動的テーブルの追加カウント数(動的テーブルを参照しない場合は0)
  • Delta Base: 相対インデックスのベースとなる、Required Insert Countからの差分 (正の場合と負の場合で表現方法が異なる)
  • Compressed Headers: 圧縮された実際の各ヘッダリスト

ヘッダブロックを受け取ったデコーダが、Required Insert Countで示された数より自身の動的テーブルに追加されたエントリが少ない場合は、まだこのヘッダブロックをデコードすることは出来ないので、十分な追加のEncoder Instructionsが送られてくるまで待つことになる。ブロックされうるストリームの上限は別途SETTINGS_QPACK_BLOCKED_STREAMSパラメータを使用して指定することが出来る。

相対インデックス

先述の通り、HEADERSフレームの送信と、動的ヘッダテーブルへのエントリ追加のパケットは順番が入れ替わって処理される可能性があります。どのような順番でも正しく動的テーブル上のエントリを参照できるようにするために相対インデックスを用います。

エンコーダがHeader Block Instructionsを作成した時に基準となるインデックス(Base)を設定して、そこからの差分でインデックスを参照します。これを相対インデックスと呼んでいます。

相対インデックスはBaseのインデックスを基準に絶対インデックスとは逆に増えていきます。Post-BaseインデックスはBaseインデックスを基準に同じ方向に増えていきます。
f:id:ASnoKaze:20190407212759p:plain

動的テーブルを参照する際は、この相対インデックスやPost-Baseインデックスを使用して動的テーブルのエントリを指し示します。

相対インデックスとPost-Baseインデックスが分からえているのは、ワンパスエンコーディングを行うためです。エンコーダがHeader Block Instructionsを作成する際のことを考えます。

エンコードしたいヘッダのリストが有る際に、まずBaseを決めます。順々にヘッダをエンコードしていき、動的テーブルが使えれば使用します。しかし、エンコードを開始してから、エンコードしようとしているヘッダが動的テーブルにはなく動的テーブルに追加したいとします。その際には、Baseより絶対インデックスが大きい場所にエントリを追加することになります。このようにエンコード中に追加したエントリは、Post-Baseインデックスを使うことで参照できます。一通りのヘッダのエンコードが終わったら、Required Insert Countを決定し、そこからの差分を求めBaseフィールドに設定しHeader Block Instructionsの出来上がりです。こうすることで、ワンパスのエンコーディングが行えます。

Compressed Headers

ヘッダブロックであるCompressed Headersは下記の表現の集合です。1つのヘッダ名と値のペアは必ず下記のいずれかの表現によって表されます。

  • Indexed Header Field: 静的テーブルもしくは動的テーブルの相対インデックスの指定のみからなる。ヘッダ名・値とも示されたインデックスのエントリのものになる。
  • Indexed Header Field With Post-Base Index: 動的テーブルのPost-Baseインデックスの指定のみからなる。ヘッダ名・値とも示されたインデックスのエントリのものになる。
  • Literal Header Field With Name Reference: ヘッダ名については静的テーブルもしくは動的テーブルの相対インデックスを用いて指定する。ヘッダ値に関しては文字列(リテラル)で指定する。
  • Literal Header Field With Post-Base Name Reference: ヘッダ名については動的テーブルのPost-Baseインデックスを用いて指定する。ヘッダ値に関しては文字列(リテラル)で指定する。
  • Literal Header Field Without Name Reference: ヘッダ名・値とも文字列(リテラル)で指定する。

(文字列を使う場合はハフマン符号を使用できます)

エラーハンドリング

テーブルの範囲外を参照したり、不適切な値が検出された場合は下記のエラーを発生させストリームやコネクションを終了します

  • HTTP_QPACK_DECOMPRESSION_FAILED: デコーダがHeader Block Instructionsの解釈に失敗し、デコードを継続できない
  • HTTP_QPACK_ENCODER_STREAM_ERROR : デコーダがencoderストリーム上で受信したEncoder Instructionsの解釈に失敗した
  • HTTP_QPACK_DECODER_STREAM_ERROR :エンコーダがdecoderストリーム上で受信したDecoder Instructionsの解釈に失敗した

その他

今回は、数値表現について詳しく触れませんでしたが、QPACKのインデックスなどの値を指定する際は可変長表現を利用します。小さな値はより少ない長さで表現することが出来ます。

その他にもRequired Insert Countも短い長さで表現できるように工夫されています。

QPACKという名称について

最後に、QPACKという名称について触れておきます。
もともと別にQPACKと、QCRAMという提案仕様が出されていましたが、議論の結果QCRAMという提案が採用されました。そして、このQCRAMがQPACKに名称変更されて、今のQPACKの元となっています。

HTTP over QUICがHTTP/3となったのを期に、HPACK3という名称にしてはどうかという意見が出ましたが、そうはなりませんでした。
github.com

中間証明書を要求しないTLSフラグ拡張

TLSハンドシェイクでは、サーバはエンドエンティティ証明書と任意でルート証明書までたどるための中間証明書を送信します。

不要であればこの中間証明書を送信しないようにする「Suppressing Intermediate Certificates in TLS」という提案がMartin Thomson氏より出されています。

以前、証明書を圧縮する提案仕様と合わせて使用できます。
asnokaze.hatenablog.com

TLSハンドシェイクのうちサーバが最初に送るデータ量を削減するのは以下のメリットが考えられます。

  • パケットが後続しない状況でのロス検出が不利なためパケットが少ないにこしたことがない。
  • QUIC, HTTP/3ではTLSハンドシェイクを利用するが、アンプ攻撃の増幅率をへらすことができる。

Suppressing Intermediate Certificates in TLS

Suppressing Intermediate Certificates in TLS」の仕様中では、すべての中間証明書のリストは大きいが量は限られていると書かれています。

クライアントが完全な中間証明書のリストを持つことを想定しており、その場合は後述するTLSフラグ拡張を用いて0xTBD (未決定) の値を1に設定します。

このフラグを受け取ったサーバはエンドエンティティの証明書のみを送信します。

TLS Flags

クライアントやサーバがTLSの特定の機能をサポートしていることを示すのにTLS拡張が使用されてきました。

しかしTLS拡張は4バイトを使用します。フラグとして利用するのには少々もったいありません。

そこで別途提出されている「A Flags Extension for TLS 1.3」という提案では、フラグ領域をもつTLS拡張領域を定義しています。

このようになります。

      enum {
         ...
         tls_flags(TBD),
         (65535)
      } ExtensionType;

クライアントが1に設定したフラグをサーバも受け入れる場合は同じようにフラグを送り返します。

すべての中間証明書ってどのくらいなの

Common CA Database によると、ルートと中間証明書の証明書は5415エントリとなっており、1つあたり数Kでもブラウザにバンドルするにも現実的な量な気がしました。

WiresharkでのQUICの復号(decrypt)

2019/5/17追記 QUICの暗号化について説明を書きました
asnokaze.hatenablog.com

2020/09/16「WiresharkがHTTP/3に対応した - ASnoKaze blog


WiresharkIETF版 QUICパケットのdecryptに対応しているので、やってみる。

Wiresharkの細かい対応状況については以下の通りです。
Tools · quicwg/base-drafts Wiki · GitHub

最新のソースコード内に書かれているTODOとしては、以下の通り。

 * to-do list:
 * DONE key update via KEY_PHASE bit (untested)
 * TODO 0-RTT decryption

https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-quic.c#L173

(なお今回はGoogle QUICについては言及しない)

準備

必要なもの

  • Wireshark (今回は3.0.0を使用)
  • パケットダンプ
  • 該当通信のKEYLOGファイル

QUICのパケットをdecryptするために、KEYLOGファイルが必要です。各実装によってQUIC通信時に使用したシークレットをファイルとして出力できるようになってるものがあります。

Cloudflareが実装してるQUICHEのサンプルクライアントでは、SSLKEYLOGFILE 環境変数に出力先を指定しておくと出力されます。
https://github.com/cloudflare/quiche

SSLKEYLOGFILE=./keylog target/debug/examples/http3-client https://localhost:4433/index.html --no-verify

(ChromeもすでにSSLKEYLOGFILE環境変数を設定すれば、下記のようなデータが出力されます)

KEYLOGファイルは以下のような中身をしています。

QUIC_CLIENT_HANDSHAKE_TRAFFIC_SECRET .....
QUIC_SERVER_HANDSHAKE_TRAFFIC_SECRET .....
QUIC_CLIENT_TRAFFIC_SECRET_0 .....
QUIC_SERVER_TRAFFIC_SECRET_0 .....
QUIC_EXPORTER_SECRET .....

このKEYLOGファイルをWiresharkに食わせてやれば、パケットが復号されます。

復号

Wiresharkの設定(Advanced)から、tls.keylog_fileを得られたKEYLOGファイルに設定します
f:id:ASnoKaze:20190321235440p:plain

Protected Payloadが復号されます。STREAMフレーム内の、HTTP/3のレスポンスデータが見えているところです。
f:id:ASnoKaze:20190321235642p:plain

QUICにおいてNAT検出を行う拡張フレームの提案仕様

QUICを使用している際に、その通信がNATによってアドレス変換を行われているかを検出する「QUIC Address Extension」という提案仕様がAppleの人らによって出されています。

具体的には、送信元IPアドレスがなんであるかを通信相手に確認します。そうすることで、途中でIPアドレスが変換されているかを確認することが出来ます。

NATがいなければ、NATリバインディングを避けるためにPINGフレームを送る必要もありません。一方で、NATを検出した場合はコネクションIDとポートを定期的に変えることがよりプライバシーを高めることになります。

概要

自身のIPでアドレスが相手にどのように見えているか確認する、「PUBLIC_ADDRESS_REQUESTフレーム」と「PUBLIC_ADDRESS_RESPONSE フレーム」を定義します。

f:id:ASnoKaze:20190317170136p:plain

  • PUBLIC_ADDRESS_REQUESTフレームを送信することで、自身のIPアドレスを相手に確認要求を出します。
  • PUBLIC_ADDRESS_REQUESTフレームを受け取った側は、相手のIPアドレスとポートをPUBLIC_ADDRESS_RESPONSEフレームにお繰り返します。

こうして、自身の送信元IPアドレスがなんであるかを確認することが出来ます。

もちろん、PUBLIC_ADDRESS_REQUESTフレームとPUBLIC_ADDRESS_RESPONSEフレームは暗号化されているため、第三者に見ることは出来ません。

その他

同様の拡張がTLSでも提案されています。「TLS Client Network Address Extension」という提案仕様で、著者は一部異なりますが同じくAppleの人らによる提案です。

HTTP/2をバイトストリームトランスポートとして利用する提案仕様

HTTP/2コネクション上で任意のバイトストリームをやりとりできるようにする「Using HTTP/2 as a Transport for Arbitrary Bytestreams」という仕様がAppleの人らによって提案されている。

IETF103でも議論(資料)になったが、その後 draft-01 が出ているのが現状である。

ユースケースとしては任意のバイトストリームをトンネリングしたり、一つのコネクション上で複数のバイトストリームをやり取りするのにストリームを利用するといった背景があるようだ。

通信の流れ

以前解説したWebsocket over HTTP/2と同じように、HTTP/2コネクションを確立したあとにストリーム上でConnectメソッドを用いる。
asnokaze.hatenablog.com

大まかな流れは以下の通り
f:id:ASnoKaze:20190313111855p:plain

  • SETTINGSフレームでお互いにこの仕様に対応していることを確認する。「SETTINGS_ENABLE_CONNECT_PROTOCOL」および「SETTINGS_ENABLE_BIDIRECTIONAL_CONNECT」パラメータを1にセットして交換する
  • クライアントはCONNECTメソッドで、:protocolヘッダにbytestreamもしくはdatagramを設定する
  • サーバ側は問題なければ200を返す
  • 以降そのストリームはbytestreamもしくはdatagram通信用となる。データはDATAフレームによって運ばれ、双方向に通信される。

bytestreamとdatagramの違いは、DATAフレーム毎に境界を持つかの違いである。datagramの場合はDATAフレームごとにデータの境界を持ち、アプリケーションに渡される場合にその境界を維持する必要がある。