RFC 8307 WebSocketにおけるWell-Known URIの標準化

RFC 8307 Well-Known URIs for the WebSocket Protocol」でWebSocketにおいても Well-Known URIが使用できるようになりました。

この界隈では珍しく、個人ドラフトの-00から一気にRFCになっています
https://datatracker.ietf.org/doc/rfc8307/

Well-Known URI

httpやhttpsでは、Well-Known URIというものがあり特定用途に使用するURIが予約できるようになっています (RFC 5785)

例えば、https://example.com/.well-known/○○○○○ のように /.well-known/ 以下に特定用途のURIが予約されます。

現在登録されているWell-Known URIの一覧は、IANAのページから確認できます (URL)

例えば以下のような物があります

  • /.well-known/acme-challenge/ : 証明書自動発行のためのプロトコルであるACMEで、チャレンジに使用される
  • /.well-known/http-opportunistic : HTTPで日和見暗号を使用する際の情報が記載される
  • /.well-known/dns-query : DNS over HTTPのリクエスト先URIとして使用される(策定中の仕様)

WebSocketの場合

Well-Known URI自体を定義するRFC5785ではHTTPとHTTPS スキームの場合についてのみ言及しており、ws://やwss://での使用は定義されておりませんでした。

そこでRFC 8307では、ws://やwss://でもHTTPの場合と同様に Well-Known URIに対応する事が決められました。

用途

なぜこのような仕様が出てきたのか一見わからなかったのですがMLを遡ると、「CoAP (Constrained Application Protocol) over TCP, TLS, and WebSockets」という提案仕様においてWebSocketでもWell-Known URIを使うようだ。

CoAPは、IoTデバイスなどのリソース・ネットワークが制限された環境下での使用を目的とした軽量アプリケーションプロトコルで、この提案仕様はそのCoAPをWebSocket上で通信させる仕様のようだ。

以下のように、ws://でもWell-Known URIを使用している

ws://example.org/.well-known/coap

具体例を見るに、WebSocketのアップグレード手順内で使用されるエンドポイントのようだ。

            GET /.well-known/coap HTTP/1.1
            Host: example.org
            Upgrade: websocket
            Connection: Upgrade
            Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
            Sec-WebSocket-Protocol: coap
            Sec-WebSocket-Version: 13

なるほど。

2017年 振り返り

2017年を振り返る

今年はブログ記事をたくさん書いた。
60記事、そのうち internet-draftタグの付いたものは33にもなる、W3Cは10であった。

ブログに加え、登壇や執筆などを行えた。さらに、Mozaic.fmにも出させてもらえた。
mozaic.fm

某省庁に呼ばれたり、IETFにもつづがなくオフライン参加出来た。

反面、うまく進められなかった案件もあるし、手を動かしたりすることも少なかったし、コードも全然書けていない。

来年は、標準化にコミット出来るように頑張りたい。

ただ、どんなに忙しくても、華はなくても、バズらなくてもInternet-Draftを読んでブログ書く事は続けたい

今年も皆様ありがとうございました。来年もよろしくお願いします。

今年の記事一覧 (降順)

DTLSにコネクションIDを導入する提案仕様

追記 20180702
The DTLS Protocol Version 1.3」で、DTLS1.3の仕様でconnection_id拡張について言及されています


DTLSにコネクションIDを導入する「The Datagram Transport Layer Security (DTLS) Connection Identifier」という提案が出ています。

DTLS1.3の議論を進める中で出てきたトピックであり、既にWG Draft になっています。

DTLSとセッション

DTLSはUDPのようなコネクションのないトランスポート上で暗号化された通信を行うためのプロトコルです。下位層にコネクションのないトランスポートを使っている一方で、現在のDTLSは送信元IP・送信先IP・送信元ポート番号・送信先ポート番号でDTLSのセッションを識別しています。

そのため、それらのうちどれかが変わってしまうと、鍵交換を含むハンドシェイクを初めからやり直す必要があります。特にIoTデバイスなどが普及していく中で、移動する端末や通信頻度が低いもの(NATリバインディングが起こる)は容易に起こるでしょう。さらにそういった端末はリソースが潤沢でない可能性があります。

DTLS Connection Identifier では、DTLSにコネクションIDを導入しIPやポート番号が変わっても通信を維持できるようにします。これにより、ハンドシェイクを1からやり直す手間が省けます。

DTLS Connection Identifier

The Datagram Transport Layer Security (DTLS) Connection Identifier」では、DTLS1.2とDTLS1.3両方について対応します。

この提案仕様では、DTLSに以下を追加します

  • connection_id拡張:ClientHelloとServerHelloで使われる、コネクションIDの使用をネゴシエーションするための拡張
  • RequestConnectionId:DTLS1.3で相手にコネクションIDを要求するメッセージ
  • NewConnectionId:DTLS1.3で相手にコネクションIDを通知するメッセージ (コネクションIDをすぐに使うか、予備か指定可)
  • Record Layer Extensions:レコードに現在のコネクションIDを指定できるように変更される。

例えば、DTLS1.2では、以下のようにcidが追加されます。

     struct {
        ContentType type;
        ProtocolVersion version;
        uint16 epoch;
        uint48 sequence_number;
        opaque cid[cid_length];               // New field
        uint16 length;
        select (CipherSpec.cipher_type) {
           case block:  GenericBlockCipher;
           case aead:   GenericAEADCipher;
        } fragment;
     } DTLSCiphertext;

コネクションIDの使用

DTLS1.3及びDTLS1.2で微妙に手順は異なるが、ハンドシェイク中にconnection_id拡張でコネクションIDの使用が合意されると、以降コネクションIDが含まれたメッセージを交換するようになる。

以下は、DTLS1.2の例である
f:id:ASnoKaze:20171230233015p:plain

将来のQUICをデプロイしやすくするための取り組みと議論 (QUIC GREASE)

MozillaのM. Thomson氏より「More Apparent Randomization for QUIC」というinternet draftが出ています。


## QUICのここまで
QUICはIETFで標準化が進められています。当初は2018年3月が一つのマイルストーンになっていましたが、スコープとマイルストーンの議論をへて、8ヶ月後ろ倒しの2018年11月がIESGへの提出目標となっています(URL)。


後ろ倒しになりましたが、中間ミーティングや実装を持ち寄っての相互接続テストを積極的に行っていくことで議論はより加速する見込みになっています。


また、あわせてスコープの議論も行われ、QUIC V1ではアプリケーションプロトコルとしてHTTPのみをターゲットとすることになっています。V1を策定している最中ですが、将来的にV2が出来た時にいかにそれをデプロイし易いようにするかという議論が盛り上がっています。

Google QUICでは、バージョンアップに伴い既に問題が起きている事が下記の論文で報告されています。
asnokaze.hatenablog.com


TLS1.3でも、この問題に苦しめられ何度も試行錯誤をやりなおしている。

TLS1.3と疎通性の問題

IETF100でも報告されているとおり(URL)、TLS1.2の接続失敗が2.2%だったのに対して、TLS1.3では3.9%でした。


TLS1.3のようにプロトコルのバージョンをあげた時に、FWといった中間装置が不具合を起こし通信をブロックしていたのです。Googleでは、Canonのプリンタなど幾つかを実際に買い動作確認を行ってたりします(URL)


このように、プロトコルのバージョンを上げると中間装置が誤作動を起こしてデプロイが困難になるということがわかってきています。


TLSでは、未知の拡張で不具合が起きないことを確かめるように、以下のようなとりくみも行われています。
jovi0608.hatenablog.com

QUICでの工夫

QUICでは多くの部分を暗号化するとともに、TLSハンドシェイク相当部分を隠蔽する仕組みが有ります。


通常平文であるTLSハンドシェイクのメッセージですが、connection idとV1固有のsaltから導出されるシークレットで暗号化します。これは、メッセージの機密性を得るためのものではなく、将来プロトコルのバージョンを上げた時に、V2固有のsaltを使うようにすることで、V2に対応していないノードはメッセージを見ることが出来ないようするためです。

    quic_version_1_salt = afc824ec5fc77eca1e9d36f37fb2d46518c36639

    handshake_secret = HKDF-Extract(quic_version_1_salt,
                                    client_connection_id)

    client_handshake_secret =
                       HKDF-Expand-Label(handshake_secret,
                                         "QUIC client handshake secret",
                                         "", Hash.length)
    server_handshake_secret =
                       HKDF-Expand-Label(handshake_secret,
                                         "QUIC server handshake secret",
                                         "", Hash.length)

このように、最新のバージョン固有のパラメータを使って隠蔽することで、対応してないなら見ることが出来ないようにすることで、将来のバージョンでの中間装置からの影響を緩和しようとしています。
( quic_version_1_salt は、この仕組が導入された draft06のコミットハッシュです (URL) 。これを使うことでdraft-06以降にうまれた値ということが分かります。 )


さらに、以前書いたVariantsもパケットのフォーマットを将来的に固定することで影響を緩和しようとするものです。
asnokaze.hatenablog.com

More Apparent Randomization for QUIC

話が長くなりましたが、QUICにまだ隠蔽されていないPacket Types・Packet Numbers・KEY_PHASEというフィールドについても隠蔽できないかについて検討しているのが、「More Apparent Randomization for QUIC」です。


このInternet Draftでは、小さなフィールドをシークレットで暗合するのは適切でないとし、幾つかの隠蔽案を検討しています。


それぞれの方式でフィールド毎にメリット・デメリットがあるが、このInternet Draftでは考察までにとどめている。


これらの方式はinvariantsの領域にも使用できるが、将来的にこの仕組を使い続ける必要が出来てしまうため、バージョン固有の領域などに使うことで将来的にも改善していくことができる。

ライブコンテンツにおけるHTTP Rangeリクエストを改善する提案仕様 (RFC8673)

追記2019/11/28
RFC8673になりました


ライブコンテンツやログデータといった常に大きくなり続けるコンテンツに対するRangeリクエストを改善する提案仕様がIETFのHTTPbisで出ています。

その仕様は「HTTP Random Access and Live Content」であり、すでにWGアイテムとなっておりWorking Group Last Callを迎えようとしてます。

現状のライブコンテンツとRangeリクエス

常にコンテンツが大きくなり続けるファイルに対して、Rangeリクエストでデータを受信し続ける場合を考えます。クライアントはRangeリクエストを繰り返すような流れになります。

クライアントはRangeヘッダで1000バイト目より後ろを要求します。Rangeヘッダでは後ろの明示しないことも出来ます

HEAD /resource HTTP/1.1
Range: bytes=1000-
...


サーバはライブコンテンツのうち現在利用可能な範囲を返しますが、Content-Rangeヘッダでレスポンスデータの範囲を示します。 /* をつけることでこのコンテンツの完全な長さが不明であることを示しますが。今回のレスポンスは1000-2000バイトのコンテンツになります。

HTTP/1.1 206 Partial Content
Content-Range: bytes 1000-2000/*
...


クライアントは増えた分のコンテンツを読み込むためにまたRangeリクエストを送信します。

HEAD /resource HTTP/1.1
Range: bytes=2000-
...

提案仕様

上記の例では、Rangeリクエストを繰り返し最新分のデータを取得します。これは、毎回HTTPリクエストを行うのでオーバーヘッドがあるとともに、最新データの取得が送れる事になります。

そこで、Rangeリクエストでもライブコンテンツを受信し続けられるようにするのが今回の「HTTP Random Access and Live Content」という仕様です。

クライアントは"live"コンテンツにRangeリクエストを送る際、end positionに大きな値を指定します

GET /resource HTTP/1.1
Range: bytes=1230000-999999999999


この仕様に対応するサーバは、このリクエストを"live"コンテンツへのリクエストとして解釈し、Content-RangeにRangeヘッダのend positionと同じ値を指定します。

HTTP/1.1 206 Partial Content
Content-Range: bytes 1230000-999999999999/*

上記のレスポンスを受け取ったクライアントは、end positionに同じ値が設定されているのでサーバがこの仕様に対応しており、liveコンテンツのレスポンスであることがわかります。

実装

IETFで発表されたとおり、下記URLで動作確認できます (発表資料)

通常のリクエストをすると、現在可能な部分までのレスポンスしか取得できません

$ curl  http://ietf100.ecaspia.com:8000/live/nasatv.ts -v -H "Range:bytes=40403203-"   >/dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 72.14.183.246...
* Connected to ietf100.ecaspia.com (72.14.183.246) port 8000 (#0)
> GET /live/nasatv.ts HTTP/1.1
> Host: ietf100.ecaspia.com:8000
> User-Agent: curl/7.47.0
> Accept: */*
> Range:bytes=40403203-
>
< HTTP/1.1 206 Partial Content
< Content-range: 40403203-42617155/*
< Date: Sun, 03 Dec 2017 07:09:53 GMT
< Transfer-encoding: chunked
< Content-type: video/mp2t
< Accept-ranges: bytes
< Cache-control: max-age=3600
<


今回の"live"コンテンツ用のRangeリクエストをすると、継続してデータがダウンロードされます

$ curl  http://ietf100.ecaspia.com:8000/live/nasatv.ts -v -H "Range:bytes=40403203-9999999999999"   >/dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 72.14.183.246...
* Connected to ietf100.ecaspia.com (72.14.183.246) port 8000 (#0)
> GET /live/nasatv.ts HTTP/1.1
> Host: ietf100.ecaspia.com:8000
> User-Agent: curl/7.47.0
> Accept: */*
> Range:bytes=40403203-9999999999999
>
< HTTP/1.1 206 Partial Content
< Content-range: 40403203-9999999999999/*
< Date: Sun, 03 Dec 2017 07:12:36 GMT
< Transfer-encoding: chunked
< Content-type: video/mp2t
< Accept-ranges: bytes
< Cache-control: max-age=3600
<

QUIC Invariantsの議論 (RFC 8999)

2021/09/12 追記: このとき議論されていた仕様は RFC8999として標準化されています


QUICは現在IETFで標準化が進めれています。

QUIC WG出来、本格的に標準化が開始して1年ほど立ったところで、スケジュールとスコープの議論(QUIC - Our schedule and scope)が出てきており、QUIC Version1としてどの機能を標準化する話が行われてる一方で、Invariantsというトピックが出てきています。

QUIC Version 1と今後のバージョンで、パケットのレイアウト・パラメータを不変な値、将来のバージョンで変更されないものとするかという議論です。

TLS1.3の議論で起こっているように、プロトコルのバージョンアップによってファイアウォールなどの中間装置が誤作動し通信が阻害される事があるというのはおおきな課題になってきています。

実際、Google QUICでもパケットのレイアウトを変更した所一部のファイアウォールでおかしな挙動をしたことが、Googleの論文でも触れられています。
asnokaze.hatenablog.com

そこで、QUICでは将来のバージョンをデプロイしやすくするために、変更されないパラメータというのを事前に決めていく流れに有ります。

Version-Independent Properties of QUIC

上記のとおり、QUICのパラメータと意味と場所を固定にする提案は、「Version-Independent Properties of QUIC」として提出されています。

まだ議論の最中ですが、今のところ、各パケットのコネクションID・バージョン番号の位置及び、バージョンネゴシエーションパケットは将来も変更されないとしています。

これによってそのコネクションのバージョンが何なのか経路上からも確認できるので、何かしらの処理があるにせよ非対応のバージョンでおかしな挙動をするファイアウォールは少なくなるものと思われます

ロングヘッダ

ロングヘッダでは、ロングヘッダを示す先頭1bit、Connection ID、Versionの位置が将来的にも固定されます
f:id:ASnoKaze:20171202150839p:plain

ショートヘッダ

ショートヘッダでは、ショートヘッダを示す先頭1bit、Connection IDの位置が将来的にも固定されます
f:id:ASnoKaze:20171202151114p:plain

バージョンネゴシエーションパケット

バージョンネゴシエーションパケットでは、ロングヘッダの固定に加え、バージョンが0であり、続いてサポートバージョンが続く事が将来的にも固定されます
f:id:ASnoKaze:20171202151149p:plain

その他

その他にもこのドキュメントでは、現在のQUICではそうなっているが将来変更される可能性もあるので、幾つかの仮定をすべきでない事も列挙しています。

例えば、ロングヘッダがコネクション確立時のみに使用されるという仮定や、パケット番号の出現位置に関する仮定があげれれています。

QUICのSpin Bitの議論

20181111追記
IETF103でも意見が割れ議論となりましたが、Humの結果以下のようになりました。

Spin Bitの仕様は拡張仕様となり必ずしも実装する必要はなくなりました。QUICのコア仕様ではスピンビット用のビットを確保する(好きな台を入れて良い)だけとなっております。なおSpin Bitの領域は、ネゴシエーションもおこなわずに使用されます。


QUICはUDP上でTCP+TLS+HTTP/2の機能を持つ新しいプロトコルであり、IETFで絶賛標準化中です。7月にあったIETF99より、Spin Bitというトピックが議論されているので簡単に書く。

以前、下記記事にも書いたとおりQUICではACKなどを含む殆どの制御情報も暗号化されており、経路上でパケットを観測しても得られる情報は多くありません。
asnokaze.hatenablog.com

パケットロス率やRTTの情報(タイムスタンプ)はネットワークの最適化に利用されているようで、TCPのようにQUICでもそれらの情報を経路上から見れるようなパケットにするか?という議論の中で出てきたのがSpin Bitです。

Spint Bitはパケットが往復するたびにトグルするフラグで、このフィールドは暗号化されていないので経路上からも観測できます。このフラグを観測することで概ねのRTTが推測できる仕組みになっています。

Spint Bit

Spin Bitの仕様については、コアドキュメントではなく「The Addition of a Spin Bit to the QUIC Transport Protocol」という提案にて定義されている。

f:id:ASnoKaze:20171201003908p:plain

ハンドシェイクで使用されるロングヘッダのRTTを測定するのは容易であるので、Spin Bitのフラグはショートヘッダにのみ存在する。Sで表記されている部分がSpin Bitであり、現在仕様とくらべてフラグ部分の位置がずれただけである。

このSpin Bitは以下のように設定されます

  • サーバが送るSpin Bitは、クライアントから受けっ取ったパケットの内、パケット番号が最も大きいメッセージのSping Bitと同じ値になります。
  • クライアントが送るSpin Bitは、サーバから受け取ったパケットの内、パケット番号が最も大きいメッセージのSpin Bitをトグルした値になります。ただし最初に送るときは0。

全てのパケットが順番通りに送られたとすると、Spin Bitは以下のようにトグルされます。パケットがちょうど1往復したときにトグルされることがわかります。

f:id:ASnoKaze:20171201010247p:plain:w300

しかしタイムスタンプなどを含まないため、サーバ・クライアント側での処理時間や何かしらの待ち時間があるとその影響をうける点や、パケットの順番の入れ替わりに対してはヒューリスティックな対応が必要なようです。

その他

このSpin BitをQUICの仕様に組み込むにあたって、デザインチームによる実験が行われています。
これは実際にQUICの実装にSpin Bitを組み込み幾つかの通信状況でRTTの測定を行うことと、プライバシー上の問題の考察です。

詳しい報告は以下のとおりです。
Latency Spinbit Implementation Experience

RTTの測定について一定の有効性があることが確認されるとともに、プライバシーの問題(特に地理情報の特定等)についても現状(あくまで現状)問題がないという話になっています。

今後もこのようにトランスポートレイヤの変更が議論になる可能性はありますが、このようにデザインとして十分に実験・検討する作業が行われるものと思われます。

チェアである、mnotもMLで触れています
Spin bit discussion - where we're at