From fdb7c9b9953deead6dcaff5a2aa13a581180fabe Mon Sep 17 00:00:00 2001 From: Roger Date: Tue, 11 Jan 2011 20:52:02 +0800 Subject: [PATCH 1/5] new option to disable fullness checking On high latency links, the PING/PONG round trip triggered by fullness checking could kill the bandwidth. Disabling it could result in >10x bandwidth increase in some setups where the existing latency is already high and the available bandwidth is also high. --- assembler.py | 1 + main.py | 5 +++-- ssh.py | 5 +++-- ssnet.py | 7 ++++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/assembler.py b/assembler.py index c478e37..e65d11e 100644 --- a/assembler.py +++ b/assembler.py @@ -21,6 +21,7 @@ while 1: break verbose = verbosity +no_fullness = no_fullness0 sys.stderr.flush() sys.stdout.flush() main() diff --git a/main.py b/main.py index 66954f8..cab6a2b 100755 --- a/main.py +++ b/main.py @@ -1,6 +1,6 @@ #!/usr/bin/env python import sys, os, re -import helpers, options, client, server, firewall, hostwatch +import helpers, options, client, server, firewall, hostwatch, ssnet import compat.ssubprocess as ssubprocess from helpers import * @@ -51,6 +51,7 @@ sshuttle --server sshuttle --firewall sshuttle --hostwatch -- +f,disable-fullness turn off fullness checking (could 10x bandwidth on high latency link) l,listen= transproxy to this ip address and port number [127.0.0.1:0] H,auto-hosts scan for remote hostnames and update local /etc/hosts N,auto-nets automatically determine subnets to route @@ -73,7 +74,7 @@ o = options.Options('sshuttle', optspec) if opt.daemon: opt.syslog = 1 helpers.verbose = opt.verbose - +ssnet.no_fullness = opt.disable_fullness try: if opt.server: if len(extra) != 0: diff --git a/ssh.py b/ssh.py index ac7f411..9d3a8eb 100644 --- a/ssh.py +++ b/ssh.py @@ -1,6 +1,6 @@ import sys, os, re, socket, zlib import compat.ssubprocess as ssubprocess -import helpers +import helpers, ssnet from helpers import * @@ -63,8 +63,9 @@ def connect(ssh_cmd, rhostport, python, stderr): import sys; skip_imports=1; verbosity=%d; + no_fullness0=%d; exec compile(sys.stdin.read(%d), "assembler.py", "exec") - """ % (helpers.verbose or 0, len(content)) + """ % (helpers.verbose or 0, ssnet.no_fullness or 0, len(content)) pyscript = re.sub(r'\s+', ' ', pyscript.strip()) diff --git a/ssnet.py b/ssnet.py index 62fa378..9cd42a0 100644 --- a/ssnet.py +++ b/ssnet.py @@ -35,7 +35,7 @@ cmd_to_name = { CMD_HOST_LIST: 'HOST_LIST', } - +no_fullness = 0 def _add(l, elem): if not elem in l: @@ -308,7 +308,7 @@ class Mux(Handler): return total def check_fullness(self): - if self.fullness > 32768: + if not no_fullness and self.fullness > 32768: if not self.too_full: self.send(0, CMD_PING, 'rttest') self.too_full = True @@ -326,7 +326,8 @@ class Mux(Handler): debug2(' > channel=%d cmd=%s len=%d (fullness=%d)\n' % (channel, cmd_to_name.get(cmd,hex(cmd)), len(data), self.fullness)) - self.fullness += len(data) + if not no_fullness: + self.fullness += len(data) def got_packet(self, channel, cmd, data): debug2('< channel=%d cmd=%s len=%d\n' From 8fde1155da2444e9614484385a5bc30d6feadd25 Mon Sep 17 00:00:00 2001 From: Avery Pennarun Date: Tue, 25 Jan 2011 21:07:01 -0800 Subject: [PATCH 2/5] Implement the optional fullness checking a bit more like I like it. Looks like it worked before, but personal preference is a killer. The new name is "--no-latency-control". --- assembler.py | 1 - client.py | 15 ++++++++++----- main.py | 8 +++++--- server.py | 4 +++- ssh.py | 19 +++++++++++-------- ssnet.py | 7 +++---- 6 files changed, 32 insertions(+), 22 deletions(-) diff --git a/assembler.py b/assembler.py index e65d11e..c478e37 100644 --- a/assembler.py +++ b/assembler.py @@ -21,7 +21,6 @@ while 1: break verbose = verbosity -no_fullness = no_fullness0 sys.stderr.flush() sys.stdout.flush() main() diff --git a/client.py b/client.py index dbd11de..e584933 100644 --- a/client.py +++ b/client.py @@ -189,7 +189,8 @@ class FirewallClient: raise Fatal('cleanup: %r returned %d' % (self.argv, rv)) -def _main(listener, fw, ssh_cmd, remotename, python, seed_hosts, auto_nets, +def _main(listener, fw, ssh_cmd, remotename, python, latency_control, + seed_hosts, auto_nets, syslog, daemon): handlers = [] if helpers.verbose >= 1: @@ -200,7 +201,8 @@ def _main(listener, fw, ssh_cmd, remotename, python, seed_hosts, auto_nets, try: (serverproc, serversock) = ssh.connect(ssh_cmd, remotename, python, - stderr=ssyslog._p and ssyslog._p.stdin) + stderr=ssyslog._p and ssyslog._p.stdin, + options=dict(latency_control=latency_control)) except socket.error, e: if e.args[0] == errno.EPIPE: raise Fatal("failed to establish ssh session (1)") @@ -300,11 +302,13 @@ def _main(listener, fw, ssh_cmd, remotename, python, seed_hosts, auto_nets, raise Fatal('server died with error code %d' % rv) ssnet.runonce(handlers, mux) + if latency_control: + mux.check_fullness() mux.callback() - mux.check_fullness() -def main(listenip, ssh_cmd, remotename, python, seed_hosts, auto_nets, +def main(listenip, ssh_cmd, remotename, python, latency_control, + seed_hosts, auto_nets, subnets_include, subnets_exclude, syslog, daemon, pidfile): if syslog: ssyslog.start_syslog() @@ -344,7 +348,8 @@ def main(listenip, ssh_cmd, remotename, python, seed_hosts, auto_nets, try: return _main(listener, fw, ssh_cmd, remotename, - python, seed_hosts, auto_nets, syslog, daemon) + python, latency_control, + seed_hosts, auto_nets, syslog, daemon) finally: try: if daemon: diff --git a/main.py b/main.py index cab6a2b..719f86a 100755 --- a/main.py +++ b/main.py @@ -1,6 +1,6 @@ #!/usr/bin/env python import sys, os, re -import helpers, options, client, server, firewall, hostwatch, ssnet +import helpers, options, client, server, firewall, hostwatch import compat.ssubprocess as ssubprocess from helpers import * @@ -51,7 +51,6 @@ sshuttle --server sshuttle --firewall sshuttle --hostwatch -- -f,disable-fullness turn off fullness checking (could 10x bandwidth on high latency link) l,listen= transproxy to this ip address and port number [127.0.0.1:0] H,auto-hosts scan for remote hostnames and update local /etc/hosts N,auto-nets automatically determine subnets to route @@ -61,6 +60,7 @@ x,exclude= exclude this subnet (can be used more than once) v,verbose increase debug message verbosity e,ssh-cmd= the command to use to connect to the remote [ssh] seed-hosts= with -H, use these hostnames for initial scan (comma-separated) +no-latency-control sacrifice latency to improve bandwidth benchmarks D,daemon run in the background as a daemon syslog send log messages to syslog (default if you use --daemon) pidfile= pidfile name (only if using --daemon) [./sshuttle.pid] @@ -74,11 +74,12 @@ o = options.Options('sshuttle', optspec) if opt.daemon: opt.syslog = 1 helpers.verbose = opt.verbose -ssnet.no_fullness = opt.disable_fullness + try: if opt.server: if len(extra) != 0: o.fatal('no arguments expected') + server.latency_control = opt.latency_control sys.exit(server.main()) elif opt.firewall: if len(extra) != 1: @@ -109,6 +110,7 @@ try: opt.ssh_cmd, remotename, opt.python, + opt.latency_control, sh, opt.auto_nets, parse_subnets(includes), diff --git a/server.py b/server.py index 24dd462..ae7a921 100644 --- a/server.py +++ b/server.py @@ -111,6 +111,7 @@ def main(): helpers.logprefix = ' s: ' else: helpers.logprefix = 'server: ' + debug1('latency control setting = %r\n' % latency_control) routes = list(list_routes()) debug1('available routes:\n') @@ -172,5 +173,6 @@ def main(): raise Fatal('hostwatch exited unexpectedly: code 0x%04x\n' % rv) ssnet.runonce(handlers, mux) - mux.check_fullness() + if latency_control: + mux.check_fullness() mux.callback() diff --git a/ssh.py b/ssh.py index 9d3a8eb..9a6270a 100644 --- a/ssh.py +++ b/ssh.py @@ -1,6 +1,6 @@ import sys, os, re, socket, zlib import compat.ssubprocess as ssubprocess -import helpers, ssnet +import helpers from helpers import * @@ -14,14 +14,16 @@ def readfile(name): raise Exception("can't find file %r in any of %r" % (name, path)) -def empackage(z, filename): +def empackage(z, filename, data=None): (path,basename) = os.path.split(filename) - content = z.compress(readfile(filename)) + if not data: + data = readfile(filename) + content = z.compress(data) content += z.flush(zlib.Z_SYNC_FLUSH) - return '%s\n%d\n%s' % (basename,len(content), content) + return '%s\n%d\n%s' % (basename, len(content), content) -def connect(ssh_cmd, rhostport, python, stderr): +def connect(ssh_cmd, rhostport, python, stderr, options): main_exe = sys.argv[0] portl = [] @@ -52,7 +54,9 @@ def connect(ssh_cmd, rhostport, python, stderr): z = zlib.compressobj(1) content = readfile('assembler.py') - content2 = (empackage(z, 'helpers.py') + + optdata = ''.join("%s=%r\n" % (k,v) for (k,v) in options.items()) + content2 = (empackage(z, 'cmdline_options.py', optdata) + + empackage(z, 'helpers.py') + empackage(z, 'compat/ssubprocess.py') + empackage(z, 'ssnet.py') + empackage(z, 'hostwatch.py') + @@ -63,9 +67,8 @@ def connect(ssh_cmd, rhostport, python, stderr): import sys; skip_imports=1; verbosity=%d; - no_fullness0=%d; exec compile(sys.stdin.read(%d), "assembler.py", "exec") - """ % (helpers.verbose or 0, ssnet.no_fullness or 0, len(content)) + """ % (helpers.verbose or 0, len(content)) pyscript = re.sub(r'\s+', ' ', pyscript.strip()) diff --git a/ssnet.py b/ssnet.py index 9cd42a0..62fa378 100644 --- a/ssnet.py +++ b/ssnet.py @@ -35,7 +35,7 @@ cmd_to_name = { CMD_HOST_LIST: 'HOST_LIST', } -no_fullness = 0 + def _add(l, elem): if not elem in l: @@ -308,7 +308,7 @@ class Mux(Handler): return total def check_fullness(self): - if not no_fullness and self.fullness > 32768: + if self.fullness > 32768: if not self.too_full: self.send(0, CMD_PING, 'rttest') self.too_full = True @@ -326,8 +326,7 @@ class Mux(Handler): debug2(' > channel=%d cmd=%s len=%d (fullness=%d)\n' % (channel, cmd_to_name.get(cmd,hex(cmd)), len(data), self.fullness)) - if not no_fullness: - self.fullness += len(data) + self.fullness += len(data) def got_packet(self, channel, cmd, data): debug2('< channel=%d cmd=%s len=%d\n' From 9877a8d6fbc0907a5c842a043e3ba88a849e4491 Mon Sep 17 00:00:00 2001 From: Avery Pennarun Date: Tue, 25 Jan 2011 21:14:35 -0800 Subject: [PATCH 3/5] options.py: generate usage string correctly for no-* options. Signed-off-by: Avery Pennarun --- options.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/options.py b/options.py index 25322fb..20a4e28 100644 --- a/options.py +++ b/options.py @@ -122,8 +122,8 @@ class Options: defval = None flagl = flags.split(',') flagl_nice = [] - for f in flagl: - f,dvi = _remove_negative_kv(f, _intify(defval)) + for _f in flagl: + f,dvi = _remove_negative_kv(_f, _intify(defval)) self._aliases[f] = _remove_negative_k(flagl[0]) self._hasparms[f] = has_parm self._defaults[f] = dvi @@ -135,7 +135,7 @@ class Options: self._aliases[f_nice] = _remove_negative_k(flagl[0]) self._longopts.append(f + (has_parm and '=' or '')) self._longopts.append('no-' + f) - flagl_nice.append('--' + f) + flagl_nice.append('--' + _f) flags_nice = ', '.join(flagl_nice) if has_parm: flags_nice += ' ...' From a30c4d7ccb3c731cce24eebc133bc68690420853 Mon Sep 17 00:00:00 2001 From: Avery Pennarun Date: Tue, 25 Jan 2011 21:19:11 -0800 Subject: [PATCH 4/5] options: remove unused 'exe' parameter The 'exe' parameter was added in the hope of using it for additional contextual information in the help text that Options generates. It was till then abandoned and was judged as superflous information. Remove the 'exe' parameter from Options' constructor. (copied from the 'bup' project) Signed-off-by: Gabriel Filion --- main.py | 2 +- options.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index 719f86a..5597177 100755 --- a/main.py +++ b/main.py @@ -68,7 +68,7 @@ server (internal use only) firewall (internal use only) hostwatch (internal use only) """ -o = options.Options('sshuttle', optspec) +o = options.Options(optspec) (opt, flags, extra) = o.parse(sys.argv[1:]) if opt.daemon: diff --git a/options.py b/options.py index 20a4e28..2f3fff6 100644 --- a/options.py +++ b/options.py @@ -76,9 +76,8 @@ class Options: By default, the parser function is getopt.gnu_getopt, and the abort behaviour is to exit the program. """ - def __init__(self, exe, optspec, optfunc=getopt.gnu_getopt, + def __init__(self, optspec, optfunc=getopt.gnu_getopt, onabort=_default_onabort): - self.exe = exe self.optspec = optspec self._onabort = onabort self.optfunc = optfunc From d9b1bb52e5e994f5d3c9c3a77efea5e81910b6ea Mon Sep 17 00:00:00 2001 From: Avery Pennarun Date: Tue, 25 Jan 2011 21:30:29 -0800 Subject: [PATCH 5/5] man page for the --no-latency-control option. --- sshuttle.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/sshuttle.md b/sshuttle.md index c52c6cd..6168740 100644 --- a/sshuttle.md +++ b/sshuttle.md @@ -1,6 +1,6 @@ -% sshuttle(8) Sshuttle 0.44 +% sshuttle(8) Sshuttle 0.46 % Avery Pennarun -% 2010-12-31 +% 2011-01-25 # NAME @@ -109,6 +109,22 @@ entire subnet to the VPN. if you use this option to give it a few names to start from. +--no-latency-control +: sacrifice latency to improve bandwidth benchmarks. ssh + uses really big socket buffers, which can overload the + connection if you start doing large file transfers, + thus making all your other sessions inside the same + tunnel go slowly. Normally, sshuttle tries to avoid + this problem using a "fullness check" that allows only + a certain amount of outstanding data to be buffered at + a time. But on high-bandwidth links, this can leave a + lot of your bandwidth underutilized. It also makes + sshuttle seem slow in bandwidth benchmarks (benchmarks + rarely test ping latency, which is what sshuttle is + trying to control). This option disables the latency + control feature, maximizing bandwidth usage. Use at + your own risk. + -D, --daemon : automatically fork into the background after connecting to the remote server. Implies `--syslog`.