mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-04-27 04:39:45 +02:00
iptables: more resilient startup/cleanup.
Now the sudo iptables subprocess persists for the entire life of sshuttle. The benefits of this are: - no need to authenticate again at shutdown (failure of which could cause us to not clean up iptables) - if the main process dies unexpectedly, iptables still gets cleaned up - the password prompt can happen *before* starting the ssh/server process, which means it'll stand out and the password prompt won't be overwritten.
This commit is contained in:
parent
ca14231aae
commit
a21e8c7a3c
49
client.py
49
client.py
@ -13,17 +13,40 @@ def original_dst(sock):
|
|||||||
return (ip,port)
|
return (ip,port)
|
||||||
|
|
||||||
|
|
||||||
def iptables_setup(port, subnets):
|
class IPTables:
|
||||||
subnets_str = ['%s/%d' % (ip,width) for ip,width in subnets]
|
def __init__(self, port, subnets):
|
||||||
argv = (['sudo', sys.argv[0]] +
|
self.port = port
|
||||||
['-v'] * (helpers.verbose or 0) +
|
self.subnets = subnets
|
||||||
['--iptables', str(port)] + subnets_str)
|
subnets_str = ['%s/%d' % (ip,width) for ip,width in subnets]
|
||||||
rv = subprocess.call(argv)
|
self.argv = (['sudo', sys.argv[0]] +
|
||||||
if rv != 0:
|
['-v'] * (helpers.verbose or 0) +
|
||||||
raise Fatal('%r returned %d' % (argv, rv))
|
['--iptables', str(port)] + subnets_str)
|
||||||
|
self.p = subprocess.Popen(self.argv,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
line = self.p.stdout.readline()
|
||||||
|
self.check()
|
||||||
|
if line != 'READY\n':
|
||||||
|
raise Fatal('%r expected READY, got %r' % (self.argv, line))
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
rv = self.p.poll()
|
||||||
|
if rv:
|
||||||
|
raise Fatal('%r returned %d' % (self.argv, rv))
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.p.stdin.write('GO\n')
|
||||||
|
self.p.stdin.flush()
|
||||||
|
|
||||||
|
def done(self):
|
||||||
|
self.p.stdin.close()
|
||||||
|
self.p.stdout.close()
|
||||||
|
rv = self.p.wait()
|
||||||
|
if rv:
|
||||||
|
raise Fatal('cleanup: %r returned %d' % (self.argv, rv))
|
||||||
|
|
||||||
|
|
||||||
def _main(listener, listenport, use_server, remotename, subnets):
|
def _main(listener, ipt, use_server, remotename):
|
||||||
handlers = []
|
handlers = []
|
||||||
if use_server:
|
if use_server:
|
||||||
if helpers.verbose >= 1:
|
if helpers.verbose >= 1:
|
||||||
@ -47,7 +70,7 @@ def _main(listener, listenport, use_server, remotename, subnets):
|
|||||||
|
|
||||||
# we definitely want to do this *after* starting ssh, or we might end
|
# we definitely want to do this *after* starting ssh, or we might end
|
||||||
# up intercepting the ssh connection!
|
# up intercepting the ssh connection!
|
||||||
iptables_setup(listenport, subnets)
|
ipt.start()
|
||||||
|
|
||||||
def onaccept():
|
def onaccept():
|
||||||
sock,srcip = listener.accept()
|
sock,srcip = listener.accept()
|
||||||
@ -118,7 +141,9 @@ def main(listenip, use_server, remotename, subnets):
|
|||||||
listenip = listener.getsockname()
|
listenip = listener.getsockname()
|
||||||
debug1('Listening on %r.\n' % (listenip,))
|
debug1('Listening on %r.\n' % (listenip,))
|
||||||
|
|
||||||
|
ipt = IPTables(listenip[1], subnets)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return _main(listener, listenip[1], use_server, remotename, subnets)
|
return _main(listener, ipt, use_server, remotename)
|
||||||
finally:
|
finally:
|
||||||
iptables_setup(listenip[1], [])
|
ipt.done()
|
||||||
|
62
iptables.py
62
iptables.py
@ -22,24 +22,7 @@ def ipt(*args):
|
|||||||
raise Exception('%r returned %d' % (argv, rv))
|
raise Exception('%r returned %d' % (argv, rv))
|
||||||
|
|
||||||
|
|
||||||
# This is some iptables voodoo for setting up the Linux kernel's transparent
|
def do_it(port, subnets):
|
||||||
# proxying stuff. If subnets is empty, we just delete our sshuttle chain;
|
|
||||||
# otherwise we delete it, then make it from scratch.
|
|
||||||
#
|
|
||||||
# We name the chain based on the transproxy port number so that it's possible
|
|
||||||
# to run multiple copies of sshuttle at the same time. Of course, the
|
|
||||||
# multiple copies shouldn't have overlapping subnets, or only the most-
|
|
||||||
# recently-started one will win (because we use "-I OUTPUT 1" instead of
|
|
||||||
# "-A OUTPUT").
|
|
||||||
#
|
|
||||||
# sshuttle is supposed to clean up after itself by deleting extra chains on
|
|
||||||
# exit. In case that fails, it's not the end of the world; future runs will
|
|
||||||
# supercede it in the transproxy list, at least, so the leftover iptables
|
|
||||||
# chains are mostly harmless.
|
|
||||||
def main(port, subnets):
|
|
||||||
assert(port > 0)
|
|
||||||
assert(port <= 65535)
|
|
||||||
|
|
||||||
chain = 'sshuttle-%s' % port
|
chain = 'sshuttle-%s' % port
|
||||||
|
|
||||||
# basic cleanup/setup of chains
|
# basic cleanup/setup of chains
|
||||||
@ -63,4 +46,45 @@ def main(port, subnets):
|
|||||||
'--to-ports', str(port),
|
'--to-ports', str(port),
|
||||||
'-m', 'ttl', '!', '--ttl', '42' # to prevent infinite loops
|
'-m', 'ttl', '!', '--ttl', '42' # to prevent infinite loops
|
||||||
)
|
)
|
||||||
subnets_str = ['%s/%d' % (ip,width) for ip,width in subnets]
|
|
||||||
|
|
||||||
|
# This is some iptables voodoo for setting up the Linux kernel's transparent
|
||||||
|
# proxying stuff. If subnets is empty, we just delete our sshuttle chain;
|
||||||
|
# otherwise we delete it, then make it from scratch.
|
||||||
|
#
|
||||||
|
# We name the chain based on the transproxy port number so that it's possible
|
||||||
|
# to run multiple copies of sshuttle at the same time. Of course, the
|
||||||
|
# multiple copies shouldn't have overlapping subnets, or only the most-
|
||||||
|
# recently-started one will win (because we use "-I OUTPUT 1" instead of
|
||||||
|
# "-A OUTPUT").
|
||||||
|
#
|
||||||
|
# This code is supposed to clean up after itself by deleting extra chains on
|
||||||
|
# exit. In case that fails, it's not the end of the world; future runs will
|
||||||
|
# supercede it in the transproxy list, at least, so the leftover iptables
|
||||||
|
# chains are mostly harmless.
|
||||||
|
def main(port, subnets):
|
||||||
|
assert(port > 0)
|
||||||
|
assert(port <= 65535)
|
||||||
|
|
||||||
|
debug1('iptables manager ready.\n')
|
||||||
|
sys.stdout.write('READY\n')
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
# ctrl-c shouldn't be passed along to me. When the main sshuttle dies,
|
||||||
|
# I'll die automatically.
|
||||||
|
os.setsid()
|
||||||
|
|
||||||
|
# we wait until we get some input before creating the rules. That way,
|
||||||
|
# sshuttle can launch us as early as possible (and get sudo password
|
||||||
|
# authentication as early in the startup process as possible).
|
||||||
|
sys.stdin.readline()
|
||||||
|
try:
|
||||||
|
do_it(port, subnets)
|
||||||
|
|
||||||
|
# Now we wait until EOF or any other kind of exception. We need
|
||||||
|
# to stay running so that we don't need a *second* password
|
||||||
|
# authentication at shutdown time - that cleanup is important!
|
||||||
|
while sys.stdin.readline():
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
do_it(port, [])
|
||||||
|
3
main.py
3
main.py
@ -83,5 +83,6 @@ except Fatal, e:
|
|||||||
log('fatal: %s\n' % e)
|
log('fatal: %s\n' % e)
|
||||||
sys.exit(99)
|
sys.exit(99)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
log('\nKeyboard interrupt: exiting.\n')
|
log('\n')
|
||||||
|
log('Keyboard interrupt: exiting.\n')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
3
ssh.py
3
ssh.py
@ -32,7 +32,8 @@ def connect(rhost):
|
|||||||
os.setsid()
|
os.setsid()
|
||||||
s1a,s1b = os.dup(s1.fileno()), os.dup(s1.fileno())
|
s1a,s1b = os.dup(s1.fileno()), os.dup(s1.fileno())
|
||||||
s1.close()
|
s1.close()
|
||||||
p = subprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup)
|
p = subprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup,
|
||||||
|
close_fds=True)
|
||||||
os.close(s1a)
|
os.close(s1a)
|
||||||
os.close(s1b)
|
os.close(s1b)
|
||||||
return p, s2
|
return p, s2
|
||||||
|
Loading…
Reference in New Issue
Block a user