続・mod_limitipconn

嗚呼勘違い。mod_limitipconnの動作を勘違いしていました。昨日作ったパッチは意味のないものだったのでなかったことにします。
わかりやすく解説しているサイトがあったので引用します。

http://www.netnice.org/pukiwiki.php?mod_limitipconn より

制限を設けたURI以下のみで同時アクセス数を計測しているわけではなく、あるクライアントのサーバ全体への同時アクセス数を計測し判定している点に注意が必要。
例えば/path/to/limit配下にMaxConnPerIP 10と設定している場合、あるクライアントが/path/to/NO_limitへ同時に8件アクセスしている状況で、同時に別のディレクトリ /path/to/limitへさらに2件以上アクセスした場合にはアクセスが拒否される。
この場合、管理者の感覚としては単一のクライアントが/path/to/limit以下に10以上のリクエストを行っている場合にだけアクセスを拒否すると期待してしまいがちなので注意が必要と思われる。

これは勘違いしますねorz ソースを見ていて変だなと思いました。
mod_limitipconnのようにスコアボードを探索する方式では、送信元IPアドレス、バーチャルホスト、リクエストの先頭64バイト("GET /index.html HTTP/1.0"みたいなもの)くらいしかわからないので、LocationやDirectoryごとにアクセス数をカウントするのはそのままでは無理っぽいです。

同じサイトで紹介されているmod_access_limitは、まさに望みの動作なのですが、Apache1.3系にしか対応してないのと、Apache本体にパッチを当てる必要があるという制約があります。
http://www.netnice.org/pukiwiki.php?mod_access_limit

Apache2系に移植することは可能だと思いますが、やはりApache本体にパッチを当てる必要があるのでメンテナンスの観点から難があります。

苦し紛れに、スコアボードから取得できるリクエストの先頭64バイトを使って同時接続数を制限するMaxConnPerURIというディレクティブを追加してみました。
たとえば以下のように使用します。

<Location /path/to/limit>
	MaxConnPerURI 10
</Location>

それなりに動作はしますが、リクエストの先頭64バイトしか使えないので、当然長いURIやGETでパラメータを渡しているCGIなどはだめです。また、Directoryなどで指定してもファイル1つごとにしか制限できません。これじゃ実用には堪えませんね。
いちおうパッチ。

--- mod_limitipconn.c.orig	
+++ mod_limitipconn.c	
@@ -36,7 +36,7 @@
 #include "scoreboard.h"
 
 #define MODULE_NAME "mod_limitipconn"
-#define MODULE_VERSION "0.22"
+#define MODULE_VERSION "0.22b"
 
 module AP_MODULE_DECLARE_DATA limitipconn_module;
 
@@ -44,6 +44,7 @@
 
 typedef struct {
     signed int limit;       /* max number of connections per IP */
+    signed int limit_uri;  /* max number of connections per URI */
     apr_array_header_t *no_limit;   /* array of MIME types exempt from limit
 				 checking */
     apr_array_header_t *excl_limit; /* array of MIME types to limit check; all
@@ -82,6 +83,9 @@
     /* running count of number of connections from this address */
     int ip_count = 0;
 
+    /* running count of number of connections */
+    int ip_count_uri = 0;
+
     /* Content-type of the current request */
     const char *content_type;
 
@@ -110,7 +114,7 @@
     address = r->connection->remote_ip;
 
     /* A limit value of 0 by convention means no limit. */
-    if (cfg->limit == 0) {
+    if (cfg->limit == 0 && cfg->limit_uri == 0) {
 	return OK;
     }
 
@@ -161,6 +165,9 @@
             ) {
 		ip_count++;
 	    }
+		if (strncmp(r->the_request, ws_record->request, 64) == 0) {
+			ip_count_uri++;
+		}
 	    break;
 	    case
 	SERVER_DEAD:
@@ -180,12 +187,19 @@
     }
 
     if ((ip_count > cfg->limit) && (cfg->limit)) {
-      ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Rejecting client at %s", 
+      ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Connections reached MaxConnPerIP. Rejecting client at %s", 
 		   address);
       /* set an environment variable */
       apr_table_setn(r->subprocess_env, "LIMITIP", "1");
       /* return 503 */
       return HTTP_SERVICE_UNAVAILABLE;
+    } else if ((ip_count_uri > cfg->limit_uri) && (cfg->limit_uri)) {
+      ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Connections reached MaxConnPerURI. Rejecting client at %s", 
+		   address);
+      /* set an environment variable */
+      apr_table_setn(r->subprocess_env, "LIMITIP", "1");
+      /* return 503 */
+      return HTTP_SERVICE_UNAVAILABLE;
     } else {
 	return OK;
     }
@@ -209,6 +223,24 @@
     return NULL;
 }
 
+/* Parse the MaxConnPerURI directive */
+static const char *limit_uri_config_cmd(cmd_parms *parms, void *mconfig,
+				    const char *arg)
+{
+    limitipconn_dir_config *cfg = (limitipconn_dir_config *) mconfig;
+
+    signed long int limit = strtol(arg, (char **) NULL, 10);
+
+    /* No reasonable person would want more than 2^16. Better would be
+       to use LONG_MAX but that causes portability problems on win32 */
+    if ((limit > 65535) || (limit < 0)) {
+	return "Integer overflow or invalid number";
+    }
+
+    cfg->limit_uri = limit;
+    return NULL;
+}
+
 /* Parse the NoIPLimit directive */
 static const char *no_limit_config_cmd(cmd_parms *parms, void *mconfig,
 				       const char *arg)
@@ -233,6 +265,8 @@
 static command_rec limitipconn_cmds[] = {
     AP_INIT_TAKE1("MaxConnPerIP", limit_config_cmd, NULL, OR_LIMIT,
      "maximum simultaneous connections per IP address"),
+    AP_INIT_TAKE1("MaxConnPerURI", limit_uri_config_cmd, NULL, OR_LIMIT,
+     "maximum simultaneous connections per Location"),
     AP_INIT_ITERATE("NoIPLimit", no_limit_config_cmd, NULL, OR_LIMIT,
      "MIME types for which limit checking is disabled"),
     AP_INIT_ITERATE("OnlyIPLimit", excl_limit_config_cmd, NULL, OR_LIMIT,