diff options
Diffstat (limited to 'network/mod_limitipconn/mod_limitipconn.c')
-rw-r--r-- | network/mod_limitipconn/mod_limitipconn.c | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/network/mod_limitipconn/mod_limitipconn.c b/network/mod_limitipconn/mod_limitipconn.c new file mode 100644 index 0000000000..a53683c7db --- /dev/null +++ b/network/mod_limitipconn/mod_limitipconn.c @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2000-2002 David Jao <[EMAIL PROTECTED]> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, this permission notice, and the + * following disclaimer shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_protocol.h" +#include "http_core.h" +#include "http_main.h" +#include "http_log.h" +#include "ap_mpm.h" +#include "apr_strings.h" +#include "scoreboard.h" + +#define MODULE_NAME "mod_limitipconn" +#define MODULE_VERSION "0.22" + +module AP_MODULE_DECLARE_DATA limitipconn_module; + +static int server_limit, thread_limit; + +typedef struct { + signed int limit; /* max number of connections per IP */ + + /* array of MIME types exempt from limit checking */ + apr_array_header_t *no_limit; + int no_limit_set; + + /* array of MIME types to limit check; all other types are exempt */ + apr_array_header_t *excl_limit; + int excl_limit_set; +} limitipconn_config; + +static void *limitipconn_create_config(apr_pool_t *p, server_rec *s) +{ + limitipconn_config *cfg = (limitipconn_config *) + apr_pcalloc(p, sizeof (*cfg)); + + /* default configuration: no limit (unset), and both arrays are empty */ + cfg->limit = -1; + cfg->no_limit = apr_array_make(p, 0, sizeof(char *)); + cfg->excl_limit = apr_array_make(p, 0, sizeof(char *)); + + return cfg; +} + +/* Simple merge: Per vhost entries overrides main server entries */ +static void *limitipconn_merge_config(apr_pool_t *p, void *BASE, void *ADD) +{ + limitipconn_config *base = BASE; + limitipconn_config *add = ADD; + + limitipconn_config *cfg = (limitipconn_config *) + apr_pcalloc(p, sizeof (*cfg)); + + cfg->limit = (add->limit == -1) ? base->limit : add->limit; + cfg->no_limit = add->no_limit_set ? add->no_limit : base->no_limit; + cfg->excl_limit = add->excl_limit_set ? add->excl_limit : base->excl_limit; + + return cfg; +} + +/* The handler runs as a quick handler so we can arrange for it to be called + before mod_cache. Being a quick handler means that we have a lot of + limitations, the basic ones are that the only thing we know is the URL and + that if we return OK it means that we handle the entire reply of the + request including populating the brigades with data. */ +static int limitipconn_handler(request_rec *r, int lookup) +{ + /* get configuration information */ + limitipconn_config *cfg = (limitipconn_config *) + ap_get_module_config(r->server->module_config, &limitipconn_module); + + /* convert Apache arrays to normal C arrays */ + char **nolim = (char **) cfg->no_limit->elts; + char **exlim = (char **) cfg->excl_limit->elts; + + const char *address; + + /* loop index variables */ + int i; + int j; + + /* running count of number of connections from this address */ + int ip_count = 0; + + /* Content-type of the current request */ + const char *content_type; + + /* scoreboard data structure */ + worker_score *ws_record; + + /* We decline to handle subrequests: otherwise, in the next step we + * could get into an infinite loop. */ + if (!ap_is_initial_req(r)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "mod_limitipconn: SKIPPED: Not initial request"); + return DECLINED; + } + + /* A limit value of 0 by convention means no limit, negative means + unset (no limit). */ + if (cfg->limit <= 0) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "mod_limitipconn: OK: No limit"); + return DECLINED; + } + +#ifdef RECORD_FORWARD + if ((address = apr_table_get(r->headers_in, "X-Forwarded-For")) == NULL) +#endif + address = r->connection->remote_ip; + + /* Only check the MIME-type if we have MIME-type stuff in our config. + That extra subreq can be quite expensive. */ + if(cfg->no_limit->nelts > 0 || cfg->excl_limit->nelts > 0) { + /* Look up the Content-type of this request. We need a subrequest + * here since this module might be called before the URI has been + * translated into a MIME type. */ + content_type = ap_sub_req_lookup_uri(r->uri, r, NULL)->content_type; + + /* If there's no Content-type, use the default. */ + if (!content_type) { + content_type = ap_default_type(r); + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "mod_limitipconn: uri: %s Content-Type: %s", + r->uri, content_type); + + /* Cycle through the exempt list; if our content_type is exempt, + * return OK */ + for (i = 0; i < cfg->no_limit->nelts; i++) { + if ((ap_strcasecmp_match(content_type, nolim[i]) == 0) + || (strncmp(nolim[i], content_type, strlen(nolim[i])) == 0)) + { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "mod_limitipconn: OK: %s exempt", content_type); + return DECLINED; + } + } + + /* Cycle through the exclusive list, if it exists; if our MIME type + * is not present, bail out */ + if (cfg->excl_limit->nelts) { + int excused = 1; + for (i = 0; i < cfg->excl_limit->nelts; i++) { + if ((ap_strcasecmp_match(content_type, exlim[i]) == 0) + || + (strncmp(exlim[i], content_type, strlen(exlim[i])) == 0)) + { + excused = 0; + } + } + if (excused) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "mod_limitipconn: OK: %s not excluded", + content_type); + return DECLINED; + } + } + } + + /* Count up the number of connections we are handling right now from + * this IP address */ + for (i = 0; i < server_limit; ++i) { + for (j = 0; j < thread_limit; ++j) { + ws_record = ap_get_scoreboard_worker(i, j); + switch (ws_record->status) { + case SERVER_BUSY_READ: + case SERVER_BUSY_WRITE: + case SERVER_BUSY_KEEPALIVE: + case SERVER_BUSY_LOG: + case SERVER_BUSY_DNS: + case SERVER_CLOSING: + case SERVER_GRACEFUL: + if ((strcmp(address, ws_record->client) == 0) +#ifdef RECORD_FORWARD + || (strcmp(address, ws_record->fwdclient) == 0) +#endif + ) { + ip_count++; + } + break; + default: + break; + } + } + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "mod_limitipconn: vhost: %s uri: %s current: %d limit: %d", + r->server->server_hostname, r->uri, ip_count, cfg->limit); + + if (ip_count > cfg->limit) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, + "Rejected, too many connections from this host."); + /* set an environment variable */ + apr_table_setn(r->subprocess_env, "LIMITIP", "1"); + /* return 503 */ + return HTTP_SERVICE_UNAVAILABLE; + } else { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "mod_limitipconn: OK: Passed all checks"); + return DECLINED; + } +} + +/* Parse the MaxConnPerIP directive */ +static const char *limit_config_cmd(cmd_parms *parms, void *dummy, + const char *arg) +{ + limitipconn_config *cfg = (limitipconn_config *) + ap_get_module_config(parms->server->module_config, + &limitipconn_module); + + 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 = limit; + return NULL; +} + +/* Parse the NoIPLimit directive */ +static const char *no_limit_config_cmd(cmd_parms *parms, void *dummy, + const char *arg) +{ + limitipconn_config *cfg = (limitipconn_config *) + ap_get_module_config(parms->server->module_config, + &limitipconn_module); + + *(char **) apr_array_push(cfg->no_limit) = apr_pstrdup(parms->pool, arg); + cfg->no_limit_set = 1; + return NULL; +} + +/* Parse the OnlyIPLimit directive */ +static const char *excl_limit_config_cmd(cmd_parms *parms, void *dummy, + const char *arg) +{ + limitipconn_config *cfg = (limitipconn_config *) + ap_get_module_config(parms->server->module_config, + &limitipconn_module); + + *(char **) apr_array_push(cfg->excl_limit) = apr_pstrdup(parms->pool, arg); + cfg->excl_limit_set = 1; + return NULL; +} + +/* Array describing structure of configuration directives */ +static command_rec limitipconn_cmds[] = { + AP_INIT_TAKE1("MaxConnPerIP", limit_config_cmd, NULL, RSRC_CONF, + "maximum simultaneous connections per IP address"), + AP_INIT_ITERATE("NoIPLimit", no_limit_config_cmd, NULL, RSRC_CONF, + "MIME types for which limit checking is disabled"), + AP_INIT_ITERATE("OnlyIPLimit", excl_limit_config_cmd, NULL, RSRC_CONF, + "restrict limit checking to these MIME types only"), + {NULL}, +}; + +/* Set up startup-time initialization */ +static int limitipconn_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, +server_rec *s) +{ + ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, + MODULE_NAME " " MODULE_VERSION " started."); + ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit); + ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit); + return OK; +} + +static void register_hooks(apr_pool_t *p) +{ + static const char * const after_me[] = { "mod_cache.c", NULL }; + + /* We must run as a quick handle so we can deny connections before + mod_cache gets to serve them */ + ap_hook_quick_handler(limitipconn_handler, NULL, after_me, APR_HOOK_FIRST); + + ap_hook_post_config(limitipconn_init, NULL, NULL, APR_HOOK_MIDDLE); +} + +module AP_MODULE_DECLARE_DATA limitipconn_module = { + STANDARD20_MODULE_STUFF, + NULL, /* create per-dir config structures */ + NULL, /* merge per-dir config structures */ + limitipconn_create_config, /* create per-server config structures */ + limitipconn_merge_config, /* merge per-server config structures */ + limitipconn_cmds, /* table of config file commands */ + register_hooks +}; + |