From 15c42696939ca10c633f2ece0402f4e1b05adb7d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Arkadiusz=20Mi=C5=9Bkiewicz?= Date: Tue, 26 Mar 2002 23:26:35 +0000 Subject: [PATCH] 50725cfa06f1a4349e456cbf2157e287 mod_bandwidth.c Changed files: mod_bandwidth.c -> 1.1 --- mod_bandwidth.c | 1174 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1174 insertions(+) create mode 100644 mod_bandwidth.c diff --git a/mod_bandwidth.c b/mod_bandwidth.c new file mode 100644 index 0000000..dccc38f --- /dev/null +++ b/mod_bandwidth.c @@ -0,0 +1,1174 @@ +/* ==================================================================== + * 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 : 17 July 1999 + * Version : 2.0.3 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 ) + * + *************************************************************************** + * 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) + * + * 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; + 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); + 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 "BandWidth must be a number of bytes/s"; + + BWPulse=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)" }, +{ "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 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 ((bw_rate==0 && bw_f_rate==0) || bw_f_rate < 0 || !directory) return DECLINED; + + 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); + + 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); + 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) { + 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 :) + */ + + 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; + + 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) { + 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; + + 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 :) + */ + + 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; + + 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) { + 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; + + 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 */ +}; -- 2.44.0