ChangeLog0000644000000000000000000000235611421345376011342 0ustar rootrootV0.92 ------------ 2010-07-20 : - Fixed ap_get_server_banner unknown on older apache version V0.91 ------------ 2010-05-27 : - Fixed weird behaviour on Windows Hosts. (mod_bw.txt) - Added high resolution timers for windows. (speed improvements) - Fixed stupid bug that caused crash when mod is enabled but there is not a single limit. v0.9 ------------ 2010-05-24 : - Fixed an "invisible" memory leak - Fixed MinBandWidth X -1 skipping all bw control. - Added status callback - Code cleanup. No more warnings or stuff in Visual Studio v0.8 ------------ 2007-03-17 : - Added user agent match on bandwidth and connections limiting - Fixed issue of symbols not found on some platforms - Updated documentation accordingly v0.7 ------------ 2005-08-29 : - Changed License to Apache Software License v2.0 - Changed coded style. Using GNU indent. - Changed function name from is_filetype to match_ext - Removed check for "no configurations" (confid < 0) - Removed BandwidthDebug directive. - Removed simple allocation on Win32 platform. Using SHM now. - Removed no-limiting for error pages. - Added Support for APR > 0.9 - Added Support for extra-large files (2G, 4G) with APR > 1 - Borrowed code (in_domain and ip match) from mod_access) - IPV6 Support LICENSE0000644000000000000000000002613611376524706010604 0ustar rootroot Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. mod_bw.c0000644000076400007640000012137611421345546011502 0ustar brucebruce/* Apache2 - Mod_bw v0.92 Author : Ivan Barrera A. (Bruce) HomePage : Http://Ivn.cl/apache Release Date : 20-Jul-2010 Status : Stable License : Licensed under the Apache Software License v2.0 It must be included as LICENSE in this package. Platform : Linux/x86 (Tested with Fedora Core 4, Suse, etc) Linux/x86_64 (Redhat Enterprise 4) FreeBSD/x86 (Tested on 5.2) MacOS X/Ppc x86 (Tested on both platforms) Solaris 8/sparc (Some notes on compile) Microsoft Windows (Win XP, Win2003. Win7, Others should work) HP-UX 11 (Thanks to Soumendu Bhattacharya for testing) Notes : This is a stable version of mod_bw. It should work with almost any MPM (tested with WinNT/prefork/Worker MPM). We are reaching the End of mod_bw series 0.x. As soon as this last changes are confirmed by the users (perhaps some changes at request), i'll set this release to version 1.0 final. Limitations : This mod doesn't know how fast the client is downloading a file, so it just divides the bw assigned between the users. MaxConnections works only for the given scope. (i.e , all will limit maxconnections from all,not per ip or user) Changelog : 2010-07-20 : Fixed ap_get_server_banner unknown on older apache version 2010-05-27 : Fixed weird behaviour on Windows Hosts. (mod_bw.txt) Added high resolution timers for windows. (speed improvements) Fixed stupid bug that caused crash when mod is enabled but there is not a single limit. 2010-05-24 : Code Cleanup. No more warnings or stuff in Visual Studio 2010-04-28 : Bruce's Birthday Gift : A callback to the stats of the mod :) 2010-04-06 : Fixed "Invisible" memory leak. Only seen when serving HUGE streams. 2010-03-10 : Fixed MinBandwidth screwing limits. Another unsigned/signed screw up. */ #define VERSION "0.92" #include "apr_version.h" #include "apr_buckets.h" #include "apr_strings.h" #include "apr_atomic.h" #include "apr_lib.h" #include "apr_shm.h" #include "ap_config.h" #include "util_filter.h" #include "ap_mpm.h" #include "httpd.h" #include "http_config.h" #include "http_request.h" #include "http_core.h" #include "http_protocol.h" #include "http_log.h" #include "http_main.h" #include "util_script.h" #include "http_core.h" #include "scoreboard.h" #if defined(WIN32) #include #include #endif #define MIN_BW 256 /* Minimal bandwidth 256 bytes */ #define PACKET 8192 /* Default packet at 8192 bytes */ #define MAX_VHOSTS 1024 /* Default number of vhosts to show in stats */ #define MAX_BUF 1024 /* Max length of a temporal buffer */ #define BANDWIDTH_DISABLED 1<<0 #define BANDWIDTH_ENABLED 1<<1 /* Compatibility with regex on apache less than 2.1 */ #if !AP_MODULE_MAGIC_AT_LEAST(20050127,0) typedef regex_t ap_regex_t; #define AP_REG_EXTENDED REG_EXTENDED #define AP_REG_ICASE REG_ICASE #endif /* Compatibility with obsolete ap_get_server_version() */ #if !AP_MODULE_MAGIC_AT_LEAST(20051115,4) #define ap_get_server_banner ap_get_server_version #endif /* Compatibility for APR < 1 */ #if ( defined(APR_MAJOR_VERSION) && (APR_MAJOR_VERSION < 1) ) #define apr_atomic_inc32 apr_atomic_inc #define apr_atomic_dec32 apr_atomic_dec #define apr_atomic_add32 apr_atomic_add #define apr_atomic_cas32 apr_atomic_cas #define apr_atomic_set32 apr_atomic_set #endif /* Enum types of "from address" */ enum from_type { T_ALL, T_IP, T_HOST, T_AGENT }; /* - Stats of each conf - - id = Configuration ID - v_name = Vhost name - time = Time of the last data update - bandwidth = Estimated bandwidth measured - bytes_count = Bytes sent last second - connection_ = Number of simultaneos clientes downloading - lock = Lock, to avoid simultaneous write access to shm */ typedef struct { apr_uint32_t id; char *v_name; apr_uint32_t connection_count; apr_uint32_t bandwidth; apr_uint32_t bytes_count; apr_uint32_t counter; volatile apr_uint32_t lock; apr_time_t time; } bw_data; /* A temporal context to save our splitted brigade */ typedef struct ctx_struct_t { apr_bucket_brigade *bb; struct timeval wait; } ctx_struct; /* With sid we count the shared memory needed. BwBase, is a holder to the shared memory base addres */ static char *vnames[MAX_VHOSTS]; static int sid = 0; bw_data *bwbase; apr_shm_t *shm; /* Limits for MaxConnections based on directory */ typedef struct { apr_uint32_t sid; union { char *from; apr_ipsubnet_t *ip; } x; ap_regex_t *agent; enum from_type type; apr_uint32_t max; } bw_maxconn; /* Limits for bandwidth and minimal bandwidth based on directory */ typedef struct { apr_uint32_t sid; union { char *from; apr_ipsubnet_t *ip; } x; ap_regex_t *agent; enum from_type type; apr_int32_t rate; } bw_entry; /* Limits for bandwidth based on file size */ typedef struct { apr_uint32_t sid; char *file; apr_uint32_t size; apr_uint32_t rate; } bw_sizel; /* Per directory configuration structure */ typedef struct { apr_array_header_t *limits; apr_array_header_t *minlimits; apr_array_header_t *sizelimits; apr_array_header_t *maxconnection; int packet; int error; char *directory; } bandwidth_config; /* Per server configuration structure */ typedef struct { int state; int force; } bandwidth_server_config; /* Module declaration */ module AP_MODULE_DECLARE_DATA bw_module; /*---------------------------------------------------------------------* * Configurations Directives * *---------------------------------------------------------------------*/ /* Set the mod enabled ... or disabled */ static const char *bandwidthmodule(cmd_parms * cmd, void *dconf, int flag) { bandwidth_server_config *sconf; sconf = (bandwidth_server_config *) ap_get_module_config(cmd->server-> module_config, &bw_module); sconf->state = (flag ? BANDWIDTH_ENABLED : BANDWIDTH_DISABLED); return NULL; } /* Set force mode enabled ... or disabled */ static const char *forcebandwidthmodule(cmd_parms * cmd, void *dconf, int flag) { bandwidth_server_config *sconf; sconf = (bandwidth_server_config *) ap_get_module_config(cmd->server-> module_config, &bw_module); sconf->force = (flag ? BANDWIDTH_ENABLED : BANDWIDTH_DISABLED); return NULL; } /* Set the packetsize used in the context */ static const char *setpacket(cmd_parms * cmd, void *s, const char *pack) { bandwidth_config *conf = (bandwidth_config *) s; int temp; if (pack && *pack && apr_isdigit(*pack)) temp = atol(pack); else return "Invalid argument"; if ((temp < 1024) || (temp > 131072)) return "Packet must be a number of bytes between 1024 and 131072"; conf->packet = temp; return NULL; } /* Set the error to send when maxconnections is reached */ static const char *bandwidtherror(cmd_parms * cmd, void *s, const char *err) { bandwidth_config *conf = (bandwidth_config *) s; int temp; if (err && *err && apr_isdigit(*err)) temp = atol(err); else return "Invalid argument"; if ((temp < 300) || (temp > 999)) return "Error must be a number between 300 and 599"; conf->error = temp; return NULL; } /* Set the maxconnections on a per host basis */ static const char *maxconnection(cmd_parms * cmd, void *s, const char *from, const char *maxc) { bandwidth_config *conf = (bandwidth_config *) s; bw_maxconn *a; int temp; char *str; char *where = (char *) apr_pstrdup(cmd->pool, from); apr_status_t rv; char msgbuf[MAX_BUF]; if (maxc && *maxc && apr_isdigit(*maxc)) temp = atoi(maxc); else return "Invalid argument"; if (temp < 0) return "Connections must be a number of simultaneous connections allowed/s"; a = (bw_maxconn *) apr_array_push(conf->maxconnection); a->x.from = where; if (!strncasecmp(where,"u:",2)) { /* Do not limit based on origin, but on user agent */ a->type = T_AGENT; a->agent = ap_pregcomp(cmd->pool, where+2, 0); if (a->agent == NULL) return "Regular expression could not be compiled."; } else if (!strcasecmp(where, "all")) { a->type = T_ALL; } else if ((str = strchr(where, '/'))) { *str++ = '\0'; rv = apr_ipsubnet_create(&a->x.ip, where, str, cmd->pool); if(APR_STATUS_IS_EINVAL(rv)) { /* looked nothing like an IP address */ return "An IP address was expected"; } else if (rv != APR_SUCCESS) { apr_strerror(rv, msgbuf, sizeof msgbuf); return apr_pstrdup(cmd->pool, msgbuf); } a->type = T_IP; } else if (!APR_STATUS_IS_EINVAL(rv = apr_ipsubnet_create(&a->x.ip, where, NULL, cmd->pool))) { if (rv != APR_SUCCESS) { apr_strerror(rv, msgbuf, sizeof msgbuf); return apr_pstrdup(cmd->pool, msgbuf); } a->type = T_IP; } else { /* no slash, didn't look like an IP address => must be a host */ a->type = T_HOST; } a->max = temp; return NULL; } /* Set the bandwidth on a per host basis */ static const char *bandwidth(cmd_parms * cmd, void *s, const char *from, const char *bw) { bandwidth_config *conf = (bandwidth_config *) s; bw_entry *a; long int temp; char *str; char *where = (char *) apr_pstrdup(cmd->pool, from); apr_status_t rv; char msgbuf[MAX_BUF]; if (bw && *bw && apr_isdigit(*bw)) temp = atol(bw); else return "Invalid argument"; if (temp < 0) return "BandWidth must be a number of bytes/s"; a = (bw_entry *) apr_array_push(conf->limits); a->x.from = where; if (!strncasecmp(where,"u:",2)) { /* Do not limit based on origin, but on user agent */ a->type = T_AGENT; a->agent = ap_pregcomp(cmd->pool, where+2, 0); if (a->agent == NULL) return "Regular expression could not be compiled."; } else if (!strcasecmp(where, "all")) { a->type = T_ALL; } else if ((str = strchr(where, '/'))) { *str++ = '\0'; rv = apr_ipsubnet_create(&a->x.ip, where, str, cmd->pool); if(APR_STATUS_IS_EINVAL(rv)) { /* looked nothing like an IP address */ return "An IP address was expected"; } else if (rv != APR_SUCCESS) { apr_strerror(rv, msgbuf, sizeof msgbuf); return apr_pstrdup(cmd->pool, msgbuf); } a->type = T_IP; } else if (!APR_STATUS_IS_EINVAL(rv = apr_ipsubnet_create(&a->x.ip, where, NULL, cmd->pool))) { if (rv != APR_SUCCESS) { apr_strerror(rv, msgbuf, sizeof msgbuf); return apr_pstrdup(cmd->pool, msgbuf); } a->type = T_IP; } else { /* no slash, didn't look like an IP address => must be a host */ a->type = T_HOST; } if (sid < MAX_VHOSTS) { vnames[sid] = apr_pcalloc(cmd->pool,apr_snprintf(msgbuf,MAX_BUF,"%s,%s",cmd->server->server_hostname,where) ); vnames[sid] = (char *) apr_pstrdup(cmd->pool, msgbuf); } a->rate = temp; a->sid = sid++; return NULL; } /* Set the minimum bandwidth to send */ static const char *minbandwidth(cmd_parms * cmd, void *s, const char *from, const char *bw) { bandwidth_config *conf = (bandwidth_config *) s; bw_entry *a; long int temp; char *str; char *where = (char *) apr_pstrdup(cmd->pool, from); apr_status_t rv; char msgbuf[MAX_BUF]; if (bw && *bw && (*bw == '-' || apr_isdigit(*bw))) temp = atol(bw); else return "Invalid argument"; a = (bw_entry *) apr_array_push(conf->minlimits); a->x.from = where; if (!strncasecmp(where,"u:",2)) { /* Do not limit based on origin, but on user agent */ a->type = T_AGENT; a->agent = ap_pregcomp(cmd->pool, where+2, 0); if (a->agent == NULL) return "Regular expression could not be compiled."; } else if (!strcasecmp(where, "all")) { a->type = T_ALL; } else if ((str = strchr(where, '/'))) { *str++ = '\0'; rv = apr_ipsubnet_create(&a->x.ip, where, str, cmd->pool); if(APR_STATUS_IS_EINVAL(rv)) { /* looked nothing like an IP address */ return "An IP address was expected"; } else if (rv != APR_SUCCESS) { apr_strerror(rv, msgbuf, sizeof msgbuf); return apr_pstrdup(cmd->pool, msgbuf); } a->type = T_IP; } else if (!APR_STATUS_IS_EINVAL(rv = apr_ipsubnet_create(&a->x.ip, where, NULL, cmd->pool))) { if (rv != APR_SUCCESS) { apr_strerror(rv, msgbuf, sizeof msgbuf); return apr_pstrdup(cmd->pool, msgbuf); } a->type = T_IP; } else { /* no slash, didn't look like an IP address => must be a host */ a->type = T_HOST; } a->rate = temp; return NULL; } /* Set the large file bandwidth limit */ static const char *largefilelimit(cmd_parms * cmd, void *s, const char *file, const char *size, const char *bw) { bandwidth_config *conf = (bandwidth_config *) s; bw_sizel *a; long int temp, tsize; char msgbuf[MAX_BUF]; if (strlen(file) < 1) return "You must enter a filetype (use * for all)"; if (bw && *bw && (*bw == '-' || apr_isdigit(*bw))) temp = atol(bw); else return "Invalid argument"; if (size && *size && apr_isdigit(*size)) tsize = atol(size); else return "Invalid argument"; if (temp < 0) return "BandWidth must be a number of bytes/s"; if (tsize < 0) return "File size must be a number of Kbytes"; a = (bw_sizel *) apr_array_push(conf->sizelimits); a->file = (char *) file; a->size = tsize; a->rate = temp; if (sid < MAX_VHOSTS) { vnames[sid] = apr_pcalloc(cmd->pool,apr_snprintf(msgbuf,MAX_BUF,"%s,%s",cmd->server->server_hostname,file) ); vnames[sid] = (char *) apr_pstrdup(cmd->pool, msgbuf); } a->sid = sid++; return NULL; } /*----------------------------------------------------------------------------* * Helper Functions * *----------------------------------------------------------------------------*/ /* Match the input, as part of a domain */ static int in_domain(const char *domain, const char *what) { int dl = strlen(domain); int wl = strlen(what); if ((wl - dl) >= 0) { if (strcasecmp(domain, &what[wl - dl]) != 0) return 0; /* Make sure we matched an *entire* subdomain --- if the user * said 'allow from good.com', we don't want people from nogood.com * to be able to get in. */ if (wl == dl) return 1; /* matched whole thing */ else return (domain[0] == '.' || what[wl - dl - 1] == '.'); } else return 0; } /* Get the bandwidth limit based on from address */ static long get_bw_rate(request_rec * r, apr_array_header_t * a) { bw_entry *e = (bw_entry *) a->elts; const char *remotehost = NULL; int i; int gothost = 0; const char *uastr = NULL; for (i = 0; i < a->nelts; i++) { switch (e[i].type) { case T_AGENT: uastr = apr_table_get(r->headers_in, "User-Agent"); if (e[i].agent && uastr && ap_regexec(e[i].agent, uastr, 0, NULL, 0)==0 ) return (e[i].rate); break; case T_ALL: return e[i].rate; case T_IP: if (apr_ipsubnet_test(e[i].x.ip, r->connection->remote_addr)) { return e[i].rate; } break; case T_HOST: if (!gothost) { int remotehost_is_ip; remotehost = ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_DOUBLE_REV, &remotehost_is_ip); if ((remotehost == NULL) || remotehost_is_ip) gothost = 1; else gothost = 2; } if ((gothost == 2) && in_domain(e[i].x.from, remotehost)) return (e[i].rate); break; } } return 0; } /* Match the pattern with the last digist from filename An asterisk means any. */ static int match_ext(const char *file, char *match) { if (file == NULL || match == NULL) return 0; if (strlen(match) > strlen(file)) return 0; if (strncmp(match, "*", 1) == 0) return 1; file += strlen(file) - strlen(match); if (strncmp(match, file, strlen(match)) == 0) return 1; return 0; } /* Get the bandwidth limit based on filesize */ static long get_bw_filesize(request_rec * r, apr_array_header_t * a, apr_uint32_t filesize, const char *filename) { bw_sizel *e = (bw_sizel *) a->elts; int i; apr_uint32_t tmpsize = 0, tmprate = 0; if (!filesize) return (0); filesize /= 1024; for (i = 0; i < a->nelts; i++) { if ((e[i].size <= filesize) && match_ext(filename, e[i].file)) if (tmpsize <= e[i].size) { tmpsize = e[i].size; tmprate = e[i].rate; } } return (tmprate); } /* Get the MaxConnections allowed */ static int get_maxconn(request_rec * r, apr_array_header_t * a) { bw_maxconn *e = (bw_maxconn *) a->elts; const char *remotehost = NULL; int i; int gothost = 0; const char *uastr = NULL; for (i = 0; i < a->nelts; i++) { switch (e[i].type) { case T_AGENT: uastr = apr_table_get(r->headers_in, "User-Agent"); if (e[i].agent && uastr && ap_regexec(e[i].agent, uastr, 0, NULL, 0)==0 ) return (e[i].max); break; case T_ALL: return e[i].max; case T_IP: if (apr_ipsubnet_test(e[i].x.ip, r->connection->remote_addr)) { return e[i].max; } break; case T_HOST: if (!gothost) { int remotehost_is_ip; remotehost = ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_DOUBLE_REV, &remotehost_is_ip); if ((remotehost == NULL) || remotehost_is_ip) gothost = 1; else gothost = 2; } if ((gothost == 2) && in_domain(e[i].x.from, remotehost)) return (e[i].max); break; } } return 0; } /* Get an id based on bandwidth limit */ static int get_sid(request_rec * r, apr_array_header_t * a) { bw_entry *e = (bw_entry *) a->elts; const char *remotehost = NULL; int i; int gothost = 0; const char *uastr; remotehost = ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_HOST, NULL); for (i = 0; i < a->nelts; i++) { switch (e[i].type) { case T_AGENT: uastr = apr_table_get(r->headers_in, "User-Agent"); if (e[i].agent && uastr && ap_regexec(e[i].agent, uastr, 0, NULL, 0)==0 ) return (e[i].sid); break; case T_ALL: return e[i].sid; case T_IP: if (apr_ipsubnet_test(e[i].x.ip, r->connection->remote_addr)) { return e[i].sid; } break; case T_HOST: if (!gothost) { int remotehost_is_ip; remotehost = ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_DOUBLE_REV, &remotehost_is_ip); if ((remotehost == NULL) || remotehost_is_ip) gothost = 1; else gothost = 2; } if ((gothost == 2) && in_domain(e[i].x.from, remotehost)) return (e[i].sid); break; } } return -1; } /* Get an id based on filesize limit */ static int get_f_sid(request_rec * r, apr_array_header_t * a, apr_uint32_t filesize, const char *filename) { bw_sizel *e = (bw_sizel *) a->elts; int i; apr_uint32_t tmpsize = 0, tmpsid = -1; if (!filesize) return (0); filesize /= 1024; for (i = 0; i < a->nelts; i++) { if ((e[i].size <= filesize) && match_ext(filename, e[i].file)) if (tmpsize <= e[i].size) { tmpsize = e[i].size; tmpsid = e[i].sid; } } if (tmpsid < 0) return -1; return (tmpsid); } /* Update memory (shm) counters, which holds the bw data per context */ static void update_counters(bw_data * bwstat, ap_filter_t * f) { apr_time_t nowtime; /* Refresh only if 1s has passed */ nowtime = apr_time_now(); if (bwstat->time < (nowtime - 1000000)) { /* And if we got lock */ if (apr_atomic_cas32(&bwstat->lock, 1, 0) == 0) { /* Calculate bw used in the last timeinterval */ bwstat->bandwidth = (apr_uint32_t) ( (bwstat->bytes_count / (double) (nowtime - bwstat->time)) * 1000000); /* Reset counters */ bwstat->bytes_count = 0; /* Set timestamp */ bwstat->time = apr_time_now(); /* Release lock */ apr_atomic_set32(&bwstat->lock, 0); } } } static int callback(request_rec * r) { int t; bw_data *bwstat; if (r->header_only) { return OK; } if (r->args && !strncasecmp(r->args,"csv",3)) { ap_set_content_type(r, "text/plain"); ap_rputs("id,vhost,scope,lock,count,bw,bytes,hits\n",r); for (t = 0; t < sid; t++) { bwstat = bwbase + t; ap_rprintf(r,"%d,%s,%d,%d,%d,%d,%d\n",t,bwstat->v_name,bwstat->lock,bwstat->connection_count,bwstat->bandwidth,bwstat->bytes_count,bwstat->counter); } return OK; } ap_set_content_type(r, "text/html"); ap_rputs(DOCTYPE_HTML_3_2, r); ap_rputs("\n", r); ap_rputs(" \n", r); ap_rputs(" mod_bw Status\n", r); ap_rputs(" \n", r); ap_rputs(" \n", r); ap_rputs("

mod_bw : Status callback\n", r); ap_rputs("

\n", r); ap_rputs("

\n", r); ap_rprintf(r, " Apache HTTP Server version: \"%s\"\n", ap_get_server_banner()); ap_rputs("
\n", r); ap_rprintf(r, " Server built: \"%s\"\n", ap_get_server_built()); ap_rputs("

\n", r);; ap_rputs(" \n", r); for (t = 0; t < sid; t++) { bwstat = bwbase + t; /* This inits the struct that will contain current bw use */ ap_rputs("
",r); ap_rprintf(r,"id : %d
",t); ap_rprintf(r,"name : %s
",bwstat->v_name); ap_rprintf(r,"lock : %d
",bwstat->lock); ap_rprintf(r,"count: %d
",bwstat->connection_count); ap_rprintf(r,"bw : %d
",bwstat->bandwidth); ap_rprintf(r,"bytes: %d
",bwstat->bytes_count); ap_rprintf(r,"hits : %d
",bwstat->counter); } ap_rputs(" \n", r); ap_rputs("\n", r); return OK; } /*----------------------------------------------------------------------------* * The Handler and the Output Filter. Core of the mod. * *----------------------------------------------------------------------------*/ /* With this handler, we can *force* the use of the mod. */ static int handle_bw(request_rec * r) { bandwidth_server_config *sconf = (bandwidth_server_config *) ap_get_module_config(r->server-> module_config, &bw_module); bandwidth_config *conf = (bandwidth_config *) ap_get_module_config(r->per_dir_config, &bw_module); bw_data *bwstat; apr_int32_t confid; /* Only work on main request/no subrequests */ if (r->main) return DECLINED; if (strcmp(r->handler, "modbw-handler")==0) return callback(r); /* Return if module is not enabled */ if (sconf->state == BANDWIDTH_DISABLED) return DECLINED; /* Get the ID of the memory space we are using */ confid = get_sid(r, conf->limits); /* Only if we have a valid space */ if (confid >= 0) { /* We "get" the data of the current configuration */ bwstat = bwbase + confid; apr_atomic_add32(&bwstat->counter, 1); /* If we are too busy, deny connection */ confid = get_maxconn(r, conf->maxconnection); if ((bwstat->connection_count >= (apr_uint32_t) confid) && (confid > 0)) return conf->error; } /* Add the Filter, if in forced mode */ if (sconf->force == BANDWIDTH_ENABLED) ap_add_output_filter("mod_bw", NULL, r, r->connection); /* Pass the control */ return DECLINED; } static int bw_filter(ap_filter_t * f, apr_bucket_brigade * bb) { request_rec *r = f->r; bandwidth_config *conf = (bandwidth_config *) ap_get_module_config(r->per_dir_config, &bw_module); bandwidth_server_config *sconf = (bandwidth_server_config *) ap_get_module_config(r->server-> module_config, &bw_module); ctx_struct *ctx = f->ctx; apr_bucket *b = APR_BRIGADE_FIRST(bb); bw_data *bwstat, *bwmaxconn; int confid = -1, connid = -1; apr_size_t packet = conf->packet, bytes = 0; apr_off_t bblen = 0; long int bw_rate, bw_min, bw_f_rate, cur_rate = 0, sleep; const char *buf; const char *filename; /* Only work on main request/no subrequests */ if (r->main) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } /* Return as fast as possible if the module is not enabled */ if (sconf->state == BANDWIDTH_DISABLED) { ap_pass_brigade(f->next, bb); return APR_SUCCESS; } /* Get the bw rates */ bw_rate = get_bw_rate(r, conf->limits); bw_min = get_bw_rate(r, conf->minlimits); confid = connid = get_sid(r, conf->limits); /* Get the filename */ filename = r->finfo.fname; if (filename == NULL) filename = r->filename; /* Get the File Rate. r->finfo.size is not used anymore. */ bblen = r->bytes_sent; bw_f_rate = get_bw_filesize(r, conf->sizelimits, (off_t) bblen, filename); /* Check if we've got an ilimited client */ if ((bw_rate == 0 && bw_f_rate == 0) || bw_f_rate < 0) { ap_pass_brigade(f->next, bb); return APR_SUCCESS; } /* - File size limit has precedence over location limit, if it's slower. - The minimal bw used, will never be less than the default minimal bw. - If file size is zero, all files apply */ if (bw_f_rate && (bw_rate > bw_f_rate || !bw_rate)) { confid = get_f_sid(r, conf->sizelimits, (off_t) bblen, filename); bw_rate = bw_f_rate; } if (bw_min < 0) bw_min = bw_rate; else if (!bw_min) bw_min = MIN_BW; /* Initialize our temporal space */ if (ctx == NULL) { apr_bucket_alloc_t *bucket_alloc = apr_bucket_alloc_create(f->r->pool); f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx)); ctx->bb = apr_brigade_create(f->r->pool, bucket_alloc); } /* We "get" the data of the current configuration */ bwstat = bwbase + confid; /* If we have a valid bandwidth limit per host, get the maxconn limit */ if (connid >= 0) bwmaxconn = bwbase + connid; else bwmaxconn = bwstat; /* Add 1 active connection to the record */ apr_atomic_inc32(&bwmaxconn->connection_count); /* Verbose Output */ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "ID: %i Directory : %s Rate : %ld Minimum : %ld Size rate : %ld", confid, conf->directory, bw_rate, bw_min, bw_f_rate); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "clients : %d/%d rate/min : %ld,%ld", bwmaxconn->connection_count, (connid >= 0) ? get_maxconn(r, conf->maxconnection) : 0, bw_rate, bw_min); /* - We get buckets until a sentinel appears - Read the content of the bucket, and send it to the next filter, piece by piece */ while (b != APR_BRIGADE_SENTINEL(bb)) { /* If the bucket is EOS end here */ if (APR_BUCKET_IS_EOS(b) || APR_BUCKET_IS_FLUSH(b)) { APR_BUCKET_REMOVE(b); APR_BRIGADE_INSERT_TAIL(ctx->bb, b); ap_pass_brigade(f->next, ctx->bb); /* Delete 1 active connection */ apr_atomic_dec32(&bwmaxconn->connection_count); return APR_SUCCESS; } if (apr_bucket_read(b, &buf, &bytes, APR_NONBLOCK_READ) == APR_SUCCESS) { /* This changed, cause of the limit handling error.. see below */ while (bytes > 0) { /* - Ok, i'm doing lots of things here. The bw the client will have, is the bw available divided by the number of clients. - The minimum bw, will always be MIN_BW. If all bw is used, and new connections arrives, they'll have MIN_BW bw available. */ cur_rate = (long int) bw_rate / bwmaxconn->connection_count; if (cur_rate > bw_rate) cur_rate = bw_rate; if (cur_rate < bw_min) cur_rate = bw_min; /* - Some times we got a bw that is less than packetsize. That causes to have a delay between packets > 1s. There are some clients that will timeout if we took too long between packets, so , we adapt the packetsize, to always be sending data, every 1s top. */ if (cur_rate <= conf->packet) packet = cur_rate; else packet = conf->packet; /* This was a really weird issue. If the bytes available are less than the packet to send.. all bw was used. Limit that */ if (bytes < packet) packet = bytes; /* Here we get the time we need to sleep to get the specified bw */ sleep = (long int) (1000000 / ((double) cur_rate / (double) packet)); #if defined(WIN32) if (sleep < 200000 && cur_rate > 1024) { sleep = 200000; packet = cur_rate/5; if (bytes < packet) packet = bytes; } #endif /* Here, we are going to split the bucket, and send it on piece at a time, doing a "delay" between each piece. That way, we send the data at the specified rate. */ apr_bucket_split(b, packet); APR_BUCKET_REMOVE(b); APR_BRIGADE_INSERT_TAIL(ctx->bb, b); /* Decrease our counter */ bytes -= packet; /* Flush and move to the next bucket */ ap_pass_brigade(f->next, ctx->bb); b = APR_BRIGADE_FIRST(bb); /* Add the number of bytes transferred, so we can get an estimated bw usage */ apr_atomic_add32(&bwstat->bytes_count, packet); /* If the connection goes to hell... go with it ! */ if (r->connection->aborted) { /* Verbose. Tells when the connection was ended */ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "Connection to hell"); apr_atomic_dec32(&bwmaxconn->connection_count); return APR_SUCCESS; } /* Sleep ... zZZzZzZzzzz */ apr_sleep(sleep); /* Refresh counters, so we can keep working :) */ update_counters(bwstat, f); } } /* A leftover bucket. Pass it as the others */ APR_BUCKET_REMOVE(b); APR_BRIGADE_INSERT_TAIL(ctx->bb, b); b = APR_BRIGADE_FIRST(bb); /* Add the number of bytes to the counter */ apr_atomic_add32(&bwstat->bytes_count, bytes); /* Pass the final brigade */ ap_pass_brigade(f->next, ctx->bb); } /* Delete 1 active connection to the record */ apr_atomic_dec32(&bwmaxconn->connection_count); /* Give the control to the next filter's */ return APR_SUCCESS; } /*----------------------------------------------------------------------------* * Module Init functions * *----------------------------------------------------------------------------*/ static int bw_init(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, server_rec * s) { apr_status_t status; apr_size_t retsize; apr_size_t shm_size; bw_data *bwstat; int t; #if defined(WIN32) TIMECAPS resolution; #endif /* These two help ensure that we only init once. */ void *data; const char *userdata_key = "ivn_shm_bw_limit_module"; apr_pool_userdata_get(&data, userdata_key, s->process->pool); if (!data) { apr_pool_userdata_set((const void *) 1, userdata_key, apr_pool_cleanup_null, s->process->pool); return OK; } /* Init APR's atomic functions */ status = apr_atomic_init(p); if (status != APR_SUCCESS) return HTTP_INTERNAL_SERVER_ERROR; shm_size = (apr_size_t) sizeof(bw_data) * sid; /* If there was a memory block already assigned.. destroy it */ if (shm) { status = apr_shm_destroy(shm); if (status != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "mod_bw : Couldn't destroy old memory block\n"); return status; } else { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "mod_bw : Old Shared memory block, destroyed."); } } /* Create shared memory block */ status = apr_shm_create(&shm, shm_size, NULL, p); if (status != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "mod_bw : Error creating shm block\n"); return status; } /* Check size of shared memory block */ retsize = apr_shm_size_get(shm); if (retsize != shm_size) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "mod_bw : Error allocating shared memory block\n"); return status; } /* Init shm block */ bwbase = apr_shm_baseaddr_get(shm); if (bwbase == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "mod_bw : Error creating status block.\n"); return status; } memset(bwbase, 0, retsize); ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, "mod_bw : Memory Allocated %d bytes (each conf takes %d bytes)", (int) retsize, (int) sizeof(bw_data)); if (retsize < (sizeof(bw_data) * sid)) { ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, "mod_bw : Not enough memory allocated!! Giving up"); return HTTP_INTERNAL_SERVER_ERROR; } for (t = 0; t < sid; t++) { bwstat = bwbase + t; /* This inits the struct that will contain current bw use */ bwstat->time = apr_time_now(); bwstat->lock = 0; bwstat->connection_count = 0; bwstat->bandwidth = 0; bwstat->bytes_count = 0; bwstat->counter = 0; bwstat->v_name = vnames[t]; } ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, "mod_bw : Version %s - Initialized [%d Confs]", VERSION, sid); #if defined(WIN32) // Set the timer resolution to its minimum if (timeGetDevCaps (&resolution, sizeof (TIMECAPS)) == TIMERR_NOERROR) { ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, "mod_bw : Supported resolution for Timers [ Min: %d Max: %d ]",resolution.wPeriodMin,resolution.wPeriodMax); if (timeBeginPeriod (resolution.wPeriodMin) == TIMERR_NOERROR) ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, "mod_bw : Enabling High resolution timers [ %d ms ]",resolution.wPeriodMin); else ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "mod_bw : Can't enable High Resolution timers. Speed might be reduced."); } #endif return OK; } static void *create_bw_config(apr_pool_t * p, char *path) { bandwidth_config *new = (bandwidth_config *) apr_palloc(p, sizeof(bandwidth_config)); new->limits = apr_array_make(p, 20, sizeof(bw_entry)); new->minlimits = apr_array_make(p, 20, sizeof(bw_entry)); new->sizelimits = apr_array_make(p, 10, sizeof(bw_sizel)); new->maxconnection = apr_array_make(p, 10, sizeof(bw_maxconn)); new->directory = (char *) apr_pstrdup(p, path); new->packet = PACKET; new->error = HTTP_SERVICE_UNAVAILABLE; return (void *) new; } static void *create_bw_server_config(apr_pool_t * p, server_rec * s) { bandwidth_server_config *new; new = (bandwidth_server_config *) apr_pcalloc(p, sizeof (bandwidth_server_config)); new->state = BANDWIDTH_DISABLED; new->force = BANDWIDTH_DISABLED; return (void *) new; } /*----------------------------------------------------------------------------* * Apache register functions * *----------------------------------------------------------------------------*/ static void register_hooks(apr_pool_t * p) { /* - Register a handler, which enforces mod_bw if needed - Register the Output Filter - And the init function of the mod. */ ap_hook_handler(handle_bw, NULL, NULL, APR_HOOK_FIRST); ap_register_output_filter("mod_bw", bw_filter, NULL, AP_FTYPE_TRANSCODE); ap_hook_post_config(bw_init, NULL, NULL, APR_HOOK_MIDDLE); } /* Command Table */ static const command_rec bw_cmds[] = { AP_INIT_TAKE2("MaxConnection", maxconnection, NULL, RSRC_CONF | ACCESS_CONF, "a domain (or ip, or all) and the max connections allowed"), AP_INIT_FLAG("BandWidthModule", bandwidthmodule, NULL, RSRC_CONF | ACCESS_CONF, "On or Off to enable or disable (default) the whole bandwidth module"), AP_INIT_FLAG("ForceBandWidthModule", forcebandwidthmodule, NULL, RSRC_CONF | ACCESS_CONF, "On or Off to enable or disable (default) the mod catching every request"), AP_INIT_TAKE1("BandWidthPacket", setpacket, NULL, RSRC_CONF | ACCESS_CONF, "Size of the maximun packet to use."), AP_INIT_TAKE2("BandWidth", bandwidth, NULL, RSRC_CONF | ACCESS_CONF, "a domain (or ip, or all) and a bandwidth limit (in bytes/s)"), AP_INIT_TAKE2("MinBandWidth", minbandwidth, NULL, RSRC_CONF | ACCESS_CONF, "a domain (or ip, or all) and a minimal bandwidth limit (in bytes/s)"), AP_INIT_TAKE1("BandWidthError", bandwidtherror, NULL, RSRC_CONF | ACCESS_CONF, "a http error number. Useful to deliver standar (or personal) error messages"), AP_INIT_TAKE3("LargeFileLimit", largefilelimit, NULL, RSRC_CONF | ACCESS_CONF, "a file extension, a filesize (in Kbytes) and a bandwidth limit (in bytes/s)"), {NULL} }; module AP_MODULE_DECLARE_DATA bw_module = { STANDARD20_MODULE_STUFF, create_bw_config, /* dir config creater */ NULL, /* dir merger --- default is to override */ create_bw_server_config, /* server config */ NULL, /* merge server config */ bw_cmds, /* command table */ register_hooks /* register_hooks */ }; mod_bw.txt0000644000000000000000000006211411421345520011565 0ustar rootrootApache2 - Mod_bw v0.92 Author : Ivan Barrera A. (Bruce) HomePage : Http://Ivn.cl/apache Release Date : 20-Jul-2010 Status : Stable License : Licensed under the Apache Software License v2.0 It must be included as LICENSE in this package. Platform : Linux/x86 (Tested with Fedora Core 4, Suse, etc) Linux/x86_64 (Redhat Enterprise 4) FreeBSD/x86 (Tested on 5.2) MacOS X/Ppc x86 (Tested on both platforms) Solaris 8/sparc (Some notes on compile) Microsoft Windows (Win XP, Win2003, Win7. Others should work) HP-UX 11 (Thanks to Soumendu Bhattacharya for testing) Notes : This is a stable version of mod_bw. It should work with almost any MPM (tested with WinNT/prefork/Worker MPM). We are reaching the End of mod_bw series 0.x. As soon as this last changes are confirmed by the users (perhaps some changes at request), i'll set this release to version 1.0 final. Limitations : This mod doesn't know how fast the client is downloading a file, so it just divides the bw assigned between the users. MaxConnections works only for the given scope. (i.e , all will limit maxconnections from all,not per ip or user) Changelog : 2010-07-20 : Fixed ap_get_server_banner unknown on older apache version 2010-05-27 : Fixed weird behaviour on Windows Hosts. (mod_bw.txt) Added high resolution timers for windows. (speed improvements) Fixed stupid bug that caused crash when mod is enabled but there is not a single limit. 2010-05-24 : Code Cleanup. No more warnings or stuff in Visual Studio 2010-04-28 : Bruce's Birthday Gift : A callback to the stats of the mod :) 2010-04-06 : Fixed "Invisible" memory leak. Only seen when serving HUGE streams. 2010-03-10 : Fixed MinBandwidth screwing limits. Another unsigned/signed screw up. ------------------------------------------------------------------------------ Wow.. Didnt hope to release fixes so soon. Yesterday, Jacky Yuen contacted me about a bug in the mod. It didn't look like the mod's fault, but i took a deeper look. The issue : On his windows server (win2003, and XP) the mod was working, but was unable to reach high speeds (1MB/s). (i heard something like this before, and all you need is to change the packet size). However, doing this, didn't fix the issue. I tried this on my server, and i was able to get about 500KB/s. Setting the packet size to 16384 gave me 1MB/s. Jacky told me he still had problems, so i tried all windows systems i got around. A windows 2003 server, a Windows 7 (32bits) showed the problem (max speed was 490KB/s) . A Windows 7 64bits and Win 2003 SP2 didnt. The most awesome part, is that if you run Google Chrome, a Flash application, or Windows Media Player, the mod is able to deliver up to 1MB/s. Took me a while, but i found that Windows doesnt run a high resolution timer all the time. In the servers with the issue, the timer run every 10 ms. Whenever you run some of the mentioned apps, they set the timer to the highest speed possible (in my equipment, 1 ms). This setting affects all other applications, so the mod was able to sleep small times to deliver data as it needed. So, i wrote 2 fixes. The first one, i didnt wanted to, but i made the mod to set the timer to the highest resolution possible (it is written to the logs if you want to peek). And the second, is to avoid waiting small times to send data. Minimum time to sleep is 200ms now, and data is adjusted for this. (only for windows). Both fixes are under defines that will compile just for windows, so linux users won't notice any changes. The good thing, for windows users, is that with this fixes, i've been able to get speeds up to 2.45MB/s under windows!. Using apache by itself, i just got 1.2MB/s. Just enabling the mod and setting a high limit, the speed got pumped up. That's it. Now i'll be back working on the next release. -- (previous readme.txt text follows dated 24/May/2010) Again... It has been a while since i've upted the code. (work, personal life, money issues, the same stuff we all fight daily) However, that doesn't mean i forgot about it. I've just been working my *** off. I've got many emails with suggestions, some bugs, etc.. I'm doing one of the last updates of this line of mod_bw (0.x). It's mainly a couple of bugfix, and a little callback to get stats on the running mod. I hope it wont break anything.. (of course i've tested it a lot). Ah!, i said this is one of the last updates. Yes. This is because this line of code is limiting the possibilities of the mod, so i've started a new mod_bw using other set of techniques. (so, it is highly experimental, and uses completely new instructions). This new branch of the mod was born thanks to the email sent by Borislav Borislavov (icn.bg), who needed some special features to be implemented, not possible with the current code. So, if you wanted the mod to do per-ip limiting, traffic limiting, etc.. keep checking my site. I'll be releasing a new branch of mod_bw to public soon. (Unfortunately, it won't be released under the Apache license yet). Now, back to this mod : First, i've fixed two annoying bugs. - MinBandWidth -1 was screwing things up sometimes. All because i was using an unsigned integer to store the -1. Yeah, my bad. - The limiting handler was leaking memory. A few bytes at a time, but for large files (really, really large) this could mean one of the apache childs consuming almost all memory. Thanks to Christian Spielberger who insisted and helped me to find this. (i was sure there was something.. but my servers recycle apache childs pretty often). Fun Fact : I found the bug, was cleaning my test code, and he sent me an email with the exact same solution i came up. Great minds think alike, eh ? :) Then, i made a status callback for the mod. This callback will show some stats on the running mod, for each memory segment used to limit bandwidth. Is it a simple stat. However, i received many emails asking for this. This is easy to achieve : In your admin vhost (if you have one.. If not, any vhost you want to check), use a location to set a handler for the callback. Suppose the vhost for 127.0.0.1 : SetHandler modbw-handler Now you can get information of the mod by visiting http://127.0.0.1/modbw You can get the same information in csv format at http://127.0.0.1/modbw?csv Please, test this changes, let me know how it works. If you have some ideas (i.e. information to add in the stats), email me. If it can or can't be done in this branch of the mod, i'll let you know. ------------------------------------------------------------------------------ Contents : 1 .- Notices 1.1 - ChangeLog and TODO 1.2 - Important Caveats 2 .- Installing 2.1 - Windows 2.2 - Linux 3 .- Getting it to Work, Directives 3.1 - BandWidthModule 3.2 - ForceBandWidthModule 3.3 - BandWidth 3.4 - MinBandWidth 3.5 - LargeFileLimit 3.6 - BandWidthPacket 3.7 - BandWidthError 3.8 - MaxConnection 3.9 - Status Callback 4 .- Examples 4.1 - Misc Examples 5 .- FAQ 6 .- Thanks ------------------------------------------------------------------------------ 1.- Notices 1.1 - ChangeLog & TODO : This has been moved to the files ChangeLog and TODO. Available on the packages and CVS at SourceForge. 1.2 - Important Caveats : There is a couple situations in which you might get unexpected results with mod_bw. From the emails i've got, most of this situations are when using mod_proxy, and sometimes mod_php. If you are using one of this mods, and you see mod_bw output in the error log but there is no limiting being done, the first think you have to try, is to change the LoadModule directive on httpd.conf. Put mod_bw directive as the first one in the list, and then all the others. (or at least, put it before mod_proxy and mod_php). This should fix the issue. Also, there are some static values defined in the code, that *might* cause weird issues in specific cases. The main two, are listed here : #define MAX_VHOSTS 1024 #define MAX_BUF 1024 For the new status page, the mod assumes you wont have more than 1024 mod_bw enabled virtual hosts. If you do, please, change this number. It won't cause any troubles, it just mean the max number of "spaces" to reserve when storing the vhost names in memory. If you dont fix this number, only the first MAX_VHOSTS vhosts will show at the status page. The second line, is the length of the temporal space the mod uses to create some strings. If you have really long vhost names, you might find the status page shows these names truncated at some point, and show incorrect info. You will need to increase this number. ------------------------------------------------------------------------------ 2.- How To Install : 2.1 - Windows In Windows, you have to download the binary dll from the site (or compile one yourself) that matches your apache version, and Install it under modules on your apache tree. Then edit httpd.conf, and add the LoadModule sentence. If there is no matching binary dll with your version of apache (the latest) you can ask me to compile it for you. Post a message in the home site as a feature request. Example : Download mod_bw-2.2.14.dll from the home site, to c:\apache2\modules Edit httpd.conf, and add LoadModule bw_module modules/mod_bw-2.2.14.dll Restart Apache. 2.2 - Linux Well.. (under *nix) you *have* to compile it.. (unless your distribution has it available as a package. Many already do ! Thanks !) REQUIREMENTS : You will need apache2 headers in the include path. In redhat/suse or others this mean apache-devel or httpd-devel installed. (or any other that provides http/apr headers and apxs2 tool) You also need support for SHM in your OS. Usually you will have this. For this to work, you MUST have apxs (or apxs2) installed. You might need to use its full path (i.e. /usr/sbin/apxs .....). Well, you have to do this : apxs -i -a -c mod_bw.c or apxs2 -i -a -c mod_bw.c This will bring out some stuff... nothing to worry (unless you see an error). If it says ok, then you are ready. Restart apache (service httpd restart on redhat, mandrake, and others... apachectl restart .. rcapache2 restart or... well, you should know) Note On Solaris Users : If you got problems compiling it on Solaris, remember to check you have libgcc correctly installed, and the ld_library_path set up correctly. I didnt, so i compiled this way : export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib apxs -i -a -c mod_bw.c -L/usr/local/lib -lgcc_s And then started apache, and everything worked fine. Note On SuSe Users : You NEED to have installed apache2 header files (apache2-devel rpm). Otherwise you'll get lots of include files error. If you didn't manage to get it installed, drop me a letter. But please be sure you tried everything. Sometimes it is just a forgotten step. ------------------------------------------------------------------------------ 3.- Getting it to Work : Ok.. this is the most confusing part. This mod, is able to limit bandwidth usage on every virtual host or directory. htaccess files will not be supported on this branch. (0.x) * Configuration Directives ***************************************************************************** 3.1 - BandWidthModule [On|Off] You need to set this to On for the mod to work.. By default, the mod is disabled, and wont limit anything. Example : BandWidthModule On 3.2 - ForceBandWidthModule [On|Off] By default, the mod wont catch every request. If you enable it, every request will be processed by the mod. Example : (normal use) AddOutputFilterByType MOD_BW text/html text/plain (enabling Force) ForceBandWidthModule On 3.3 - BandWidth [From] [bytes/s] This takes 2 parameters. From is the origin of the connections. It could be a full host, part of a domain, an ip address, a network mask (i.e 192.168.0.0/24 or 192.168.0.0/255.255.255.0) or all. The second parameter indicates the total speed available to the Origin. If speed is 0, there is no limit. Example : BandWidth localhost 10240 BandWidth 192.168.218.5 0 ( Order is relevant. First entries have precedence ) As for version 0.8, an user agent matching capability was introduced. If you want to limit all clients using certain browser, you can limit doing this : BandWidth u:[User-Agent] [bytes/s] User agent is a regular expression which will match the one sent by the browser. This is easier to explain with examples : Example : BandWidth "u:^Mozilla/5(.*)" 10240 BandWidth "u:wget" 102400 First one, will match a browser that identifies itself as Mozilla/5(etc) Second one, will match a browser that has wget in any part of its id. 3.4 - MinBandWidth [From] [bytes/s] This takes 2 parameters. From is the origin of the connections. It could be a full host, part of a domain, an ip address, a network mask (i.e 192.168.0.0/24 or 192.168.0.0/255.255.255.0) or all. The second parameter indicates the minimun speed each client will have. What does this mean ? If you have a total of 100kbytes speed, and you put MinBandWidth at 50kbytes, it doesnt matter how many clients you have, all of them will have a minimun of 50kbytes of total speed to download. If speed is 0, you will be using the default minimun (256 bytes/s). There is a special value of -1. This value means that each client will have a top speed determined by the BandWidth directive. See the examples. Examples : BandWidth all 102400 MinBandWidth all 50000 The example above will set a top speed of 100kb for the 1º client. If more clients come, it will be splitted accordingly but everyone will have at least 50kb (even if you have 50 clients) BandWidth all 50000 MinBandWidth all -1 This example, makes everyone have 50kb as top speed. 3.5 - LargeFileLimit [Type] [Minimum Size] [bytes/s] Type, is the last part of a file, or * for all. You can use .tgz to match only tar-compressed files, .avi to match video files, or * to match all. Minimum Size, is the size (in kbytes) of the file, to be matched. That way you can match huge video files that hog your bandwidth. The last parameter... is obvious. The speed allowed. Example : LargeFileLimit .avi 500 10240 This limits .avi files over (or equal to) 500kb to 10kbytes/s 3.6 - BandWidthPacket [Size] Probably you never need to touch this. It defaults to 8192 which is good for almost any speed. It must be a size between 1024 and 131072. A Small packet will cause the top speed to be lower, and the mod using more time splitting. If you use a Size too big, the mod will adjust it to lower speeds. If you are using the mod in high speed networks, this is, you want to set limits of megabits/s, you will be better using packet sizes of 16384, or 32768. 3.7 - BandWidthError [Error] This directive is useful to deliver a personalized error code. By default, when maxconnections is reached, the mod will issue a 503 HTTP_SERVICE_UNAVAILABLE code. For some users, it is annoying to have an error message, and don't knowing why. You could use an ErrorDocument to point error 503 to a page explaining that you are under a heavy load of connections, but sometimes 503 is issued by the server for other reasons. So, with this directive, you can set the error code to return when maxconnections is reached. You can use any error code between 300 and 599. Please note, that some of the error codes are already used, so before using any number, take a look to a list of the codes (search for http error codes in google). When testing, i've used the error code 510, which hasn't been defined yet. And Example, with Personalized Error Page : ErrorDocument 510 /errors/maxconexceeded.html BandWidthError 510 Note : Sometimes, the personalized page didn't appear. I'm not sure, but in many cases, it got fixed, by making the page size over 1024bytes. Anyways, if you need help using ErrorDocument, refer to the apache Documentation. 3.8 - MaxConnection [From] [Max] This takes 2 parameters. From is the origin of the connections. It could be a full host, part of a domain, an ip address, a network mask (i.e 192.168.0.0/24 or 192.168.0.0/255.255.255.0) or all. The second parameter, is the max connections allowed from the origin. Any connection over Max, will get a 503 Service Temporarily Unavailable There is a catch. You NEED to have a BandWidth limit for the same origin. It doesnt need to be a low limit. But you need one. (unlimited, doesn't count) You might wonder why. It's because im using them same memory space of the bandwidth limit to count the connections, so i can save memory space. If you dont put a BandWidth using the same origin, MaxConnections will be ignored. Example : BandWidth all 102400000 MaxConnection all 20 or BandWidth all 102400000 BandWidth 192.168.0.0/24 10240 MaxConnection all 20 MaxConnection 192.168.0.0/24 5 As for version 0.8, an user agent matching capability was introduced. If you want to limit all clients using certain browser, you can limit doing this : MaxConnection u:[User-Agent] [Max] User agent is a regular expression which will match the one sent by the browser. This is easier to explain with examples : Example : MaxConnection "u:^Mozilla/5(.*)" 5 MaxConnection "u:wget" 5 First one, will match a browser that identifies itself as Mozilla/5(etc) Second one, will match a browser that has wget in any part of its id. Please, rememeber that every speed, will depend mostly on your connection. You can't get more speed if you dont have it. Remember also.. if you dont follow the instructions, and get some weird results, recheck your config before sending me an email. 3.9 - Status Callback Since v0.9, the mod can display a simple status page, showing stats from the running mod. This stats show the exact information being used by the mod to do the limiting in that second. For this to work, you need to set a handler on any vhost. You might want to set this under an admin vhost, or set some policies to make it private. Your call. Example (let's assume the vhost is for 127.0.0.1) : SetHandler modbw-handler Now, you can get the status info at http://127.0.0.1/modbw ( Or download a CSV of the stats at http://127.0.0.1/modbw?csv ) The information provided is the following : id : 0 // This is just a correlative number for each config. name : work.ivn.cl,all // The vhost name, and the scope of the rule lock : 0 // If the memory segment is being used (0 = no) count: 0 // Number of users connected to this rule bw : 0 // Bandwidth currently being used by the rule bytes: 0 // Number of bytes last sent. Only true if count>0 hits : 0 // Number of times anyone has accesed this rule. Simple, yet useful ! ------------------------------------------------------------------------------ 4.- Examples 4.1 - Misc examples Limit every user to a max of 10Kb/s on a vhost : BandwidthModule On ForceBandWidthModule On Bandwidth all 10240 MinBandwidth all -1 Servername www.example.com Limit al internal users (lan) to 1000 kb/s with a minimum of 50kb/s , and files greater than 500kb to 50kb/s. BandwidthModule On ForceBandWidthModule On Bandwidth all 1024000 MinBandwidth all 50000 LargeFileLimit * 500 50000 Servername www.example.com Limit avi and mpg extensions to 20kb/s. BandwidthModule On ForceBandWidthModule On LargeFileLimit .avi 1 20000 LargeFileLimit .mpg 1 20000 Servername www.example.com Using it the "right" way, with output filter by mime type (for text) to 5kb/s: BandwidthModule On AddOutputFilterByType MOD_BW text/html text/plain Bandwidth all 5000 Servername www.example.com If you need help on doing more complex setup, post it in my webpage, or send me an email. ------------------------------------------------------------------------------ 5.- FAQ (No particular order) 1.- Why should i use mod_bw ? If you want to restrict the top speed a site is able to use, or limit the max connections allowed per site, or just to try the mod. Some people told me, they use it primarily to stop small sites hogging all the bandwidth when serving video, images, or other content. 2.- How do i ... ? First, read the documentation. it is pretty straightforward to use. If you can't make it work, or if you want to ask for a feature, visit the home site, and post a request. Remember to read the documentation and the faq. If the request is already posted, i'll just delete the duplicates. 3.- What's the difference with mod_bwshare, mod_throttle, etc ? The main difference, is that this mod, is aimed to the Apache 2 API. Some other differences, is, how this mod works, and the directives it implements. I took some ideas from other mods, as the shared memory implementation (mod_bandwidth2 from Tim Verhoeven). Most of the directives, are in origin from mod_bandiwdth for apache 1. 4.- How does it works ? The mod, will set a shared memory holding all of the configuration you make. In this space, it will also, keep a "count" of the info currently using (as current connections, bw used, time, and bytes sent). When you assign a bw limit, the mod will "split" the data, and will send it piece by piece, with a small delay between pieces. The delay will be adjusted so at least 1 piece is sent in a second, thus eviting browser timeout. If there are two or more clients downloading from the same vhost, the limit will be "splitted" too. So, if you have a bw limit of 16Kb and two clients, each one will have 8Kb to download. You can also set a fixed download rate, so each client will have a fixed maximun download rate. This is a simple explanation of how this works. Take a look at the code if you want to know more about it. 5.- Can you make it do ... ? Post a request for a feature in the home site. I'll respond there. 6.- Can you make mod_bw work for ... ? See question 5. Anyways, if i dont own (or have access) to the hardware needed, i won't be able to help you. 7.- I'm having some trouble in windows ... Well, thanks to the guys at Microsoft, who kindly offered access to MSDN to the Apache Community, the dll's i post on my site are produced using licensed Visual C, and i can help with any Windows Issue! Post or send me your problem. I'll be glad to help. 8.- The mod is not limiting certain Directory Ok... first, read the documentation. Then the FAQ. If it isn't working, then look if you have defined correctly the limits within that directory. If you have a limit in a vhost, and inside the vhost, a directory with another limit, a new context with the configs of the directory is created, and only have the configurations you added there. In example : BandWidthModule On BandWidth all 16384 LargeFileLimit * 500 4096 LargeFileLimit * 100 1024 This wont limit Directory / to 16384. The Directory wont "inherit" the settings from the vhost if you use some of the mod's directives. ------------------------------------------------------------------------------ 6.- Thanks Thanks to all the users who have wrote. I've received many bug alerts, many ideas, some proposed patches, and lots of thanks. I've also got some Paypal donations that have helped with the payment of the domain names, etc.. (thanks a lot!) I think i'll put some of the names in the page instead of this document. If you feel you deserve to appear in the page, send me an email. Big thanks to the Microsoft guys, who allowed me to have licensed access to Microsoft software, allowing me to support the windows community. And, thanks to the Google Summer of Code crew, that allowed me to participate in the SoC 2005. ------------------------------------------------------------------------------ Ivan Barrera A. Ivn Systems Software -(Bruce@Ivn.cl)- TODO0000644000000000000000000000060411376524706010257 0ustar rootrootTodo : - Test and more tests (before making this final) The following features won't be included in this branch of the mod. This is because the limitations of the actual code base. - Limiting on INPUT - Per User Bandwidth - Htaccess support As the new code base is already being written, this features will appear sometime in the development of the new mod. legacy code.