SSブログ

TCPのRSTが飛ぶタイミングと認証が必要なHTTP POST [TCP]



久々のTCPネタ。

TCPのRSTについてこの記事でも簡単に触れた。
このRSTがどのようなタイミングで送信されるかというと、OSのソケットバッファに
受信データがまだ残っているにもかかわらず、アプリがcloseを実行したときに送信される(Linuxのお話)。

つまり、まだ受け取るべきデータが残っているにもかかわらず、有無を言わさずアプリが
closeを実行してきたので、これは異常な状態だ、ということでRSTを送信し、
その受け取るべきデータを送ってきた送信者側に、「もうデータは送らないでくれ」と伝えたいのである。

さて、これを踏まえて考えると、実は下記のようなケースで問題が発生する。

  1. マシンA(HTTPクライアント)はHTTP POSTのヘッダ(仮に1024バイトとする)とHTTP POSTのボディ(仮に8192バイトとする)をマシンB(HTTPサーバ)に送信した。
  2. マシンBのOSは、9216バイト(1024+8192)すべてネットワークから受信し、受信ソケットバッファに蓄えた。
  3. マシンBのHTTPサーバアプリは、適当なバイト(仮に2048バイトとする)をreadした。
  4. マシンBのHTTPサーバアプリは、readした2048byteのデータをHTTPにのっとってparseし、HTTP POSTヘッダを解釈した。
  5. マシンBのHTTPサーバアプリは、受け付けたHTTP POSTリクエストは認証が必要だと判断し、まずは"401 Unauthorized"レスポンスのレスポンスラインのみをwriteした。
  6. マシンBのHTTPサーバアプリは、続いて401エラーのレスポンスヘッダ群をwriteした。
  7. マシンBのHTTPサーバアプリは、401エラーを返す処理を終えたのでソケットをcloseした。
  8. マシンBのOSは、401エラーのレスポンスラインをマシンAにむけてネットワークに送信した。
  9. マシンBのOSは、Nagleアルゴリズムにより401エラーのレスポンスヘッダ群のネットワーク送信は待機している。
  10. マシンBのOSは、closeを検出し、401エラーのレスポンスヘッダ群を返すことなくRSTをマシンAにむけてネットワークに送信した。
  11. マシンAは、401エラーのレスポンスヘッダ群を受信することなくRSTを受信してしまい、認証のための情報(nonceなど)が受け取れないので認証が実行できない。

この現象を回避するためには、

マシンBがHTTP POSTボディまで全て受信してから、認証フェーズに入ればよい、

もしくは、

レスポンスラインとレスポンスヘッダ群を同時にwriteすればよい、

ということではあるが、HTTPレイヤで下記のような手順を踏むことで解決することもできる。

マシンAがHTTP POSTのボディはすぐに送信せずに、HTTP POSTリクエストのヘッダに、
"Expect: 100-continue"を含めて送信する。

もし、マシンBが認証が必要だと判断した場合は、401エラーを上記のフローと同様に返してくるが、
POSTボディを送信していないので、RSTが飛んでこない。

また、認証が必要ない場合は、マシンBから"100 Continue"レスポンスが返ってくるので、
これを受けてマシンAはPOSTボディを送信すれば良い。


UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI:ソケットとXTI

UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI:ソケットとXTI

  • 作者: W.リチャード スティーヴンス
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2000/04
  • メディア: 単行本



マスタリングTCP/IP 入門編 第4版

マスタリングTCP/IP 入門編 第4版

  • 作者: 竹下 隆史
  • 出版社/メーカー: オーム社
  • 発売日: 2007/02/24
  • メディア: 大型本



Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus)

Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus)

  • 作者: 山本 陽平
  • 出版社/メーカー: 技術評論社
  • 発売日: 2010/04/08
  • メディア: 単行本(ソフトカバー)







タグ:TCP RST HTTP POST

人気ブログランキングへ

RSTを受信したときの挙動 [TCP]



TCPにはRSTというパケットがある。
RSTとは、簡単に言うと強制終了を示すものである。

このRSTを受信したときの挙動がLinuxとWindowsで違うことを発見したのでメモしておく。
(WindowsはXPとVistaです。)

Linuxの場合、
パケット1→パケット2→RSTという順序で受信したとすると、
readシステムコールは、パケット1、パケット2をユーザプログラムに読み込ませた後にエラーを返す。

Windowsの場合、
パケット1→パケット2→RSTという順序で受信したとすると、
パケット1、パケット2がソケットバッファに残っていたとしても、
recv関数は、ユーザプログラムにパケット1、パケット2を受信させることなくエラーを返す。

RSTが相手から送られてきた時点で、そのストリームは異常な状態に陥ったということなので、
受信済みのデータ(パケット1、パケット2)をユーザに読ませるかどうかは重要ではないはず、
ということで、このような差が生まれてしまったのだろう。


マスタリングTCP/IP 入門編 第4版

マスタリングTCP/IP 入門編 第4版

  • 作者: 竹下 隆史
  • 出版社/メーカー: オーム社
  • 発売日: 2007/02/24
  • メディア: 大型本







タグ:TCP RST

人気ブログランキングへ

NagleアルゴリズムとDelayed Ack問題 [TCP]



TCP の世界で、Nagle アルゴリズムと Delayed Ack が組み合わさった時の
問題は結構有名な話。

ネットワークソフトウェアエンジニアのバイブル


UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI:ソケットとXTIUNIXネットワークプログラミング〈Vol.1〉




にもしっかりと載っている。

簡単に説明しておくと、

Nagleアルゴリズム
送信データ(セグメント)をOSが溜め込む。溜め込んだデータを送信するタイミングは下記の通り。 OS(TCP)の気持ちも書いておく。
* すでに送信しているデータのAckが返るまで。
 → お、さっき送ったデータのAckを受け取ったぞ。
   じゃあ次のデータを相手に送ってやらなきゃ。
* 溜め込んでいるデータ量がMSSを超えるまで。
 → やべ、データをたくさん(量的に)溜め込みすぎた。
* タイムアウトを迎えるまで(200ms?)
 → やべ、データをたくさん(時間的に)溜め込みすぎた。


Delayed Ack
Ackの送信を遅らせる。Ackを送信するタイミングを下記の通り。 OSの気持ちも書いておく。 * Ackをpiggybackできる送信データがある。
 → 送信データがあるのでさっき受け取ったデータのAckもついでに返すよ。
* 一定量以上データを受信した。
 → そろそろAckを相手に返してやらないと相手も不安になるよね。
* タイムアウトを迎えるまで。
 → そろそろAckを相手に返してやらないと相手も不安になるよね。


この二つは、ともにネットワークに細切れのデータがポコポコ流れないように
効率化するための仕組み。
通常は、特に問題ないが、二つが組み合わさると問題が発生する場合がある。

1. マシンAのアプリaは、データを32byteのデータと96byteのデータを続けて送信。
2. マシンAのOSは、32byteのデータを即座に送信。
3. マシンAのOSは、96byteのデータの送信を即座に行わず溜め込む(Nagle)。
4. マシンBのOSは、32byteのデータを受信。
5. マシンBのアプリbは32byteのデータを受信。
  続く96byteのデータを受け取るまで待機するstateだった。
6. マシンBのOSは、32byteのデータのAckを送信しない(Delayed Ack)。
7. マシンAのOSは、96byteのデータをNagleにおけるタイムアウトを迎えるまで送信できない。

こんなフローが起こることって本当にあるの?と思うかもしれないが、
実は身近な例が HTTP POST

マシンAのアプリaは、1で32byteのHTTP POSTのRequest Headerをwriteし、
続けて96byteのRequest Bodyをwriteしたとする。

この場合、往々にして上記のケースに当てはまり、Request BodyのデータをOSは
Nagleのタイムアウトを迎えるまで待たされる。

解決策は下記の通り、

* アプリはRequest HeaderとRequest Bodyを一辺にwriteする。
つまり128byteのwriteをするか、それぞれのデータを同時にwritevする。
(想定しているOSはLinux。Windowsもきっと一緒だと思うけど、writevがあるのかは知らない。)
アプリaが、128byteのデータの塊をOSに渡してあげれば、
たいていの場合、OSは128byteのデータをいっぺんに送信するので、上記の問題は起こらない。

* Nagleアルゴリズムをオフにする
socketオプションでTCP_NODELAYを指定し、マシンA側でNagleアルゴリズムをオフにする。


HTTPの場合は、1コネクションで双方向にインタラクティブに通信することは(まず)なく、
A→Bという向きでデータが流れたら(Request)、
その後はB→Aという向きでデータが流れる(Response)
ので、Nagleはオフで良いと思う。

ちなみに、Delayed Ackをオフにする方法は、TCP_QUICKACKをsocketオプションで指定することだが、
この設定は持続的なものではなく、ユーザの計り知れないタイミングで再びDelayed Ackが有効に
なってしまうのでやめたほうが良い。




タグ:TCP Nagle 遅延Ack

人気ブログランキングへ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。