QUICのコネクションマイグレーションについて

目次

概要

QUICはUDPを使っており、QUICレイヤにコネクションを管理するためのコネクションIDを持ちます。そのため、送信元IP・ポート番号、送信先IP・ポート番号が変わっても、コネクションが切断することなく通信を続けることができます。これをコネクションマイグレーションと呼びます(QUIC-TRANSPORTの仕様)。

もちろん、QUICをトランスポートプロトコルとして使用するHTTP/3でもこの恩恵に預かります。

GoogleではすでにQUICのコネクションマイグレーション機能を実験しており、ネットワーク起因のエラーの減少に一定の効果が得られているようです(IETF104での発表資料PDF)

コネクションのマイグレーションには主に2つあります

  • キャリア回線からWi-Fi回線への移行など、エンドポイントが意図して使用するネットワークを変更するケース
  • NATリバインディングなど、エンドポイントが意図せずポート番号などが変わるケース

前者は事前に移行先のネットワークが利用できることを検証してから切り替えますが、後者はそのようにはいきません。

いずれにせよ、このコネクションのマイグレーションは安全に行われる必要があります。通信を行っている二者は鍵を共有しており、コネクションのマイグレーションが起きたとしても、鍵を知らない第三者が通信に割り込んで解読するようなことはできません。

その他にも以下のような攻撃も対策されています。

  • アンプ攻撃
  • コネクションのトラッキング
  • 攻撃者自身を経由するようなPATHの構築

セキュリティ対策

アンプ攻撃

通信経路上の第三者がパケットの送信元IPを書き換え、エンドポイントの意図しないマイグレーションが発生したことを装います。そうすることで、送信元IPが書き換えられたパケットに対する応答として、そのIPアドレスにデータが送信されます。ここで、大量のデータが流れればアンプ攻撃となります。

QUICでは、マイグレーション時に関わらず、通信相手がそのIPアドレスの所持証明をできない場合は、送信できるデータ量が制限されます。コネクションのマイグレーション時も、後述するPath Validationを行い、そのIPアドレスが正しい通信相手であることを確認します。

コネクションのトラッキング

QUICでは、コネクションIDを識別子として用います。このコネクションIDはQUICパケットヘッダに平文で格納されます。

そのため経路上の第三者は、このコネクションIDを観測することができます。

もし、コネクションマイグレーション前後で同じコネクションIDを使用している場合は、通信者がどこからどこへ移動してるか観測者はトラッキングできます。会社のWi-Fi -> LTE ->自宅のWi-Fi というふうに、どういうふうに移動したかも追うことができます。

そのため、QUICでは事前に暗号化して送信されるNEW_CONNECTION_IDフレームにて、予備のコネクションIDを相手に通知しておきます。意図してコネクションのマイグレーションを行う場合は、この呼びのコネクションIDを用いることで第三者がコネクションマイグレーション前後の通信を関連付けられないようになっています。(お互い、新しい送信先コネクションIDを使用します)

もちろんNATリバインディングなど、意図せず起こるコネクションマイグレーション時は新しいコネクションIDを使用できませんが、時間をあけてから通信を行う場合は予防的に新しいコネクションIDを仕様することが推奨されています。

予備のコネクションIDを消費した場合は、NEW_CONNECTION_IDで相手から新しく予備のコネクションIDをもらえます。

攻撃者自身を経由するようなPATHの構築

パケットを観測できる通信経路外の攻撃者は、パケットをコピーし、本来のパケットが到着するよりも先に攻撃者のパケットを届けることで、攻撃者自身を経由する経路へとコネクションマイグレーションさせることができます。

この場合、攻撃者は届いたパケットを正しく二者に配送することでPath Validationは成功します。しかし、仮に成功したとしても、マイグレーション前の経路の方が高速であれば、もとの経路に戻すことも選択できるしょう。また、コピーされたパケットが重複して届くことに対して、ヒューリスティックな対応も考えられます。

コネクションマイグレーションの手順

QUICのハンドシェイクが終わったあとであれば、コネクションマイグレーションを行うことができます。ただし、トランスポートパラーメタとしてdisable_active_migrationが設定されている場合は除きます。

エンドポイントが意図して能動的に行うコネクションマイグレーションの場合で流れを説明をします(共通部分もあります)。

事前にコネクションマイグレーションをする前に、マイグレーション先のPATHで正しく通信できるか検証を行います。これをPath Validationと呼びます。

Path validation

8バイトのランダムな値を含むPATH_CHALLENGEフレームを送信します。このPATH_CHALLENGEフレームを受け取ったエンドポイントは、同じ値を持つPATH_RESPONSEフレームでお繰り返します。

f:id:ASnoKaze:20190916013416p:plain

このやり取りも、二者間で共有されている鍵を使っているため、第三者が正しく応答することはできません。

なお、PATH_CHALLENGEフレームとともにNEW_CONNECTION_IDフレームも送ることで、相手も必ず新しいPATHで新しいコネクションIDが使用できます。

もしタイムアウトした場合はPath validationは失敗となり、もともと使用していたPATHを使用します。

コネクションマイグレーションの開始

PATH_CHALLENGE、 PATH_RESPONSE、 NEW_CONNECTION_ID、 PADDINGフレームはプロービングレームと呼ばれ、実際の利用するコネクションのマイグレーションはまだ引き起こしません。

それ以外のフレーム(非プロービングフレーム)を新しいPATHで送信することで、コネクションのマイグレーションが行われ、新しいPATHでデータを送信し始めます。

コネクションマイグレーションへの対応

一方、通信相手の方は新しいアドレスから非プロービングフレームが届いたときに、相手がコネクションをマイグレーションしたことを知ります。

これを契機にに、新しいPATHで応答を開始し、こちらからもPath Validationを行います。こちらでも相手がそのIPアドレスを所持している正しい通信相手であることを確認する必要があります。

f:id:ASnoKaze:20190916020450p:plain

NATリバインディングのように、エンドポイントが意図せずIPやポートが変わった場合は、事前の手続きはなくこの処理が行われます。

コネクションのマイグレーションが終わると、エンドポイントは古いPATHを破棄できます。

輻輳制御とECN

新しいPATHでは、RTTや帯域が異なるため、輻輳コントローラを初期値にセットしなければなりません。

また、ECNをサポートしているかもわからないため、その確認作業も再度行う必要があります。

HTTPリクエストのレートリミットを示すRateLimitレスポンスヘッダの提案仕様

一定時間内でWeb APIを叩く回数などが制限されているサービス等は多々あります。制限に引っかかった場合は429 Too Many Requests レスポンスが返されます。

このとき、レスポンスヘッダやボディでリミットに関する情報が提供される場合がありますが、標準化された方法はありません。

そこで、先日提出された提案仕様「RateLimit Header Fields for HTTP」では、HTTPレスポンスでレートリミットに関する情報を通知するヘッダを定義します。

  • RateLimit-Limit: 単位時間内のリクエスト上限
  • RateLimit-Remaining: 現在の単位時間内の残りリクエスト回数
  • RateLimit-Reset: 上限がリセットされるまでの期間(秒)、もしくはタイムスタンプ

例1

今の上限は100で、残り20、リセットされるまで56秒です。

RateLimit-Limit: 100
RateLimit-Remaining: 20
RateLimit-Reset: 56
例2

RateLimit-Limitヘッダには","区切りで複数のウィンドを指定することができます。
以下の例では、1時間ごとに3600, 1日で5000リクエストの制限があることを示しています。

次のシチュエーションは次のとおりです。最初の14時間で4900リクエストを消費してしまったので、今の制限はトータル5000リクエストの制限状態で有ることを示しています。
そして、上限がリセットされるのは10時間後となっています。

RateLimit-Limit: 5000, 1000;window=3600, 5000;window=86400
RateLimit-Remaining: 100
RateLimit-Reset: 36000
例3

RateLimit-Resetは、IMF-fixdate形式でも指定できます

RateLimit-Reset: Tue, 15 Nov 1994 08:12:31 GMT

Mixed Content Level 2の仕様について

2年前ほど前に「Mixed Content Level 2の議論」に書いたとおり、Mixed Content Level 2についての議論は以前から行われていました。

2019/08/22の日付で、w3cリポジトリにEditor’s Draftとして「Mixed Content Level 2」のページが加わっております。
w3c.github.io

まだ、TODOのものもありますがアルゴリズム部分については書かれています。

Mixed Content Level 1では表示できていた Mixed Content な画像,音声,動画もブロックされる可能性があります。

Mixed Content Level 2

  • http://~なoptionally-blockable content(画像,音声,動画)は、自動でhttpsのURLとしてアクセスされます
  • httpsでアクセスに失敗した場合はブロックされます。

これによって、すべてのMixed Contentはブロックされることになり、CSPのblock-all-mixed-contentは廃止されます。

影響

chromeでは音声と動画を対象に、beta版の50%でこの機能を試しているようです。

httpsへの自動アップグレードにより、1%ほどのページ表示でリソースの読み込みの失敗を観測しているようです。

今後

今月開催されるTPAC 2019で議論される予定となっています
github.com

Double-keyed HTTP cache に関するメモ

whatwgでfetchに関して「Double-keyed HTTP cache」という議論がされています。

github.com

ブラウザ側でも動きがあり、下記で議論がされています

背景

HTTPのキャッシュは、そのリソースがどのページ(ドメイン)で読み込まれたかに関わらずに共有で使用されます。しかし、そのキャッシュ状況をサイドチャネル攻撃で調べることによって、特定のリソースが別のページによってロードされているか確認することができます。

この方法を用いて、ユーザの検索履歴や連絡先情報などが取得可能であることを示した「Mass XS-Search using Cache Attack」という例もあります。

この問題への対策がDouble-keyed HTTP cacheです。

Double-keyed HTTP cache

Double-keyed HTTP cacheでは、ページを開いた際のアドレスバーに表示されているオリジンをキャッシュのキーとして使用します。

https:///a.example.comで読み込んだリソースのキャッシュは、https://b.example.comでは使用できなくなります。

これによって、そのリソースがキャッシュされているかはクロスドメインでは確認できなくなります。

Googleの調査ではChromeの開発版での統計データでは、キャッシュのヒット率が4%ほど低下し、キャッシュから読み込まれるデータ量は39.1%から37.8%に低下したと述べています。first contentful paintは大きく変わらなかったとしています。

また、クロスオリジンでキャッシュに読み込む機能のある、クロスオリジンのprefetch、HTTP/2サーバプッシュ(特にキャンセルによってキャッシュの有無が把握可能)については検討が必要そうです(関連資料)。

その他にもCDNへの影響など、議論は引き続き行われそうです。

TCP/QUIC相互変換のポートフォワードツールを書いた

TCP/QUICのポートフォワードツールを書いた。

概要

IETFで標準化が進められているトランスポートプロトコルQUIC。

UDPを利用しており、エンドポイントのIPアドレスが変わってもコネクションが切れなかったり、より良い再送制御が行えたりと長所は多くある。しかし、QUICをサポートしているアプリケーションプロトコル、実装が現状多くはない。

QUICの恩恵に預かるために、TCPとQUICを相互変換するポートフォワードツール 「t2q2t」 を書いた。(実態としてはただのProxy)

ただし、ハンドシェイク回数が増えるのでコネクション確立時のオーバーヘッドは高い

利用例

ユースケースとしては例えば:

f:id:ASnoKaze:20190818085413p:plain
クライアントとサーバそれぞれでt2q2tを実行する。

  • クライアント: TCPで0.0.0.0:2022でリッスンし、QUICで192.168.0.1:22に転送する
  • サーバ: QUICで0.0.0.0:2022でリッスンし、TCP127.0.0.1:22に転送する
# t2q2t <convert mode> <listen addr> <connect addr>

# クライアントサイド (TCP <-> QUIC)
$ ./t2q2t t2q 0.0.0.0:2022 192.168.0.1:2022

# サーバサイド (TCP <-> QUIC)
$ ./t2q2t q2t 0.0.0.0:2022 127.0.0.1:22

t2q2tとはローカルホスト通信を行い、ホスト間ではSSH over QUICを利用できるようになる。
(2重に暗号化処理を行うことになるが、QUICの特性であるロスリカバリの恩恵を受けるほか、IPアドレスが変わってもコネクションとかは切れないはず)

convert mode

サブコマンドは2種類。それぞれリッスン "アドレス:ポート"と、転送先 "アドレス:ポート" を引数にとる

  • t2q: TCPでリッスンして、QUICで転送する
  • q2t: QUICでリッスンして、TCPで転送する

その他

一応、SSHが問題なくフォワードされ。ログインできることは確認した。

注意事項として、t2q2tは、ALPN識別子として「t2q2t」を使用します。 他のQUIC実装と通信することは意図していない。

実装として、エラーハンドリングが雑なのでまだまだ怪しい。QUIC使う部分は毎回コネクション貼ってるので、ストリームの多重化を利用したいところ(8/22 対応済み)。簡単に挙動を確認したところ、単一コネクションになったので、複数TCPコネクションが帯域を食い合うこともなくなった。輻輳制御上も有利なはず。

性能評価や細かい改善とかはおいおい

t2q2tの読み方

決めてない...orz

curlのHTTP/3実験実装を触ってみる

First HTTP/3 with curl | daniel.haxx.se」で書かれている通り、curlがHTTP/3の実験実装を公開したので試す。

ライブラリとしてngtcp2を使う方法と、cloudflareのquicheを使う方法があるが今回はquicheを使う。

基本的には、Documentにかかれている通り。

なお、環境はUbuntu18.04

ビルド

boringSSLのビルド

$ git clone --recursive https://github.com/cloudflare/quiche
$ mkdir -p quiche/deps/boringssl/build
$ cd quiche/deps/boringssl/build
$ cmake -DCMAKE_POSITION_INDEPENDENT_CODE=on ..
$ make -j`nproc`
$ cd ..
$ mkdir .openssl/lib -p
$ cp build/crypto/libcrypto.a build/ssl/libssl.a .openssl/lib
$ ln -s $PWD/include .openssl

quicheのビルド

$ curl https://sh.rustup.rs -sSf | sh
$ source $HOME/.cargo/env

$ cd ../..
$ QUICHE_BSSL_PATH=$PWD/deps/boringssl cargo build --release

curlのビルド
サーバがレスポンスヘッダでHTTP/3対応を示すのに使われるalt-svcも有効にするために、"--enable-alt-svc"をつけてconfigureする

$ cd ..
$ git clone https://github.com/curl/curl
$ ./buildconf
$ ./configure --with-ssl=$PWD/../quiche/deps/boringssl/.openssl --with-quiche=$PWD/../quiche --enable-debug --enable-alt-svc
$ make -j`nproc`

うまく行っていれば、configure時に下記の通り表示される

  Alt-svc:          enabled
...
  HTTP3:            enabled (quiche)

試す

上記の通り、alt-svcを使わずに直にHTTP/3で接続しに行く場合は --http3-direct をつけてアクセスする

$ src/curl --http3-direct https://www.facebook.com/  -v -s -o /dev/null
* STATE: INIT => CONNECT handle 0x563e1077b528; line 1362 (connection #-5000)
* Added connection 0. The cache now contains 1 members
* STATE: CONNECT => WAITRESOLVE handle 0x563e1077b528; line 1403 (connection #0)
*   Trying 31.13.82.36:443...
* Connecting socket 4 over QUIC
* Sent QUIC client Initial, ALPN: h3-22
* STATE: WAITRESOLVE => WAITCONNECT handle 0x563e1077b528; line 1482 (connection #0)
* Connected to www.facebook.com () port 443 (#0)
* STATE: WAITCONNECT => SENDPROTOCONNECT handle 0x563e1077b528; line 1538 (connection #0)
* Marked for [keep alive]: HTTP default
* STATE: SENDPROTOCONNECT => PROTOCONNECT handle 0x563e1077b528; line 1553 (connection #0)
* quiche established connection!
* STATE: PROTOCONNECT => DO handle 0x563e1077b528; line 1572 (connection #0)
* Using HTTP/3 Stream ID: 0 (easy handle 0x563e1077b528)
(略)

パケットキャプチャをすると、ちゃんとIETF QUIC draft-22バージョンで接続していることが確認できた
f:id:ASnoKaze:20190807031738p:plain

動画上にコメントを表示する"弾幕"の仕様

W3Cの「Chinese Web Interest Group」から、Unofficial Draftとして「弹幕规范」(英語版: Bullet Chatting Proposal)というドキュメントが公開されています。

仕様上でも「use cases and requirements for Danmaku」と書かれている通り、動画上にコメントの弾幕を流すユースケースと新しいエレメントを定義するドキュメントのようです。China MobileやBilibili Inc.の方が共著として入っています。

国内ではニコニコ動画が有名ですが、中国ではBilibiliやAcFunといったサイトなどがこの弾幕機能を持つ動画共有サイトとして有名なようです。

このドキュメントでは、新しくbulletchatlistエレメントとbulletchat エレメントをRecommended APIとして定義しています。

  • bulletchatlist: コメントの表示領域
  • bulletchat: 各コメント
<bulletchatlist area="70" >
  <bulletchat mode="scroll" >This is Content</bulletchat>
  <bulletchat mode="bottom" >Fixed Content</bulletchat>
</bulletchatlist>

f:id:ASnoKaze:20190805001724p:plain
(引用: Bullet Chatting Proposal)

  • bulletchatlistは、表示領域を示すarea属性を持ちます。コメント同士を重ねて表示するallowOverlap属性を持ちます。
  • bulletchatは、そのコメントをどのように表示するかのmode (右から流れるscroll, 下に固定表示するbottomなど)。また、表示及び非表示時に発火するイベントなども定義されています。

Demo

公式のDemoとして動くものが作られています。
w3c.github.io

おわりに

すでにGithubW3C organization配下で文書が管理されています。Interest Groupということで勧告文書は出せないと思うのですが、この先どうなるのかは興味があります。