From f1b33dab29be705096246d826b0b9aecace8a208 Mon Sep 17 00:00:00 2001 From: Avery Pennarun Date: Thu, 15 Jul 2010 14:07:01 -0400 Subject: Add a --exclude option for excluding subnets from routing. Also, add 127.0.0.0/8 to the default list of excludes. If you want to route 0/0, you almost certainly *don't* want to route localhost to the remote ssh server's localhost! Thanks to Edward for the suggestion. --- client.py | 16 ++++++++++------ firewall.py | 47 +++++++++++++++++++++++++++++++---------------- main.py | 9 ++++++++- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/client.py b/client.py index 3afac58..b6d32f9 100644 --- a/client.py +++ b/client.py @@ -20,10 +20,11 @@ def original_dst(sock): class FirewallClient: - def __init__(self, port, subnets): + def __init__(self, port, subnets_include, subnets_exclude): self.port = port self.auto_nets = [] - self.subnets = subnets + self.subnets_include = subnets_include + self.subnets_exclude = subnets_exclude argvbase = ([sys.argv[0]] + ['-v'] * (helpers.verbose or 0) + ['--firewall', str(port)]) @@ -67,8 +68,10 @@ class FirewallClient: def start(self): self.pfile.write('ROUTES\n') - for (ip,width) in self.subnets+self.auto_nets: - self.pfile.write('%s,%d\n' % (ip, width)) + for (ip,width) in self.subnets_include+self.auto_nets: + self.pfile.write('%d,0,%s\n' % (width, ip)) + for (ip,width) in self.subnets_exclude: + self.pfile.write('%d,1,%s\n' % (width, ip)) self.pfile.write('GO\n') self.pfile.flush() line = self.pfile.readline() @@ -185,7 +188,8 @@ def _main(listener, fw, use_server, remotename, seed_hosts, auto_nets): mux.check_fullness() -def main(listenip, use_server, remotename, seed_hosts, auto_nets, subnets): +def main(listenip, use_server, remotename, seed_hosts, auto_nets, + subnets_include, subnets_exclude): debug1('Starting sshuttle proxy.\n') listener = socket.socket() listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -212,7 +216,7 @@ def main(listenip, use_server, remotename, seed_hosts, auto_nets, subnets): listenip = listener.getsockname() debug1('Listening on %r.\n' % (listenip,)) - fw = FirewallClient(listenip[1], subnets) + fw = FirewallClient(listenip[1], subnets_include, subnets_exclude) try: return _main(listener, fw, use_server, remotename, diff --git a/firewall.py b/firewall.py index 3444c7c..6025c64 100644 --- a/firewall.py +++ b/firewall.py @@ -43,14 +43,23 @@ def do_iptables(port, subnets): ipt('-I', 'OUTPUT', '1', '-j', chain) ipt('-I', 'PREROUTING', '1', '-j', chain) - # create new subnet entries - for snet,swidth in subnets: - ipt('-A', chain, '-j', 'REDIRECT', - '--dest', '%s/%s' % (snet,swidth), - '-p', 'tcp', - '--to-ports', str(port), - '-m', 'ttl', '!', '--ttl', '42' # to prevent infinite loops - ) + # create new subnet entries. Note that we're sorting in a very + # particular order: we need to go from most-specific (largest swidth) + # to least-specific, and at any given level of specificity, we want + # excludes to come first. That's why the columns are in such a non- + # intuitive order. + for swidth,sexclude,snet in sorted(subnets, reverse=True): + if sexclude: + ipt('-A', chain, '-j', 'RETURN', + '--dest', '%s/%s' % (snet,swidth), + '-p', 'tcp') + else: + ipt('-A', chain, '-j', 'REDIRECT', + '--dest', '%s/%s' % (snet,swidth), + '-p', 'tcp', + '--to-ports', str(port), + '-m', 'ttl', '!', '--ttl', '42' # to prevent infinite loops + ) def ipfw_rule_exists(n): @@ -103,6 +112,7 @@ def ipfw(*args): def do_ipfw(port, subnets): sport = str(port) + xsport = str(port+1) # cleanup any existing rules if ipfw_rule_exists(port): @@ -120,11 +130,16 @@ def do_ipfw(port, subnets): 'from', 'any', 'to', 'any', 'established') # create new subnet entries - for snet,swidth in subnets: - ipfw('add', sport, 'fwd', '127.0.0.1,%d' % port, - 'log', 'tcp', - 'from', 'any', 'to', '%s/%s' % (snet,swidth), - 'not', 'ipttl', '42') + for swidth,sexclude,snet in sorted(subnets, reverse=True): + if sexclude: + ipfw('add', sport, 'skipto', xsport, + 'log', 'tcp', + 'from', 'any', 'to', '%s/%s' % (snet,swidth)) + else: + ipfw('add', sport, 'fwd', '127.0.0.1,%d' % port, + 'log', 'tcp', + 'from', 'any', 'to', '%s/%s' % (snet,swidth), + 'not', 'ipttl', '42') def program_exists(name): @@ -228,10 +243,11 @@ def main(port): elif line == 'GO\n': break try: - (ip,width) = line.strip().split(',', 1) + (width,exclude,ip) = line.strip().split(',', 2) except: raise Fatal('firewall: expected route or GO but got %r' % line) - subnets.append((ip, int(width))) + subnets.append((int(width), bool(int(exclude)), ip)) + try: if line: debug1('firewall manager: starting transproxy.\n') @@ -258,7 +274,6 @@ def main(port): raise Fatal('expected EOF, got %r' % line) else: break - finally: try: debug1('firewall manager: undoing changes.\n') diff --git a/main.py b/main.py index 048f8ea..d92d3bd 100755 --- a/main.py +++ b/main.py @@ -53,6 +53,7 @@ l,listen= transproxy to this ip address and port number [default=0] H,auto-hosts scan for remote hostnames and update local /etc/hosts N,auto-nets automatically determine subnets to route r,remote= ssh hostname (and optional username) of remote sshuttle server +x,exclude= exclude this subnet (can be used more than once) v,verbose increase debug message verbosity seed-hosts= with -H, use these hostnames for initial scan (comma-separated) noserver don't use a separate server process (mostly for debugging) @@ -79,6 +80,11 @@ try: else: if len(extra) < 1 and not opt.auto_nets: o.fatal('at least one subnet (or -N) expected') + includes = extra + excludes = ['127.0.0.0/8'] + for k,v in flags: + if k in ('-x','--exclude'): + excludes.append(v) remotename = opt.remote if remotename == '' or remotename == '-': remotename = None @@ -95,7 +101,8 @@ try: remotename, sh, opt.auto_nets, - parse_subnets(extra))) + parse_subnets(includes), + parse_subnets(excludes))) except Fatal, e: log('fatal: %s\n' % e) sys.exit(99) -- cgit v1.2.3