Updated 2015-02-12 07:28:50 by arjen

vkvalli 16-Aug-2014: This program acts as a bandwidth throttling proxy.
usage:
proxy.tcl <local_port> <remote_host> <remote_port> <bandwidth> <period>
The last parameter <period> is optional.
The bandwidth is in bytes/per second.
Function:
It throttles the downstream bandwidth and not the upstream bandwidth. period regulates the burstness of traffic. The default is 50. This is optimal for interactive applications like rdp. For filetransfer like applications - 200 + might be optimal. The bandwidth applies to each connection and not sum of all connections.
Requirements:
It needs TclOO . So version 8.5 and above required.
Bugs:
error catching on local socket read is not done. It assumes one char is one byte. Keeping period too low, ie 10, will increase cpu load significantly.
Logic:
The core is handler object. It is created on a connection. It's method local_read and remote_read act as handlers for readable fileevent on the channels.
From the bandwidth input, what is the bytes to be read per time-period is calculated. The default time-period is 50 millisec. global variables lbuflength and period carry these values.
There is quota allocated for each time-period. As remote_read keeps reading data from remote_host, this quota keeps decreasing. Once quota becomes zero, remote_read stops reading from chan.
A timer gets activated at the beginning of time-period and sets the quota to max, ie lbuflength. The quota is maintained by instance variable limit.
Methods:
initialize - chan configuration, event handler setup to object methods, starts timer
finalize - channel closing, timer stopping, self-destruction
timer - start - starts the timer, stops - stops it. resets quota on start
local_read - reads local chan
remote_read - if quota available, reads remote, decreases quota
limit_reset - resets quota to max, ie lbuflength
 package require TclOO
 set debug_level 3
 proc debug {level msg} {
  global debug_level
  if {$debug_level > $level} {
   puts $msg
  }
 }
 oo::class create handler {} 
 oo::define handler {
  method initialize {lchan1 rchan1} {
    my variable lchan
    my variable rchan 
    my variable lbuffer
    set lchan $lchan1
    set rchan $rchan1
    fconfigure $lchan -translation binary -buffering none -blocking 0
    fconfigure $rchan -translation binary -buffering none -blocking 0 
    fileevent  $lchan readable [list [self] local_read ]
    fileevent  $rchan readable [list [self] remote_read ]
    my timer start
    # dumper register [self]
    debug 1 "handler [self] created"
  }
  method finalize {} {
   my variable lchan
   my variable rchan
   my variable lbuffer
    my timer stop
    catch {close $lchan}
    catch {close $rchan}
    # dumper deregister [self]
    after 0 [self] destroy
  }
  method local_read {} {
   my variable lchan
   my variable rchan
   if [eof $lchan] {
    my finalize
    return
   } 
   set data [read $lchan]
   if [catch {puts -nonewline $rchan $data}] {
    my finalize
   }
  }
  method remote_read {} {
   my variable lchan
   my variable rchan
   my variable limit
   if [eof $rchan] {
    my finalize
   } 
   if {$limit > 0} {
      set data [read $rchan $limit]
      incr limit -[string length $data]
      if [eof $lchan] {
        my finalize
        return
      }
      if [catch {puts -nonewline $lchan $data}] {
        my finalize
        return
      } 
   }
  }
  method timer {mode} {
    global period
    my variable timer_id
    switch $mode {
      start {
             my limit_reset
             set timer_id [after $period [list [self] timer start] ]
             }
      stop {
             after cancel $timer_id
            }
    }
  }
  method limit_reset {} {
    my variable limit
    global lbuflength
    set limit $lbuflength
  }
 }
 if {[llength $argv] < 4} {
  puts "usage: args: lport rhost rport bandwidth"
  exit
 }
 set lport [lindex $argv 0]
 set rhost [lindex $argv 1]
 set rport [lindex $argv 2]
 set bwidth [lindex $argv 3]
 set period [lindex $argv 4]
 if {$period eq ""} {
  set period 50
 }

 set lbuflength [expr ($bwidth * $period ) / 1000  ]

 proc accept {sock addr p} {
  global rhost rport
  set conn [socket -async $rhost $rport]
  set obj_name handler_$sock
  handler create  $obj_name 
  $obj_name initialize $sock $conn
 }
 set server [socket -server accept $lport]
 vwait forever