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

20190408追記
最新版について記事を書きました
asnokaze.hatenablog.com

QUICとヘッダ圧縮

HTTP over QUICの仕様は現状、HTTP/2と同様HPACK(RFC 7541)を利用してHTTPヘッダの圧縮を行う。

HPACKはHTTP/2を想定としており、トランスポートはTCPであり順番通りにパケットが届く想定の仕様となっている。具体的には、動的テーブルにおいてエントリーの追加やそれに伴う暗黙的なエントリのエヴィクションが発生してもエンコーダ側とデコーダ側で状態が同期できるのがHPACKである。

QUICはUDPを使うとともに、同一のストリーム上でのみデータの順番が保証される仕組みになっている。これによってパケットロスが他のストリームをブロックすることがなくなるというメリットがあるが、前述の通りHPACKを使っている限りヘッダのデコードは順番通りにしか処理が出来ない。結局としてパケットロスがHTTPヘッダのデコード部分についてはブロックする可能性が出てくる。

QUICの標準化を行っているIETFのQUIC WGでは、HTTP over QUICで使用する新しいヘッダ圧縮の方式が議論されている。
現在案は2つある

  • QPCAK: HPACKのワイヤフォーマット自体変更する
  • QCRAM: HPACKのワイヤフォーマットを維持する

QPACKについては、以前ブログに書いたとおりです(仕様はブログを書いてから更新されているため注意)
asnokaze.hatenablog.com

今回はQCRAMについて説明する

QCRAM

QCRAMは「Header Compression for HTTP over QUIC」という仕様であり、QPACKと区別してQCRAMと呼ばれる。仕様の著者はGoogleのCharles 'Buck' Krasic氏である。

QCRAMではHPACKのワイヤフォーマットにプレフィックスを付ける形で、動的テーブルの参照におけるヘッドオブラインブロッキングを緩和する。

順番が保証されない状態で、HPACKの動的テーブルを使うときの問題点は主に2つある

  • Insertion Pointよりエントリが挿入されることで、その他のエントリも index値 が変化する
  • エントリが挿入されることによって、テーブル上限に達した場合は Dropping Point より古いエントリが削除される
           <----------  Index Address Space ---------->
           <-- Static  Table -->  <-- Dynamic Table -->
           +---+-----------+---+  +---+-----------+---+
           | 1 |    ...    | s |  |s+1|    ...    |s+k|
           +---+-----------+---+  +---+-----------+---+
                                  ^                   |
                                  |                   V
                           Insertion Point      Dropping Point

https://tools.ietf.org/html/rfc7541#section-2.3.3


QCRAMではHEADERSフレームとPUSH_PROMISEフレームにおけるHPACK形式で表現されるヘッダブロックに以下のprefixを追加します

       0 1 2 3 4 5 6 7
      +-+-+-+-+-+-+-+-+
      |Fill       (8+)|
      +---------------+
      |Evictions  (8+)|
      +---------------+
  • Fill: 現在の動的テーブルのエントリー数
  • Evictions: エヴィクション回数

それぞれHPACKにおける整数表現が使用されます。また両方を足すと今まで挿入したエントリ数となります。

f:id:ASnoKaze:20170805204704p:plain

このprefixを使えば、挿入によってindex値がずれていたとしてもEvictionsとFillの情報から指し示しているindexが求められます。指し示すindex値がすでに到着して入ればそれを使用します。そのindex値が指し示すエントリがまだ挿入されていなければ待つことになります。

また、エヴィクションに関してもエンコーダが明示的にエヴィクションされた回数を送ってくるので、デコーダ側は自身のエヴィクション回数と比べて多ければエヴィクション出来ることになります。(保持する上限は別途実装依存となる)

その他

QCRAMの仕様はprefixの仕組みが大きいがそれ以外にも幾つかの支持がある。
例えば、QUICレイヤのAckをHTTPレイヤに伝えることで、相手に届いたことを確認してからそのindexを使うように進めている。また、動的テーブル上のエントリを再登録するIndexed Header Field with Duplicationなどの定義が与えられている。

minqを弄って memcached over quic を簡単に実装してみる

IETF QUIC

QUICの標準化がIETFで進められています。

QUICといえば既にChromeGoogleのサービスで使用されていますが、そちらはGoogle QUICであり、IETFで標準化されているものとは微妙に異なっています。

IETF QUICの方は仕様の策定の議論が重ねられるとともに、7月に行われたIETF99で幾つかの実装が持ち寄られ相互接続テストが行われている状況です。しかし、「First Implementation」に書かれている通り1-RTTのハンドシェイクにフォーカスした相互接続テストであり、HTTPマッピング, 0-RTT, Key Updateなどといった多くの機能は今回のテストの対象外でした。

現在は引き続き仕様の議論を続けるとともに、10月に行われるQUIC WGの中間会議で行われるであろう次回の接続テストに向けてテスト項目( Second Implementation Draft)の議論が行われています。

相互通信テストでは上に乗っけるプロトコルがまだ決定しておりません。
QUIC WGの一旦の目標はHTTP over QUICが目標ですが、相互通信テストではもっと簡単なプロトコルが選ばれるかもしれません。
また、DNS over QUICなども別途仕様の方は議論されています。

既存実装

既存の実装としては、QUIC WGのWikiにまとめれています。

versionにdraftと書かれているものがIETF版 QUICで、Q035などのバージョンのものはGoogle QUICの実装になります。

IETF QUICの実装の実装状況は、まだまだマチマチな状況ですが概ね「First Implementation」相当のものかと思われます。

minqを弄ってみる

IETF QUICのGo実装の一つに ekr 氏の minqがあります (ekr氏は、TLS1.3の仕様のauthorでもあります)
github.com

今回は、minqのbin以下のclient/main.go と server/main.goを弄って雑に memcached over quic ぽいものを作ってみました
(意味があるかは別として)

github.com
(中身は本当に簡易です)

minq側のREADME.mdの通り一旦セットアップして、
serverを起動してから、clientで接続しに行くと、telnetと同じ要領でsetとgetを叩くとこんな感じです
(stream 1で通信されます。同一ストリームなので順番は保証されます)

$go run ./server.go > null &

$ go run ./client.go
State changed to  3
State changed to  5
Connection established
set hoge 0 0 4
test
STORED

$ go run ./client.go
State changed to  3
State changed to  5
Connection established
get hoge
VALUE hoge 0 4
test
END

今回はお遊びですが実装が進んだらまた何かプロトコルを乗っけてみたいなと思いました。

Chromeがシマンテックの古い証明書を信頼しなくなる今後のスケジュール(2017/09/13更新)

2017/09/13 更新

Googleのブログで公式アナウンスが出ました。
一部対応者の表記がDigiCertになりましたが、大きなスケジュール変更はありません
security.googleblog.com


今年の上旬より、Chromeが古いシマンテックの証明書を失効扱いするニュースが世間を賑わせたのは記憶にあたらしいかと思います。
internet.watch.impress.co.jp


議論は続いており、Blinkの開発者メーリングリストの「Intent to Deprecate and Remove: Trust in existing Symantec-issued Certificates」にてシマンテックGoogle(+ Mozilla)が協議を重ねていることがわかります。

本日、「Google ChromeChromiumプロジェクトを代表して」という形で最終になるであろうFinal Planが提案されています。

原文を見ることをオススメしますが、自分用に簡単にメモ書きします。

補足ですが、Mozilla側はまた別ですのでそちらも注視しておく必要があります
(https://groups.google.com/forum/#!msg/mozilla.dev.security.policy/gn1i2JNVCnc/y7IRQALJBgAJ)

今後の予定

Chrome66での対応

Chrome66より 2016年6月1日よりも前に発行された証明書がdistrustされます

  • Beta: 2018/3/15 予定
  • Stable: 2018/4/17 予定

Chrome70での対応

Chrome70より、シマンテックManaged Partner Infrastructure(2017/12/1)より以前に発行した証明書がdistrustされるようになります。

  • Beta: 2018/9/13 予定
  • Stable: 2018/10/23 予定

タイムテーブル

日付 イベント
2017年7月27日 ~ 2018年3月15日 2016年6月1日より前に発行されたシマンテック発行のTLSサーバー証明書を使用しているサイト運営者は、これらの証明書を置き換える必要があります。
2017年10月24日 Chrome62より distrustされる証明書がdevtoolで警告されます
2017年12月1日 シマンテックによれば、Managed Partner Infrastructureが有効になります。
この時点より以前に発行された証明書は将来的に機能しなくなっていきます(Chrome70)
2018年3月15日 Chrome66 Beta公開
2016年6月1日以前に発行された証明書が信頼されなくなります
2018年4月17日 Chrome66 Stable公開
2018年9月13日 Chrome70 Beta公開
Managed Partner Infrastructure(2017/12/1)以前に発行された証明書が信頼されなくなります
2018年10月23日 Chrome70 Stable公開

HTTP/2でのプライオリティ・プレースホルダの提案仕様

元々はHTTP over QUICでの議論に端を発するが、HTTP over QUICの仕様の著者でもあるMicrosoftのMike Bishop氏から「Priority Placeholders in HTTP/2」という仕様が提案されている。

解説すると長くなるので割りと雑目ですみません

HTTP2のプライオリティ制御

まずHTTP/2のプライオリティ制御について軽く説明する。

HTTP/2ではHTTPリクエストが並列的に送信される。そこで、クライアント側からHTTPリクエストを送る際にその要求の優先度を指定できる。サーバはその優先度を尊重してHTTPレスポンスを返す。

これによって、ページの表示に大事なCSSなどは早く、画像などは後でほしいと言ったことが実現されている。

この優先度では各ストリームにDependencyとWegithが設定される。Dependencyは各ストリームの依存関係(ストリームAが終わってから、ストリームBといった制御)、Weightは各コンテンツを返すのに使用するサーバリソースの比である。

すべてのストリームはストリームID=0もしくは他のどれかのストリームに依存する。つまり、木を構成することになり、Priority Treeと呼ばれる。

f:id:ASnoKaze:20170728010502p:plain
(引用: HTTP/2 Deep Dive: Priority & Server Push // Speaker Deck )

丸がストリームID, 四角がWegith,矢印がDependencyを表します。

上記図に示すとおり、Firefoxではストリーム3, 5, 7, 10をidle状態のまま各ストリームをグループに分けるために使用します。画像群やCSS群といったストリーム毎に一括で優先度処理するような形になっています。

ここでポイントになるのは

  • idle状態のストリームを用いて各ストリームの優先度をグループ化している(idleストリーム自体は以後使用しない)
  • HTTP/2ではcloseしたストリームもPriorityに影響してくる(closeしたストリームに依存するストリームを後から追加できる)

QUICとHTTP/2のプライオリティ

現段階ではQUICでもHTTP/2同様のプライオリティ制御方式を使用します。HTTP/2ではTCPを用いていたため送信した順序どおりに受信され処理されていたため、クライアントとサーバでPriority Treeを同期できました。しかし、QUICでは同一ストリーム上でのみ順番が保証されているため、Priorityフレームはconnection controlストリームで送られる事になっています。

また、一点大事な事として、HTTP over QUICでは各ストリームIDを飛ばすことなく使用しなければなりません。

つまりidleのストリームを用いてストリームの依存をグループ分け出来なくなっている。

Priority Placeholders in HTTP/2

Mike Bishop氏の「Priority Placeholders in HTTP/2」では、Priority Tree内でグループ分けをしたい場合は、それ専用のプレースホルダを使用します。このPriority PlaceholdersはSETTINGSフレームで各エンドポイントが合意した場合に使用できます(使用できるプレースホルダ数も合わせて通知されます)。

プレースホルダプレースホルダIDを持っており、新しく追加されたDEPENDENT_ON_PLACEHOLDERフラグがon担っている場合はdependencyの指定がプレースホルダIDという事になります。

プレースホルダの作成はPLACEHOLDER_PRIORITYフレームで作成されます。

       0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |0|                    Placeholder ID (31)                      |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |0|                  Stream Dependency (31)                     |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |   Weight (8)  |
      +-+-+-+-+-+-+-+-+

Flagやペイロードの意味はPRIORITYフレームとほぼ同等です。

この仕様では、プライオリティ・プレースホルダを使っている場合、サーバはアクティブでないストリームをPriority Treeから削除出来ることになっています(MAY)。詳しいやり方も仕様中に書かれています。

議論

冒頭で述べたように、元々はQUICでのIssueが最初にあり、IETFオフラインミーティングを通して議論されてきており、その結果として今回の提案仕様となった。

議論はML上で続いており「QUICの問題を解決するには、プレースホルダとして「ストリームID」を指定できるように、PRIORITYフレームにフラグを追加してはどうか?」
という話も出ている。

Nginxで、リクエストを複製するmirrorモジュールが標準搭載された

[20170809追記] nginx-1.13.4に ngx_http_mirror_module は含まれました


Nginxで、リクエストを複製するmirrorモジュールがコミットされ、何もせずとも使用できるようになりそうです(現状最新コミットをビルドする必要あり)。

例えば本番環境のproxyからリクエストを複製して開発環境に流すような事も出来ます。もちろん複製処理は本来のリクエスト処理をブロックしません。

例えば以下のように、mirrorに来たリクエストを複製してバックエンドサーバに投げるようにしてみます
f:id:ASnoKaze:20170724032715p:plain

conf

    server {
        listen       80 ;
        server_name  localhost;

        mirror_request_body on;
        log_subrequest on;

        location /mirror {
            mirror /proxy; #/proxy宛にリクエストを複製する
        }
        location /proxy {
            proxy_pass http://127.0.0.1/proxyed/;#任意のサーバにproxyする
        }
    }
  • mirror ディレクティブでリクエストを複製し、自身の任意のPATHにリクエストを送ります。今回は別のサーバに送るために一旦 /proxy宛に複製します($request_uriは変更されません)。proxy_passではいつも通り任意のサーバにリクエストを送ります。
  • log_subrequestを有効にすると複製したリクエスト自体もaccess.logに記録されるようになります。
  • mirror_request_body はおそらくPOSTのデータと言ったリクエストボディも合わせて複製されるようになるものだと思います。

もちろん2つ以上複製することもできます

動作確認

上記 .conf では、access.logに3つのリクエストが記録される。
本来のリクエスト、log_subrequest onによって記録される複製されたリクエスト、自身にproxyされたのを受け取ったリクエス

vagrant@vagrant:~/nginx-mirror$ curl localhost/mirror
mirror

vagrant@vagrant:~/nginx-mirror$ tail -f /usr/local/nginx/logs/access.log
127.0.0.1 - - [23/Jul/2017:18:08:14 +0000] "GET /proxyed/ HTTP/1.0" 200 0 "-" "curl/7.47.0"
127.0.0.1 - - [23/Jul/2017:18:08:14 +0000] "GET /mirror HTTP/1.1" 200 256 "-" "curl/7.47.0"
127.0.0.1 - - [23/Jul/2017:18:08:14 +0000] "GET /mirror HTTP/1.1" 200 7 "-" "curl/7.47.0"

(本来のリクエストのログは複製処理が終わったあとに出るが、本来のリクエストへのレスポンス処理はブロックされていないように見えました)

追記2018/04/11
tagomoris先生の検証記事が非常に分かりやすくて素晴らしいです
tagomoris.hatenablog.com



便利!!

iphone(iOS 11)でMultiPath TCPを使う

すでにアナウンスされているとおり、iOS11よりMultipathTCP (MPTCP)のAPIサードパーティデベロッパーに公開されます。

実はiPhoneをお使いの人はすでに、iOS7からsiriでMPTCPが使われていたので裏ではすでに使われておりましたが、実際にみんなが試せるような状況になってきました。

MultiPathTCP (MPTCP)とは

MultipathTCPは複数のインターフェースを用いてTCPコネクションをはる事のできる、TCP拡張の一つです。例えばスマートフォンであればLTEWi-Fiといった2つのインターフェースを用いてコネクションをはり、両方の経路それぞれを併用してデータ通信をします。

大きなメリットとしては、使える帯域が増えるということだけではなくアプリケーション側からは1つのTCPコネクションに見えており仮に片方のコネクションが切れたとしてもTCPコネクションは維持できる点です。

Appleの発表では上記ios7のsiriにおいて、95パーセンタイル 20%の高速化と、接続失敗を5分の1に出来たようです。

ハンドシェイクと仕様

MultiPathTCPを使用するためにはクライント・サーバ両方が対応している必要があり、その際はTCP 3ウェイハンドシェク時にオプションとしてMP_CAPABLEが指定されてます。

もちろん既存のTCPコネクション(MP_CAPABLEが有効)に、別経路の通信(subflow)を追加する場合、経路上の機器からは一見ただのTCP 3ウェイハンドシェクが行われたように見えます(MP_JOINオプションが有効になってます)。

詳しくは、RFC6824を読まれるのが良いかと思います(改訂作業中:RFC6824bis)

(もちろん帯域公平性や、悪意あるユーザによるsubflowの追加を防ぐ機能があります (参考:TCP.next // Speaker Deck))

iOS11におけるMPTCP

すでにbeta版で試せるようですが、残念ながら手元に試せる環境がないため、すでに試されている人のコードを拝借させていただきます。
MPTCP experiments on iOS 11 beta — MPTCP

let config = URLSessionConfiguration.ephemeral

config.multipathServiceType = URLSessionConfiguration.MultipathServiceType.handover
let session = URLSession(configuration: config)

let url = URL(string: "http://multipath-tcp.org/data/uml/vmlinux_64")

let task = session.dataTask(with: url!, completionHandler:{...})

task.resume()

URLSessionConfiguration.MultipathServiceTypeは4つのmodeがあり、違いは以下のとおりです

  • none: MultipathTCPを使用しない
  • handover: Wi-Fi回線を優先します。LTEなどのキャリア回線からコネクションを維持したままシームレスにWi-fiにハンドオーバーします
  • interactive: レイテンシを最小にするような経路を使用します。データ量が少ない場合のみ仕様すべきで
  • aggregate: 両方の経路を使用してスループットを向上させます(デベロッパーのみが使用できます)

大まかにデベロッパーがモードを選択できることがわかります。

しかし、ファイアウォールなどによりMP_CAPABLEやMP_JOIN付きハンドシェイクがブロックされてしまったときの挙動や、レイテンシの向きの考慮(参考:One Way Latency Considerations for MPTCPdraft-song-mptcp-owl-01 - One Way Latency Considerations for MPTCP)や、NAT64下での問題(参考pdf)など、実際に試してみないと分からない気はします。


LinuxでのMPTCP対応

実際に試す場合はサーバサイドも準備する必要がありますので、少々古い記事ではありますが参考までに
asnokaze.hatenablog.com

Happy Eyeballs Version 2 の仕様 (RFC8305)

2017/12/22 RFC8305として標準化されました。
なお、下記内容は標準化中の内容につき一部古いかと思われます。


IPv4IPv6両方が利用可能なデュアルスタック環境において、通信する際にIPv6IPv4を並列的に試してより早く通信の確立が出来た方を試す Happy Eyeballs という仕組みがあります。

より環境の良い方を使うだけでなく、IPv6で通信を試みてからうまくいかない場合にIPv4にフォールバックするのでは無いためより早く通信通信の確立ができます。

Happy EyeballsはRFC 6555で標準化されておりますが、Appleの人によって提案されているHappy Eyeballs Version 2の仕様がIETFで議論されているので簡単に読みます。

Happy Eyeballs Version 2

Happy Eyeballs Version 2は、RFC 6555の頃より何点か変更があります。

まず、大きなところで、名前解決について非同期名前解決の方式が追加されました。今まで名前解決についての規定はありませんでした。AとAAAAを同期的に名前解決する実装もありましたが、早く名前解決出来た方から通信を開始すべき(SHOULD)ということになりました。

従来実装

提案方式

(3月に行われたIETF98の資料(pdf)より)

この非同期式名前解決が実施できない場合は(APIがない場合)、IPアドレスファミリごとに別スレッドを使って同等の事ができる。

その他

  • Happy Eyeballs中に、名前解決の結果が変わった場合の対応について。削除された場合は施行を継続、アドレスが増えた場合は適宜施行リストに追加
  • 以前の通信結果(IPアドレス毎のRTTや、以前TCP Fast Openを使った)などにより、通信施行順番の変更が可能に
  • NAT64 と DNS64を持つIPv6 only環境について

今後

3月に行われたIETF98では、議事録を読む限り興味を持ってる人も多く、またその後にWG Draftに昇格していることからも引き続きホットな議論になるものと思われる。

今月7月に行われるIETF99のv6ops WGのAgendaにもHappy Eyeballs Version 2の議論が行われる予定と鳴っている。