Apacheにmod_rpafモジュールを組み込んでKeepAliveを有効にするとIPアドレスに変なゴミが入るけどなんで?なお話。まだ仮説の段階ですが原因と解決策。(レポート後2004/03/17にリリースされたmod_rpaf v0.5で改修されています)
naoyaさんのWeblogを読んでいてmod_rpafモジュール(version 0.4)がKeepAlive Onの際に変な動きをするというお話を知りました。かなり今更な感じの反応でアレですが、ちょっと興味が在ったので調べてみたところmod_rpaf version 0.4のマズそうなところを見つけたのでご報告。mod_rpafの概要
通常のWebサーバへのリクエストは[client] -- request --> [Web server]と、クライアント(Webブラウザ等)がWebサーバに直接リクエストを発行します。このためWebサーバ側でログを採取する場合、単純に接続元であるクライアントのIPアドレス等を記録すれば済みます。
しかし負荷分散などの目的でリバースプロキシを導入した場合のリクエストは
[client] -- request --> [proxy] -- request --> [Web server]と、クライアントとWebサーバの間にHTTP Proxyが介入することになります。このためWebサーバから見るとリバースプロキシがリクエストを発行しているように見え、通常通りIPアドレス等を記録しようとしてもリバースプロキシのIPアドレスを記録してしまうことになります。
リバースプロキシ経由のリクエストにはクライアントのホストアドレスを記録したX-Forwarded-Forヘッダが付与されているので、Webサーバ側でこのリクエストヘッダを参照すれば実際のクライアントのホストアドレスを取得できます。このX-Forwarded-Forヘッダを元にApacheの内部情報を上書きすることで、リバースプロキシを介さずにクライアントが直接リクエストしたように見せるモジュールがmod_rpafモジュールです。
より突っ込んだ話をするとmod_rpafモジュールは、リクエストを受け取った直後にApacheが実行しているap_run_post_read_requestフェーズに介入し、
- リクエストヘッダ(r->headers_in)にX-Forwarded-Forが在るか
- 在る場合はその値を元に、接続元アドレスの情報(r->connection->remote_ip)を上書き
mod_rpafの怪しいところ
実際にクライアントのアドレスを上書きしているコードはr->connection->remote_ip = ap_pstrdup(r->pool, ((char **)arr->elts)[((arr->nelts)-1)]);という感じです。一見何も問題無さげですがconnectionフィールド配下(この場合remote_ip)に、リクエストプール(r->pool)からアロケートした文字列をセットしているのがヤヴァぃげです。
怪しさの背景
Apacheはライフサイクルが異なる複数のリソースプールを持っています。クライアントがApacheに接続している間に利用されるリソースプールとしてコネクションプールがあります。コネクションプールはクライアントが接続すると生成され、切断すると破棄されます。このプールはr->connection->poolからアクセスできます。
さらに、リクエストを受け取りレスポンスを返す間に利用されるリソースプールとしてリクエストプールがあります。リクエストプールはリクエストを受け取ると生成され、レスポンスを返すと破棄されます。このプールは
r->poolからアクセスできます。
KeepAliveを使用しない場合は
- コネクションプール生成
- リクエストプール生成
- リクエストを処理、レスポンスを送信
- リクエストプール破棄
- コネクションプール破棄
- コネクションプール生成
- リクエストプール生成(1つめ)
- リクエストを処理、レスポンスを送信
- リクエストプール破棄
- リクエストプール生成(2つめ)
- リクエストを処理、レスポンスを送信
- リクエストプール破棄
... - コネクションプール破棄
問題のmod_rpafモジュールversion 0.4の実装ではr->connection->remote_ipフィールドにリクエストプールで複製した文字列がセットされています。そしてリクエストを一つ処理し終わった時点でリクエストプールは破棄されるので、それと同時にr->connection->remote_ipフィールドが指し示すリソース(文字列)も破棄され値は予測不能になります。(鼻から悪魔が出るかもしれません)
仮定
もしリバースプロキシ + KeepAliveの場面で全てのリクエストにX-Forwarded-Forヘッダが付与されているならばこの問題は起こりえませんが、1つめのリクエストにだけしか付与されないとすると上記のとおりremote_ipフィールドは期待した値を保持していません。回避策は単純で、r->connection->remote_ipフィールドに対して、コネクションプールでアロケートしたリソースをセットするだけです。つまり
r->connection->remote_ip = ap_pstrdup(r->connection->pool, ((char **)arr->elts)[((arr->nelts)-1)]);こういうこと。いちおうpatchも置いときましたので試してもらえる方ぼしゅー。(mod_rpaf v0.5でApache 1.3.xおよび2.0.x用ともにこの点は修正済みです)
言うまでもありませんが仮定ですのでよろしく。また、Apacheモジュールのプログラミングにご興味がありましたら是非パンダ本をご賞味くださいませ:-) ←宣伝