[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#139117: Bandwidth throttling for redir'd connections [patch]



Package: wnpp
Version: 2.1-2
Severity: wishlist
Tags: patch

Hi Bernd,

I've hacked redir to accept a --bandwidth switch to limit the
bandwidth of redirected connections to a certain amount, e.g. with
--bandwidth=1Mbit or --bandwidth=10kbyte. I've successfully used my
hacked version with and without --inetd.

Possible uses:

- Limit bandwidth of connections made from localhost to localhost. The
  kernel traffic shaping features don't work in that case. (That's
  what I'm using it for; to test my download manager app on a machine
  which is not connected to any network most of the time...;)

- Limit traffic if you don't have root on a machine.

The attached patch must be applied on top of the Debian one. Please
also forward this upstream. Hm, the manpage should also be changed to
include the new switch...

Cheers,

  Richard

-- 
  __   _
  |_) /|  Richard Atterer
  | \/¯|  http://atterer.net
  ¯ '` ¯

--- redir.c.orig	Mon Mar 18 21:50:47 2002
+++ redir.c	Tue Mar 19 11:10:12 2002
@@ -52,9 +52,21 @@
  *  has connected.
  */
 
+/* 2002-03-19
+ *
+ * New features:
+ *  - Limit bandwidth to desired amount in mbit/kbit/bit/mbyte/kbyte/byte:
+ *    --bandwidth 768kbit, --bandwidth 100kbyte
+ *  - Increased size of internal buffer, resulting in larger throughput
+ *    and less CPU usage.
+ *
+ * Added by Richard Atterer <richard at atterer.net>
+ */
+
 #define  VERSION "2.0"
 
 #include <stdio.h>
+#include <ctype.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <string.h>
@@ -69,6 +81,7 @@
 #include <arpa/inet.h>
 #include <netdb.h>
 #include <errno.h>
+#include <time.h>
 
 #ifdef USE_TCP_WRAPPERS
 #include <tcpd.h>
@@ -88,6 +101,7 @@
 int ftp = 0;
 int transproxy = 0;
 char * ident = NULL;
+int bandwidth = 0; /* Bandwidth limit in bytes per sec, or 0==infinite */
 
 #ifdef USE_TCP_WRAPPERS
 struct request_info request;
@@ -141,10 +155,24 @@
     fprintf(stderr, "\t\t--bind_addr=IP\tbind() outgoing IP to given addr\n");
     fprintf(stderr, "\t\t--ftp\t\tredirect passive ftp connections\n");
     fprintf(stderr, "\t\t--transproxy\trun in linux's transparent proxy mode\n");
+    fprintf(stderr, "\t\t--bandwidth infinite|x{bit|kbit|mbit|byte|kbyte|mbyte}\n");
+    fprintf(stderr, "\t\t            \tlimit amount of data forwarded per second\n");
     fprintf(stderr, "\n\tVersion %s - $Id$\n", VERSION);
     exit(2);
 }
 
+/* Case-insensitive comparison. b must be all lowercase. */
+int
+strequals(const char* a,
+          const char* b)
+{
+    while (1) {
+        if (*a == '\0' && *b == '\0') return 1;
+        if (tolower(*a) != *b) return 0;
+        ++a; ++b;
+    }
+}
+
 void
 parse_args(int argc,
 	   char * argv[],
@@ -158,7 +186,8 @@
 	   int * dosyslog,
 	   char ** bind_addr,
 	   int * ftp,
-	   int * transproxy)
+	   int * transproxy,
+	   int * bandwidth)
 {
      static struct option long_options[] = {
 	  {"lport", required_argument, 0, 'l'},
@@ -174,6 +203,7 @@
 	  {"syslog",   no_argument,       0, 's'},
 	  {"ftp",      no_argument,       0, 'f'},
 	  {"transproxy", no_argument,     0, 'p'},
+	  {"bandwidth", required_argument, 0, 'w'},
 	  {0,0,0,0}		/* End marker */
      };
      
@@ -188,6 +218,7 @@
     *target_addr = NULL;
     *target_port = 0;
     *local_port = 0;
+    *bandwidth = 0;
 
     while ((opt = getopt_long(argc, argv, "disfpn:t:b:a:l:r:c:", 
 			      long_options, &option_index)) != -1) {
@@ -241,6 +272,23 @@
 	     (*transproxy)++;
 	     break;
 
+        case 'w': {
+            char* p = optarg;
+            *bandwidth = 0;
+            if (strequals(p, "infinite")) break;
+            while (*p >= '0' && *p <= '9')
+                *bandwidth = *bandwidth * 10 + (*p++ - '0');
+            if (strequals(p, "bit")) *bandwidth /= 8;
+            else if (strequals(p, "kbit")) *bandwidth = *bandwidth*1000/8;
+            else if (strequals(p, "mbit")) *bandwidth = *bandwidth*1000000/8;
+            else if (strequals(p, "byte")) ;
+            else if (strequals(p, "kbyte")) *bandwidth *= 1024;
+            else if (strequals(p, "mbyte")) *bandwidth *= 1024*1024;
+            else { redir_usage(argv[0]); exit(1); }
+            if (*bandwidth == 0) *bandwidth = 1;
+            break;
+        }
+
 	default:
 	    redir_usage(argv[0]);
 	    exit(1);
@@ -392,11 +440,16 @@
     fd_set c_iofds;
     int max_fd;			/* Maximum numbered fd used */
     struct timeval timeout;
+    struct timeval time_prev, time_now;
+    signed long max_bucketsize = bandwidth * 5 * 1000; /* 5 sec */
+    signed long bucketsize = 0; /* Contains nr of bytes * 1000 */
     unsigned long bytes;
     unsigned long bytes_in = 0;
     unsigned long bytes_out = 0;
     unsigned int start_time, end_time;
-    char buf[4096];
+    int buf_size = (bandwidth == 0 || bandwidth/4 + 1 > 65536
+                    ? 65536 : bandwidth/4 + 1);
+    char buf[65536];
 
     /* Record start time */
     start_time = (unsigned int) time(NULL);
@@ -410,6 +463,8 @@
     FD_SET(insock, &iofds);
     FD_SET(outsock, &iofds);
 
+    /* Init bandwidth limiting */
+    if (gettimeofday(&time_prev, NULL)) perror("gettimeofday");
     
     if (insock > outsock) {
 	max_fd = insock;
@@ -431,15 +486,16 @@
 	    break;
 	}
 
+        bytes = 0;
 	if(FD_ISSET(insock, &c_iofds)) {
-	    if((bytes = read(insock, buf, sizeof(buf))) <= 0)
+	    if((bytes = read(insock, buf, buf_size)) <= 0)
 		break;
 	    if(write(outsock, buf, bytes) != bytes)
 		break;
 	    bytes_out += bytes;
 	}
 	if(FD_ISSET(outsock, &c_iofds)) {
-	    if((bytes = read(outsock, buf, sizeof(buf))) <= 0)
+	    if((bytes = read(outsock, buf, buf_size)) <= 0)
 		break;
 	    /* if we're correcting for PASV on ftp redirections, then
 	       fix buf and bytes to have the new address, among other
@@ -451,6 +507,31 @@
 		      break;
 	    bytes_in += bytes;
 	}
+        if (bytes > 0 && bandwidth > 0) {
+            /* Throttle bandwidth */
+            unsigned long msec_elapsed;
+            if (gettimeofday(&time_now, NULL)) perror("gettimeofday");
+            msec_elapsed = (time_now.tv_sec - time_prev.tv_sec) * 1000;
+            if (time_now.tv_usec >= time_prev.tv_usec)
+              msec_elapsed += (time_now.tv_usec - time_prev.tv_usec) / 1000;
+            else
+              msec_elapsed -= (time_prev.tv_usec - time_now.tv_usec) / 1000;
+            time_prev = time_now;
+            bucketsize += msec_elapsed * bandwidth;
+            if (bucketsize > max_bucketsize) bucketsize = max_bucketsize;
+            if (bytes * 1000 <= bucketsize) {
+                bucketsize -= bytes * 1000; /* OK - no throttling */
+            } else {
+                /* +bandwidth/2 would be correct, but /4 gives
+                   slightly better results in practice. */
+                unsigned long msec_sleep = ((bytes * 1000 - bucketsize)
+                                            + bandwidth/4) / bandwidth;
+                /*debug1("Bandwidth exceeded - sleeping for %ld millisecs\n",
+                         msec_sleep);*/
+                usleep(msec_sleep * 1000);
+                bucketsize = -msec_sleep * bandwidth;
+            }
+        }
     }
     debug("Leaving main copyloop\n");
 
@@ -739,7 +820,7 @@
     debug("parse args\n");
     parse_args(argc, argv, &target_addr, &target_port, &local_addr, 
 	       &local_port, &timeout, &dodebug, &inetd, &dosyslog, &bind_addr,
-	       &ftp, &transproxy);
+	       &ftp, &transproxy, &bandwidth);
 
     /* Set up target */
     target.sin_family = AF_INET;



Reply to: