/* mod_tsunami 3.0 This Apache module limits the number of simultaneous requests per vhost. Copyright (C) 2002-2004 Bertrand Demiddelaer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "httpd.h" #include "http_config.h" #include "http_conf_globals.h" #include "scoreboard.h" #include "http_log.h" #define TSUNAMI_VERSION "3.0" module MODULE_VAR_EXPORT tsunami_module; typedef struct tsunami_conf { int engine; int engine_set; int trigger; int trigger_set; int max; int max_set; } tsunami_conf; static void init_tsunami(server_rec *s, pool *p) { tsunami_conf *conf=(tsunami_conf *)ap_get_module_config (s->module_config, &tsunami_module); if (conf->engine==1&&ap_extended_status==0) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s, "TsunamiEngine can be enabled only when ExtendedStatus is 'On'"); exit(1); } ap_add_version_component("mod_tsunami/" TSUNAMI_VERSION); } static void *create_tsunami_server_config(pool *p, server_rec *dummy) { tsunami_conf *newcfg=(tsunami_conf *)ap_pcalloc(p, sizeof(tsunami_conf)); newcfg->engine=0; newcfg->engine_set=0; newcfg->trigger=0; newcfg->trigger_set=0; newcfg->max=0; newcfg->max_set=0; return (void *)newcfg; } static void *merge_tsunami_server_config(pool *p, void *parent, void *child) { tsunami_conf *parent_cfg=(tsunami_conf *)parent; tsunami_conf *child_cfg=(tsunami_conf *)child; tsunami_conf *conf=(tsunami_conf *)ap_pcalloc(p, sizeof(tsunami_conf)); if (child_cfg->engine_set) { conf->engine=child_cfg->engine; conf->engine_set=1; } else { conf->engine=parent_cfg->engine; conf->engine_set=parent_cfg->engine_set; } if (child_cfg->trigger_set) { conf->trigger=child_cfg->trigger; conf->trigger_set=1; } else { conf->trigger=parent_cfg->trigger; conf->trigger_set=parent_cfg->trigger_set; } if (child_cfg->max_set) { conf->max=child_cfg->max; conf->max_set=1; } else { conf->max=parent_cfg->max; conf->max_set=parent_cfg->max_set; } return (void *)conf; } static const char *set_tsunami_engine(cmd_parms *cmd, void *foo, int arg) { tsunami_conf *conf=(tsunami_conf *)ap_get_module_config (cmd->server->module_config, &tsunami_module); conf->engine_set=1; conf->engine=arg?1:0; return NULL; } static const char *set_tsunami_trigger(cmd_parms *cmd, void *foo, char *arg) { tsunami_conf *conf=(tsunami_conf *)ap_get_module_config (cmd->server->module_config, &tsunami_module); conf->trigger_set=1; conf->trigger=atoi(arg); if (conf->trigger<0) return "TsunamiTrigger argument must be greater or equal to 0"; return NULL; } static const char *set_tsunami_max(cmd_parms *cmd, void *foo, char *arg) { tsunami_conf *conf=(tsunami_conf *)ap_get_module_config (cmd->server->module_config, &tsunami_module); conf->max_set=1; conf->max=atoi(arg); if (conf->max<0) return "TsunamiMaxConnections argument must be greater or equal to 0"; return NULL; } static const command_rec tsunami_cmds[] = { {"TsunamiEngine", set_tsunami_engine, NULL, RSRC_CONF, FLAG, "enables or disables tsunami engine"}, {"TsunamiTrigger", set_tsunami_trigger, NULL, RSRC_CONF, TAKE1, "number of total simultaneous requests allowed before starting tsunami checking"}, {"TsunamiMaxConnections", set_tsunami_max, NULL, RSRC_CONF, TAKE1, "number of maximum simultaneous requests allowed per vhost"}, {NULL} }; static int tsunami_filter(request_rec *r) { tsunami_conf *conf=(tsunami_conf *)ap_get_module_config(r->server->module_config, &tsunami_module); if (conf->engine!=0&& conf->max!=0&& r->prev==NULL&& r->main==NULL&& ap_exists_scoreboard_image()) { server_rec *sr=r->server; short_score *ss=ap_scoreboard_image->servers; int total_count=conf->trigger; int vhost_count=conf->max; int i=0; #ifdef TSUNAMI_DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "processing tsunami_filter() (trigger: %d / max: %d)", total_count, vhost_count); #endif ap_sync_scoreboard_image(); while(i++status) { case SERVER_BUSY_KEEPALIVE: case SERVER_BUSY_WRITE: case SERVER_BUSY_READ: case SERVER_BUSY_DNS: #ifdef TSUNAMI_DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, (sr==ss->vhostrec)?"child %d of %d is busy with this request's vhost":"child %d of %d is busy with another vhost", i, ap_daemons_limit); #endif if (sr==ss++->vhostrec) --vhost_count; if (--total_count<0&&vhost_count<0) { #ifdef TSUNAMI_DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "request denied (total: %d / vhost: %d)", conf->trigger-total_count, conf->max-vhost_count); #endif return HTTP_SERVICE_UNAVAILABLE; } break; default: ++ss; break; } } #ifdef TSUNAMI_DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "request allowed (total: %d / vhost: %d)", conf->trigger-total_count, conf->max-vhost_count); #endif } return DECLINED; } module MODULE_VAR_EXPORT tsunami_module = { STANDARD_MODULE_STUFF, init_tsunami, /* initializer */ NULL, /* dir config creater */ NULL, /* merge dir configs */ create_tsunami_server_config, /* server config */ merge_tsunami_server_config, /* merge server configs */ tsunami_cmds, /* command table */ NULL, /* handlers */ tsunami_filter, /* filename translation */ NULL, /* check_user_id */ NULL, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ NULL, /* fixups */ NULL, /* logger */ NULL, /* header parser */ NULL, /* child_init */ NULL, /* child_exit */ NULL /* post read-request */ };