QUICの現状確認をしたい 2018/3 (QPACK, Spin Bit, Invariants)

QUICの現状確認をしたい。
(あまり追えてないのでつらい)

前回
asnokaze.hatenablog.com

次回
asnokaze.hatenablog.com


目次

仕様の状況

3月頭に4つのコアドキュメントのdraft-10が出ている。

あまり差分は大きくないが、トランスポートでは前回分で書いたコネクションマイグレーション時に使うPATH_CHALLENGE、PATH_RESPONSEフレームが追加されたほか、ACK周りの再送するフレームに関する情報が追記された。

TLS利用の仕様では、QHKDF-Expandに関する記述が増えている。

おそらく、もうそろそろ draft-11 が出ると思われる。

また、その他の仕様では、QUICのHTTPヘッダ圧縮方式については長らく議論されていたが、QCRAMと呼ばれていた仕様がWG Draftとなっている。なお、非常に紛らわしいところであるが、QCRAMはQPACKに改名された。

QPACKについては以前書いたが、少なからずの変更が入っている。
asnokaze.hatenablog.com

また、将来のQUICバージョンアップデートをデプロイしやすくするための、invariantsと呼ばれる仕様もdraft-01が出ている。WG Last Callとなっている。

asnokaze.hatenablog.com

マイルストーンの変更

WGのマイルストーンに、Header Compression for HTTP over QUICが追加されたほか。

以前、チェアから意見が出たとおり、マルチパス QUICに関するマイルストーンは削除されました。

QUIC V1では、マルチパスQUICは対応されません。
QUIC V1では、マルチパスQUICは対応されません。

IETF101

3/17 ~ 3/23 にかけて、IETF 101がロンドンで開催された。
QUICに関しては、QUIC WGのセッションが2つ開催されたほか、ハッカソンで実装を持ち寄っての相互接続テストも実施された。

下記の発表が行われました

  • Hackathon Update
  • EDITORS UPDATE
  • QUIC DTLS and Stream 0
  • Invariants
  • ECN
  • Spin Bit Proposal

議事録及び発表資料はGithubで公開されている
wg-materials/ietf101 at master · quicwg/wg-materials · GitHub

相互接続性テスト

4th Implementation Draftで、相互接続テストが実施された。これは、 draft-09 と TLS1.3 draft-23の実装です。

f:id:ASnoKaze:20180331005345p:plain

  • V: Version Negotiation
  • H: Handshake
  • D: Stream Data
  • C: Connection Close
  • R: Resumption
  • Z: 0-RTT
  • S: Stateless Retry

次回のinteropは、5月にリモートでの開催が予定されている。
5th Implementation Draftとして、draft-11 と TLS1.3 draft-28で相互接続テストが実施される。

Spin Bit

発表資料: wg-materials/spin-101.pdf at master · quicwg/wg-materials · GitHub

以前書いたSpin BitをQUICとして正式に採用するかという議論が白熱しました。
asnokaze.hatenablog.com

会場参加の実装者の賛同はあまり得られておりませんでしたが、下記の通り hum を取ることになりました。

  • (a) QUIC V1にSpin Bitを入れない
  • (b) 予約bitを確保し、拡張仕様として別ドキュメントで定義する
  • (c) まだわからない

結果としては、(b)となったようです。
後日MLでチェアのmnot氏が下記のようにまとめています。
Spin Bit -- a Path Forward

QUIC DTLS and Stream 0

発表資料:wg-materials/Stream0-EKR.pdf at master · quicwg/wg-materials · GitHub

以前書いた通り、EKR氏よりQUIC over DTLSの提案がありました。
asnokaze.hatenablog.com

この話を発端に、QUICのレイヤリングとストリーム0に関しては専門のチームが結成され、検討が進められるようです。

下記のメール参照
Stream 0 Design Team

A First Look at QUIC in the Wild

発表資料:https://datatracker.ietf.org/meeting/101/materials/slides-101-maprg-a-first-look-at-quic-in-the-wild-00

maprg では、QUICのインターネットでの利用状況に関する発表がありました。ヨーロッパのTier-1 ISPでの、QUIC, HTTPSトラフィック量比較などの調査結果が報告されています

InvariantsのWGLC

Version-Independent Properties of QUICがWG Last Callとなっている。

チェアの「Working Group Last Call: QUIC Invariants」に詳しく書いてあるが、これは QUICの不変部分がより高いレベルで変更されないと信頼できるようにするためである。WGLC後すぐに先のステップに進むのではなく、そのタイミングで停滞させるようである。

なお、強い理由があればInvariantsの仕様を変更する事ももちろんある。

このような戦略的なWGLCは面白いなと思いました。

Symmetric connection IDs

Draft-11で入る、大きな変更点があります。
github.com

Coneection-IDが、Source Connection IDとDestination Connection IDに変更され、さらに可変長となりました。これによって、クライアントのパケット処理がよりシンプルになるようです。

この変更によって、Long Headerは下記の通りになります
f:id:ASnoKaze:20180331013127p:plain

Short Headerは下記の通りになります。Short HeaderはDestination Connection IDだけになります。Lengthが無いのは、その長さがお互いにわかっているため、Lengthはありません。
f:id:ASnoKaze:20180331013233p:plain

あまり自分もちゃんと理解できないので、詳しい人がいれば補足してほしい...

ChromeがWebSockets over HTTP/2に対応したので試す

以前書いたとおり、Websockets over HTTP/2の仕様である「Bootstrapping WebSockets with HTTP/2」が現在標準化が進められている。
asnokaze.hatenablog.com

これにより、複数のWebsocket通信が1つのTCPコネクションに束ねられる。

一つのページで複数WebSocketを使っていたり、複数タブを開いて各ページでWebSocketを利用しているとどうしてもコネクションの本数が多くなってしまう。様々なメリットがあるが、コネクションの数がへらせるのはサーバ側でも嬉しい部分がある。

もちろんひとつに束ねられたWebSocketをばらして処理をする必要があり、サーバやProxy側の実装が整う必要はある。

さて、このWebsockets over HTTP/2にChrome Canaryが対応したので実際に試してみる。

WebSockets over HTTP/2

「WebSockets over HTTP/2」の概要に簡単に触れる

WebSockets over HTTP/2を利用するにはサーバ側からSETTINGSフレームで「ENABLE_CONNECT_PROTOCOL = 1」を送る必要がある。そのため、既存のサーバに対してブラウザが勝手にHTTP/2でWebScoket通信を試みるようなことはない。

具体的な通信手順は以前書いた時より仕様が進んでおり、HEADERSフレームを使用するようになっている。
f:id:ASnoKaze:20180310012152p:plain:w400

Chromeで Websockets over HTTP/2を有効にする

現状はChrome Canaryで有効にすることが出来る。
起動オプションに "--enable-websocket-over-http2"を与えて起動すると、Websockets over HTTP/2が有効になる。

Chromeの実行パスがわからない場合は、URLバーにchrome://version/と打ち、コマンドラインの項目から確認できる

試す

ざっくり適当に、モックサーバを用意した。

f:id:ASnoKaze:20180310014954p:plain

HTTP/2で接続しにいって、WebSocketのonopenイベントが発火するところまで確認した。
(地味な動作確認画面だ...)

QUIC over DTLSの提案仕様

20180601追記
asnokaze.hatenablog.com


QUIC over DTLS」という提案仕様ekr氏が出され、QUIC WGのメーリングリストで「Proposal: Run QUIC over DTLS」としてDTLS上でQUICのメッセージを通信するように変更する提案がされている。

QUICのスタック

現在のQUICのスタックは図のようになっている。
f:id:ASnoKaze:20180306233002p:plain

QUICが提供する信頼性のあるトランスポート上のStream 0でTLS1.3のハンドシェイクのメッセージ(Crypto Handshake)をやりとり、そこから得れられたシークレットから鍵を生成し通信を暗号化します。

詳細は、tatsuhiro-t先生の記事が詳しいです。
https://qiita.com/tatsuhiro-t/items/2c4e40923c5e359ca235qiita.com

しかし、ekr氏の提案で述べられているように、Crypto HandshakeはStream 0に特殊なルールを適用することになっていたり、0-RTTやACKに関するルールを複雑化していると述べています。また、QUICとTLSスタックを密結合にしているとも書かれています。

提案されている 「QUIC over DTLS」では、QUICのスタックは以下のようになります。
f:id:ASnoKaze:20180306234235p:plain

DTLSのハンドシェイクを行った後、その上でQUICのメッセージをやり取りするようになります。
それに伴い、この提案仕様でQUIC部分も変更される部分が出てきます。

  • QUICのVersion Negotiationの変更 (DTLS1.3のsupported_versionsに加え quic_versionsのネゴシエーション)
  • Transport ParametersはTLS拡張へ
  • DTLSの提供するACKと、QUICの提供するACKが分離する

もちろん、QUICとDTLSスタックとやり取りする必要もあるし、そのままDTLSを利用できない部分もあります。

DTLSの変更

QUICの仕様も既に長い間議論されてきており、沢山の事が考慮され現在の形になっています。

そのような様々な考慮事項を満たすために、DTLSはそのまま使用できません。提案仕様の「3. Required Changes to DTLS」にかかれているとおり、幾つか変更する必要があります。

  • DTLS1.3 Connection ID: 別途議論されているDTLSにコネクションIDを導入する拡張の仕様 (「DTLSにコネクションIDを導入する提案仕様」)
  • ハンドシェイクメッセージの難読化
  • ネゴシエーションパケットの難読化
  • パケット番号の暗号化
  • Stateless Reset: サーバがリブートなどして、コネクションの状態情報を失った時に接続をリセット する手段を提供する

各種メッセージの難読化については、以前書いたとおり
asnokaze.hatenablog.com

実装

ekr氏は既に「QUIC over DTLS」を実装しています。
github.com

議論

QUIC WGのメーリングリストで、議論は盛り上がっています。

やはり大きな変更となるため、興味を持つものの好意的でない意見の方が多い印象です。今まで沢山の議論を重ね、GQUICからの知見も得て、QUICは今の仕様になっています。今のデザインのまま、まだまだ改善できる余地はあるのではないかという感じでしょうか。

今月ある IETF101のアジェンダにも既に含まれています。議論となることでしょう。
github.com

HTTP/1.1 (RFC 7230 〜 7235) の改訂作業がはじまる

HTTP/1.1の仕様は下記の通り、6つのRFCで標準化されています。

これら、HTTPリクエスト・レスポンスヘッダや、ステータスコード、キャッシュや認証の仕組みといったHTTPの意味(セマンティクス)はHTTP/2でもHTTP over QUICでも変わっていません。

つまり、引き続きHTTPのセマンティクスを定義しているこれらのドキュメントは整備(メンテナンス)し続ける必要があります。

RFCは一度出てしまうと変更することは出来ず、errataと言うかたちで訂正が蓄積していきますが、どこかで改訂版を出す必要が出てきます。上記RFCが出て4年が経ち既にIssueはいくらか溜まっており、その一覧はhttpbis WGのgithubリポジトリで確認できます。現状では48個のissueが登録されています。
github.com

そこで、IETFのhttpbis WGでは、次のHTTP標準の改訂作業が行われる機運が高まっており、前々回のIETF99(2017年11月頃)でもその議論が行われています。発表資料は以下のより確認できる。
github.com

HTTP/1.1は既に一度改定しており、その時は次のHTTPと言う意味で、HTTPbisと呼ばれていました。そして3回目の改訂としてHTTPtreと呼ばれるのが今回のドキュメントです

  • 最初: RFC 2616
  • HTTPbis: RFC 7230 〜 7235
  • HTTPtre: 次のドキュメント

(ちなみに、WGの名前はhttpbisのままで行くようです)

HTTPtre

本日、そのHTTPtreのドラフトが提出されました。Authorは、HTTPbisの改訂でも根強く尽力されたJulian F. Reschke氏と、Roy T. Fielding氏です。

それぞれ、RFC 7230 〜 7235と対応して、各改訂版は下記のとおりです。

Message Syntax and RoutingはHTTP/1.1固有のものですが、それ以外はHTTPとしてのセマンティクスを定義しているため、ドキュメントタイトルから1.1という部分がなくなっています。

これらのドキュメントは、個人ドラフトの00版ですがすでに変更が入っており、各ドキュメントの最後に変更点が記述されています。

HTTPtreのスコープとスケジュール

HTTPtreの作業項目と、そのドラフト更新の目処については、チェアのmnotが去年の段階で言及しています。
DRAFT: more details for HTTPtre

それを見るとHTTPbis(6年かかった)に比べれば、短い期間で改訂作業が進められそうです。

まずは、このドキュメントを元に、今月行われるIETF101で議論を行い、これから編集作業が進んでいくものと思われます。

QUICにおけるヘッダ圧縮の提案仕様 QPACK(旧QCRAM) その2 (draft-04)

20180313追記
QCRAMと呼ばれていた仕様は、QPACKに改称されました。
github.com


HTTP over QUICでは、HTTP/2のフレームを利用するが、HTTPヘッダ圧縮にHTTP/2のHPACKをそのまま使用するのはHoLBの問題が知られている。

HPACKでは、ヘッダが送った順番通りに届く事を想定しており、送信側と受信側が完全に同じ状態を保てる。しかし、QUICではメッセージの届く順番は入れ替わる可能性があり、動的テーブルの参照する際にまだそのインデックスが挿入されていないというケースが生じうる。QUICでは、届いたメッセージから処理できるはずが、ここで待ち状態になってしまう。

そこでQUICでのHTTPヘッダ圧縮は新しい仕組みを導入することが検討されており、幾つか提案されているなかの一つが「Header Compression for HTTP over QUIC」であり、QCRAMと呼ばれている。

そのQCRAMが Call for Adoption となっている、またGo実装でもあるminhqでもQCRAMが試験的に実装されている。IETF101に向けて、事前に仕様を読んでおく。

今回は、前回のdraft-02からのアップデートのみ触れる。詳しくは前回を参照のこと。
asnokaze.hatenablog.com

draft-04

Call for Adoption となっている、現在のQCRAMはdraft-04であり、draft-02から幾つかの変更点がある。

主な変更点である、下記について書く

  • HEADER_ACK
  • BLOCKING flag
  • ヘッダブロックプレフィックス
HEADER_ACK フレーム

新しく HEADER_ACKフレームが定義される。これは、デコーダ側がヘッダブロックを処理した際にControl Streamでエンコーダ側へ通知される。このHEADER_ACKフレームをエンコーダ側が受信することで、送ったヘッダがどこまで処理されてるか判別することが出来るようになる。

f:id:ASnoKaze:20180212221216p:plain

  • Stream ID: ヘッダブロックを処理したストリームID(可変長)

trailersなどで複数回ヘッダブロックを処理する可能性があるが、おそらくその都度送信される。

BLOCKING フラグ

HEADERSフレームとPUSH_PROMISEフレームにBLOCKING フラグが追加されます。

前述のHEADER_ACKによってエンコーダ側は送信したヘッダブロックがどこまで処理されているかを知ることが出来ます。そこで、ヘッダブロックを送信する際に、そのデコーダ側がヘッダブロックを処理するのにブロックされうるのかが分かります。

このフラグが0の場合は、デコーダ側はすぐにそのヘッダブロックを処理できます。1の場合は、依存するヘッダブロックが処理されるまで待つ可能性があります。

ヘッダブロックプレフィックス

以前の記事で説明したとおり、QCRAMではヘッダブロックにプレフィックスが追加されます。このプレフィックスもdraft-02から変更されています。

このヘッダブロックプレフィックスは、BLOCKING フラグによって変わります。
BLOCKINGフラグが0の場合が左、BLOCKINGフラグが1の場合は右です。


f:id:ASnoKaze:20180212231947p:plain

  • Base Index: 現在のヘッダブロックを初果する前の、追加されたエントリの合計
  • Depends: Base Indexの位置よりどれくらい前のインデックスに依存するか。Base Index - Dependsが依存する最大のインデックス値になります。

HPACKではエントリを挿入するたびにインデックス値がずれていきましたが、Base Indexから場所を計算することで順番通りでない挿入でも正しい場所にエントリを挿入できます。

Dependsがあることで、ブロックの処理を開始する前に必要なインデックス値を確認できるようになっています。

NginxがHTTP2サーバプッシュに対応したので試す

追記
[nginx-announce] nginx-1.13.9
http://mailman.nginx.org/pipermail/nginx-announce/2018/000207.html

1.13.9でサーバプッシュがサポートされました


先程、Nginxでサーバプッシュをサポートするコミットが入ったので試す。

HTTP/2: server push.
http://hg.nginx.org/nginx/rev/641306096f5b

ビルド

$ git clone https://github.com/nginx/nginx.git
$ cd ./nginx

$ ./auto/configure  --with-http_ssl_module --with-http_v2_module
$ make
$ sudo make install #一旦インストールしてしまう

設定

一旦 h2cで設定する。

$ sudo vim /usr/local/nginx/conf/nginx.conf
    server {
        listen       80 http2; #h2を有効化
        server_name  localhost;

        http2_push_preload on; #preload方式のプッシュを有効化
        location / {
            root   html;
            add_header "Link" "</test.js>;rel=preload"; #Linkヘッダを付ける
            index  index.html index.htm;
        }
    }
  • http2_push_preload onとすることで、Linkヘッダでpreload指定されたファイルをプッシュするようになる (リバースプロキシでバックエンドから送っても良い)
  • 今回は、add_header ヘッダで直接Linkヘッダを指定

preloadの詳しい仕様は
https://w3c.github.io/preload/ 参照

試す

$ sudo ./objs/nginx #起動

#nghttpで確認する。適宜インストールしてね
$ nghttp http://localhost -vn
....
[  0.004] recv (stream_id=13) :method: GET
[  0.004] recv (stream_id=13) :path: /test.js
[  0.004] recv (stream_id=13) :authority: localhost
[  0.004] recv (stream_id=13) :scheme: http
[  0.004] recv PUSH_PROMISE frame <length=23, flags=0x04, stream_id=13>                                                                                                 

ちゃんと、PUSH_PROMISEが送られている。

良さそう。

ちなみに、Apacheの場合はこちら
asnokaze.hatenablog.com

Bundled HTTP Exchanges とは (WebPackagingの議論より)

20180615追記
Bundled HTTP Exchangesの仕様がIETFに提出されました
https://tools.ietf.org/html/draft-yasskin-wpack-bundled-exchanges-00


WebPackagingという仕様が議論されている。簡単な概要は以下の記事に書いたとおり
asnokaze.hatenablog.com

WICG/webpackageで書かれている通り、WebPackagingは以下の2つに分離して標準化が進められそうです

  • Signed HTTP exchanges: HTTPリクエスト/レスポンスをブラウザが信頼できるように署名する仕組み
  • Bundled HTTP exchanges: 複数のHTTPリクエスト/レスポンスを一つのCBOR形式で表現できるようにする仕組み


Signed HTTP exchangesの方は、既にIETFに提案しようが出ており下記の記事で軽く触れた。
asnokaze.hatenablog.com


Bundled HTTP exchangesの方は、未だIETFには提出されていないが、著者様の個人リポジトリにdraftがあるため軽く読んでおく。
(まだIETFに提出されていないため変更される可能性があります)

Bundled HTTP exchanges

仕様は「Bundled HTTP Exchanges」から確認できる

bundleは複数のHTTPリクエストレスポンスを一つにまとめる。データ構造はCDDLで記述する。

bundle = [
  ; 🌐📦 in UTF-8.
  magic: h'F0 9F 8C 90 F0 9F 93 A6',
  section-offsets: {* (($section-name .within tstr) => uint) },
  sections: [* $section ],
  length: bytes .size 8,  ; Big-endian number of bytes in the bundle.
]

$section-name /= "index" / "manifest" / "critical" / "responses"

$section /= index / manifest / critical / responses

responses = [*response]

まず最初にメタ情報のロードが行われる

  • bundleは🌐📦であるmagicから開始される
  • 続いて、0以上のsection-offsetsが続く。
    • section-offsetsのkeyは、"index", "manifest", "critical", "responses"のどれかである (未知のセクションは無視される)
    • section-offsetsのvalueは、それらのセクションが何バイト目から開始されるかのオフセットである。
  • sectionは各セクションの構造毎にパースされる
    • responsesは実際にデータをロードする際にパースされる。
  • lengthは、このbundleの長さ

indexセクション

HTTPリクエストに対応するレスポンスが、responsesセクションのどこに存在するかのインデックス。responsesセクション開始からのオフセットと長さが与えられる。

index = {* http-request => [ offset: uint,
                             length: uint] }

http-requestは下記の構造で表現される

http-request = {
  * bstr => bstr
}

manifestセクション

bundle自身を識別するためのマニフェストファイル(Web App Manifest形式)へのHTTPリクエスト情報。

manifestセクションはhttp-requestからのみなる

manifest = http-request

詳しくは (Web App Manifest形式)

criticalセクション

将来のバージョンを想定して、未知のセクションは無視されるが、サポートすべきセクション名を記述できる。

criticalセクションは以下の通り

critical = [*tstr]

ココに書かれているsection-nameをサポートしていない場合はパースは失敗となる。

responseセクション

responseセクションは下記の通りである

response = [headers: {* bstr => bstr}, payload: bstr]

各responseはレスポンスヘッダと、HTTPボディであるペイロードからなる。ヘッダとしてステータスコードを示す":status"を含めなければなりません。