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.
This commit is contained in:
Avery Pennarun 2010-07-15 14:07:01 -04:00
parent 3a25f709e5
commit f1b33dab29
3 changed files with 49 additions and 23 deletions

View File

@ -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,

View File

@ -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')

View File

@ -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)