diff --git a/Changelog b/Changelog deleted file mode 100644 index 358bdcd..0000000 --- a/Changelog +++ /dev/null @@ -1,51 +0,0 @@ -V0.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 - - diff --git a/README.md b/README.md index ab44e29..4afae32 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,7 @@ -Apache2 - Mod_bw v0.92 +Apache2.4+ - Mod_bw v0.92 -Author : Ivan Barrera A. (Bruce) - -HomePage : Http://Ivn.cl - -Release Date : 20-Jul-2010 - -Status : Stable +Author : Sam Osheroff +Original Author : Ivan Barrera A. (Bruce) License : Licensed under the Apache Software License v2.0 It must be included as LICENSE in this package. @@ -19,646 +14,25 @@ Platform : Linux/x86 (Tested with Fedora Core 4, Suse, etc) 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. - ------------------------------------------------------------------------------- - - Some time ago, 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), post them. 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 +Notes : This fork is based on stable version 0.92 of mod_bw. While it was written + to address limitations with Windows, the changes could be useful to other + platforms. -3.4 - MinBandWidth + In Windows, the sleep timer has a minimum resolution of 1ms but the necessary + sleep duration to create the desired bandwidth could esaily be less than 1ms. + The module now calculates roughly how many sleep cycles to "skip" to get 10ms + to pass, then it sleeps for 10ms. This sleep duration was selected because some + Windows editions have a minimum 10ms resolution. -3.5 - LargeFileLimit - -3.6 - BandWidthPacket - -3.7 - BandWidthError - -3.8 - MaxConnection - -3.9 - Status Callback - -4 .- Examples - -4.1 - Misc Examples - -5 .- FAQ - ------------------------------------------------------------------------------- - -1.- Notices - - 1.1 - ChangeLog & TODO : - - This has been moved to the files ChangeLog and TODO. - - 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 + The module now attempts to roughly calculate the client's bandwidth over a 15ms + period. If the client's appearant bandwidth is less than the available during + the 15ms interval, no sleeping occurs, since the client's lower bandwidth is + already the limiting factor. - 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 over GitHub. - ------------------------------------------------------------------------------- - -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 - GitHub 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_bandwidth 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 through GitHub. - -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 (try 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. - ------------------------------------------------------------------------------- - - Ivan Barrera A. - - Ivn Systems Software - - + Some enhancements were made to the debug logging. + + Lastly, there was a bug in the hook function (used for forced operations) + where modules that create subrequests caused the output filter to get installed + twice. Inserting the output filter is now skipped when the subrequest + causes an internal redirect. + diff --git a/TODO b/TODO deleted file mode 100644 index a6d89ef..0000000 --- a/TODO +++ /dev/null @@ -1,15 +0,0 @@ -Todo : - - - 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. diff --git a/mod_bw.c b/mod_bw.c index 2b24696..2797e3e 100644 --- a/mod_bw.c +++ b/mod_bw.c @@ -24,11 +24,11 @@ Platform : Linux/x86 (Tested with Fedora Core 4, Suse, etc) 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 + 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 +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 @@ -36,11 +36,13 @@ Limitations : This mod doesn't know how fast the client is Changelog : +2021 : See github + 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. + 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. @@ -71,11 +73,10 @@ Changelog : #include "scoreboard.h" #if defined(WIN32) - #include - #include +#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 */ @@ -86,25 +87,28 @@ Changelog : /* 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 +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 +#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 +#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 + +#pragma message("This file is: " __FILE__) + /* Enum types of "from address" */ enum from_type { T_ALL, @@ -113,7 +117,7 @@ enum from_type { T_AGENT }; -/* +/* - Stats of each conf - - id = Configuration ID @@ -130,7 +134,7 @@ typedef struct char *v_name; apr_uint32_t connection_count; apr_uint32_t bandwidth; - apr_uint32_t bytes_count; + unsigned long long bytes_count; apr_uint32_t counter; volatile apr_uint32_t lock; apr_time_t time; @@ -141,16 +145,18 @@ typedef struct ctx_struct_t { apr_bucket_brigade *bb; struct timeval wait; + unsigned long long bw_interval_bytes, client_bw; + apr_time_t bw_interval_start_time; + long sleep_bypasses_left, sleep_bypasses_total; } ctx_struct; -/* With sid we count the shared memory needed. +/* 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 { @@ -193,7 +199,7 @@ typedef struct apr_array_header_t *minlimits; apr_array_header_t *sizelimits; apr_array_header_t *maxconnection; - int packet; + unsigned int packet; int error; char *directory; } bandwidth_config; @@ -211,39 +217,39 @@ 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) + /* 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); + (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) +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); + (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) +static const char *setpacket(cmd_parms *cmd, void *s, const char *pack) { - bandwidth_config *conf = (bandwidth_config *) s; + bandwidth_config *conf = (bandwidth_config *)s; int temp; if (pack && *pack && apr_isdigit(*pack)) @@ -260,9 +266,9 @@ static const char *setpacket(cmd_parms * cmd, void *s, const char *pack) } /* Set the error to send when maxconnections is reached */ -static const char *bandwidtherror(cmd_parms * cmd, void *s, const char *err) +static const char *bandwidtherror(cmd_parms *cmd, void *s, const char *err) { - bandwidth_config *conf = (bandwidth_config *) s; + bandwidth_config *conf = (bandwidth_config *)s; int temp; if (err && *err && apr_isdigit(*err)) @@ -279,14 +285,14 @@ static const char *bandwidtherror(cmd_parms * cmd, void *s, const char *err) } /* Set the maxconnections on a per host basis */ -static const char *maxconnection(cmd_parms * cmd, void *s, const char *from, - const char *maxc) +static const char *maxconnection(cmd_parms *cmd, void *s, const char *from, + const char *maxc) { - bandwidth_config *conf = (bandwidth_config *) s; + bandwidth_config *conf = (bandwidth_config *)s; bw_maxconn *a; int temp; char *str; - char *where = (char *) apr_pstrdup(cmd->pool, from); + char *where = (char *)apr_pstrdup(cmd->pool, from); apr_status_t rv; char msgbuf[MAX_BUF]; @@ -297,19 +303,19 @@ static const char *maxconnection(cmd_parms * cmd, void *s, const char *from, if (temp < 0) return - "Connections must be a number of simultaneous connections allowed/s"; + "Connections must be a number of simultaneous connections allowed/s"; - a = (bw_maxconn *) apr_array_push(conf->maxconnection); + a = (bw_maxconn *)apr_array_push(conf->maxconnection); a->x.from = where; - if (!strncasecmp(where,"u:",2)) - { + 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); + 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; @@ -317,7 +323,7 @@ static const char *maxconnection(cmd_parms * cmd, void *s, const char *from, else if ((str = strchr(where, '/'))) { *str++ = '\0'; rv = apr_ipsubnet_create(&a->x.ip, where, str, cmd->pool); - if(APR_STATUS_IS_EINVAL(rv)) { + if (APR_STATUS_IS_EINVAL(rv)) { /* looked nothing like an IP address */ return "An IP address was expected"; } @@ -326,14 +332,14 @@ static const char *maxconnection(cmd_parms * cmd, void *s, const char *from, 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; } @@ -344,14 +350,14 @@ static const char *maxconnection(cmd_parms * cmd, void *s, const char *from, } /* Set the bandwidth on a per host basis */ -static const char *bandwidth(cmd_parms * cmd, void *s, const char *from, - const char *bw) +static const char *bandwidth(cmd_parms *cmd, void *s, const char *from, + const char *bw) { - bandwidth_config *conf = (bandwidth_config *) s; + bandwidth_config *conf = (bandwidth_config *)s; bw_entry *a; long int temp; char *str; - char *where = (char *) apr_pstrdup(cmd->pool, from); + char *where = (char *)apr_pstrdup(cmd->pool, from); apr_status_t rv; char msgbuf[MAX_BUF]; @@ -364,16 +370,16 @@ static const char *bandwidth(cmd_parms * cmd, void *s, const char *from, if (temp < 0) return "BandWidth must be a number of bytes/s"; - a = (bw_entry *) apr_array_push(conf->limits); + a = (bw_entry *)apr_array_push(conf->limits); a->x.from = where; - if (!strncasecmp(where,"u:",2)) + 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); + a->agent = ap_pregcomp(cmd->pool, where + 2, 0); if (a->agent == NULL) - return "Regular expression could not be compiled."; - + return "Regular expression could not be compiled."; + } else if (!strcasecmp(where, "all")) { a->type = T_ALL; @@ -381,7 +387,7 @@ static const char *bandwidth(cmd_parms * cmd, void *s, const char *from, else if ((str = strchr(where, '/'))) { *str++ = '\0'; rv = apr_ipsubnet_create(&a->x.ip, where, str, cmd->pool); - if(APR_STATUS_IS_EINVAL(rv)) { + if (APR_STATUS_IS_EINVAL(rv)) { /* looked nothing like an IP address */ return "An IP address was expected"; } @@ -403,25 +409,23 @@ static const char *bandwidth(cmd_parms * cmd, void *s, const char *from, } 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); + 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) +static const char *minbandwidth(cmd_parms *cmd, void *s, const char *from, const char *bw) { - bandwidth_config *conf = (bandwidth_config *) s; + bandwidth_config *conf = (bandwidth_config *)s; bw_entry *a; long int temp; char *str; - char *where = (char *) apr_pstrdup(cmd->pool, from); + char *where = (char *)apr_pstrdup(cmd->pool, from); apr_status_t rv; char msgbuf[MAX_BUF]; @@ -430,13 +434,13 @@ static const char *minbandwidth(cmd_parms * cmd, void *s, const char *from, else return "Invalid argument"; - a = (bw_entry *) apr_array_push(conf->minlimits); + a = (bw_entry *)apr_array_push(conf->minlimits); a->x.from = where; - if (!strncasecmp(where,"u:",2)) + 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); + a->agent = ap_pregcomp(cmd->pool, where + 2, 0); if (a->agent == NULL) return "Regular expression could not be compiled."; @@ -447,7 +451,7 @@ static const char *minbandwidth(cmd_parms * cmd, void *s, const char *from, else if ((str = strchr(where, '/'))) { *str++ = '\0'; rv = apr_ipsubnet_create(&a->x.ip, where, str, cmd->pool); - if(APR_STATUS_IS_EINVAL(rv)) { + if (APR_STATUS_IS_EINVAL(rv)) { /* looked nothing like an IP address */ return "An IP address was expected"; } @@ -474,10 +478,10 @@ static const char *minbandwidth(cmd_parms * cmd, void *s, const char *from, } /* Set the large file bandwidth limit */ -static const char *largefilelimit(cmd_parms * cmd, void *s, const char *file, - const char *size, const char *bw) +static const char *largefilelimit(cmd_parms *cmd, void *s, const char *file, + const char *size, const char *bw) { - bandwidth_config *conf = (bandwidth_config *) s; + bandwidth_config *conf = (bandwidth_config *)s; bw_sizel *a; long int temp, tsize; char msgbuf[MAX_BUF]; @@ -501,14 +505,14 @@ static const char *largefilelimit(cmd_parms * cmd, void *s, const char *file, 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 = (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); + 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++; @@ -520,11 +524,11 @@ static const char *largefilelimit(cmd_parms * cmd, void *s, const char *file, * Helper Functions * *----------------------------------------------------------------------------*/ -/* Match the input, as part of a domain */ + /* 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); + size_t dl = strlen(domain); + size_t wl = strlen(what); if ((wl - dl) >= 0) { if (strcasecmp(domain, &what[wl - dl]) != 0) @@ -544,9 +548,9 @@ static int in_domain(const char *domain, const char *what) } /* Get the bandwidth limit based on from address */ -static long get_bw_rate(request_rec * r, apr_array_header_t * a) +static long get_bw_rate(request_rec *r, apr_array_header_t *a) { - bw_entry *e = (bw_entry *) a->elts; + bw_entry *e = (bw_entry *)a->elts; const char *remotehost = NULL; int i; int gothost = 0; @@ -557,14 +561,14 @@ static long get_bw_rate(request_rec * r, apr_array_header_t * a) 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 ) + 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)) { + if (apr_ipsubnet_test(e[i].x.ip, r->connection->client_addr)) { return e[i].rate; } break; @@ -573,7 +577,7 @@ static long get_bw_rate(request_rec * r, apr_array_header_t * a) int remotehost_is_ip; remotehost = ap_get_remote_host(r->connection, r->per_dir_config, - REMOTE_DOUBLE_REV, &remotehost_is_ip); + REMOTE_DOUBLE_REV, &remotehost_is_ip); if ((remotehost == NULL) || remotehost_is_ip) gothost = 1; @@ -585,14 +589,14 @@ static long get_bw_rate(request_rec * r, apr_array_header_t * a) return (e[i].rate); break; } - + } return 0; } -/* - Match the pattern with the last digist from filename - An asterisk means any. +/* + Match the pattern with the last digist from filename + An asterisk means any. */ static int match_ext(const char *file, char *match) { @@ -611,10 +615,10 @@ static int match_ext(const char *file, char *match) } /* 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) +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; + bw_sizel *e = (bw_sizel *)a->elts; int i; apr_uint32_t tmpsize = 0, tmprate = 0; @@ -635,9 +639,9 @@ static long get_bw_filesize(request_rec * r, apr_array_header_t * a, } /* Get the MaxConnections allowed */ -static int get_maxconn(request_rec * r, apr_array_header_t * a) +static int get_maxconn(request_rec *r, apr_array_header_t *a) { - bw_maxconn *e = (bw_maxconn *) a->elts; + bw_maxconn *e = (bw_maxconn *)a->elts; const char *remotehost = NULL; int i; int gothost = 0; @@ -648,14 +652,14 @@ static int get_maxconn(request_rec * r, apr_array_header_t * a) 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 ) + 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)) { + if (apr_ipsubnet_test(e[i].x.ip, r->connection->client_addr)) { return e[i].max; } break; @@ -664,7 +668,7 @@ static int get_maxconn(request_rec * r, apr_array_header_t * a) int remotehost_is_ip; remotehost = ap_get_remote_host(r->connection, r->per_dir_config, - REMOTE_DOUBLE_REV, &remotehost_is_ip); + REMOTE_DOUBLE_REV, &remotehost_is_ip); if ((remotehost == NULL) || remotehost_is_ip) gothost = 1; @@ -682,9 +686,9 @@ static int get_maxconn(request_rec * r, apr_array_header_t * a) } /* Get an id based on bandwidth limit */ -static int get_sid(request_rec * r, apr_array_header_t * a) +static int get_sid(request_rec *r, apr_array_header_t *a) { - bw_entry *e = (bw_entry *) a->elts; + bw_entry *e = (bw_entry *)a->elts; const char *remotehost = NULL; int i; int gothost = 0; @@ -692,21 +696,21 @@ static int get_sid(request_rec * r, apr_array_header_t * a) remotehost = ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_HOST, - NULL); + 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 ) + 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)) { + if (apr_ipsubnet_test(e[i].x.ip, r->connection->client_addr)) { return e[i].sid; } break; @@ -715,7 +719,7 @@ static int get_sid(request_rec * r, apr_array_header_t * a) int remotehost_is_ip; remotehost = ap_get_remote_host(r->connection, r->per_dir_config, - REMOTE_DOUBLE_REV, &remotehost_is_ip); + REMOTE_DOUBLE_REV, &remotehost_is_ip); if ((remotehost == NULL) || remotehost_is_ip) gothost = 1; @@ -733,10 +737,10 @@ static int get_sid(request_rec * r, apr_array_header_t * a) } /* 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) +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; + bw_sizel *e = (bw_sizel *)a->elts; int i; apr_uint32_t tmpsize = 0, tmpsid = -1; @@ -759,7 +763,7 @@ static int get_f_sid(request_rec * r, apr_array_header_t * a, apr_uint32_t files } /* Update memory (shm) counters, which holds the bw data per context */ -static void update_counters(bw_data * bwstat, ap_filter_t * f) +static void update_counters(bw_data *bwstat, ap_filter_t *f) { apr_time_t nowtime; @@ -770,8 +774,8 @@ static void update_counters(bw_data * bwstat, ap_filter_t * f) 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)) * + bwstat->bandwidth = (apr_uint32_t)( + (bwstat->bytes_count / (double)(nowtime - bwstat->time)) * 1000000); /* Reset counters */ @@ -786,7 +790,7 @@ static void update_counters(bw_data * bwstat, ap_filter_t * f) } } -static int callback(request_rec * r) +static int callback(request_rec *r) { int t; bw_data *bwstat; @@ -795,20 +799,20 @@ static int callback(request_rec * r) return OK; } - if (r->args && !strncasecmp(r->args,"csv",3)) + 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); - + 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); + 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); @@ -821,7 +825,7 @@ static int callback(request_rec * r) ap_rputs(" \n", r); ap_rputs("

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

\n", r);; @@ -831,47 +835,44 @@ static int callback(request_rec * r) 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("
", 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; - - + 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) + /* 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_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); + (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) + /* Only work on main request/no subrequests and no redirets */ + if (r->main || r->prev) return DECLINED; - if (strcmp(r->handler, "modbw-handler")==0) return callback(r); - + if (strcmp(r->handler, "modbw-handler") == 0) return callback(r); /* Return if module is not enabled */ if (sconf->state == BANDWIDTH_DISABLED) @@ -889,7 +890,7 @@ static int handle_bw(request_rec * r) /* If we are too busy, deny connection */ confid = get_maxconn(r, conf->maxconnection); - if ((bwstat->connection_count >= (apr_uint32_t) confid) && (confid > 0)) + if ((bwstat->connection_count >= (apr_uint32_t)confid) && (confid > 0)) return conf->error; } @@ -901,28 +902,34 @@ static int handle_bw(request_rec * r) return DECLINED; } -static int bw_filter(ap_filter_t * f, apr_bucket_brigade * bb) +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_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); + (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; + apr_size_t bw_rate, bw_min, bw_f_rate, cur_rate = 0; + apr_interval_time_t sleep; const char *buf; const char *filename; + long current_sleep_bypasses = 0; + unsigned long long new_client_bw = 0; + char sizebuf[5][5]; /* buffers for human-readable size/rate conversion */ /* Only work on main request/no subrequests */ - if (r->main) { + if (r->status != HTTP_OK + || r->main + || (r->handler && strcmp(r->handler, "default-handler") == 0)) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } @@ -945,7 +952,7 @@ static int bw_filter(ap_filter_t * f, apr_bucket_brigade * bb) /* 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); + bw_f_rate = get_bw_filesize(r, conf->sizelimits, (off_t)bblen, filename); /* Check if we've got an ilimited client */ @@ -960,7 +967,7 @@ static int bw_filter(ap_filter_t * f, apr_bucket_brigade * bb) - 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); + confid = get_f_sid(r, conf->sizelimits, (off_t)bblen, filename); bw_rate = bw_f_rate; } @@ -969,13 +976,6 @@ static int bw_filter(ap_filter_t * f, apr_bucket_brigade * bb) 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; @@ -988,20 +988,32 @@ static int bw_filter(ap_filter_t * f, apr_bucket_brigade * bb) /* 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); + /* Initialize our temporal space, which survives multiple invokations for the same request */ + 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); + ctx->bw_interval_start_time = apr_time_now(); + ctx->bw_interval_bytes = 0; + ctx->sleep_bypasses_left = 0; + ctx->sleep_bypasses_total = 0; + ctx->client_bw = 0; + + /* Verbose Output - but only the first time the filter is invoked per connection */ + ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, r, + "ID: %i; Directory : %s; File : %s; Rate : %s; Minimum : %s; Size rate : %s;", + confid, conf->directory, filename, apr_strfsize(bw_rate, sizebuf[0]), apr_strfsize(bw_min, sizebuf[1]), apr_strfsize(bw_f_rate, sizebuf[2])); + + ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, r, + "clients : %d/%d; rate/min : %s/s,%s/s", bwmaxconn->connection_count, + (connid >= 0) ? get_maxconn(r, conf->maxconnection) : 0, + apr_strfsize(bw_rate, sizebuf[0]), apr_strfsize(bw_min, sizebuf[1])); + } /* - We get buckets until a sentinel appears - - Read the content of the bucket, and send it to the next filter, piece - by piece + - 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 */ @@ -1009,6 +1021,7 @@ static int bw_filter(ap_filter_t * f, apr_bucket_brigade * bb) APR_BUCKET_REMOVE(b); APR_BRIGADE_INSERT_TAIL(ctx->bb, b); ap_pass_brigade(f->next, ctx->bb); + apr_brigade_cleanup(ctx->bb); /* Delete 1 active connection */ apr_atomic_dec32(&bwmaxconn->connection_count); @@ -1017,15 +1030,16 @@ static int bw_filter(ap_filter_t * f, apr_bucket_brigade * bb) 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 + - 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 + - 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; + cur_rate = (long int)bw_rate / bwmaxconn->connection_count; if (cur_rate > bw_rate) cur_rate = bw_rate; @@ -1050,21 +1064,64 @@ static int bw_filter(ap_filter_t * f, apr_bucket_brigade * bb) /* Here we get the time we need to sleep to get the specified bw */ sleep = - (long int) (1000000 / - ((double) cur_rate / (double) packet)); + (unsigned long)(1000000 / + ((double)cur_rate / (double)packet)); + + /* if more than 15ms has elapsed, roughly calculate client's actual bandwidth */ + if (apr_time_now() - ctx->bw_interval_start_time > 15000) { + new_client_bw = (ctx->bw_interval_bytes * 1000 / (unsigned long long)(apr_time_now() - ctx->bw_interval_start_time)) * 1000; + /* if the client bandwidth changed more then 1K or is now more than available, log it (if module debug is on) */ + if (ctx->client_bw == 0 || + (unsigned long long)(ctx->client_bw / 1024) != (unsigned long long)(new_client_bw / 1024) || + new_client_bw > cur_rate) + { + /* Verbose logging */ + ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, r, "Client BW: %s/s; Available BW: %s/s; %s", apr_strfsize(new_client_bw, sizebuf[0]), apr_strfsize(cur_rate, sizebuf[1]), (new_client_bw > cur_rate ? "Limiting!" : "Not limiting!")); + } + ctx->client_bw = new_client_bw; + ctx->bw_interval_bytes = 0; + ctx->bw_interval_start_time = apr_time_now(); + } + #if defined(WIN32) - if (sleep < 200000 && cur_rate > 1024) - { - sleep = 200000; - packet = cur_rate/5; - if (bytes < packet) - packet = bytes; - } + /* For Windows, assume the minimum sleep time is 10ms. If the needed sleep time is less than 10ms, calculate + how many packets to send before sleeping for 10ms. For very small buckets, this can result in an + absurdly high number of packets to send, probably more than there are buckets, so it doesn't matter. */ + if (sleep < 10000 && (ctx->client_bw > cur_rate || ctx->client_bw == 0)) + { + /* calculate, based on available bandwidth right now, how many packets can be sent in 10 ms */ + current_sleep_bypasses = (long)((double)10000 / (double)(sleep + 1)); /* plus 1 microsecond to avoid divide by zero errors */ + + sleep = 10000; + + if (ctx->sleep_bypasses_left == 0) { /* If not currently counting down packets... */ + ctx->sleep_bypasses_total = current_sleep_bypasses; /* setup counters */ + ctx->sleep_bypasses_left = current_sleep_bypasses; + ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, r, + "Available BW: %s/s. Packet: %s. Bypassing sleep for %i packet(s). File: %s.", apr_strfsize(cur_rate, sizebuf[0]), apr_strfsize(packet, sizebuf[1]), ctx->sleep_bypasses_left, filename); + } + else { + /* If number of packets to send in 10ms has changed since last calc because available bandwidth changed, recalcuate */ + if (ctx->sleep_bypasses_total != current_sleep_bypasses) { + /* new packet counter = current_sleep_bypasses minus packets already sent */ + ctx->sleep_bypasses_left = current_sleep_bypasses - (ctx->sleep_bypasses_total - ctx->sleep_bypasses_left); + + if (ctx->sleep_bypasses_left < 0) { + ctx->sleep_bypasses_left = 0; /* if new packets to send is less than zero, reset to zero so we sleep now*/ + } + ctx->sleep_bypasses_total = current_sleep_bypasses; /* Update the total packets to send in this period */ + ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, r, + "*Available BW: %s/s. Packet: %s. Bypassing sleep for %i packet(s). File: %s.", apr_strfsize(cur_rate, sizebuf[0]), apr_strfsize(packet, sizebuf[1]), ctx->sleep_bypasses_left, filename); + } + } + } + else { + ctx->sleep_bypasses_left = 0; + } #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 + doing a "delay" between each piece. That way, we send the data at the specified rate. */ apr_bucket_split(b, packet); @@ -1073,25 +1130,36 @@ static int bw_filter(ap_filter_t * f, apr_bucket_brigade * bb) /* Decrease our counter */ bytes -= packet; + ctx->bw_interval_bytes += packet; /* Flush and move to the next bucket */ ap_pass_brigade(f->next, ctx->bb); + apr_brigade_cleanup(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); + apr_atomic_add64(&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"); + ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, r, "ID: %i; Connection went away for %s!", confid, filename); apr_atomic_dec32(&bwmaxconn->connection_count); return APR_SUCCESS; } - /* Sleep ... zZZzZzZzzzz */ - apr_sleep(sleep); + /* Sleep ... but only if the time is right (see Windows clock resolution fix above) */ + if (ctx->sleep_bypasses_left > 0) { + ctx->sleep_bypasses_left--; + } + else { + /* Only sleep if the client's calculated bw is greater than available or client bw not yet known */ + if (ctx->client_bw > cur_rate || ctx->client_bw == 0) { + apr_sleep(sleep); + ctx->bw_interval_start_time += sleep; /* Adjust interval start time to exclude sleep time. */ + } + ctx->sleep_bypasses_left = 0; + } /* Refresh counters, so we can keep working :) */ update_counters(bwstat, f); @@ -1102,11 +1170,12 @@ static int bw_filter(ap_filter_t * f, apr_bucket_brigade * bb) 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); + /* Add the number of bytes to the counter - switched to the 64bit counter for bigger files */ + apr_atomic_add64(&bwstat->bytes_count, bytes); /* Pass the final brigade */ ap_pass_brigade(f->next, ctx->bb); + apr_brigade_cleanup(ctx->bb); } /* Delete 1 active connection to the record */ @@ -1121,8 +1190,8 @@ static int bw_filter(ap_filter_t * f, apr_bucket_brigade * bb) /*----------------------------------------------------------------------------* * Module Init functions * *----------------------------------------------------------------------------*/ -static int bw_init(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, - server_rec * s) +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; @@ -1130,7 +1199,7 @@ static int bw_init(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, bw_data *bwstat; int t; #if defined(WIN32) - TIMECAPS resolution; + TIMECAPS resolution; #endif /* These two help ensure that we only init once. */ @@ -1139,8 +1208,8 @@ static int bw_init(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, 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); + apr_pool_userdata_set((const void *)1, userdata_key, + apr_pool_cleanup_null, s->process->pool); return OK; } @@ -1149,7 +1218,7 @@ static int bw_init(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, if (status != APR_SUCCESS) return HTTP_INTERNAL_SERVER_ERROR; - shm_size = (apr_size_t) sizeof(bw_data) * sid; + shm_size = (apr_size_t)sizeof(bw_data) * sid; /* If there was a memory block already assigned.. destroy it */ @@ -1157,11 +1226,12 @@ static int bw_init(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, 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"); + "mod_bw : Couldn't destroy old memory block\n"); return status; - } else { + } + else { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, - "mod_bw : Old Shared memory block, destroyed."); + "mod_bw : Old Shared memory block, destroyed."); } } @@ -1169,32 +1239,32 @@ static int bw_init(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, 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"); + "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"); + "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"); + "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)); + "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"); + "mod_bw : Not enough memory allocated!! Giving up"); return HTTP_INTERNAL_SERVER_ERROR; } @@ -1212,55 +1282,54 @@ static int bw_init(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, } ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, - "mod_bw : Version %s - Initialized [%d Confs]", VERSION, - sid); + "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) + // 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."); - } + "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) +static void *create_bw_config(apr_pool_t *p, char *path) { bandwidth_config *new = - (bandwidth_config *) apr_palloc(p, sizeof(bandwidth_config)); + (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->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) +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)); + (bandwidth_server_config *)apr_pcalloc(p, + sizeof + (bandwidth_server_config)); new->state = BANDWIDTH_DISABLED; new->force = BANDWIDTH_DISABLED; @@ -1271,7 +1340,7 @@ static void *create_bw_server_config(apr_pool_t * p, server_rec * s) * Apache register functions * *----------------------------------------------------------------------------*/ -static void register_hooks(apr_pool_t * p) +static void register_hooks(apr_pool_t *p) { /* - Register a handler, which enforces mod_bw if needed @@ -1310,6 +1379,7 @@ static const command_rec bw_cmds[] = { {NULL} }; +APLOG_USE_MODULE(bw); module AP_MODULE_DECLARE_DATA bw_module = { STANDARD20_MODULE_STUFF, create_bw_config, /* dir config creater */