Download | Plain Text | No Line Numbers
- --- /dev/null 1970-01-01 01:00:00.000000000 +0100
- +++ CHANGES.tcpserver-limits-patch 2006-01-26 18:59:34.000000000 +0100
- @@ -0,0 +1,28 @@
- +20060126 Added support for "always reject" (setting MAXCONNIP and/or
- +MAXCONNC to 0) and fixing a bug when sometimes DIEMSG would not be
- +shown (by Mark Powell)
- +
- +20050903 Added support for Solaris (by Jorge Valdes).
- +Moved MAXLOAD code to getprocla().
- +Modified documentation a little to accommodate recent changes.
- +
- +20050130 reinstated /proc/loadavg support for those compiling on Linux
- +with dietlibc (see #define NO_GETLOADAVG at top of tcpserver.c).
- +Also, we now compile on 64bit platforms (we avoid including unistd.h if
- +using getloadavg(3), so we don't conflict with readwrite.h header file)
- +Needed if your compile was breaking with:
- +readwrite.h:4: error: syntax error before "read"
- +readwrite.h:4: warning: data definition has no type or storage class
- +SUMMARY: If 20040725 worked for you, there is no reason to upgrade
- +(no new features of bugfixes)
- +
- +20040725 adds a sleep(1) before terminating (to prevent too high load from
- +many rapid fork()/exit() calls. It also changes the method for checking
- +system load to getloadavg(3) instead of parsing /proc/loadavg, therefore
- +making it working on *BSD and other non-Linux systems in addition to Linux.
- +It also adds DIEMSG="xxx" support.
- +
- +20040327 fixes a bug in 20040124 related to MAXLOAD (it would not work
- +correctly when load was higher than 10.00)
- +
- +
- --- /dev/null 1970-01-01 01:00:00.000000000 +0100
- +++ README.tcpserver-limits-patch 2006-01-26 18:56:02.000000000 +0100
- @@ -0,0 +1,136 @@
- +See CHANGES.tcpserver-limits-patch for changes summary.
- +
- +:::COMPILING:::
- +
- + For MAXLOAD variable to have effect, you have 3 options:
- +
- +(1) By default the patch assumes that you have working getloadavg(3)
- + (most modern UN*Xoids have, including Linux and FreeBSD). No changes
- + are needed to standard ucspi-tcp compilation procedures.
- +
- +(2) If you have Solaris system:
- + - conf-cc needs to be modified to include "-DSOLARIS"
- + - Makefile needs to be modified so that tcpserver links with "-lkstat"
- + (add "-lkstat" to the end of line 748 after "`cat socket.lib`")
- +
- +(3) If you have a non-Solaris system without getloadavg(3), but with
- + readable '/proc/loadavg' (in linux-2.4.x/2.6.x syntax); for example
- + if you're compiling on Linux system with dietlibc:
- + - conf-cc needs to be modified to include "-DNO_GETLOADAVG"
- +
- +
- +:::USING:::
- +
- +This patch (20060126) makes tcpserver from DJB's ucspi-tcp-0.88 package (see
- +http://cr.yp.to/ucspi-tcp.html) to modify its behavior if some environment
- +variables are present.
- +
- +The variables can be preset before starting tcpserver (thus acting as
- +default for all connections), or, if you use 'tcpserver -x xxx.cdb', they
- +can be set (or overridden) from xxx.cdb. If none of the variables are set,
- +tcpserver behaves same as non patched version (except for negligible
- +performance loss). Any or all variables can be set, as soon as first limit
- +is reached the connection is dropped. I'd recommend using .cdb files
- +exclusively though, as you can then modify configuration without killing
- +tcpserver.
- +
- +The variables are:
- +
- +(1) MAXLOAD
- + maximum 1-minute load average * 100. For example, if you have line
- + :allow,MAXLOAD="350"
- + in your rules file from which you created .cdb, the connection will be
- + accepted only if load average is below 3.50
- +
- + See COMPILING instructions above for info on supported systems.
- +
- +(2) MAXCONNIP
- + maximum connections from one IP address. tcpserver's -c flag defines
- + maximum number of allowed connections, but it can be abused if
- + just one host goes wild and eats all the connections - no other host
- + would be able to connect then. If you created your .cdb with:
- + :allow,MAXCONNIP="5"
- + and run tcpserver -c 50, then each IP address would be able to have at
- + most 5 concurrent connections, while there still could connect 50
- + clients total.
- + 0 is valid value and means 'always reject'
- +
- +(3) MAXCONNC
- +
- + maximum connections from whole C-class (256 addresses). Extension of
- + MAXCONNIP, as sometimes the problematic client has a whole farm of
- + client machines with different IP addresses instead of just one IP
- + address, and they all try to connect. It might have been more useful to
- + be able to specify CIDR block than C-class, but I've decided to KISS.
- +
- + for example tcpserver -c 200, and .cdb with:
- + :allow,MAXCONNC="15"
- + will allow at most 15 host from any x.y.z.0/24 address block, while
- + still allowing up to 200 total connections.
- + 0 is valid value and means 'always reject'
- +
- +(4) DIEMSG
- +
- + if set and one of the above limits is exceeded, this is the message
- + to be sent to client (CRLF is always added to the text) before terminating
- + connection. If unset, the connection simply terminates (after 1 sec delay)
- + if limit is exceeded.
- +
- + For example:
- + DIEMSG="421 example.com Service temporarily not available, closing
- + transmission channel"
- +
- +Notes:
- +
- +- if a connection is dropped due to some of those variables set, it will be
- + flagged (if you run tcpserver -v) with "LOAD:", "MAXCONNIP:" or
- + "MAXCONNC:" at the end of the "tcpserver: deny" line. If that bothers you
- + (eg. you have a strict log parsers), don't apply that chunk of the patch.
- +
- +- the idea for this patch came from my previous experience with xinetd, and
- + need to limit incoming bursts of virus/spam SMTP connections, since I was
- + running qmail-scanner to scan incoming and outgoing messages for viruses
- + and spam.
- +
- +When you make changes, please check that they work as expected.
- +
- +Examples (for tcprules created .cdb)
- +(a) 192.168.:allow,MAXLOAD="1000"
- + :allow,MAXCONNIP="3"
- +
- + this would allow any connection from your local LAN (192.168.*.*
- + addresses) if system load is less than 10.00. non-LAN connections would
- + be accepted only if clients from that IP address have not already opened
- + more than 2 connections (as your connection would be last allowed -- 3rd)
- +
- +(b) 192.168.:allow
- + 5.6.7.8:allow,MAXCONNIP="3"
- + 1.2.:allow,MAXLOAD="500",MAXCONNIP="1",MAXCONNC="5"
- + :allow,MAXLOAD="1000",MAXCONNIP="3",DIEMSG="421 example.com unavailable"
- +
- + if client connects from 192.168.*.* (ex: your LAN), it is allowed.
- + if it connects from 5.6.7.8 (ex: little abusive customer of yours),
- + it is allowed unless there are already 3active connections from 5.6.7.8
- + to this service
- + if it connects from 1.2.*.* (ex: some problematic networks which caused
- + you grief in the past) it will connect only if load is less than 5.0,
- + there is less than 5 active connections from whole C class
- + (1.2.*.0/24), and if that specific IP address does not already have
- + connection open.
- + in all other cases, the client will be permitted to connect if load is
- + less than 10.00 and client has 2 or less connections open. If load is
- + higher than 10.00 or there are 3 or more connections open from this
- + client, the message "421 example.com unavailable" will be returned to
- + the client and connection terminated.
- +
- +
- +Any bugs introduced are my own, do not bother DJB with them.
- +If you find any, or have neat ideas, or better documentation, or whatever,
- +contact me.
- +
- +the latest version of the patch can be found at:
- +http://linux.voyager.hr/ucspi-tcp/
- +
- +Enjoy,
- +Matija Nalis,
- +mnalis-tcpserver _at_ voyager.hr
- --- tcpserver.c 2006-09-12 15:54:11.000000000 +0200
- +++ tcpserver.c 2006-09-12 17:59:43.000000000 +0200
- @@ -1,3 +1,11 @@
- +#ifdef __dietlibc__
- +#define NO_GETLOADAVG
- +#endif
- +
- +#include <stdlib.h>
- +#ifdef NO_GETLOADAVG
- +#include <unistd.h>
- +#endif
- #include <sys/types.h>
- #include <sys/param.h>
- #include <netdb.h>
- @@ -59,11 +67,29 @@
- static stralloc tmp;
- static stralloc fqdn;
- static stralloc addresses;
- +static stralloc diemsg_buf;
- +
- +#ifdef SOLARIS
- +#include <kstat.h>
- +static kstat_ctl_t *kc;
- +#ifndef FSCALE
- +#define FSHIFT 8 /* bits to right of fixed binary point */
- +#define FSCALE (1<<FSHIFT)
- +#endif /* FSCALE */
- +
- +#define loaddouble(la) ((double)(la) / FSCALE)
- +#endif
-
- char bspace[16];
- buffer b;
-
- +typedef struct
- +{
- + char ip[4];
- + pid_t pid;
- +} baby;
-
- +baby *child;
-
- /* ---------------------------- child */
-
- @@ -72,6 +98,10 @@
- int flagdeny = 0;
- int flagallownorules = 0;
- char *fnrules = 0;
- +unsigned long maxload = 0;
- +long maxconnip = -1;
- +long maxconnc = -1;
- +char *diemsg = "";
-
- void drop_nomem(void)
- {
- @@ -110,6 +140,8 @@
- strerr_die4sys(111,DROP,"unable to read ",fnrules,": ");
- }
-
- +unsigned long limit = 40;
- +
- void found(char *data,unsigned int datalen)
- {
- unsigned int next0;
- @@ -125,5 +157,13 @@
- if (data[1 + split] == '=') {
- data[1 + split] = 0;
- env(data + 1,data + 1 + split + 1);
- + if (str_diff(data+1, "MAXLOAD") == 0) scan_ulong(data+1+split+1,&maxload);
- + if (str_diff(data+1, "MAXCONNIP") == 0) scan_ulong(data+1+split+1,&maxconnip);
- + if (str_diff(data+1, "MAXCONNC") == 0) scan_ulong(data+1+split+1,&maxconnc);
- + if (str_diff(data+1, "DIEMSG") == 0) {
- + if (!stralloc_copys(&diemsg_buf,data+1+split+1)) drop_nomem();
- + if (!stralloc_0(&diemsg_buf)) drop_nomem();
- + diemsg = diemsg_buf.s;
- + }
- }
- break;
- @@ -133,6 +173,49 @@
- }
- }
-
- +unsigned long getprocla(void)
- +{
- +#ifdef SOLARIS
- + kstat_t *ksp;
- + kstat_named_t *knp;
- + double lavg;
- + kstat_chain_update(kc);
- + ksp = kstat_lookup(kc, "unix", 0, "system_misc");
- + kstat_read(kc,ksp,NULL);
- + knp = kstat_data_lookup(ksp,"avenrun_1min");
- + lavg = loaddouble(knp->value.ui32);
- + return (unsigned long)(lavg * 100);
- +#else
- +#ifdef NO_GETLOADAVG
- + int lret;
- + int i;
- + unsigned long u1, u2;
- + char *s;
- + static stralloc loadavg_data = {0};
- +
- + lret = openreadclose("/proc/loadavg", &loadavg_data, 10);
- + if (lret != -1) {
- + /* /proc/loadavg format is:
- + * 13.08 3.04 1.00 34/170 14190 */
- + s = loadavg_data.s;
- + i = scan_ulong (s, &u1); s+=i;
- + if ((i>0) && (i<5) && (*s == '.')) { /* load should be < 10000 */
- + i = scan_ulong (s+1,&u2);
- + if (i==2) { /* we require two decimal places */
- + return (u1 * 100 + u2);
- + }
- + return (u1 * 100);
- + }
- + }
- +#else
- + double result;
- + if (getloadavg(&result, 1) == 1) {
- + return (result * 100);
- + }
- +#endif
- +#endif
- +}
- +
- void doit(int t)
- {
- int j;
- @@ -211,6 +294,24 @@
- }
- }
-
- + unsigned long curload = 0;
- + if (maxload) {
- + curload = getprocla();
- + if (curload > maxload) flagdeny = 2;
- + }
- +
- + if (!flagdeny && (maxconnip != -1 || maxconnc != -1)) {
- + unsigned long u;
- + long c1=0, cc=0;
- + for (u=0; u < limit; u++)
- + if (child[u].pid != 0) {
- + if (byte_equal(child[u].ip, 3, remoteip)) cc++;
- + if (byte_equal(child[u].ip, 4, remoteip)) c1++;
- + }
- + if (maxconnc != -1 && (cc >= maxconnc)) flagdeny = 4;
- + if (maxconnip != -1 && (c1 >= maxconnip)) flagdeny = 3;
- + }
- +
- if (verbosity >= 2) {
- strnum[fmt_ulong(strnum,getpid())] = 0;
- if (!stralloc_copys(&tmp,"tcpserver: ")) drop_nomem();
- @@ -223,11 +324,35 @@
- cats(":"); safecats(remoteipstr);
- cats(":"); if (flagremoteinfo) safecats(tcpremoteinfo.s);
- cats(":"); safecats(remoteportstr);
- + if (flagdeny == 2) {
- + char curloadstr[FMT_ULONG];
- + curloadstr[fmt_ulong(curloadstr,curload)] = 0;
- + cats(" "); safecats ("LOAD"); cats(":"); safecats(curloadstr);
- + }
- + if (flagdeny == 3) {
- + char maxconstr[FMT_ULONG];
- + maxconstr[fmt_ulong(maxconstr,maxconnip)] = 0;
- + cats(" "); safecats ("MAXCONNIP"); cats(":"); safecats(maxconstr);
- + }
- + if (flagdeny == 4) {
- + char maxconstr[FMT_ULONG];
- + maxconstr[fmt_ulong(maxconstr,maxconnc)] = 0;
- + cats(" "); safecats ("MAXCONNC"); cats(":"); safecats(maxconstr);
- + }
- cats("\n");
- buffer_putflush(buffer_2,tmp.s,tmp.len);
- }
-
- - if (flagdeny) _exit(100);
- + if (flagdeny) {
- + if (*diemsg) {
- + buffer_init(&b,write,t,bspace,sizeof bspace);
- + buffer_puts(&b,diemsg);
- + if (buffer_putsflush(&b,"\r\n") == -1)
- + strerr_die2sys(111,DROP,"unable to print diemsg: ");
- + }
- + sleep(1);
- + _exit(100);
- + }
- }
-
-
- @@ -253,7 +378,6 @@
- _exit(100);
- }
-
- -unsigned long limit = 40;
- unsigned long numchildren = 0;
-
- int flag1 = 0;
- @@ -278,6 +402,7 @@
- {
- int wstat;
- int pid;
- + unsigned long u;
-
- while ((pid = wait_nohang(&wstat)) > 0) {
- if (verbosity >= 2) {
- @@ -286,11 +411,14 @@
- strerr_warn4("tcpserver: end ",strnum," status ",strnum2,0);
- }
- if (numchildren) --numchildren; printstatus();
- + for (u=0; u < limit; u++) if (child[u].pid == pid) { child[u].pid = 0; break; }
- + if (u == limit) strerr_die1x(111,"tcpserver: ERROR: dead child not found?!"); /* never happens */
- }
- }
-
- main(int argc,char **argv)
- {
- + pid_t pid;
- char *hostname;
- char *portname;
- int opt;
- @@ -331,6 +459,10 @@
- }
- argc -= optind;
- argv += optind;
- + x = env_get("MAXLOAD"); if (x) scan_ulong(x,&maxload);
- + x = env_get("MAXCONNIP"); if (x) scan_ulong(x,&maxconnip);
- + x = env_get("MAXCONNC"); if (x) scan_ulong(x,&maxconnc);
- + x = env_get("DIEMSG"); if (x) diemsg = x;
-
- if (!verbosity)
- buffer_2->fd = -1;
- @@ -352,6 +484,10 @@
- }
-
- if (!*argv) usage();
- +
- + child = calloc(sizeof(baby),limit);
- + if (!child)
- + strerr_die2x(111,FATAL,"out of memory for MAXCONNIP tracking");
-
- sig_block(sig_child);
- sig_catch(sig_child,sigchld);
- @@ -393,6 +529,9 @@
-
- close(0);
- close(1);
- + #ifdef SOLARIS
- + kc = kstat_open();
- + #endif
- printstatus();
-
- for (;;) {
- @@ -405,7 +544,7 @@
- if (t == -1) continue;
- ++numchildren; printstatus();
-
- - switch(fork()) {
- + switch(pid=fork()) {
- case 0:
- close(s);
- doit(t);
- @@ -420,6 +559,16 @@
- case -1:
- strerr_warn2(DROP,"unable to fork: ",&strerr_sys);
- --numchildren; printstatus();
- + break;
- + default:
- + for (u=0; u < limit; u++)
- + if (child[u].pid == 0) {
- + byte_copy(child[u].ip,4,remoteip);
- + child[u].pid = pid;
- + break;
- + }
- + if (u == limit)
- + strerr_die1x(111,"tcpserver: ERROR: no empty space for new child?!"); /* never happens */
- }
- close(t);
- }
-