Download | Plain Text | Line Numbers


diff -Naur ucspi-tcp6-1.00.orig/src/CHANGES.tcpserver-limits-patch ucspi-tcp6-1.00/src/CHANGES.tcpserver-limits-patch
--- ucspi-tcp6-1.00.orig/src/CHANGES.tcpserver-limits-patch	1970-01-01 01:00:00.000000000 +0100
+++ ucspi-tcp6-1.00/src/CHANGES.tcpserver-limits-patch	2014-05-18 16:01:53.991206542 +0200
@@ -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)
+
+
diff -Naur ucspi-tcp6-1.00.orig/src/README.tcpserver-limits-patch ucspi-tcp6-1.00/src/README.tcpserver-limits-patch
--- ucspi-tcp6-1.00.orig/src/README.tcpserver-limits-patch	1970-01-01 01:00:00.000000000 +0100
+++ ucspi-tcp6-1.00/src/README.tcpserver-limits-patch	2014-05-18 20:26:06.659001385 +0200
@@ -0,0 +1,122 @@
+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 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 makes tcpserver from Dr. Erwin Hoffmann's ucspi-tcp6 1.00a package
+(see http://www.fehcom.de/ipnet/ucspi-tcp6.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 /24 (IPv4) or /64 (IPv6). 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.
+
+
+The origin patch is from Matija Nalis, http://linux.voyager.hr/ucspi-tcp/
diff -Naur ucspi-tcp6-1.00.orig/src/tcpserver.c ucspi-tcp6-1.00/src/tcpserver.c
--- ucspi-tcp6-1.00.orig/src/tcpserver.c	2014-01-08 13:05:43.000000000 +0100
+++ ucspi-tcp6-1.00/src/tcpserver.c	2014-05-18 20:29:12.437079915 +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 <unistd.h>
 #include <sys/param.h>
@@ -64,11 +72,20 @@
 static stralloc tmp;
 static stralloc fqdn;
 static stralloc addresses;
+static stralloc diemsg_buf;
+
+#define loaddouble(la) ((double)(la) / FSCALE)
 
 char bspace[16];
 buffer b;
 
+typedef struct
+{
+  char ip[16];
+  pid_t pid;
+} baby;
 
+baby *child;
 
 /* ---------------------------- child */
 
@@ -77,6 +94,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)
 {
@@ -114,6 +135,8 @@
   strerr_die4sys(111,DROP,"unable to read ",fnrules,": ");
 }
 
+unsigned long limit = 40;
+
 void found(char *data,unsigned int datalen)
 {
   unsigned int next0;
@@ -129,6 +152,14 @@
 	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;
     }
@@ -137,6 +168,37 @@
   }
 }
 
+unsigned long getprocla(void)
+{
+#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
+}
+
 void doit(int t)
 {
   int mappedv4 = 0;
@@ -254,6 +316,29 @@
     }
   }
 
+  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 (ipv4 || mappedv4 || ip6_isv4mapped(remoteip)) {
+	  if (byte_equal(child[u].ip, 15, remoteip)) cc++;
+	}
+	else {
+	  if (byte_equal(child[u].ip, 8, remoteip)) cc++;
+	}
+	if (byte_equal(child[u].ip, 16, 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();
@@ -266,11 +351,35 @@
     cats(":"); safecats(stripaddr);
     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);
+  }
 }
 
 
@@ -297,7 +406,6 @@
   _exit(100);
 }
 
-unsigned long limit = 40;
 unsigned long numchildren = 0;
 
 int flag1 = 0;
@@ -322,6 +430,7 @@
 {
   int wstat;
   int pid;
+  unsigned long u;
 
   while ((pid = wait_nohang(&wstat)) > 0) {
     if (verbosity >= 2) {
@@ -330,11 +439,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 */
   }
 }
 
 int main(int argc,char **argv)
 {
+  pid_t pid;
   char *hostname;
   int opt;
   struct servent *se;
@@ -377,6 +489,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;
@@ -397,6 +513,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);
@@ -459,7 +579,7 @@
     if (t == -1) continue;
     ++numchildren; printstatus();
 
-    switch(fork()) {
+    switch(pid=fork()) {
       case 0:
         close(s);
         doit(t);
@@ -474,6 +594,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,16,remoteip);
+	    child[u].pid = pid;
+	    break;
+	  }
+	if (u == limit)
+	  strerr_die1x(111,"tcpserver: ERROR: no empty space for new child?!"); /* never happens */
     }
     close(t);
   }