トップ «前月 最新 翌月» 追記

のろのろのろ雑記


2009年02月10日 [PC][解析] WHR-G54Sルータ内にマジなサーバを立てるとき

_ [PC][解析] WHR-G54Sルータ内にマジなサーバを立てるとき

注意事項

  • あやふやな知識ですから,鵜呑みにするのは推奨出来ません.
  • ツッコミ歓迎.

結論(やるべきこと)

  • ルータのNATは最低限に留める.
  • NATしたポートには,「接続を受け入れる」「接続を拒否する」いずれかの応答を必ず行う.
    • ファイアウォールのステルス機能によって「無応答」になることは避ける.
  • クライアントに切断処理をさせる.
  • 接続数が増えたと思ったら,ポートを閉じてしまう.
  • Webサーバなら,同時接続数を制限したり,Keep-alive timeoutを延ばしたりする.
    • ただし期待薄.

はじめに

 P2P地震情報サーバを運営していますが,その追加機能であるPRCP情報共有プラグインの設計が大変悪く,結構本気で接続を仕掛けてきます.そのために,メンテナンス明けにはアクセスが集中し,ルータには決まって次のようなメッセージが表示され,ブラウズが大変遅く感じられるようになります.

ip_conntrack: table full, dropping packet.

 ぐぐってみれば,おおよそNATセッションテーブルがいっぱいで,これ以上セッションを張れないということは分かります.でも,それでは納得がいきません.なんでそんなにいっぱいになるの?

 調べてみました.

Q. 最大接続数を50に制限しているのに,いっぱいだなんて!

 TCPの接続・切断の流れ(状態遷移図)がヒントになる.主なポイントは,次の2点.

  • 「接続をしようとしている」コネクション (SYN-RECEIVED)
  • ルータ内にあるサーバから切断処理を行い,「迷子のパケットを待つ」状態に移行したコネクション (TIME-WAIT)

 まだ確立していなかったり,終わったかのように思えたりするコネクションだけど,こうしたコネクションもNATセッションとして管理されている.ルータは可能な限りTCPを理解し,TCPの規約・制約に沿った動作を行うようになっているのである.

 だから,最大接続数が50であっても,こうしたセッションによってセッションテーブルはいっぱいになりうる.

Q. SYN-RECEIVEDってなに?

 接続要求を受信した状態のこと.

 例えば,TCP ポート2000〜2009をサーバに転送するよう,ルータを設定したとしよう.ただし,この10個のポートすべてをいつも使用しているわけではない.サーバのファイアウォールでは,2000〜2002の3つのポートは通信のために接続を許可しているが,2003〜2009はブロックしている.また,安全性を高めるために,接続要求を無視するステルスモードを使っている.

 ここで,TCP 2003に接続要求が入ったとする.ルータは接続要求を検出して,NATセッションとしてテーブルに追加するだろう.しかし,サーバはステルスモードによってブロックしていて,何の応答も返さない.

|    サーバ    |        ルータ        | クライアント |
|              |                      |              |
|              |                      | <= 接続要求  |
|              |  [セッション発生] <= |      | (SYN) |
|              | <=    ||             |      |       |
| [FWが無視]<= |       || セッション  |      |       |
|              |       ||      管理中 |    [???]     |
|              |       ||             | (反応が      |
|              |       ||             |   こないぞ?) |
|              |       ||   (60秒後)  |      |       |
|              |  [セッション破棄]    |  [時間切れ]  |
|              |                      |              |

 このとき,クライアントが接続要求をキャンセルするか,ルータ内部でSYN-RECEIVEDのタイムアウト(WHR-G54Sで60秒)を迎えるまで,NATセッションは残り続けてしまう.

Q. TIME-WAITってなに?

 セッションを切断した後,ネットワークを漂流するパケットが消滅するまで一定時間待つ状態のこと.

 あなたが,画像を多用するWebサイトをホストしているとしよう.今日多く見られる「行儀の悪いWebブラウザ」が,RFCの推奨を無視して8ものセッションを確立してしまった.Webサーバは,Keep-aliveタイムアウトになったので,8つのセッションを切断した.

 ここで,8つのセッションはTIME-WAITと呼ばれる状態に移行する.TIME-WAITのタイムアウトはまちまちだが,WHR-G54Sなどでは,2分間NATセッションとしてテーブルに残り続けている.

|    サーバ    |        ルータ        | クライアント |
|              |                      |              |
|      |       |          ||          |      |       |
|   通信中 <=> |  [セッション管理中]  | <=> 通信中   |
|      |       |          ||          |      |       |
|  切断処理 => |          ||          |      |       |
|      |       | => [切断処理検出] => |      |       |
|      |       |          ||          | => 切断受信  |
|      |       |          ||          | <= 切断OK    |
|      |       | <=  [切断OK検出]  <= |      |       |
|  切断完了 <= |          ||          |   [CLOSED]   |
|      |       |          ||          |              |
| [TIME-WAIT]  |     [TIME-WAIT ]     |              |
|      |       |          ||          |              |
|      |       |          ||          |              |
|      |       |          || (2分後)  |              |
|   [破 棄]    |   [セッション破棄]   |              |
|              |                      |              |

 ここで問題.「8セッションを確立する行儀の悪いブラウザと,1024のセッションテーブルを持ったルータがあります(サーバはルータ内にあります).このルータ内のサーバには,1時間当たり何個のブラウザと通信できるでしょうか?

 答え: 1時間あたりおよそ3700個.ある瞬間には, 1024 / 8 = 128 個のブラウザからのセッションを受け入れ可能である.それらのセッションは,通信を確立してから5秒で切断すると仮定すると,セッションとして残存する時間は 通信時間 5秒 + TIME-WAIT 2分 = 2分5秒 である.つまり,2分5秒あたり128個のブラウザが接続可能である.よって,単純計算で1時間あたりおよそ3700個

 余談になるが,「ブラウザの同時接続数を増やして高速化」などといった行為は,Webサーバ管理者にとって頭を抱える要因になることがお分かりいただけると思う.参考までに,RFC 2616では「2より多く(3以上)の同時接続はすべきでない(SHOULD NOT)」とされている.Internet ExplorerとFirefoxは標準でこれを守るようになっている.しかし,私が愛用するOperaは,4の同時接続をしてしまう行儀の悪いブラウザであった.

Q. SYN-RECEIVEDとか,TIME-WAITのほかに,どんな接続がNATセッションとして管理されるの?

 TCPの状態で言えば,CLOSED以外すべて

 UDPだと,パケットがやり取りされて一定時間以内,という風になっている.

Q. NATセッションとして管理されないために(管理される時間を短くするために),どうすればいいの?

 TCPセッションの状態をCLOSEDに持っていけばいい.そうすれば,NATセッションのテーブルから削除されるだろう.

Q. 具体的に,CLOSEDに持っていくにはどうすればいい?

 1つは,クライアント(相手)に切断処理を行ってもらうこと.そうすれば,TIME-WAIT状態はクライアント側に発生し,サーバやルータにはセッションが残らずに済む.httpだとそうした制御は難しいが,独自プロトコルであれば,出来るだけクライアントが切断処理を行うように作っておこう.

 2つめは,SYN-RECEIVEDのままルータに留まらせないこと.つまり, ファイアウォールのステルスモードによる無応答を避けることだ.例えば,Windows ファイアウォールでプログラム単位の設定を行っていると,プログラムが起動していない場合はステルスモードになってしまう.NAT設定は最低限に留めた上で,設定したポートについては責任を持って「受け入れる」か「閉じている」かの応答をしたほうがよい.

 接続要求が大量にある場合は,いっそポートを閉じてしまう.丁寧にも,エラーを返してすぐに切断処理に入るような設計をしていると,そのぶんTIME-WAITが残ってしまうので,セッション数が増大してしまう.一方,ポートを閉じてしまえば,「閉じている(RST/ACK)」応答をしてセッションをすぐに破棄するので,セッション数を消費せずに済む.

|    サーバ    |        ルータ        | クライアント |
|              |                      |              |
|              |                      | <= 接続要求  |
|              | <=[セッション発生]<= |      | (SYN) |
|           <= |          ||          |      |       |
| (このポート  |          ||          |      |       |
|  閉じてる…) |          ||          |      |       |
|              |          ||          |      |       |
|  接続拒否 => |          ||          |      |       |
|    (RST/ACK) | =>  [拒否を検出]  => |      |       |
|              |          ||          | => 拒否検出  |
|              |   [セッション破棄]   |      |       |
|              |                      |   [CLOSED]   |
|              |                      |              |

 さらに,バックログを活用する.プログラム内にソケット(セッション)を抱え込まず,処理できる量だけをバックログから引き取るよう,十分注意して設計しよう.処理しきれず,バックログがいっぱいになったときには,TCP/IPスタックが「拒否(RST/ACK)」応答をするようになっているから,セッション数はそれ以上消費されない.

Q. どれくらいで"table full"になるの?

 実験では,約2000を超えたときに発生した.これは,BUFFALOのQ&A IPマスカレードテーブルのセッション数はいくつですか?(無線親機ルータモデル、BroadStation) に一致する.

Q. "table full"になったらどうなるの?

 セッションテーブルに空きが出来るまで,新しい接続が出来ない.WHR-G54S特有の現象としては,処理能力がほぼ限界に近いらしく,設定画面すら満足に開けないという悲惨な状況に陥る.

Q. Webサーバでも,どうにかTIME-WAITを減らせない?

 利用者の傾向によって,減らせるかもしれないし,減らせないかもしれない.あまり興味のない分野になるので,実測値に基づく実際的(practical)な方法ではなく,あくまで理論上での話を進める.

 仮に,1分間に5つのページにアクセスするような利用者がいたとしよう.このとき,Keep-alive timeoutを5秒に設定すると,恐らくページ毎に新しくセッションを確立し,それがTIME-WAITとして残ることになる.じゃあ,Keep-alive timeoutを1分にするとどうだろう? 1回のセッション確立(同時接続数ぶん)で済む計算になり,セッション数は減少する.もちろん,1ページにしかアクセスしないユーザがいれば,1分間長くセッションを張り続けてしまうことにはなるので,適するケースと適さないケースがある.

 TIME-WAITとは無関係になるが,mod_limitipconnなどを用いて接続数を制限することが有効な場合もある.

 例えば,前述したような1分のKeep-alive timeoutを設定した上で,同時接続数を2に制限しよう.行儀の悪いブラウザがやってきて,8セッションも消費して1ページだけ取得してきたようなときを考える.

 同時接続数を制限しない場合は,8セッションが Keep-alive 1分 + TIME-WAIT 2分 = 合計3分間残り続ける.かなり間違った考え方になるが,掛け算して 3分間 × 8セッション = 24分間 として計算しよう.同時接続数を制限した場合,2セッションが3分間,6セッションはTIME-WAITの2分間だけ残り続ける(mod_limitipconnがエラーを返してすぐ正常に切断する場合).掛け算して足し合わせると, 3分間 × 2セッション + 2分間 × 6セッション = 18分間 となる.

Q. 結局,私は何をすればいいの?

  • 接続要求は,「ルータが転送しない(ルータの段階で拒否応答)」「サーバが受け入れる」「サーバが断る」のいずれかで応答すること.
    • 「ルータでNATしておきながら,サーバがステルスモードで無応答」は避ける.
  • クライアントに切断処理をさせる.
    • サーバが切断すると,TIME-WAITが残る.
  • 大変なときはポートを閉じる.
    • TCP/IPスタックが拒否(RST/ACK)を返し,セッションはすぐ消滅する.
  • バックログを適切に取り扱うような設計をする.
    • 接続がいっぱいなら,TCP/IPスタックが拒否を返し,セッションはすぐ消滅する.
  • Webサーバなら,同時接続数を制限したり,Keep-alive timeoutを延ばしたりする.
    • ただし,効果は大きくない.Webサーバはかなり不利な立場にあると言える.

Q. なんでそこまでWHR-G54Sにこだわるの?

 新しくルータを買うなんて!

参考