Sec-CH-IP-Geoヘッダの仕様

The IP Geolocation HTTP Client Hint」という提案仕様が、AppleGoogleの方の共著でIETFに提出されています。

簡単に見ます。

背景

ユーザの位置情報はプライバシーの課題がありつつも、VPNサービスなどによりIPジオロケーションが取れないと困ることもありそうです。権利などの問題で配信できる国が限定されているといったこともあるかと思います。

そのために、例えばPrivate Relayでは IPジオロケーションを公開しています(URL)。

サーバとしても、VPNサービスのIPジオロケーションリストを網羅するのは難しいように思われます。そこで、クライアントからIPジオロケーション情報を通知できるようになると便利そうです。

The IP Geolocation HTTP Client Hint

この仕様では、クライアントの位置情報をHTTP Client Hintの仕組みでサーバに送信します。

サーバは、Accept-CHレスポンスヘッダでSec-CH-IP-Geoを受け入れる意思を伝えます。

    Accept-CH: Sec-CH-IP-Geo

クライアントは、Sec-CH-IP-Geoリクエストヘッダで位置情報をサーバに通知します。

    Sec-CH-IP-Geo = "US,US-AL,Alabaster"

Sec-CH-IP-Geoヘッダの位置情報表記は 「RFC 8805 A Format for Self-Published IP Geolocation Feeds」の仕様の通りになります。

おまけ

クライアントにジオロケーション情報を送ってもらう方式として、もともとAppleの方はGeoHashを用いる提案をしていました。議論のすえ、今回のような仕様に変更されました。

仕様の共同著者らがその点についてgithubで議論してたりもします。参考までに。
github.com

HTTP DatagramsとCapsuleプロトコルについて

HTTP Datagrams and the Capsule Protocol」という提案仕様が出されている。長く議論されている仕様ではあるが、動きがあったので眺めておく

背景

トランスポートプロトコルであるQUICでは、パケットロスがあったとしてもアプリケーションプロトコルのデータは再送してピアに届けられます。

「RFC9221 An Unreliable Datagram Extension to QUIC」という仕様で定義される、QUIC DATAGRAMフレーム拡張では、信頼性のないアプリケーションデータの送受信が可能になります。DATAGRAMフレームのデータはパケットロスで失われても再送する必要はありません。

DATAGRAMフレームは受信したものから処理することができます、アプリケーションの要件によりデータの再送が必要な場合はアプリケーションレイヤで再送信を行うこともできます。

f:id:ASnoKaze:20220331001525p:plain

WebTransportやMASQUE(CONNECT-UDP)といった仕様では、このQUIC DATAGRAM拡張をHTTPで利用します。ただし、普通のQUIC DATAGRAMフレームはストリーム上で送信されません。HTTPでDATAGRAMを送受信するには、HTTPメッセージと関連付ける必要があります。それを定義するのが「HTTP Datagrams and the Capsule Protocol」です。

もともとHTTP/3を前提にしていましたが、途中にProxyが居るケースなどを想定しHTTP/1.1やHTTP/2も想定したCapsuleプロトコルとともに標準化が進められています。

なお、WebTransportやMASQUE(CONNECT-UDP)については過去に書いた記事を御覧ください

(HTTP DatagramはWebTransportなどで利用されます。通常のHTTPリクエスト・レスポンスで使う想定は今のところありません)

HTTP DatagramsとCapsuleプロトコル

HTTP/3, HTTP/2, HTTP/1の場合でそれぞれ、データの送り方が違います。

  • HTTP/3では、QUIC DATAGRAMフレームを利用します
  • HTTP/2では、拡張CONNECTでトンネルを確立し、CAPSULEプロトコルを使ってデータを送信します。(信頼性のない通信はできません)
  • HTTP/1では、HTTP Upgradeの手順でトンネルを確立し、CAPSULEプロトコルを使ってデータを送信します。(信頼性のない通信はできません)

常にHTTP/3(QUIC)が使えるとは限りません、QUICがブロックされている環境では、HTTP/2やHTTP/1.1でDatagramメッセージを送れる必要があります。このときCAPSULEプロトコルという仕組みでは、TCPなHTTPでDatagramメッセージをやりとりできるようになります。

HTTP/3

HTTP/3では、QUIC DATAGRAMフレームに以下のデータを格納します
f:id:ASnoKaze:20220331002111p:plain

  • Quarter Stream IDは、このDatagramが関連付けられれたHTTPリクエストのストリームIDが格納されます (クライアントが開始したストリームが前提なので4で割った数値で十分)
  • HTTP Datagram Payloadは、アプリケーションのデータです

ご覧の通り、以前まであった、一つのストリームに複数のDatagaram フローを関連付ける仕組みはなくなりました (CONNECT-UDPといったより上位のプロトコルで定義してたりします)。

なおこの拡張を利用するには、H3_DATAGRAM SETTINGSパラメータが有効になっている必要があります。

HTTP/1.1とHTTP/2におけるコネクションの確率

DatagramメッセージをTCPなHTTPプロトコルで送信できるようにします。

HTTP/1.1の場合は Upgradeの手順をもって通信を一旦確立します。その後、そのコネクション上でデータをやり取りします
```
GET https://proxy.example.org/.well-known/masque/udp/192.0.2.42/443/ HTTP/1.1
Host: proxy.example.org
Connection: upgrade
Upgrade: connect-udp
```

HTTP/2の場合は拡張CONNECTの手順をもって通信を一旦確立します。その後、そのコネクション上でデータをやり取りします。
```
HEADERS
:method = CONNECT
:protocol = connect-udp
:scheme = https
:path = /.well-known/masque/udp/192.0.2.42/443/
:authority = proxy.example.org
```

やりとりするデータはCAPSULEプロトコルに則ります

CAPSULEプロトコル

CAPSULEプロトコルでは確立された通信路で、次の形式でメッセージを送受信します。ここに、Datagramデータが入ることになります。
f:id:ASnoKaze:20220331003315p:plain

(なお、CAPSULEプロトコルはType, Length, Valueの形式をしており、独自のTypeを定義し任意のメッセージをカプセル化できます)

おまけ、あるいは動向

HTTP Datagrams and the Capsule Protocol」ではあくまでHTTP DatagramsとCapsuleプロトコルを定義しているだけで、上位のプロトコルがどのように使うかは定義されていません。

WebTransportやCONNECT-UDPの仕様を見るとよりイメージが湧くかと思います。

先日行われたIETF113では、WebTransport over HTTP/2でCAPSULEプロトコルを利用するのか議論がありましたが結論はでてません。まだまだ動きがありそうです。

HTTP/3でQUICv2を使うためのalt-svc拡張

An Alt-Svc Parameter for QUIC Versions」という提案仕様が提出されています。

仕様の内容自体は簡単だが、背景から軽く説明していきます。

背景: HTTP/3とalt-svcヘッダ

サーバがHTTP/3に対応しているかクライアントに通知する方法がalt-svcです。

HTTP/2の頃はTLSハンドシェイク中にHTTP/1.1を使うかHTTP/2を使うかネゴシエーションしていました。HTTP/3ではそもそもQUIC(UDP)で通信がはじまりますので、その前にサーバHTTP/3に対応しているか知る必要があります。

クライアントは一度HTTP/2で接続し、サーバ側からこのようなalt-svcヘッダを返すことでHTTP/3に対応している事を通知します。

alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000

なお最近ではDNS HTTPS レコードを用いて、サーバのHTTP/3対応を知らせる方法も利用されています。
asnokaze.hatenablog.com

背景2: QUICv2とHTTP/3

RFC9000としてQUICが標準化されましたが、これはQUICバージョン1です。QUICの新しいバージョンであるQUICv2の標準化が進められています。

クライアントとサーバでどのバージョンのQUICで通信するかはQUICのバージョンネゴシエーションにより決定されます。互換性のあるバージョン同士では追加のRTTはありませんが、互換性のないバージョンを使う場合は追加のRTTが発生します。

ここでは詳しくは説明しませんが、気になる方は以前書いた記事を参照ください

asnokaze.hatenablog.com


「An Alt-Svc Parameter for QUIC Versions」

現状では、サーバがどのQUICバージョンに対応しているかクライアントは通信を行うまで分かりません。

現状だとクライアントはQUICv1を介してバージョンネゴシエーションを行うという戦略が妥当ですが、それだとサーバはいつまでもQUICv1をサポートしなければなりません。

そこで、サーバが対応しているQUICバージョンをalt-svcに記載するのがこの提案です。

例を見ると分かりやすいかと思います。

Alt-Svc: h3=":443"; quicv="1"
Alt-Svc: h3=":443"; quicv="709a50c4,1", h3=":1001"; quicv="709a50c4"

(ブログタイトルはQUICv2と書きましたが、将来のQUICバージョンで使えます。)

これで、クライアントはサーバが対応しているQUICバージョンを知ることが出来ます。

本提案によって、QUICv2以降のQUICv1と互換性がないQUICバージョンを利用する際も余計なネゴシエーションオーバヘッドが必要なくなります。

HTTPで再開可能なアップロードを可能にする提案仕様

HTTPでファイルをアップロードする時、ネットワークの寸断などによりアップロードが中途半端に終わってしまうことがあります。アップロードが途中まで成功したとしても、一度切断するとそこから再開する方法は標準化されていません。
(PUTリクエスト + Rangeヘッダによる再開をサポートしているシステムもありますが標準化されてはいません。 参考URL)

そこで、再開可能なアップロードの仕組みを定義した「Resumable Uploads Protocol」という仕様がIETFに提出されています。この仕様は、Transloadit Ltd, Apple Inc, Cloudflare などの方々の共著になっています。

Resumable Uploads Protocol の概要

Resumable Uploads Protocol」は、一度中断しても再開可能なアップロード方式を定義しています。

簡単な流れは次のとおりです

  • クライアントから、POSTやPUTでファイルのアップロードを開始する。この時、再開用トークンをupload-tokenヘッダで送る。
  • サーバは、Resumable Uploads Protocolに対応していれば、104 レスポンスを送る。
  • その後、アップロードが中断した場合は、クライアントは再開用のトークンでアップロードを再開する。

(100番台のレスポンスは、サーバからレスポンスを返しても通信は終わらない点に注意してください。通信を維持し、最終的に改めて正式なレスポンスコードを返します。)

Resumable Uploads Protocolの流れ

Resumable Uploads Protocolにおいて、アップロードの開始および再開の流れについて説明していきます
f:id:ASnoKaze:20220227230313p:plain

アップロードの開始

Resumable Uploads Protocolに対応したクライアントは、まずPOSTやPUTリクエストでファイルのアップロードを開始します。この時次のヘッダを付けます。

  • upload-token: 今回のファイルアップロードを識別するためのトークン。再開時にも利用する
  • upload-draft-interop-version: 準拠している仕様のバージョン。本仕様では1が指定される。
104レスポンス

HTTPの仕様として、100番台のレスポンスは、本来のレスポンスに先んじて返されます。その後正式なレスポンス (200~500番台)が返されます。ですので、100番台のレスポンスを返しても通信は終わりはしません。

Resumable Uploads Protocolでは、サーバがこの仕様に対応している場合は、104レスポンスを返します。104レスポンスは、ボディを受信仕切る前に返せるので、通信が途中で中断される前にクライアントに通知することが出来ます。

(なお、サーバがResumable Uploads Protocolに対応してない場合は、通常通りのPOSTリクエストとして処理されます)

通信の中断

ユーザのアクションや、ネットワークの切断、ソフトウェアの終了などの理由により、アップロードが中断される可能性があります。

アップロードの再開

ファイルのアップロードを再開するにあたって、サーバがどこまで受信したのかをまず確認します。upload-tokenのついたHEADリクエストを送り、サーバから upload-offset を取得します。これが、サーバが受信したデータです。

そのオフセットからファイルアップロードを再開します。POSTリクエストにはupload-tokenおよびupload-offsetを付け送信します。

その他

詳しく説明しませんが、仕様では次のケースについても書かれています

  • サーバ側で、受け取ったupload-tokenに紐づく途中ファイルがない場合の扱い
  • クライアント側から、途中ファイルを破棄するようにサーバに通知する方法
  • 予め長さが分かってないファイルのアップロード ( Upload-Incomplete )

Twitchの QUICを用いたライブストリーミングプロトコル Warp

Twitchは、HLSの代わりとなる、ライブ配信プロトコルWarpを開発している
(情報: MLへの投稿カンファレンスでの発表

このWarpと呼ばれるプロトコルの仕様「Warp - Segmented Live Video Transport」が、IETFに提出されています。

以前書いたFacebookのRushとは異なり、アップロードではなく配信がわのプロトコルのようです。面白そうなので、軽く目を通していこうかと思います

asnokaze.hatenablog.com

Warp

WarpはWebTransport上で実行されるようですが、今回提出された仕様では、QUICやWebTransportのコネクション確立には触れてません。

コネクションの確立後に、QUICの単方向ストリームを用いて、MP4(CMAF)を送信する方法について説明しています。

f:id:ASnoKaze:20220212003940p:plain

仕様の用語を用いると、通信はMedia producerからMedia consumerにセグメント化されたMP4が配信されます。

簡単にまとめると次の通り

  • QUIC単方向ストリームでセグメントを送信する
  • パケロス時の再送はすべてQUICレイヤに任せる (DATAGRAMフレーム使わない)
  • 各セグメントを異なるストリームを用いて、パケロス時のヘッドオブラインブロッキングを回避する
  • Media producerは各セグメントにPriorityを設定でき、高 priorityから優先して送信される (基本 最新のデータの方が優先度が高い。なので、パケロスの再送は相対的に優先度が低くなる)。オーディオストリームと、ビデオストリームで異なるPriorityの設定もできる。
  • Media consumerは、パケロスしたデータに対してどれくらい待つか自身が判断する (巻き戻し出来るようにデータは受け取って起きたいなど。) ただし、ストリームの並列数に上限があるので、優先度の低いものからMedia consumerによってクローズされる。

(疑問: リクエストURLは、WebTransport確立時に与えられているってことでいいのかな)

メッセージとセグメント

各ストリームではメッセージと一つ以上のセグメントを送ることが出来る。メッセージはセグメントのメタデータで、識別子やPriorityが含まれる。セグメントは、 初期化セグメント(ftyp, moovなど)とメディアセグメント(styp, styp, mdat)の2種類がある (CMAFの要件を満たす)。

メッセージはjson形式であり、仕様上はinit, media, priority という値が定義されている。なお拡張可能になっている。(今後形式は変わるかも)

以下に例を示すが、mediaとpriorityなどJSONは結合してもよい。

init: 後続に初期化セグメントが続くことを示すメッセージ。初期化セグメントのIDを持つ。1つずつ増加していく

{
  init: {
    id: int
  }
}

media: 後続にメディアセグメントが続くことを示すメッセージ。関連する初期化セグメントのIDとタイムスタンプを持つ。

{
  segment: {
    init: int,
    timestamp: int,
  }
}

priority: ストリームの優先度を示す。

{
  priority: {
    precedence: int,
  }
}

その他の補足

いくつかの疑問にメーリングリスト上で回答している
https://mailarchive.ietf.org/arch/msg/moq/0ZNlt5SvEzH3mroPOHjlAFpfHDI/

  • 『なぜWebRTCではないのか』
    • => 既存の仕組みよりもユーザエクスペリエンスが良くなかった
  • 『なぜDatagramを使わないのか』
    • => 輻輳制御, 再送制御, キャンセル, 多重化などを再実装することになるから
  • 『なぜHTTP上をつわかないのか』
    • =>Warpの利点である優先制御はHTTP/2やHTTP/3でも動くが、プッシュベースのプロトコルのほうが都合が良かった
  • 『なぜFragmented MP4を使うのか』
    • =>HLSやDASHが使用しており、配信プロトコルに依存せず使える。
  • 『なぜJSONを使うのか』
    • => 最初のDraftで厳密な定義を与えるのが面倒だったため

ChromeがQUICのInitialパケットに施すChaos Protection

先週、IIJ Technical WEEK 2021で山本 和彦さんが説明していた "Chrome Magic" について、知らなかったので自分用にメモ。

山本 和彦さんの発表資料はこちら。
www.iij.ad.jp

なお、QUICHEのコードではChaos Protectionと書かれているので、ここではChaos Protectionと呼ぶ。

前提: 硬直化(ossification)

ネットワークの中間装置が、パケットを観測し何かしらの処理をします。しかし、中間装置がパケットを読むときに、決め打ちで特定バイト目を取ってきて処理したりすると、将来のプロトコルが変更されたときに予期せぬ挙動をもたらすことがあります。

20017年頃、Googleの実験的プロトコルであったGoogle QUICではフラグの仕様を変えた際に、予期せず通信が阻害される場合があったと述べています。その他にも、TLS1.3の標準化においても、新しいバージョンのTLSの通信が阻害される事例が報告されました。

このようなことを、硬直化(ossification)と呼んでいます。

QUICのような最近のプロトコルでは、硬直化(ossification)を防ぐために工夫されています。たとえば、パケット暗号化を行い不用意に中間装置がパケットを処理できないようにしたり、Greaseという手法を用いて実装の予期せぬ挙動を検知するようにしています。

以前書いた記事もご参考に:
asnokaze.hatenablog.com

前提: QUICのInitialパケット

QUICはコネクションを確立する際に、クライアントからTLS Clienthelloメッセージを含む Initialパケットが送られます。このInitialパケットは仕様で定義された鍵を用いて暗号化されています。

この暗号化は、硬直化(ossification)を防ぐため行われていました。

もちろん、Initialパケットの暗号化は仕様で与えられていますので、だれでも復号することはできます(改ざんは出来ない)。冒頭紹介した発表で触れられていましたが、どうやら、SNIなどを取得するために、特定バイト列を読み込む中間装置がいるようです。

これでは、硬直化(ossification)の原因となりかねません。正しくパケットをパースするよう強制するために、導入されたのが Chaos Protectionです。

Chaos Protection

Chaos Protectionが行われた Initialパケットは次のような形になっています。ClientHelloのメッセージは、複数のCRYPTOフレームに分割されなおかつ順番もシャッフルされています。
f:id:ASnoKaze:20220124003604p:plain

一つのCRYPTOフレームは、ClientHelloのバイト列のうち指定されたOffsetからLength長のデータが格納されます。そのため、複数のCRYPTOフレームをどのような順番でInitialパケットに詰めても、それを正しく元のバイト列に直すことが出来ます。

CRYPTO Frame {
  Type (i) = 0x06,
  Offset (i),
  Length (i),
  Crypto Data (..),
}

このように正しくフレームをパースしないと、ClienthHelloのメッセージが読めなくなっています。

具体的に Chaos Protection で行われていることを、眺めていきます。

quic/core/quic_chaos_protector.cc - quiche - Git at Google

  SplitCryptoFrame();
  AddPingFrames();
  SpreadPadding();
  ReorderFrames();
  return BuildPacket(header, buffer);
  • SplitCryptoFrame: CRYPTOフレームをランダム個に適当に分割 (lengthもランダム)
  • AddPingFrames: PINGフレームをランダム個追加
  • SpreadPadding: 仕様で規定された長さまで複数*のPADDINGフレームで埋めます (*PADDINGフレームは1バイトですが、シャッフルのために長さを持つかのように扱ってる模様)
  • ReorderFrames: フレームをシャッフルする (すべてのフレームを、ランダムなフレームと位置をスワップする)

Initialパケット以外

Initialパケット以降は当事者間で共有された鍵で暗号化されるため、中間装置はパケットのほとんどの領域を読むことは出来ません。

ですので、Chaos Protectionを行う必要はありません。

HTTPの拡張CONNECTメソッド について復習する

HTTPにはCONNECTメソッドというものがあります、このメソッドを使ってProxyと接続しHTTPメッセージを中継してもらいます。

拡張CONNECTメソッドは、HTTPメッセージ以外のデータを中継してもらうのに使用します。特にHTTP/2やHTTP/3では特定のストリームを、データのトンネル用にするために、拡張CONNECTメソッドを使います。

例えば、WebTransport over HTTP/3なども拡張CONNECTメソッドを利用します。そのため、拡張CONNECTメソッドをサポートしてない単純なHTTP/3 ProxyとはWebTransport通信は行ったりは出来ません(Datagramらへんも絡む)。

今回は、その拡張CONNECTメソッド (Extended CONNECT Method) について見ていきます。

f:id:ASnoKaze:20220119010815p:plain

利用例

拡張CONNECTメソッドはもともと、WebSocket over HTTP/2の仕様で導入されました。

(HTTP/2でHTTPメッセージはストリームで管理するので、コネクションを切り替えるUpgradeは使えません。そこで拡張CONNECTを使うことになってます。標準化当時の議論 URL

その後、その他のプロトコルでも利用されるようになりました。例えば次のようなプロトコルです。

上記のように、HTTPリクエストを使ってサーバと通信したあとに、そのコネクション上でHTTPメッセージ以外のアプリケーションデータをやりとりできるようになります。もちろん、どのようなアプリケーションデータをやりとりするかはプロトコル次第ですが、HTTPレイヤではそのアプリケーションデータには関与しません (一般に、DATAフレームに格納され送信されます)。

UDP Proxying Support for HTTPは、Apple Private Relayでも利用されている技術であり、詳しくは以前の記事を参照ください
asnokaze.hatenablog.com

拡張CONNECTメソッド

拡張CONNECTメソッドはどのようなメッセージなのか具体例を以下に示します。これは、WebTransport over HTTP/3の接続リクエストの例です。

:method = CONNECT
:protocol = webtransport
:scheme = https
:path = /
:authority = server.example.com
origin: server.example.com

上記のように :protocol という疑似ヘッダを持つCONNECTメソッドが拡張CONNECTメソッドです。このリクエストに対して200番を返すことで該当のストリームは、:protocol疑似ヘッダで指定されたプロトコル用にデータをトンネルするようになります。ですので、レスポンスを返してもストリームはクローズしません。

先に上げたプロトコルでは、疑似ヘッダの値はこれらになります。

  • websocket
  • webtransport
  • connect-udp
  • connect-ip
疑似ヘッダ(pseudo-header)

疑似ヘッダについてはここで簡単に補足します。

HTTP/2やHTTP/3では、メソッド名やリクエストしたURLのスキームやパスは疑似ヘッダとして表現されます(これによりヘッダ圧縮が可能になります)。

疑似ヘッダはHTTP/2やHTTP/3の仕様で定義されていないものを送ってはなりません、受信した側はエラーとして扱う必要があります。ただし拡張CONNECTメソッドは拡張仕様であり、相手がこの拡張仕様に対応していれば :protocol疑似ヘッダを送信することが出来ます。

相手が :protocol 疑似ヘッダに対応しているかは、一般にSETTINGSパラメータで確認します。

SETTINGSパラメータ

HTTP/2やHTTP/3ではコネクションが確立したあとに、クライアントとサーバでSETTINGSパラメータを送り合い、通信に関するパラメータを交換します。その流れで、拡張CONNECTメソッドに対応しているかを確認します。

  • SETTINGS_ENABLE_CONNECT_PROTOCOL パラメータをセットして送ることで、相手に対応していることを通知します
  • もしくはWebTranportプロトコルの場合は、SETTINGS_ENABLE_WEBTRANSPORTパラメータを送ることで拡張CONNECTメソッドに対応していることを相手に通知します
その後

拡張CONNECTメソッドを用いてストリームをトンネル化したあと、どのようなデータをやりとりするかは、:protocol 疑似ヘッダで示されるプロトコルの仕様によります。