/* ==================================================================== * Copyright (c) 1995 The Apache Group. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * 4. The names "Apache Server" and "Apache Group" must not be used to * endorse or promote products derived from this software without * prior written permission. * * 5. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR * IT'S CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Group and was originally based * on public domain software written at the National Center for * Supercomputing Applications, University of Illinois, Urbana-Champaign. * For more information on the Apache Group and the Apache HTTP server * project, please see . * */ /**************************************************************************** * Title : Bandwidth management * File : mod_bandwidth.c * Author : Yann Stettler (stettler@cohprog.com) * Date : 12 January 2003 * Version : 2.0.6 for Apache 1.3+ * * Description : * Provide bandwidth usage limitation either on the whole server or * one a per connection basis based on the size of files, directory * location or remote domain/IP. * * Revision : 08/04/97 - "-1" value for LargeFileLimit to disable any * limits for that kind of files. * 01/26/98 - Include in_domain() and in_ip() in this file and * make them static so it will work with Apache 1.3x * 03/29/98 - Change to set bandwidth in VirtualHost directive * 07/17/99 - Minor changes to remove warnings at compil time * * Allow the use of the network/mask format when * setting a bandwidth for a remote host/network. * (Thanks to Sami Kuhmonen for the patch) * * New directive BandWidthPulse * * 10/14/00 - Minor bug fixed * 12/15/00 - Bug fix when using mmap * 08/29/01 - Add a directive to change the data directory * (Thanks to Awesome Walrus ) * 01/12/03 - Add MaxConnection directive to limit the number * of simultaneous connections. * *************************************************************************** * Copyright (c)1997 Yann Stettler and CohProg SaRL. All rights reserved. * Written for the Apache Group by : * * Yann Stettler * stettler@cohprog.com * http://www.cohprog.com/ * http://www.animanga.com/ * * Based on the original default_handler module and on bw_module 0.2 from * Etienne BERNARD (eb@via.ecp.fr) at VIA Centrale Reseaux, ECP, France * * Many thanks to Colba Net Inc (Montreal) for their sponsoring of * improvements to this module. * ***************************************************************************/ /* * Instruction : * ------------- * * Note : this module was writen for Apache 1.3.x and tested on it * * Installation : * * 1) Insert the following line at the end of the "Configuration" file : * Module bandwidth_module mod_bandwidth.o * * WARNING : This behaviour is not the same between the various main versions * of Apache. Please, read the instruction on our website for the * instructions concerning the latest release : * http://www.cohprog.com/v3/bandwidth/doc-en.html * * 2) Run the "Configure" program and re-compile the server with "make". * * 3) Create the following directories with "rwx" permission to everybody : * (or rwx for the user under which Apache run : Usualy "nobody") * /tmp/apachebw * /tmp/apachebw/link * /tmp/apachebw/master * * /==== by Awesome Walrus =====================\ * Or you may change this name by using BandWidthDataDir global * configuration directive. See below for details. * \==== by Awesome Walrus =====================/ * * Note that if any of those directories doesn't exist, or if they can't * be accessed by the server, the module is totaly disabled except for * logging an error message in the logfile. * * Be careful that on some systems the content of the /tmp directory * is deleted at boot time or every so often by a cronjob. If that the * case, either disable this feature or change the location of the * directories used by the module in the sources bellow. * * Server configuration directive : * -------------------------------- * * /==== by Awesome Walrus =====================\ * - BandWidthDataDir * Syntax : BandWidthDataDir * Default : "/tmp/apachebw" * Context : server config * * Sets the name of the directory used by mod_bandwidth to store * its internal temporary information. * \==== by Awesome Walrus =====================/ * * - BandWidthModule * Syntax : BandWidthModule * Default : Off * Context : per server config * * Enable or disable totaly the whole module. By default, the module is * disable so it is safe to compile it in the server anyway. * * PLEASE, NOTE THAT IF YOU SET A BANDWIDTH LIMIT INSIDE A VIRTUALHOST * BLOCK, YOU ALSO __NEED__ TO PUT THE "BandWidthModule On" DIRECTIVE * INSIDE THAT VIRTUALHOST BLOCK ! * * IF YOU SET BANDWIDTH LIMITS INSIDE DIRECTORY BLOCKS (OUTSIDE OF * ANY VIRTUALHOST BLOCK), YOU ONLY NEED TO PUT THE "BandWidthModule On" * DIRECTIVE ONCE, OUTSIDE OF ANY VIRTUALHOST OR DIRECTORY BLOCK. * * - BandWidthPulse * Syntax : BandWidthPulse * Default : * Context : per server config * * Change the algorithm used to calculate bandwidth and transmit data. * In normal mode (old mode), the module try to transmit data in packets * of 1KB. That mean that if the bandwidth available is of 512B, the * module will transmit 1KB, wait 2 seconds, transmit another 1KB and * so one. * * Seting a value with "BandWidthPulse", will change the algorithm so * that the server will always wait the same amount of time between * sending packets but the size of the packets will change. * The value is in microseconds. * For example, if you set "BandWidthPulse 1000000" (1 sec) and the * bandwidth available is of 512B, the sever will transmit 512B, * wait 1 second, transmit 512B and so on. * * The advantage is a smother flow of data. The disadvantage is * a bigger overhead of data transmited for packet header. * Setting too small a value (bellow 1/5 of a sec) is not realy * useful and will put more load on the system and generate more * traffic for packet header. * * Note also that the operating system may do some buffering on * it's own and so defeat the purpose of setting small values. * * This may be very useful on especialy crowded network connection : * In normal mode, several seconds may happen between the sending of * a full packet. This may lead to timeout or people may believe that * the connection is hanging. Seting a value of 1000000 (1 sec) would * guarantee that some data are sent every seconds... * * Directory / Server / Virtual Server configuration directive : * ------------------------------------------------------------- * * - BandWidth * Syntax : BandWidth * Default : none * Context : per directory, .htaccess * * Limit the bandwidth for files in this directory and * sub-directories based on the remote host or * address or for remote hosts. * * Ip addresses may now be specified in the network/mask format. * (Ie: 192.168.0.0/21 ) * * The is in Bytes/second. * A of "0" means no bandwidth limit. * * Several BandWidth limits can be set for the same * directory to set different limits for different * hosts. In this case, the order of the "BandWidth" * keywords is important as the module will take the * first entry which matches the client address. * * Example : * * BandWidth ecp.fr 0 * BandWidth 138.195 0 * BandWidth all 1024 * * * This will limit the bandwith for directory /home/www and * all it's subdirectories to 1024Bytes/sec, except for * *.ecp.fr or 138.195.*.* where no limit is set. * * - LargeFileLimit * Syntax : LargeFileLimit * Default : none * Context : per directory, .htaccess * * Set a maximal (in bytes/sec) to use when transfering * a file of KBytes or more. * * Several "LargeFileLimit" can be set for various files sizes * to create range. The rate used for a given file size will be * the one of the matching range. * * A of "0" mean that there isn't any limit based on * the size. * * A of "-1" mean that there isn't any limit for that type * of file. It's override even a BandWidth limit. I found this usefull * to give priority to very small files (html pages, very small pictures) * while seting limits for larger files... (users with their video files * can go to hell ! :) * * Example : * If the following limits are set : * LargeFileLimit 200 3072 * LargeFileLimit 1024 2048 * * That's mean that a file of less than 200KBytes won't be * limited based on his size. A file with a size between * 200KBytes (included) and 1023Kbytes (included) will be * limited to 3072Bytes/sec and a file of 1024Kbytes or more * will be limited to 2048Bytes/sec. * * - MinBandWidth * Syntax : MinBandWidth * Default : all 256 * Context : per directory, .htaccess * * Set a minimal bandwidth to use for transfering data. This * over-ride both BandWidth and LargeFileLimit rules as well * as the calculated rate based on the number of connections. * * The first argument is used in the same way as the first * argument of BandWidth. * * is in bytes per second. * * A rate of "0" explicitly means to use the default minimal * value (256 Bytes/sec). * * A rate of "-1" means that the minimal rate is equal to the * actual rate defined by BandWidth and LargeFileLimit. * In fact, that means that the final rate won't depend * of the number of connections but only on what was defined. * * Example : * If BandWidth is set to "3072" (3KBytes/sec) and MinBandWidth * is set to "1024" (1KBytes/sec) that means : * - if there is one connection, the file will be transfered * at 3072 Bytes/sec. * - if there is two connections, each files will be transfered * at 1536 Bytes/sec. * - if there is three or more connections, each files will be * transfered at 1024 Bytes/sec. (Minimal of 1024 Bytes/sec). * * If MinBandWidth is set to "-1" that means : * - if there is one connection, the file will be transfered * at 3072 Bytes/sec. * - if there is two or more connections, each files will be * transfered at 3072 Bytes/sec. In effect, the rate doesn't * depend anymore on the number of connections but only on * the configuration values. * * Note that the total transfer rate will never exceed your physical * bandwidth limitation. * * Note : If both a "BandWidth" and a "LargeFileLimit" limit apply, * the lowest one will be used. (But never lower than the * "MinBandWidth" rate) * * If both a virtual server limit is defined and another * apply for a directory under this virtual server, the * directory limit will over-ride it. * * If a limit is defined outside a Directory or VirtualHost * directive, it will act as default on a per virtual server * basis. (Ie: each virtual server will have that limit, * _independantly_ of the other servers) * * - MaxConnection * Syntax : MaxConnection * Default : 0 (illimited) * Context : per directory, .htaccess * * Restrict the number of maximum simultanous connections. If the * limit is reached, new connections will be rejected. " * A value of 0 mean that there isn't any limits. * * Implementation notes : * ---------------------- * * 1) This module isn't called when serving a cgi-script. In this * case, it's the functions in mod_cgi.c that handle the connection. * * That's not a bug : I didn't want to change the cgi_module and * I was too lazy to do it anyway. * * 2) The rate of data transmission is only calculated. It isn't * measured. Which mean that this module calculates the final * rate that should apply for a file and simply divides this * number by the number of actual connections subject to the * same "per directory" directives. * * 3) On the "+" side, the module regulate the speed taking * into account the actual time that was needed to send the * data. Which also mean that if data are read by the client * at a slowest rate than the limit, no time will be lost and * data will be sent as fast as the client can read them (but * no faster than the limited rate :) ... * * 4) Some kind of buffering is done as side effect. Data are * sent in packet of 1024 Bytes which seems a good value * for TCP/IP. * If another packet size is wanted, change the value of * "#define PACKET" in the codes bellow. * * 5) The default value for MinBandWidth is defined by : * "#define MIN_BW_DEFAULT" (in Bytes/sec) * * 6) Don't define "BWDEBUG" for a production server : * this would log a _lot_ of useless informations for * debuging purpose. * */ #include #include "httpd.h" #include "http_core.h" #include "http_config.h" #include "http_log.h" #include "http_request.h" #include "http_protocol.h" #include "http_main.h" #define MIN_BW_DEFAULT 256 /* Minimal bandwidth defaulted to 256Bps */ #define PACKET 1024 /* Sent packet of 1024 bytes */ /* #define MASTER_DIR "/tmp/apachebw/master" * #define LINK_DIR "/tmp/apachebw/link" */ #define MASTER_DIR "master" #define LINK_DIR "link" /* Define BWDEBUG for debuging purpose only ! */ /* #define BWDEBUG */ #ifdef BWDEBUG #undef BWDEBUG #endif #define BANDWIDTH_DISABLED 1<<0 #define BANDWIDTH_ENABLED 1<<1 #ifdef USE_MMAP_FILES #include #include /* mmap support for static files based on ideas from John Heidemann's * patch against 1.0.5. See * . */ /* Files have to be at least this big before they're mmap()d. This is to * deal with systems where the expense of doing an mmap() and an munmap() * outweighs the benefit for small files. */ #ifndef MMAP_THRESHOLD #ifdef SUNOS4 #define MMAP_THRESHOLD (8*1024) #else #define MMAP_THRESHOLD 0 #endif #endif #endif /* Limits for bandwidth and minimal bandwidth based on directory */ typedef struct { char *from; long rate; } bw_entry; /* Limits for bandwidth based on file size */ typedef struct { long size; long rate; } bw_sizel; /* Per directory configuration structure */ typedef struct { array_header *limits; array_header *minlimits; array_header *sizelimits; int maxconnection; char *directory; } bandwidth_config; /* Per server configuration structure */ typedef struct { int state; } bandwidth_server_config; int bw_handler (request_rec *); module bandwidth_module; static long int BWPulse=0; char bandwidth_data_dir[MAX_STRING_LEN]="/tmp/apachebw"; /*************************************************************************** * Configuration functions * ***************************************************************************/ static void *create_bw_config(pool *p, char *path) { bandwidth_config *new= (bandwidth_config *) ap_palloc(p, sizeof(bandwidth_config)); new->limits=ap_make_array(p,20,sizeof(bw_entry)); new->minlimits=ap_make_array(p,20,sizeof(bw_entry)); new->sizelimits=ap_make_array(p,10,sizeof(bw_sizel)); new->directory=ap_pstrdup(p, path); new->maxconnection=0; return (void *)new; } static void *create_bw_server_config(pool *p, server_rec *s) { bandwidth_server_config *new; new = (bandwidth_server_config *)ap_pcalloc(p, sizeof(bandwidth_server_config)); new->state = BANDWIDTH_DISABLED; return (void *)new; } /*************************************************************************** * Directive functions * ***************************************************************************/ static const char *bandwidthmodule(cmd_parms *cmd, bandwidth_config *dconf, int flag) { bandwidth_server_config *sconf; sconf = (bandwidth_server_config *)ap_get_module_config(cmd->server->module_config, &bandwidth_module); sconf->state = (flag ? BANDWIDTH_ENABLED : BANDWIDTH_DISABLED); return NULL; } static const char *set_bandwidth_data_dir(cmd_parms *cmd, void *dummy, char *arg) { arg = ap_os_canonical_filename(cmd->pool, arg); if (!ap_is_directory(arg)) { return "BandWidthDataDir must be a valid directory"; } ap_cpystrn(bandwidth_data_dir, arg, sizeof(bandwidth_data_dir)); return NULL; } static const char *setpulse(cmd_parms *cmd, bandwidth_config *dconf, char *pulse) { long int temp; if (pulse && *pulse && isdigit(*pulse)) temp = atol(pulse); else return "Invalid argument"; if (temp<0) return "Pulse must be a number of microseconds/s"; BWPulse=temp; return NULL; } static const char *MaxConnection(cmd_parms *cmd, void *s, char *maxc) { bandwidth_config *conf=(bandwidth_config *)s; int temp; if (maxc && *maxc && isdigit(*maxc)) temp = atoi(maxc); else return "Invalid argument"; if (temp<0) return "Connections must be a number of simultaneous connections allowed/s"; conf->maxconnection=temp; return NULL; } static const char *bandwidth(cmd_parms *cmd, void *s, char *from, char *bw) { bandwidth_config *conf=(bandwidth_config *)s; bw_entry *a; long int temp; if (bw && *bw && isdigit(*bw)) temp = atol(bw); else return "Invalid argument"; if (temp<0) return "BandWidth must be a number of bytes/s"; a = (bw_entry *)ap_push_array(conf->limits); a->from = ap_pstrdup(cmd->pool,from); a->rate = temp; return NULL; } static const char *minbandwidth(cmd_parms *cmd, void *s, char *from, char *bw) { bandwidth_config *conf=(bandwidth_config *)s; bw_entry *a; long int temp; if (bw && *bw && (*bw=='-' || isdigit(*bw))) temp = atol(bw); else return "Invalid argument"; a = (bw_entry *)ap_push_array(conf->minlimits); a->from = ap_pstrdup(cmd->pool,from); a->rate = temp; return NULL; } static const char *largefilelimit(cmd_parms *cmd, void *s, char *size, char *bw) { bandwidth_config *conf=(bandwidth_config *)s; bw_sizel *a; long int temp, tsize; if (bw && *bw && (*bw=='-' || isdigit(*bw))) temp = atol(bw); else return "Invalid argument"; if (size && *size && 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 *)ap_push_array(conf->sizelimits); a->size = tsize; a->rate = temp; return NULL; } static command_rec bw_cmds[] = { { "BandWidth", bandwidth, NULL, RSRC_CONF | OR_LIMIT, TAKE2, "a domain (or ip, or all for all) and a bandwidth limit (in bytes/s)" }, { "MinBandWidth", minbandwidth, NULL, RSRC_CONF | OR_LIMIT, TAKE2, "a domain (or ip, or all for all) and a minimal bandwidth limit (in bytes/s)" }, { "LargeFileLimit", largefilelimit, NULL, RSRC_CONF | OR_LIMIT, TAKE2, "a filesize (in Kbytes) and a bandwidth limit (in bytes/s)" }, { "MaxConnection", MaxConnection, NULL, RSRC_CONF | OR_LIMIT, TAKE1, "A number of allowed simultaneous connections" }, { "BandWidthModule", bandwidthmodule, NULL, OR_FILEINFO, FLAG, "On or Off to enable or disable (default) the whole bandwidth module" }, { "BandWidthPulse", setpulse, NULL, OR_FILEINFO, TAKE1, "a number of microseconds" }, { "BandWidthDataDir", set_bandwidth_data_dir, NULL, RSRC_CONF, TAKE1, "A writable directory where temporary bandwidth info is to be stored" }, { NULL } }; /*************************************************************************** * Internal functions * ***************************************************************************/ #ifdef USE_MMAP_FILES struct mmap { void *mm; size_t length; }; static void mmap_cleanup (void *mmv) { struct mmap *mmd = mmv; munmap(mmd->mm, mmd->length); } #endif 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; } static int in_ip(char *domain, char *what) { /* Check a similar screw case to the one checked above --- * "allow from 204.26.2" shouldn't let in people from 204.26.23 */ int a, b, c, d, e; unsigned long in, out, inmask, outmask; if (sscanf(domain, "%i.%i.%i.%i/%i", &a, &b, &c, &d, &e) < 5) { int l = strlen(domain); if (strncmp(domain,what,l) != 0) return 0; if (domain[l - 1] == '.') return 1; return (what[l] == '\0' || what[l] == '.'); } in = (a<<24)|(b<<16)|(c<<8)|d; inmask = 0xFFFFFFFF ^ ((1<<(32-e))-1); if (sscanf(what, "%i.%i.%i.%i", &a, &b, &c, &d) < 4) return 0; out = (a<<24)|(b<<16)|(c<<8)|d; return ((in&inmask) == (out&inmask)); } static int is_ip(const char *host) { while ((*host == '.') || (*host=='/') || isdigit(*host)) host++; return (*host == '\0'); } static long get_bw_rate(request_rec *r, array_header *a) { bw_entry *e = (bw_entry *)a->elts; const char *remotehost = NULL; int i; remotehost = ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_HOST); for (i=0; i< a->nelts; i++) { if (!strcmp(e[i].from,"all")) return e[i].rate; if (in_ip(e[i].from, r->connection->remote_ip)) return e[i].rate; if ((remotehost!=NULL) && !is_ip(remotehost)) { if (in_domain(e[i].from,remotehost)) return e[i].rate; } } return 0; } static long get_bw_filesize(request_rec *r, array_header *a, off_t filesize) { bw_sizel *e = (bw_sizel *)a->elts; int i; long int tmpsize=0, tmprate=0; if (!filesize) return(0); filesize /= 1024; for (i=0; i< a->nelts; i++) { if (e[i].size <= filesize ) if (tmpsize <= e[i].size) { tmpsize=e[i].size; tmprate=e[i].rate; } } return(tmprate); } static int current_connection(char *filename) { struct stat mdata; /* * Here, we will do the main work. And that won't take * long ! */ if (stat(filename, &mdata) < 0) return(1); /* strange... should not occure */ return(mdata.st_nlink-1); /* * I told you it wouldn't be long... :) */ } static struct timeval timediff(struct timeval *a, struct timeval *b) { struct timeval rslt, tmp; tmp=*a; if ((rslt.tv_usec = tmp.tv_usec - b->tv_usec) < 0) { rslt.tv_usec += 1000000; --(tmp.tv_sec); } if ((rslt.tv_sec = tmp.tv_sec - b->tv_sec) < 0) { rslt.tv_usec=0; rslt.tv_sec=0; } return(rslt); } /*************************************************************************** * Main function, module core * ***************************************************************************/ static int handle_bw(request_rec *r) { int rangestatus, errstatus; FILE *f; bandwidth_config *conf = (bandwidth_config *)ap_get_module_config(r->per_dir_config, &bandwidth_module); bandwidth_server_config *sconf = (bandwidth_server_config *)ap_get_module_config(r->server->module_config, &bandwidth_module); long int bw_rate=0, bw_min=0, bw_f_rate=0, cur_rate; int nolimit=0, bwlast=0, fd; long int tosend, bytessent, filelength; struct stat fdata; struct timeval opt_time, last_time, now, timespent, timeout; char masterfile[128], filelink[128], *directory; #ifdef USE_MMAP_FILES caddr_t mm; #endif /* This handler has no use for a request body (yet), but we still * need to read and discard it if the client sent one. */ if ((errstatus = ap_discard_request_body(r)) != OK) return errstatus; /* Return as fast as possible if request is not a GET or if module not enabled */ if (r->method_number != M_GET || sconf->state == BANDWIDTH_DISABLED) return DECLINED; if (!conf->directory) { directory=(char *)ap_document_root(r); } else { directory=conf->directory; } bw_rate=get_bw_rate(r,conf->limits); bw_min=get_bw_rate(r, conf->minlimits); bw_f_rate=get_bw_filesize(r, conf->sizelimits , r->finfo.st_size); if (!directory) return DECLINED; if ((bw_rate==0 && bw_f_rate==0) || bw_f_rate < 0) { if (!conf->maxconnection) { return DECLINED; } else { nolimit=1; } } if (r->finfo.st_mode == 0 || (r->path_info && *r->path_info)) { ap_log_reason("File does not exist", r->filename, r); return NOT_FOUND; } ap_update_mtime (r, r->finfo.st_mtime); ap_set_last_modified(r); ap_set_etag(r); if (((errstatus = ap_meets_conditions(r)) != OK) || (errstatus = ap_set_content_length (r, r->finfo.st_size))) { return errstatus; } /* * Create name of the master file. * Will use the inode and device number of the "limited" * directory. */ if (stat(directory, &fdata) < -1) { /* Dunno if this may happen... but well... */ return DECLINED; } sprintf(masterfile,"%s/%s/%d:%ld", bandwidth_data_dir, MASTER_DIR, (short int)fdata.st_dev, (long int)fdata.st_ino); /* * Check if master file already exist, else create it. * Then we create a hardlink to it using the pid as name. */ if ((fd=open(masterfile, O_RDONLY|O_CREAT, 0777)) < 0) { ap_log_printf(r->server, "mod_bandwidth : Can't create/access master file %s",masterfile); return DECLINED; } close(fd); /* * Check if the maximum number of connections allowed is reached */ if (conf->maxconnection && (conf->maxconnection <= current_connection(masterfile))) { /* return HTTP_SERVICE_UNAVAILABLE; */ return FORBIDDEN; } sprintf(filelink,"%s/%s/%d", bandwidth_data_dir, LINK_DIR, getpid()); if (link(masterfile, filelink) < 0) { ap_log_printf(r->server, "mod_bandwidth : Can't create hard link %s", filelink); return DECLINED; } f = ap_pfopen(r->pool, r->filename,"r"); if (f == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "file permissions deny server access: %s", r->filename); unlink(filelink); return FORBIDDEN; } /* * Calculate bandwidth for this file based on location limits and * file size limit. * * The following rules applies : * 1) File size limit over-rule location limit if it's slower. * 2) Whatever the resuling limit and number of connections, never * go bellow the minimal limit for this location. * 3) A file size limit of zero mean that we don't care about the * size of the file for this purpose. * */ #ifdef BWDEBUG ap_log_printf(r->server, "mod_bandwidth : Directory : %s Rate : %d Minimum : %d Size rate : %d", directory, bw_rate, bw_min, bw_f_rate); #endif if (bw_f_rate && (bw_rate > bw_f_rate || !bw_rate)) bw_rate=bw_f_rate; if (bw_min < 0) bw_min=bw_rate; else if (! bw_min) bw_min=MIN_BW_DEFAULT; #ifdef USE_MMAP_FILES ap_block_alarms(); if ((r->finfo.st_size >= MMAP_THRESHOLD) && ( !r->header_only)) { /* we need to protect ourselves in case we die while we've got the * file mmapped */ mm = mmap (NULL, r->finfo.st_size, PROT_READ, MAP_PRIVATE, fileno(f), 0); } else { mm = (caddr_t)-1; } if (mm == (caddr_t)-1) { ap_unblock_alarms(); if ((r->finfo.st_size >= MMAP_THRESHOLD) && (!r->header_only)) { ap_log_error(APLOG_MARK, APLOG_CRIT, r->server, "mmap_handler: mmap failed: %s", r->filename); } #endif rangestatus = ap_set_byterange(r); ap_send_http_header(r); if (!r->header_only) { if (!rangestatus) { filelength=r->finfo.st_size; bytessent=0; bwlast=0; while(!bwlast) { cur_rate=(long int)bw_rate / current_connection(masterfile); if (cur_rate < bw_min) cur_rate=bw_min; if (BWPulse <= 0) { /* * I feel rather foolish to use select and use the * 1/1000000 of sec for my calculation. But as long as I * am at it, let's do it well... * * Note that my loop don't take into account the time * spent outside the send_fd_length function... but in * this case, I feel that I have the moral right to do so :) */ if (nolimit) { opt_time.tv_sec=0; opt_time.tv_usec=0; } else { opt_time.tv_sec=(long int) PACKET / cur_rate; opt_time.tv_usec=(long int)PACKET*1000000/cur_rate-opt_time.tv_sec*1000000; } tosend=PACKET; if (tosend+bytessent >= filelength) { tosend=filelength-bytessent; bwlast=1; } } else { opt_time.tv_sec=(long int)BWPulse/1000000; opt_time.tv_usec=BWPulse-opt_time.tv_sec*1000000; if (nolimit) tosend=filelength; else tosend=(long int)((double)BWPulse/(double)1000000*(double)cur_rate); if (tosend+bytessent >= filelength) { tosend=filelength-bytessent; bwlast=1; } } #ifdef BWDEBUG ap_log_printf(r->server, "mod_bandwidth : Current rate : %d [%d/%d] Min : %d", cur_rate, bw_rate, current_connection(masterfile), bw_min); #endif gettimeofday(&last_time, (struct timezone *) 0); ap_send_fd_length(f, r, tosend); bytessent += tosend; if (r->connection->aborted) break; if (!bwlast) { /* We sleep... */ gettimeofday(&now, (struct timezone *) 0); timespent=timediff(&now, &last_time); timeout=timediff(&opt_time, ×pent); #ifdef BWDEBUG ap_log_printf(r->server, "mod_bandwidth : Sleep : %ld/%ld (Op time : %ld/%ld Spent : %ld/%ld)", timeout.tv_sec, timeout.tv_usec, opt_time.tv_sec, opt_time.tv_usec, timespent.tv_sec, timespent.tv_usec); #endif select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout); } } } else { long offset, length; while (ap_each_byterange(r, &offset, &length)) { fseek(f, offset, SEEK_SET); filelength=length; bytessent=0; bwlast=0; while(!bwlast) { cur_rate=(long int)bw_rate / current_connection(masterfile); if (cur_rate < bw_min) cur_rate=bw_min; if (BWPulse <= 0) { if (nolimit) { opt_time.tv_sec=0; opt_time.tv_usec=0; } else { opt_time.tv_sec=(long int) PACKET / cur_rate; opt_time.tv_usec=(long int)PACKET*1000000/cur_rate-opt_time.tv_sec*1000000; } tosend=PACKET; if (tosend+bytessent >= filelength) { tosend=filelength-bytessent; bwlast=1; } } else { opt_time.tv_sec=(long int)BWPulse/1000000; opt_time.tv_usec=BWPulse-opt_time.tv_sec*1000000; if (nolimit) tosend=filelength; else tosend=(long int)((double)BWPulse/(double)1000000*(double)cur_rate); if (tosend+bytessent >= filelength) { tosend=filelength-bytessent; bwlast=1; } } #ifdef BWDEBUG ap_log_printf(r->server, "mod_bandwidth : Current rate : %d [%d/%d] Min : %d", cur_rate, bw_rate, current_connection(masterfile), bw_min); #endif gettimeofday(&last_time, (struct timezone *) 0); ap_send_fd_length(f, r, tosend); bytessent += tosend; if (r->connection->aborted) break; if (!bwlast) { /* We sleep... */ gettimeofday(&now, (struct timezone *) 0); timespent=timediff(&now, &last_time); timeout=timediff(&opt_time, ×pent); #ifdef BWDEBUG ap_log_printf(r->server, "mod_bandwidth : Sleep : %ld/%ld (Op time : %ld/%ld Spent : %ld/%ld)", timeout.tv_sec, timeout.tv_usec, opt_time.tv_sec, opt_time.tv_usec, timespent.tv_sec, timespent.tv_usec); #endif select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout); } } } } } #ifdef USE_MMAP_FILES } else { struct mmap *mmd; mmd = ap_palloc (r->pool, sizeof (*mmd)); mmd->mm = mm; mmd->length = r->finfo.st_size; ap_register_cleanup (r->pool, (void *)mmd, mmap_cleanup, mmap_cleanup); ap_unblock_alarms(); rangestatus = ap_set_byterange(r); ap_send_http_header (r); if (!r->header_only) { if (!rangestatus) { filelength=r->finfo.st_size; bytessent=0; bwlast=0; while(!bwlast) { cur_rate=(long int)bw_rate / current_connection(masterfile); if (cur_rate < bw_min) cur_rate=bw_min; if (BWPulse <= 0) { /* * I feel rather foolish to use select and use the * 1/1000000 of sec for my calculation. But as long as I * am at it, let's do it well... * * Note that my loop don't take into account the time * spent outside the send_fd_length function... but in * this case, I feel that I have the moral right to do so :) */ if (nolimit) { opt_time.tv_sec=0; opt_time.tv_usec=0; } else { opt_time.tv_sec=(long int) PACKET / cur_rate; opt_time.tv_usec=(long int)PACKET*1000000/cur_rate-opt_time.tv_sec*1000000; } tosend=PACKET; if (tosend+bytessent >= filelength) { tosend=filelength-bytessent; bwlast=1; } } else { opt_time.tv_sec=(long int)BWPulse/1000000; opt_time.tv_usec=BWPulse-opt_time.tv_sec*1000000; if (nolimit) tosend=filelength; else tosend=(long int)((double)BWPulse/(double)1000000*(double)cur_rate); if (tosend+bytessent >= filelength) { tosend=filelength-bytessent; bwlast=1; } } #ifdef BWDEBUG ap_log_printf(r->server, "mod_bandwidth : Current rate : %d [%d/%d] Min : %d Sending : %d", cur_rate, bw_rate, current_connection(masterfile), bw_min, tosend); #endif gettimeofday(&last_time, (struct timezone *) 0); ap_send_mmap(mm, r, bytessent, tosend); bytessent += tosend; if (r->connection->aborted) break; if (!bwlast) { /* We sleep... */ gettimeofday(&now, (struct timezone *) 0); timespent=timediff(&now, &last_time); timeout=timediff(&opt_time, ×pent); #ifdef BWDEBUG ap_log_printf(r->server, "mod_bandwidth : Sleep : %ld/%ld (Op time : %ld/%ld Spent : %ld/%ld)", timeout.tv_sec, timeout.tv_usec, opt_time.tv_sec, opt_time.tv_usec, timespent.tv_sec, timespent.tv_usec); #endif select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout); } } } else { long offset, length; while (ap_each_byterange(r, &offset, &length)) { filelength=length; bytessent=0; bwlast=0; while(!bwlast) { cur_rate=(long int)bw_rate / current_connection(masterfile); if (cur_rate < bw_min) cur_rate=bw_min; if (BWPulse <= 0) { if (nolimit) { opt_time.tv_sec=0; opt_time.tv_usec=0; } else { opt_time.tv_sec=(long int) PACKET / cur_rate; opt_time.tv_usec=(long int)PACKET*1000000/cur_rate-opt_time.tv_sec*1000000; } tosend=PACKET; if (tosend+bytessent >= filelength) { tosend=filelength-bytessent; bwlast=1; } } else { opt_time.tv_sec=(long int)BWPulse/1000000; opt_time.tv_usec=BWPulse-opt_time.tv_sec*1000000; if (nolimit) tosend=filelength; else tosend=(long int)((double)BWPulse/(double)1000000*(double)cur_rate); if (tosend+bytessent >= filelength) { tosend=filelength-bytessent; bwlast=1; } } #ifdef BWDEBUG ap_log_printf(r->server, "mod_bandwidth : Current rate : %d [%d/%d] Min : %d Sending : $d", cur_rate, bw_rate, current_connection(masterfile), bw_min , tosend); #endif gettimeofday(&last_time, (struct timezone *) 0); ap_send_mmap(mm, r, offset+bytessent, tosend); bytessent += tosend; if (r->connection->aborted) break; if (!bwlast) { /* We sleep... */ gettimeofday(&now, (struct timezone *) 0); timespent=timediff(&now, &last_time); timeout=timediff(&opt_time, ×pent); #ifdef BWDEBUG ap_log_printf(r->server, "mod_bandwidth : Sleep : %ld/%ld (Op time : %ld/%ld Spent : %ld/%ld)", timeout.tv_sec, timeout.tv_usec, opt_time.tv_sec, opt_time.tv_usec, timespent.tv_sec, timespent.tv_usec); #endif select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout); } } } } } } #endif ap_pfclose(r->pool, f); /* Remove the hardlink */ unlink(filelink); return OK; } static handler_rec bw_handlers[] = { { "*/*", handle_bw }, { NULL } }; module bandwidth_module = { STANDARD_MODULE_STUFF, NULL, /* initializer */ create_bw_config, /* bw config creater */ NULL, /* bw merger --- default is to override */ create_bw_server_config, /* server config */ NULL, /* merge server config */ bw_cmds, /* command table */ bw_handlers, /* handlers */ NULL, /* filename translation */ NULL, /* check_user_id */ NULL, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ NULL, /* fixups */ NULL /* logger */ };