summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAvery Pennarun <apenwarr@gmail.com>2010-07-15 14:07:01 -0400
committerAvery Pennarun <apenwarr@gmail.com>2010-07-15 14:13:33 -0400
commitf1b33dab29be705096246d826b0b9aecace8a208 (patch)
treeb26a713d9d76b8f380c8f0d290d76d2759292db0
parent3a25f709e5aa318f40476beae549389c29138170 (diff)
Add a --exclude option for excluding subnets from routing.sshuttle-0.31
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.
-rw-r--r--client.py16
-rw-r--r--firewall.py47
-rwxr-xr-xmain.py9
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)