HTTP PUTリクエストにContent-Rangeヘッダを付けられるか?(再開可能アップロードについて)

HTTPではファイルを取得する際に、ファイルの特定範囲のみを要求するRangeリクエストを行うことができます。

ファイルをアップロードする際にContent-Rangeヘッダ(MDN)を使用してもよいのでしょうか?ファイルを分割してそのRangeごとに少しずつ送れば、途中でネットワーク断があってもそこからやり直せるので便利そうです。

例えば、次のようなPUTリクエストは正しいのでしょうか?

PUT UPLOAD_URL HTTP/1.1
Content-Length: 524888
Content-Type: video/*
Content-Range: bytes 0-524287/2000000

{bytes 0-524287}

仕様の観点で、紐解いていこうかと思います。

RFC7231

HTTP/1.1のセマンティクスを定義するRFC7231では、PUTリクエストについて下記の通り書いています (URL)

Content-Rangeヘッダーフィールドを含むPUTリクエストに対して400(Bad Request)レスポンスを送信する必要があります

つまり、仕様上は利用が許可されていません。

利用例

しかし、IETF HTTP WG のメーリングリスト(URL)では、下記のようなサービスで、Content-RangeヘッダーのついたPUTリクエストがサポートされていることが報告されています。

Youtubeでも「再開可能アップロード」としてこの方法を利用しています。
developers.google.com


これをうけ、HTTPの仕様改定にともないこのPUTリクエストについて仕様上どう扱うか議論になりました。

この、HTTPセマンティクスの改訂版仕様について触れていきます。

HTTPセマンティクス仕様の改訂版

HTTP/1.1とセマンティクスの仕様であるRFC7230 〜 RFC7235は、現在改定作業中です。

これは、HTTP/1.1のフォーマット仕様と、HTTPセマンティクスを分離するための作業です。これによってHTTP/2(http2bis)やHTTP/3(draft-ietf-quic-http)の仕様からセマンティクスのみ参照することができます。また、その他にも細かい仕様バグが修正されています。まだ、RFCとはなっていませんが、下記から参照することができます。

先日、この新しいHTTPセマンティクス仕様で、Content-Rangeヘッダのついた PUTリクエストの制限を緩和する変更が入りました。
github.com

新しい仕様では下記の通り書かれています (意訳) (URL)

一部のサーバはContent-RangeヘッダがつくPUTリクエストをサポートしていますが、サポートには一貫性がありません。


そのようなリクエストは、Content-Rangeヘッダで指定された範囲でURIリソース書き換える。

このように既存実装の存在を配慮しつつ、利用を緩和しています。

サーバがContent-Rangeヘッダの通り解釈できず、まるごと上書きしてしまう可能もあるため、事前にサーバが対応していることを知っている時のみに使用するのが良さそうです。

おまけ

部分的アップロードに関しては、じつは別の仕様があったりしました。書かれている通りPATCHメソッドを使うことが想定されています。
asnokaze.hatenablog.com

2020年 プロトコル標準化界隈とasnokaze振り返り (通算400記事目)

今年もたくさんの方に記事をお読みいただきまして、ありがたい限りです。

covit-19が世間を騒がしてます通り、公私ともに多くの混乱がありましたが無事年末を迎えることが出来ました。やはり、反省としてはアウトプットの質が下がったなと感じています。来年は、アウトプットをもっと出していきたいなと思います。

今回の振り返りは、例年恒例の個人活動の振り返りに加えて、IETF 標準化界隈についても触れておこうかと思います。

IETF界隈

今年はIETFの本会合は例年と同じく3回開催されましたが、いづれもオンライン開催でした

特にIETF107は3月に開催されましたが、急遽リモートオンリーになりました。そのため、いつもとは異なり初開催のWGやBoFのみの開催となりました。

その後も様々なフィードバックを踏まえ、IETF108, IETF109とリモートのみの開催になっています。遠隔での会議を円滑にすすめるため、ツール類 (humのとり方)なども改善されてきています。

IETF本会合では、実装を持ち寄ってハッカソンを行っていましたが、リモートオンリーになってもWGによってはハッカソンを行っているようです。やはり遠隔でのハッカソンはいつもに比べ難しいようです。その他、有志によって開催されるサイドミーティングも行われていましたが、その数は少なくなっています。

また、新しく出されたdraft (00)の去年比のグラフが共有されています。見ての通り、draft-00のドラフトは去年よりも少なくなっています。様々な要因があると思いますが、やはりオフラインのミーティングがないと、そこで生まれていたものが減ってしまうのかなと思います。

f:id:ASnoKaze:20201231162724p:plain

とはいえ、このような状況下で国内外で標準化活動が継続して行われていること自体は、多くの方の努力のたまものだと思います。本当に素晴らしいことだと思います。

asnokaze

今年も計47記事、たくさん記事を書くことが出来ました。

先に述べたとおり、公私ともに混乱の多い年であり、今年はちょっと質が下がったなと言う気がしています。

カテゴリの累計としては下記のとおりです。"雑"カテゴリが多めですねorz

f:id:ASnoKaze:20201231165535p:plain

TLS1.3 0-RTTにおけるHTTP/2 SETTINGSを改善する提案仕様

MozillaのMartin Thomson氏から「Optimizations for Using TLS Early Data in HTTP/2」という提案仕様が出ています。

もともとHTTP/2(RFC7540)を標準化した時には、TLS1.3(RFC8446)はまだありませんでした。ですので、TLS1.3で導入された 0-RTTデータ (Early Data)や 0.5-RTTデータは 考慮されていません。

HTTP/2では通信直後にSETTINGSフレームを送り合ってからHTTPメッセージの送受信を行います。相手からのSETTINGSフレームの受信を待つことは必須ではありませんが、受信できるまではINITIAL_WINDOW_SIZEやMAX_FRAME_SIZEといったパラメータに違反しないようにメッセージを送る必要があります。

また、特定のHTTP/2拡張を使う場合、例えばWebSocket over HTTP/2などの機能を使う際は、SETTINGSパラメータの交換が必須になります。

このような制約がある中で、TLS1.3 0-RTTでメッセージを送らなければなりませんでした。これを改善するのが「Optimizations for Using TLS Early Data in HTTP/2」です。

EARLY_DATA_SETTINGS

提案仕様の中では、EARLY_DATA_SETTINGSという新しいSETTINGSパラメータを定義しています。これはTLS1.3のEarly Dataでデータを送る際に、以前やり取りしたときのSETTINGSパラメータを利用できるようにします。

  • クライアントは、EARLY_DATA_SETTINGSが1のパラメータを送信することで、この拡張に対応している事をサーバに通知します
  • サーバも、EARLY_DATA_SETTINGSが1のパラメータを送信することで、この拡張に対応している事をクライアントに通知します。なお、このパラメータを送信したあとに発行されたTLS session tickets は現在のSETTINGSパラメータと関連付けられ、session ticketsチケットを利用する際には発行時のSETTINGSパラメータが尊重されます。

使用中では既存のパラメータごとにsession ticketsとともに記憶するかや、0.5-RTTデータで送られてくるサーバ側のSETTINGSフレームについてなど、サーバ・クライアントの振る舞いについて詳しく書かれています。

もちろん、TLS1.3 Early Dataはリプレイ攻撃される可能性があるため、拡張を利用するにしてもそれを考慮する必要があります。サーバ側は冪等に処理できないものに関しては425 Too Earlyを返す必要があります。

asnokaze.hatenablog.com

NTPサーバの奇妙な振る舞いとNTPv5の標準化

NTPv5の標準化が進められています。

そこで、実験として世の中のパブリックNTPサーバにNTPv5パケットを送信してみました。NTPv5の議論は始まったばかりですので、世の中のNTPサーバはNTPv5には対応しているはずがありません。

しかし結果は、驚くことにversionフィールドに5が指定されたNTPv4形式のパケットを返すサーバが居ることがわかりました。NTPv5とNTPv4パケットはバージョンやモードなどの領域を除き互換性がないため、そのようなパケットは破棄するしかありません。

その他にも奇妙な振る舞いをするNTPサーバもいたので、紹介していきたいと思います。

記事の後半では、そのような状況でNTPv5の仕様ではいくつかの工夫が盛り込まれていますので、それについて紹介します。

NTPv5について基本的なところは以前紹介したとおりです。
asnokaze.hatenablog.com

(なお、NTPv5については議論が始まったばっかりですので、今後大きな変更があるかもしれません)

実験

約80のパブリックなNTPサーバに対して下記の4つのパケットを送信します。

  • NTPv4として正常なパケット
  • NTPv4形式だが、バージョンフィールドに5を指定したもの
  • NTPv4として正常なパケットであり、未定義の拡張フィールドを付加したもの
  • NTPv5パケット (draft-mlichvar-ntp-ntpv5-00)

これに対して、どのようなパケットが返ってくるか観測します。

NTPv4として正常なパケット

下記の通り、もちろん正常なパケットが返ってきます (一部、NTPv3を応答しますが、大丈夫です)

  • 10%: NTPv3のパケットが返ってくる
  • 90%: NTPv4のパケットが返ってくる
NTPv4形式だが、バージョンフィールドに5を指定したもの

先述したような、バージョンフィールドに5が指定されたNTPv4パケットが返ってきます。

  • 10%: NTPv3のパケットが返ってくる
  • 70%: NTPv4形式のパケットで、バージョンフィールドに5が指定されたものが返ってくる
  • 20%: 応答なし

例えば、ntpdは将来のバージョンの扱いに不備があるようです
https://github.com/ntp-project/ntp/blob/master-no-authorname/ntpd/ntp_proto.c#L583

NTPv4として正常なパケットであり、未定義の拡張フィールドを付加したもの

NTPv4は拡張フィールドを付加できます。未定義の拡張フィールドを付加したパケットを送ったときのレスポンスを確認します。

  • 75%: NTPv4パケットが返ってくる
  • 25%: 応答なし

(TLS1.3がそうしたように、拡張フィールドでバージョンネゴシエーションをする余地があるか確認したかったのですが、いくつかのサーバはパケットをドロップしてしまいます)

なお、1件だけでしたが、未定義の拡張フィールドを送ると、同じ拡張フィールドをつけて送り返してくるようなサーバもいました。

NTPv5パケット

NTPv5はNTPv4パケットと互換がないため、多くは不正なパケットとして破棄されますが、例のパケットが返ってくることもあります

  • 10%: NTPv4形式のパケットで、バージョンフィールドに5が指定されたものが返ってくる
  • 90%: 応答なし

NTPv5での工夫

不正な振る舞いをするサーバに対して、NTPv5の仕様ではいくつかの工夫が入ってます。

いくつか紹介します。なお、詳細については仕様を参照ください

不正なパケットの識別

NTPv5パケットを送った場合に、バージョン5が指定されたNTPv4が返ってくると、実装としては破棄するしかありません。

どのように、そのような不正なパケットを識別するのでしょうか。実は、NTPv5ではランダムなCookieをリクエスト時に送り、レスポンスにその値をつけて返すような仕様になっています。この仕組みはNTPv5から入ったものですので、Cookieフィールドを見れば不正なパケットを識別できます。

サポートバージョンの示し方

ドロップされてしまうかもしれないので、いきなりNTPv5パケットを送りつけることはできません。そこでクライアントは、NTPv4パケットを送りつつ自身はNTPv5にも対応していることをサーバに伝えたいです。もし、サーバがNTPv5に対応していれば、NTPv5のパケットで応答が返ってくることを期待できます。

新しくNTPv4の拡張フィールドを定義し、それを使ってサポートバージョンを示すことが出来るでしょう。しかし、未知の拡張フィールドはドロップされうるので機能しません。

そこで、比較的自由に使えるNTPv4の”Reference Timestamp”フィールドにシグナルを埋め込みます。具体的には 0x4E5450354E545035 ("NTP5NTP5" のASCII値)を指定します。時刻として 2077年9月29日 7時41分です。クライアントは、このようなNTPv4パケットを送信することで自身がNTPv5に対応している事を示します。
f:id:ASnoKaze:20201229013638p:plain

なお、このようなパケットを送っても、パケットがドロップされたりはしなさそうです。

未知の拡張仕様の扱い

今までNTPでは未知の拡張仕様の扱いは決まっていませんでしたが、NTPv5では無視する事がMUSTとなりました。

終わりに

TCPTLS、HTTPがそうであるように、プロトコルの新しいバージョン・新しい拡張機能は現在のインターネットで正しく機能しない例が見つかっていました。

NTPの次期バージョンも同様の問題を抱えています。

この問題は硬直化(ossification)と呼ばれ、プロトコルの標準化についてまわる問題でしょう。簡単にどうこうなる話ではありませんが、この記事によって僅かでもインターネットが良くなればなと思います。

URLリソースの非推奨を示すDeprecationヘッダ

APIのURLや、リソースが非推奨となっている事を示す、新しいDeprecationヘッダを定義する「The Deprecation HTTP Header Field」という仕様が提出されています。

この仕様はすでに、HTTPAPI WGに採用せています。なお、HTTPAPI WGは2020年10月 新しく出来たWGで、HTTPのAPI利用のための拡張を議論するWGになっています。

Deprecationヘッダ

レスポンスに、下記の様なヘッダを付けることで、そのリソースが非推奨になっている事を示します

Deprecation: true
Deprecation: Sun, 11 Nov 2018 23:59:59 GMT
  • true: 非推奨になっている
  • 日付: 指定した日付で非推奨になる

Linkヘッダ

RFC8288 Web Linking」の仕組みを用いて、 Linkヘッダで追加の情報を与えます

relにdeprecationを指定することで、非推奨に関するドキュメントのリンクを提供できます

   Link: <https://developer.example.com/deprecation>;
         rel="deprecation"; type="text/html"

relにalternateを指定することで、代替エンドポイントを提供できます

   Deprecation: Sun, 11 Nov 2018 23:59:59 GMT
   Link: <https://api.example.com/v1/clients>; rel="alternate"

relにsuccessor-versionを指定することで、次のバージョンエンドポイントを指定します。

  Deprecation: Sun, 11 Nov 2018 23:59:59 GMT
  Sunset: Wed, 11 Nov 2020 23:59:59 GMT
  Link: <https://api.example.com/v2/customers>; rel="successor-version",
        <https://developer.example.com/deprecation>; rel="deprecation"

合わせてsunsetヘッダで廃止時間も示せますね
asnokaze.hatenablog.com

TCPとTLSを連携させるTCPLS

ルーヴァン・カトリック大学のFlorentin Rochet氏らによって「TCPLS: Closely Integrating TCP and TLS」という論文が出されています。

面白そうなのでかんたんにメモしておく。

詳しくは論文や文末記載の資料を御覧ください。

TCPLS

この論文では、TCPTLSを連携させるTCPLSを考案しています。スタック図ではTCPTLSがくっついてるようなものになりますが、くっついた新しいTCPLSというプロトコルというよりかは、TCPTLSが連携して動作できるようにするイメージです。

ですので、飛び交うパケットとしては通常のTCPTLSであり、通信経路上での対応が必要ということはなさそうです。

f:id:ASnoKaze:20201220235407p:plain

もともとTCPTLSはレイヤが分かれていますが、論文では連携させる事による様々なメリットが示されています。

  • TCPLSでは、TCP制御情報をTLS拡張やTLSレコードで送ることが出来ます。通常のTCPではオプション領域が40byteまでに制限されていたり、中間装置(ネットワーク機器)によって削除・改変される可能性があります。TCPLSでは長さの上限もなく、暗号的に保護されます。
  • TCP輻輳ウィンドウとTLSのレコード長を合わせることにより、パフォーマンスを向上させる

(アプリケーションデータはTLSレコードで送受信されます。)

f:id:ASnoKaze:20201221001212p:plain

セッションの開始

TCPLS セッションは、通常のTCPハンドシェイクが行われた後にTLSハンドシェイクが行われます。

またアプリケーションには、TCPLS SocketのAPIを提供している。例えば以下など (詳細は論文を参照)

  • tcpls_connect()
  • tcpls_handshake()
  • tcpls_send()
  • etc...

その他

論文中ではTCPLSの、Happy eyeballs、Streams 、App-level Connection migrationなどにおいてもメリットについても紹介されている。また、機能面におけるQUICとの比較も行われている。

ユースケース

TCPLSではTCPレイヤの制御情報を安全にやりとりできるため、次のような仕様をより安全にできると述べている。

ここが本題であるわけだが、一旦詳細は論文を参照していただきつつ、余力があれば後日深堀りします。

参考資料

なお、IETF109でも発表があったが、具体的に標準化をすすめるというよりかは、TFO, MPTCPでのユースケースについての理解を深めるためと発表者は述べている。

GoogleのPrivate prefetch proxyに関するメモ

先日、Chromiumブログに「Continuing our journey to bring instant experiences to the whole web」という記事が投稿されています。

この記事は、ブラウザのプリフェッチ機能とプライバシーの問題について説明をしています。またGoogleやブラウザでの取り組みについても多く書かれています。詳しくは原文をお読みください。

その中で、private prefetch proxyというものが出てくる。個人的にはとても興味深いところなので、雑にメモしておく。

prefetch機能とプライバシーの問題

Webページの表示の高速化のために、プリフェッチ機能が使用されています。prefetchはユーザが次に読み込むであろうページのリソースを事前に取得することで、ページの表示を高速化します。

特にcross-siteのプリフェッチにおる際に問題になります。プリフェッチを受けるサーバ側では、実際にユーザがアクセスしたかは別として、そのサイトをリンクしているページを開いている事がわかります。

ユーザを識別できないように、prefetch時にはCookieなどは送られないようですが、IPアドレスは分かってしまいます。

このユーザのIPアドレスを隠蔽するのに登場するのが、Private prefetch proxyです。

Private prefetch proxy

Private prefetch proxyはプリフェッチの際に使用するプロキシです。

Chromeはプリフェッチする際に、Googleが運用するPrivate prefetch proxyにHTTP/2 CONNECTメソッドで接続します。このときChromeとプリフェッチ先のサーバはend-to-endで暗号化されるため、プロキシはその中身は見ることは出来ません。プリフェッチ先のサーバはプロキシのIPアドレスしか見えないので、ユーザのIPアドレスを知ることは出来ません。

ようは通常のプロキシとそうかわらなさそうです(ただし、ユーザの手を煩わせて設定するということはありません)。また、Chromeからのみ使えるといったような制限もあるもようです。

この説明だけでは様々な疑問が出て来るかと思いますが、下記リポジトリでFAQに答えています。例えば、コンテンツブロックやISPへの影響などについて書かれています。
github.com

また、Proxyの悪事が懸念されるわけですが、"trusted private prefetch proxy" というものが登場します。

このtrustに関わる部分については、コミュニティと議論しつつ要件などを整理していくというところのようです。

おまけ

trustedなproxyと聞くと、2014年頃にHTTP/2の議論とともに「Explicit Trusted Proxy」というものがあったのを思い出す。

日本語でも言及している記事があるので参考までにどうぞ
blog.nunnun.jp

jxck.hatenablog.com