Bootstrapping WebSockets with HTTP/3 の仕様

既存の仕様ではHTTP/3上でWebSocketは使うことができません。

そこで、HTTP/3でWebSocketを使えるようにする「Bootstrapping WebSockets with HTTP/3」という仕様が、GoogleのRyan Hamilton氏によって提案されています。

背景: HTTP/2とWebSocekt

もともと、HTTP/2でWebSocketを使うためにRFC 8441「Bootstrapping WebSockets with HTTP/2」という仕様があります。この仕様では、一つのストームをWebSocket通信として使うために、:protocol 疑似ヘッダやSETTINGSパラメータを導入しています。
asnokaze.hatenablog.com

Bootstrapping WebSockets with HTTP/3

この仕様自体は、RFC 8441「Bootstrapping WebSockets with HTTP/2」の仕組みを同様にHTTP/3に導入するものです。

この仕様は、RFC 8441で導入されたExtended CONNECTメソッドと、SETTINGSパラメータをHTTP/3に導入する単純な仕様になっています。

感想

WebSocket over HTTP/3では、単純にHTTP/3に乗せるだけでも、ストリームを超えたHoLBがなくなったり、コネクションマイグレーションに対応できるといったメリットがあるものと思います。

別の議論として、HTTP/3の利点を活かす、JavaScript APIの変更も含めたWebTransportという仕様が提案されています。このWebSocket over HTTP/3では、WebTransportが提供する単方向ストリームや再送を要求しないデータ通信(DATAGRAM)といった機能は使えません。一方で、既存のWebSocketのAPIを流用するため既存のアプリケーションでもそのまま動作するものと思います。

また、現在 WebTransportやMASQUEの文脈でHTTPセマンティクスにおける、CONNECT拡張について議論が行われている最中です。そこの部分整理があるかもしれません。

(あとは、Bootstrapping WebSockets with HTTP/2から単純にマップ出来ないところがありそうな気はしている。エラーコード周りとか。)

ホスト名の最後が数字なURLの扱いについて

WHATWG URL」の仕様で、ブラウザにおいて、ホスト名がIPv4でないが、数値で終わるURLは拒否されるように変更された。

github.com

例えば次のようなものです

  • foo.0
  • bar.0.09
  • 1.2.3.4.5

今まで、これらのホスト名は、その他のドメインと同じように、通常通り名前解決されます。Issueの起案者は、すでに具体的な攻撃があるわけではないとしつつも、紛らわしさや、eTLD+1 (same-site)の扱いの問題があるとのことです。

なお、次のようなものはIPv4アドレスにマッピングされアクセスできます。

Deprecate support for URLs with non-IPv4 hostnames ending in numbers

実際に、Chromeでは、そのような変更を入れる検討が始まっています。URL仕様の変更であり、http, https, ws, wss, fileなどで対象となるURLが拒否されます。拒否は、Google's URL parsing libraryのGURLで行われます。

Intent to Prototype: Deprecate support for URLs with non-IPv4 hostnames ending in numbers

なお、現状そのようなURLの名前解決が(0.0003%ほど)行われているようですが、多くのものが入力ミスなどと考察されています。hostsファイルを指定している可能性もありますが、詳細なデータはなさそうです。

動作確認

Chrome Canaryに変更が取り込まれたので動作確認する。
今までは名前解決が出来なかったというエラーだったが、URLパースエラーとなっていることがわかる。

f:id:ASnoKaze:20210827125848p:plain

URLバーに入力した場合は、URLとしてではなく、検索キーワードとして処理される模様

HTTP/3 DATAGRAMの優先度制御の提案仕様

HTTP/3 DATAGRAMの優先度制御方式を定義した「HTTP Datagram Prioritization」が提出されました。

これについて簡単に眺めていきます。

目次

背景: HTTP/3 DATAGRAMについて

下記の記事で書いた通り、HTTP/3の拡張仕様でパケロス時に再送を必要としないアプリケーションデータの送受信ができるようになっています。
asnokaze.hatenablog.com

特に、WebSocketの次世代版ともいわれるWebTransportや、HTTP/3コネクションをトンネルさせるMASQUEといった仕様で使われるものです。

このDATAGRAM拡張仕様では、優先度制御については具体的な方法を指定していません。

(なおDATAGRAMはHTTP/3のみに限定されず、HTTP/2でも使用されます)

補足: HTTP/3の優先度

HTTPでは、一つのコネクション上で複数のリクエストを並列的に送ります。そのときに、例えばWebページのレンダリングに必要なファイルの優先度をあげることで、結果としてWebページの表示を早くします。

通常のHTTPメッセージの送受信の優先度は、クライアントからサーバに対してレスポンスの優先度を通知します。

HTTPリクエストでpriorityヘッダで、u(urgency)で優先順位と、i(incremental)で並列処理出来るか示します。

   :method = GET
   :scheme = https
   :authority = example.net
   :path = /menu.png
   priority = u=5, i

また、通信中の優先度の変更はPRIORITY_UPDATEフレームを利用します。

この方法はHTTP/3以外でも利用できます。詳しくは以前書いた通り。
asnokaze.hatenablog.com

このHTTPの優先度制御では、DATAGRAM拡張の優先度制御については具体的な方法を指定していません。

DATAGRAM の優先度

本題です。「HTTP Datagram Prioritization」ではDATAGRAM通信の優先度制御方法を定義します。

HTTP/3のDATAGRAMは必ず関連付けられるHTTPリクエストがあります。そのリクエストで、通常のHTTPメッセージの優先度制御と同じように、DATAGRAMの優先度を制御します。意図的にシンプルな設計としているようです。

priorityヘッダでduを付けることで、DATAGRAMの優先度を指定できます。

   :method = GET
   :scheme = https
   :authority = example.net
   :path = /style.css
   priority = u=0, du=2

優先度の高いものから送信し、同じ優先度のものはラウンドロビンされます。

一つのHTTPリクエストに対して複数のDATAGRAMフローを指定できるのですが、各コンテキスト毎に優先度制御方式は指定しません。

また、優先度の更新は通常のHTTP優先度制御と同様、PRIORITY_UPDATEフレームで行います。

IETF 111の発表スライド

発表予定でしたが、時間が足りなかった模様。
内容はまとまってるので、おすすめ。
https://datatracker.ietf.org/meeting/111/materials/slides-111-masque-http-dgram-priorities-01

感想

WebTransportのServer initiatedなストリームとか、サーバ開始のDATAGRAMフレーム優先度制御とか、色々ユースケースに合わせて、柔軟性(複雑性)が上がっていきそうな気もする。

IETF 111で議論ができなかったため、MASQUE WGメーリングリストで引き続き議論。

TLS Encrypted ClientHello(ECH) を BoringSSLで試してみる

TLS Encrypted ClientHello(ECH) を BoringSSLで試してみます。

なお、現時点ではBoringSSLはdraft 10版の ECH 対応です。

TLS Encrypted ClientHello(ECH) について

TLSの通信のうち、クライアントからサーバに送信されるSNI(Server Name Indication)は、通信先のホスト名が記載されます。

これは平文で送信されるため、通信の観測者はどのドメインと通信しているか分かります。これを秘匿するために Encrypted SNI (ESNI)という仕様が議論されています。

今は、TLS Encrypted ClientHello(ECH)という形で標準化が進められており、Firefoxなどが既に対応しています。

例えばこのECHを使うと、クライアントは「public.example.com」と「private.example.org」をサーバに送りますが、private.example.orgの情報は暗号化されて送れます。CDNやマネージドロードバランサが対応し、外に見えるSNIはCloudflareやクラウドベンダーのドメインとなれば、見えるSNIから暗号化されたSNIを観測者が推測するのも難しくなるでしょう。
f:id:ASnoKaze:20210723222220p:plain

古いですがもう少し詳しくは昔書いた通り。
asnokaze.hatenablog.com

実装を触ってみる

すでに幾つかの実装があり、相互接続性のテストを行っております。
https://github.com/tlswg/draft-ietf-tls-esni/wiki/Implementations

今回はBoringSSLで通信の様子を見ていきます。

ビルドする

説明に基づいてビルドします
https://boringssl.googlesource.com/boringssl/+/HEAD/BUILDING.md

ECH設定の生成

generate-ech で ech_config_listを生成します。今回は public-nameとしてpublic.example.comを指定します。

$ ./build/tool/bssl generate-ech  -out-ech-config-list ech_config_list.data \
   -out-ech-config ech_config.data \
   -out-private-key ech.key \
   -public-name public.example.com \
   -config-id 0

この ech_configにはHpkePublicKeyやpublic_nameが格納されます。

一般的に、クライアントはこのech_configはDNS HTTPS レコードを介して取得します。(DNSの通信はDoHなどで暗号化しておけば、通信の観測者はどのドメインHTTPSレコードを取得している事もわかりません)

asnokaze.hatenablog.com

通信してみる

サーバを起動しておく

$ ./build/tool/bssl server -accept 4430 \
    -ech-key ech.key \ 
    -ech-config ech_config.data 

クライアント側でつなぐ

 $ ./build/tool/bssl client -connect localhost:4430 \
    -ech-config-list ech_config_list.data \ 
    -server-name private.example.org

Connecting to 127.0.0.1:4430
Connected.
  Version: TLSv1.3
  Resumed session: no
  Cipher: TLS_AES_128_GCM_SHA256
  ECDHE curve: X25519
  Signature algorithm: ecdsa_secp256r1_sha256
  Secure renegotiation: yes
  Extended master secret: yes
  Next protocol negotiated: 
  ALPN protocol: 
  OCSP staple: no
  SCT list: no
  Early data: no
  Encrypted ClientHello: yes
  Cert subject: C = US, O = BoringSSL
  Cert issuer: C = US, O = BoringSSL

サーバ側の出力

$ ./build/tool/bssl server -accept 4430 \
    -ech-key ech.key \
    -ech-config ech_config.data 

Connected.
  Version: TLSv1.3
  Resumed session: no
  Cipher: TLS_AES_128_GCM_SHA256
  ECDHE curve: X25519
  Secure renegotiation: yes
  Extended master secret: yes
  Next protocol negotiated: 
  ALPN protocol: 
  Client sent SNI: private.example.org
  Early data: no
  Encrypted ClientHello: yes

サーバ側からするとEncrypted ClientHelloがyesになっている。また、クライアントが送った本来のSNI private.example.org が認識できています。

また、crypto.cloudflare.com がECHに対応していますが、HTTPS DNSレコードからECH Config listを取得すれば、同様にEncrypted ClientHelloが通信できました。

パケットキャプチャしてみる

上記で行った通信をパケットキャプチャしてみます。

経路上の観測者としては、ESNIは「public.example.com」しかみえません。但し、暗号化されている Enclipted Clienthelloが格納されている encrypted_client_hello 拡張 (65034 == 0xFE0A (ECH Draft-10)) がついている事が分かります。

f:id:ASnoKaze:20210723224028p:plain

QUICやHTTP/3で利用を避けるべき送信元ポートの議論

[追記] yuyarin さんが詳しい考察を書いて頂きました!

とても詳しく書かれているので参考になります!ありがとうございます
yuyarin.hatenablog.com


IETFにおいて、QUICやHTTP/3の実装が利用を避けるべき送信元ポートについて議論になりました (URL)

具体的には、下記の送信元ポートは利用を避けるべきと述べられている。

  • port 53 - DNS
  • port 123 - NTP
  • port 1900 - SSDP
  • port 5353 - mDNS
  • port 11211 - memcached

これらのポートはUDPのリフレクション攻撃 (アンプ攻撃) に利用されるポートです。リフレクション攻撃のターゲットとなるサーバ側としては、これらのポートが送信元ポートに設定されたトラフィックを大量に受け付けることになります。

f:id:ASnoKaze:20210719234157p:plain

そのため、一部のインフラ環境ではこれらの送信元ポートをブロックするように設定されているかもしれません。

QUICやHTTP/3もUDPを使用します。運悪く送信元ポートとしてこれらのポートを使ってしまうと、通信がブロックされてしまうかもしれません。その結果としてタイムアウトまで待ちを発生させたり、HTTP/2へのフォールバックを引き起こしたりします。

利用されうる送信元ポート (エフェメラルポート)

正規利用のユーザは送信元ポートとしてこれらの値を使いうるのでしょうか。

一般的にアプリケーションは送信元ポートとして、エフェメラルポートを使用します。どうやら、IANAで"DYNAMIC AND/OR PRIVATE PORTS"として定義される49152~65535がエフェメラルポートとして使用されるようです。(Linuxでは32768~60999らしい)

エフェメラルポートとしてもっと小さい番号を設定している環境もあるにはあるようです。しかし、先に上げた避けるべきポートが使われる可能性は低いでしょう。

ただ、NATやCGNATでは1024~65535範囲が使われる可能性が示唆されています (RFC4787)

IETFの動向

QUICやHTTP/3では、これらの送信元ポートの利用を避けるようにという文言が加えられそうです。
github.com

また、避けるべきポート番号をRFCに直書きするのではなく、IANAで管理できないか意見が募られています。
mailarchive.ietf.org

まだ議論が行われている最中であり、今後何かしら進展があるものと思われる。


感想

(実際これらのポートが使われるケースどれくらいあるんだろう)

HTTP/3からのダウングレード攻撃を防ぐIncompatibleProtocol拡張の仕様

Mozilla のMartin Thomson氏が「Secure Negotiation of Incompatible Protocols in TLS」という仕様を提案しているので軽く目を通す。

これは、HTTP/3とHTTS/2ように、バージョンによって下位層に使うプロトコルが異なる場合でも、ダウングレード攻撃を防ぐことを目的としている。そのために、TLSに新しくIncompatibleProtocol拡張を定義する。

HTTPを例に説明をするが、ほかのアプリケーションプロトコルについても適応できる。
(なお、HTTPの場合はAlt-Svcの仕組みがあるため、その点で幾つかの攻撃は防ぐことが出来る)

背景

クライアントとサーバが通信するプロトコルのバージョンを下げさせる、ダウングレード攻撃という攻撃があります。

攻撃の方法としては、通信をブロックし低いバージョンのプロトコルを試行させたり、DNS SVCBレコードを介して高いバージョンのプロトコルをサポートしている事を隠蔽する方法が考えられる。

TLSとQUICのALPN

QUICやTLSでは、ハンドシェイク中にALPNを通して使用するアプリケーションプロトコル及びそのバージョンをネゴシエーションします。このALPNはTLSのハンドシェイクの一部として保護されるため、クライアントとサーバが確実にアプリケーションプロトコルに合意できます。

このALPNではそのコネクションで使用できるアプリケーションプロトコルを提示します。HTTPのように、バージョンによって下位層に使うプロトコルが異なる場合では、下記のようなALPNをクライアントから提示し、サーバが選択することになります。

  • TLSのALPN: h2, http/1.1
  • QUICのALPN: h3

TLSのハンドシェイクを行うクライアントとサーバは、お互いに本当はh3に対応しているということがハンドシェイク中には知ることが出来ません。

IncompatibleProtocol拡張

TLSの拡張として、IncompatibleProtocol拡張を定義します。

  • クライアントからは、IncompatibleProtocolで空を指定します
  • サーバは、空のIncompatibleProtocolが送られてきた場合、このコネクションでは非互換なアプリケーションプロトコルをクライアントに通知します


HTTP/3に対応しているサーバに対して、HTTP/2 + TLSで接続を開始した際の例は次のとおりです。
f:id:ASnoKaze:20210719013125p:plain

こうすることで、非互換なアプリケーションプロトコルも含めサーバが対応しているアプリケーションプロトコルのバージョンを知ることが出来ます。

その後は具体的なアプリケーションプロトコルの動作に依存するかと思います。

FacebookのQUICを活用したライブ動画用プロトコルRUSHについて

Facebookの方々から「RUSH - Reliable (unreliable) streaming protocol」というプロトコル仕様が、IETFに提出されています。

簡単に眺めていきます。

Facebookとライブ動画

このRUSHはRTMP代替として、クライアント(配信者)のアプリからライブ動画を取り込むためのプロトコルのようです。Facebookではすでに使っているようです。

2018年頃のFacebookのエンジニアブログを見ると、Facebook Liveではクライアント(配信者)からの動画の取り込みに RTMPSを使っていることが紹介されています。おそらく、ココの部分を代替するのでしょう。視聴者へはCDNを通してMPEG-DASHで配信されるようです。
f:id:ASnoKaze:20210715012930j:plain

また、IETFの投稿をみると、Facebookではアプリ及びインフラでこのプロトコルを使用していると述べています (URL)。

IETFでも、Twitchの方やGoogleの方もこの仕様に興味をしています。これから議論が進んでいくところでしょう。

今回は、そんなRUSHについて簡単に仕様を眺めていきます。


クライアントとの通信においてQUICを使うメリットは大きそうです。単純に、トランスポートプロトコルとしてQUICを使うメリットについては、以前書いた記事などを参考ください。その他、QUICのストリーム管理なども以前の記事に解説を譲ります。

RUSH

RUSH - Reliable (unreliable) streaming protocol」は、ビデオとオーディそれぞれ複数のトラックをQUIC上で送信可能にするプロトコルです。

配信保証に関して制御する仕組みがあります。ライブ動画ですので、遅れてきたデータがもう不要というケースがあります。具体的には、あるビデオフレームが一定時間内に受信できなかった場合は、そのデータの再送を中止する仕組みがあります。

RUSHではこの仕組を実現するために、ビデオフレーム及びオーディオフレーム毎にQUIC双方向ストリームを利用する形になっています。QUICはトランスポートプロトコルとして、ストリーム毎にパケロスやパケットの順番の入れ替わりが考慮されます。ストリームを越えてはパケロスの影響を受けず、届いたパケットから処理を進めることが出来ます。

RUSHではビデオフレームやオーディオフレーム毎に1つストリームを使用するため、遅れてきた不要なビデオフレームやオーディオフレームは再送を中断を実現できます。その時あ、そのストリームをクローズすることになります (なので、片方向ではなく双方向ストリームを使う)。

(なお、上記のような複数QUICストリームを用いるモードをMulti Stream Mode、単一のQUICストリームを用いて全部そこでデータ送信するNormal modeが利用できます。Normal modeでは全てのメディアデータは再送されます)

ビデオフレーム及びオーディオフレーム

ビデオフレームとオーディオフレームどのような形式で送信されるか、送信メッセージ形式を確認すると分かりやすいです。

これらは、QUICストリーム上で送信されます。

ビデオフレーム

ビデオデータは下記のVideo frameというメッセージ形式で送信されます。

Video frameのフォーマットは下記のとおりです。
f:id:ASnoKaze:20210715020522p:plain

Codecや、トラックを一意に識別するためのTrack IDが含まれています。現在のところCodecはH264, H265, VP8, VP9が使えるようです。

また、IDフィールドは同一トラック内で1ずつ単調増加する識別子です。RUSHでは、1つのトラックデータを複数QUICストリームでやりとりするため、トラックデータを再構築するために使用されます。

オーディオフレーム

オーディオフレームは下記のAudio frameというメッセージ形式で送信されます。

f:id:ASnoKaze:20210715021453p:plain

ビデオフレームと同様、トラックIDやIDでトラックデータが管理されます。

Codeとしては、AAC, OPUSが利用できるようです。

その他の制御メッセージ

ビデオデータ、オーディオデータの他にも通信制御用のメッセージが定義されています。

詳細は割愛しますが下記のとおりです。

  • Connect frame
  • Connect Ack frame
  • End of Video frame
  • Error frame
  • GOAWAY frame

おわりに

まだ議論は始まったばかりです。まだまだ、大きく変わることもあるでしょう。

注目を集めているプロトコルですので、これから盛り上がっていくことがたのしみです。

以下雑感

マルチトラックでフレーム毎日ストリーム消化するようだけど、優先度制御したいばあいどうするんだろうなあ。トラックIDで制御したそう。パッとDATAGRAM使いたい気持ちもあるが、再送不要のフィードバックどうするかだけ再設計かな。