mirror of
https://github.com/sshuttle/sshuttle.git
synced 2025-07-04 08:40:30 +02:00
Compare commits
27 Commits
sshuttle-0
...
sshuttle-0
Author | SHA1 | Date | |
---|---|---|---|
41fd0348eb | |||
1907048dad | |||
82e1d1c166 | |||
a497132c01 | |||
7354600849 | |||
918725c485 | |||
95c9b788a0 | |||
ef71751846 | |||
32b4defa9b | |||
8b7605cc5d | |||
bcf1892305 | |||
fe742c928d | |||
10ce1ee5d4 | |||
a32305a275 | |||
ae32fe2a59 | |||
5070f2ffcf | |||
b219b523c2 | |||
52fbb2ebbe | |||
76d576a375 | |||
f6e6515a3c | |||
84376284db | |||
b0f061e204 | |||
c403a83ab8 | |||
da774f3f46 | |||
7d3028dee2 | |||
518df41049 | |||
76bbbfd67b |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
*~
|
*~
|
||||||
|
*.8
|
||||||
|
19
Makefile
Normal file
19
Makefile
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
PANDOC:=$(shell \
|
||||||
|
if pandoc </dev/null 2>/dev/null; then \
|
||||||
|
echo pandoc; \
|
||||||
|
else \
|
||||||
|
echo "Warning: pandoc not installed; can't generate manpages." >&2; \
|
||||||
|
echo '@echo Skipping: pandoc'; \
|
||||||
|
fi)
|
||||||
|
|
||||||
|
default: all
|
||||||
|
|
||||||
|
all: sshuttle.8
|
||||||
|
|
||||||
|
sshuttle.8: sshuttle.md
|
||||||
|
|
||||||
|
%.8: %.md
|
||||||
|
$(PANDOC) -s -r markdown -w man -o $@ $<
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *~ */*~ .*~ */.*~ *.8 *.tmp */*.tmp *.pyc */*.pyc
|
@ -54,8 +54,14 @@ This is how you use it:
|
|||||||
|
|
||||||
- <tt>./sshuttle -r username@sshserver 0.0.0.0/0 -vv</tt>
|
- <tt>./sshuttle -r username@sshserver 0.0.0.0/0 -vv</tt>
|
||||||
|
|
||||||
|
(You may be prompted for one or more passwords; first, the
|
||||||
|
local password to become root using either sudo or su, and
|
||||||
|
then the remote ssh password. Or you might have sudo and ssh set
|
||||||
|
up to not require passwords, in which case you won't be
|
||||||
|
prompted at all.)
|
||||||
|
|
||||||
That's it! Now your local machine can access the remote network as if you
|
That's it! Now your local machine can access the remote network as if you
|
||||||
were right there! And if your "client" machine is a router, everyone on
|
were right there. And if your "client" machine is a router, everyone on
|
||||||
your local network can make connections to your remote network.
|
your local network can make connections to your remote network.
|
||||||
|
|
||||||
You don't need to install sshuttle on the remote server;
|
You don't need to install sshuttle on the remote server;
|
||||||
|
47
client.py
47
client.py
@ -1,8 +1,10 @@
|
|||||||
import struct, socket, select, subprocess, errno, re
|
import struct, socket, select, errno, re
|
||||||
|
import compat.ssubprocess as ssubprocess
|
||||||
import helpers, ssnet, ssh
|
import helpers, ssnet, ssh
|
||||||
from ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
from ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
|
|
||||||
def original_dst(sock):
|
def original_dst(sock):
|
||||||
try:
|
try:
|
||||||
SO_ORIGINAL_DST = 80
|
SO_ORIGINAL_DST = 80
|
||||||
@ -29,7 +31,7 @@ class FirewallClient:
|
|||||||
['-v'] * (helpers.verbose or 0) +
|
['-v'] * (helpers.verbose or 0) +
|
||||||
['--firewall', str(port)])
|
['--firewall', str(port)])
|
||||||
argv_tries = [
|
argv_tries = [
|
||||||
['sudo'] + argvbase,
|
['sudo', '-p', '[local sudo] Password: '] + argvbase,
|
||||||
['su', '-c', ' '.join(argvbase)],
|
['su', '-c', ' '.join(argvbase)],
|
||||||
argvbase
|
argvbase
|
||||||
]
|
]
|
||||||
@ -43,9 +45,13 @@ class FirewallClient:
|
|||||||
# run in the child process
|
# run in the child process
|
||||||
s2.close()
|
s2.close()
|
||||||
e = None
|
e = None
|
||||||
|
if os.getuid() == 0:
|
||||||
|
argv_tries = argv_tries[-1:] # last entry only
|
||||||
for argv in argv_tries:
|
for argv in argv_tries:
|
||||||
try:
|
try:
|
||||||
self.p = subprocess.Popen(argv, stdout=s1, preexec_fn=setup)
|
if argv[0] == 'su':
|
||||||
|
sys.stderr.write('[local su] ')
|
||||||
|
self.p = ssubprocess.Popen(argv, stdout=s1, preexec_fn=setup)
|
||||||
e = None
|
e = None
|
||||||
break
|
break
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
@ -92,15 +98,20 @@ class FirewallClient:
|
|||||||
raise Fatal('cleanup: %r returned %d' % (self.argv, rv))
|
raise Fatal('cleanup: %r returned %d' % (self.argv, rv))
|
||||||
|
|
||||||
|
|
||||||
def _main(listener, fw, use_server, remotename, python, seed_hosts, auto_nets):
|
def _main(listener, fw, ssh_cmd, remotename, python, seed_hosts, auto_nets):
|
||||||
handlers = []
|
handlers = []
|
||||||
if use_server:
|
|
||||||
if helpers.verbose >= 1:
|
if helpers.verbose >= 1:
|
||||||
helpers.logprefix = 'c : '
|
helpers.logprefix = 'c : '
|
||||||
else:
|
else:
|
||||||
helpers.logprefix = 'client: '
|
helpers.logprefix = 'client: '
|
||||||
debug1('connecting to server...\n')
|
debug1('connecting to server...\n')
|
||||||
(serverproc, serversock) = ssh.connect(remotename, python)
|
try:
|
||||||
|
(serverproc, serversock) = ssh.connect(ssh_cmd, remotename, python)
|
||||||
|
except socket.error, e:
|
||||||
|
if e.errno == errno.EPIPE:
|
||||||
|
raise Fatal("failed to establish ssh session")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
mux = Mux(serversock, serversock)
|
mux = Mux(serversock, serversock)
|
||||||
handlers.append(mux)
|
handlers.append(mux)
|
||||||
|
|
||||||
@ -150,12 +161,9 @@ def _main(listener, fw, use_server, remotename, python, seed_hosts, auto_nets):
|
|||||||
debug1("-- ignored: that's my address!\n")
|
debug1("-- ignored: that's my address!\n")
|
||||||
sock.close()
|
sock.close()
|
||||||
return
|
return
|
||||||
if use_server:
|
|
||||||
chan = mux.next_channel()
|
chan = mux.next_channel()
|
||||||
mux.send(chan, ssnet.CMD_CONNECT, '%s,%s' % dstip)
|
mux.send(chan, ssnet.CMD_CONNECT, '%s,%s' % dstip)
|
||||||
outwrap = MuxWrapper(mux, chan)
|
outwrap = MuxWrapper(mux, chan)
|
||||||
else:
|
|
||||||
outwrap = ssnet.connect_dst(dstip[0], dstip[1])
|
|
||||||
handlers.append(Proxy(SockWrapper(sock, sock), outwrap))
|
handlers.append(Proxy(SockWrapper(sock, sock), outwrap))
|
||||||
handlers.append(Handler([listener], onaccept))
|
handlers.append(Handler([listener], onaccept))
|
||||||
|
|
||||||
@ -164,31 +172,16 @@ def _main(listener, fw, use_server, remotename, python, seed_hosts, auto_nets):
|
|||||||
mux.send(0, ssnet.CMD_HOST_REQ, '\n'.join(seed_hosts))
|
mux.send(0, ssnet.CMD_HOST_REQ, '\n'.join(seed_hosts))
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
if use_server:
|
|
||||||
rv = serverproc.poll()
|
rv = serverproc.poll()
|
||||||
if rv:
|
if rv:
|
||||||
raise Fatal('server died with error code %d' % rv)
|
raise Fatal('server died with error code %d' % rv)
|
||||||
|
|
||||||
r = set()
|
ssnet.runonce(handlers, mux)
|
||||||
w = set()
|
|
||||||
x = set()
|
|
||||||
handlers = filter(lambda s: s.ok, handlers)
|
|
||||||
for s in handlers:
|
|
||||||
s.pre_select(r,w,x)
|
|
||||||
debug2('Waiting: %d[%d,%d,%d]...\n'
|
|
||||||
% (len(handlers), len(r), len(w), len(x)))
|
|
||||||
(r,w,x) = select.select(r,w,x)
|
|
||||||
#log('r=%r w=%r x=%r\n' % (r,w,x))
|
|
||||||
ready = set(r) | set(w) | set(x)
|
|
||||||
for s in handlers:
|
|
||||||
if s.socks & ready:
|
|
||||||
s.callback()
|
|
||||||
if use_server:
|
|
||||||
mux.callback()
|
mux.callback()
|
||||||
mux.check_fullness()
|
mux.check_fullness()
|
||||||
|
|
||||||
|
|
||||||
def main(listenip, use_server, remotename, python, seed_hosts, auto_nets,
|
def main(listenip, ssh_cmd, remotename, python, seed_hosts, auto_nets,
|
||||||
subnets_include, subnets_exclude):
|
subnets_include, subnets_exclude):
|
||||||
debug1('Starting sshuttle proxy.\n')
|
debug1('Starting sshuttle proxy.\n')
|
||||||
listener = socket.socket()
|
listener = socket.socket()
|
||||||
@ -219,7 +212,7 @@ def main(listenip, use_server, remotename, python, seed_hosts, auto_nets,
|
|||||||
fw = FirewallClient(listenip[1], subnets_include, subnets_exclude)
|
fw = FirewallClient(listenip[1], subnets_include, subnets_exclude)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return _main(listener, fw, use_server, remotename,
|
return _main(listener, fw, ssh_cmd, remotename,
|
||||||
python, seed_hosts, auto_nets)
|
python, seed_hosts, auto_nets)
|
||||||
finally:
|
finally:
|
||||||
fw.done()
|
fw.done()
|
||||||
|
0
compat/__init__.py
Normal file
0
compat/__init__.py
Normal file
1305
compat/ssubprocess.py
Normal file
1305
compat/ssubprocess.py
Normal file
File diff suppressed because it is too large
Load Diff
50
firewall.py
50
firewall.py
@ -1,11 +1,12 @@
|
|||||||
import subprocess, re, errno
|
import re, errno
|
||||||
|
import compat.ssubprocess as ssubprocess
|
||||||
import helpers
|
import helpers
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
|
|
||||||
def ipt_chain_exists(name):
|
def ipt_chain_exists(name):
|
||||||
argv = ['iptables', '-t', 'nat', '-nL']
|
argv = ['iptables', '-t', 'nat', '-nL']
|
||||||
p = subprocess.Popen(argv, stdout = subprocess.PIPE)
|
p = ssubprocess.Popen(argv, stdout = ssubprocess.PIPE)
|
||||||
for line in p.stdout:
|
for line in p.stdout:
|
||||||
if line.startswith('Chain %s ' % name):
|
if line.startswith('Chain %s ' % name):
|
||||||
return True
|
return True
|
||||||
@ -17,7 +18,7 @@ def ipt_chain_exists(name):
|
|||||||
def ipt(*args):
|
def ipt(*args):
|
||||||
argv = ['iptables', '-t', 'nat'] + list(args)
|
argv = ['iptables', '-t', 'nat'] + list(args)
|
||||||
debug1('>> %s\n' % ' '.join(argv))
|
debug1('>> %s\n' % ' '.join(argv))
|
||||||
rv = subprocess.call(argv)
|
rv = ssubprocess.call(argv)
|
||||||
if rv:
|
if rv:
|
||||||
raise Fatal('%r returned %d' % (argv, rv))
|
raise Fatal('%r returned %d' % (argv, rv))
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ def do_iptables(port, subnets):
|
|||||||
|
|
||||||
def ipfw_rule_exists(n):
|
def ipfw_rule_exists(n):
|
||||||
argv = ['ipfw', 'list']
|
argv = ['ipfw', 'list']
|
||||||
p = subprocess.Popen(argv, stdout = subprocess.PIPE)
|
p = ssubprocess.Popen(argv, stdout = ssubprocess.PIPE)
|
||||||
found = False
|
found = False
|
||||||
for line in p.stdout:
|
for line in p.stdout:
|
||||||
if line.startswith('%05d ' % n):
|
if line.startswith('%05d ' % n):
|
||||||
@ -80,37 +81,47 @@ def ipfw_rule_exists(n):
|
|||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
||||||
def sysctl_get(name):
|
_oldctls = {}
|
||||||
argv = ['sysctl', '-n', name]
|
def _fill_oldctls(prefix):
|
||||||
p = subprocess.Popen(argv, stdout = subprocess.PIPE)
|
argv = ['sysctl', prefix]
|
||||||
line = p.stdout.readline()
|
p = ssubprocess.Popen(argv, stdout = ssubprocess.PIPE)
|
||||||
|
for line in p.stdout:
|
||||||
|
assert(line[-1] == '\n')
|
||||||
|
(k,v) = line[:-1].split(': ', 1)
|
||||||
|
_oldctls[k] = v
|
||||||
rv = p.wait()
|
rv = p.wait()
|
||||||
if rv:
|
if rv:
|
||||||
raise Fatal('%r returned %d' % (argv, rv))
|
raise Fatal('%r returned %d' % (argv, rv))
|
||||||
if not line:
|
if not line:
|
||||||
raise Fatal('%r returned no data' % (argv,))
|
raise Fatal('%r returned no data' % (argv,))
|
||||||
assert(line[-1] == '\n')
|
|
||||||
return line[:-1]
|
|
||||||
|
|
||||||
|
|
||||||
def _sysctl_set(name, val):
|
def _sysctl_set(name, val):
|
||||||
argv = ['sysctl', '-w', '%s=%s' % (name, val)]
|
argv = ['sysctl', '-w', '%s=%s' % (name, val)]
|
||||||
debug1('>> %s\n' % ' '.join(argv))
|
debug1('>> %s\n' % ' '.join(argv))
|
||||||
rv = subprocess.call(argv, stdout = open('/dev/null', 'w'))
|
rv = ssubprocess.call(argv, stdout = open('/dev/null', 'w'))
|
||||||
|
|
||||||
|
|
||||||
_oldctls = []
|
_changedctls = []
|
||||||
def sysctl_set(name, val):
|
def sysctl_set(name, val):
|
||||||
oldval = sysctl_get(name)
|
PREFIX = 'net.inet.ip'
|
||||||
if str(val) != str(oldval):
|
assert(name.startswith(PREFIX + '.'))
|
||||||
_oldctls.append((name, oldval))
|
val = str(val)
|
||||||
|
if not _oldctls:
|
||||||
|
_fill_oldctls(PREFIX)
|
||||||
|
if not (name in _oldctls):
|
||||||
|
debug1('>> No such sysctl: %r\n' % name)
|
||||||
|
return
|
||||||
|
oldval = _oldctls[name]
|
||||||
|
if val != oldval:
|
||||||
|
_changedctls.append(name)
|
||||||
return _sysctl_set(name, val)
|
return _sysctl_set(name, val)
|
||||||
|
|
||||||
|
|
||||||
def ipfw(*args):
|
def ipfw(*args):
|
||||||
argv = ['ipfw', '-q'] + list(args)
|
argv = ['ipfw', '-q'] + list(args)
|
||||||
debug1('>> %s\n' % ' '.join(argv))
|
debug1('>> %s\n' % ' '.join(argv))
|
||||||
rv = subprocess.call(argv)
|
rv = ssubprocess.call(argv)
|
||||||
if rv:
|
if rv:
|
||||||
raise Fatal('%r returned %d' % (argv, rv))
|
raise Fatal('%r returned %d' % (argv, rv))
|
||||||
|
|
||||||
@ -121,10 +132,11 @@ def do_ipfw(port, subnets):
|
|||||||
|
|
||||||
# cleanup any existing rules
|
# cleanup any existing rules
|
||||||
if ipfw_rule_exists(port):
|
if ipfw_rule_exists(port):
|
||||||
ipfw('del', sport)
|
ipfw('delete', sport)
|
||||||
|
|
||||||
while _oldctls:
|
while _changedctls:
|
||||||
(name,oldval) = _oldctls.pop()
|
name = _changedctls.pop()
|
||||||
|
oldval = _oldctls[name]
|
||||||
_sysctl_set(name, oldval)
|
_sysctl_set(name, oldval)
|
||||||
|
|
||||||
if subnets:
|
if subnets:
|
||||||
|
@ -28,3 +28,10 @@ def debug3(s):
|
|||||||
|
|
||||||
class Fatal(Exception):
|
class Fatal(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def list_contains_any(l, sub):
|
||||||
|
for i in sub:
|
||||||
|
if i in l:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import subprocess, time, socket, re, select, errno
|
import time, socket, re, select, errno
|
||||||
if not globals().get('skip_imports'):
|
if not globals().get('skip_imports'):
|
||||||
|
import compat.ssubprocess as ssubprocess
|
||||||
import helpers
|
import helpers
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
@ -108,7 +109,7 @@ def _check_netstat():
|
|||||||
debug2(' > netstat\n')
|
debug2(' > netstat\n')
|
||||||
argv = ['netstat', '-n']
|
argv = ['netstat', '-n']
|
||||||
try:
|
try:
|
||||||
p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=null)
|
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
|
||||||
content = p.stdout.read()
|
content = p.stdout.read()
|
||||||
p.wait()
|
p.wait()
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
@ -128,7 +129,7 @@ def _check_smb(hostname):
|
|||||||
argv = ['smbclient', '-U', '%', '-L', hostname]
|
argv = ['smbclient', '-U', '%', '-L', hostname]
|
||||||
debug2(' > smb: %s\n' % hostname)
|
debug2(' > smb: %s\n' % hostname)
|
||||||
try:
|
try:
|
||||||
p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=null)
|
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
|
||||||
lines = p.stdout.readlines()
|
lines = p.stdout.readlines()
|
||||||
p.wait()
|
p.wait()
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
@ -185,7 +186,7 @@ def _check_nmb(hostname, is_workgroup, is_master):
|
|||||||
argv = ['nmblookup'] + ['-M']*is_master + ['--', hostname]
|
argv = ['nmblookup'] + ['-M']*is_master + ['--', hostname]
|
||||||
debug2(' > n%d%d: %s\n' % (is_workgroup, is_master, hostname))
|
debug2(' > n%d%d: %s\n' % (is_workgroup, is_master, hostname))
|
||||||
try:
|
try:
|
||||||
p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=null)
|
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE, stderr=null)
|
||||||
lines = p.stdout.readlines()
|
lines = p.stdout.readlines()
|
||||||
rv = p.wait()
|
rv = p.wait()
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
|
6
main.py
6
main.py
@ -56,8 +56,8 @@ python= specify the name/path of the python interpreter on the remote server [py
|
|||||||
r,remote= ssh hostname (and optional username) of remote sshuttle server
|
r,remote= ssh hostname (and optional username) of remote sshuttle server
|
||||||
x,exclude= exclude this subnet (can be used more than once)
|
x,exclude= exclude this subnet (can be used more than once)
|
||||||
v,verbose increase debug message verbosity
|
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)
|
seed-hosts= with -H, use these hostnames for initial scan (comma-separated)
|
||||||
noserver don't use a separate server process (mostly for debugging)
|
|
||||||
server (internal use only)
|
server (internal use only)
|
||||||
firewall (internal use only)
|
firewall (internal use only)
|
||||||
hostwatch (internal use only)
|
hostwatch (internal use only)
|
||||||
@ -98,9 +98,9 @@ try:
|
|||||||
else:
|
else:
|
||||||
sh = None
|
sh = None
|
||||||
sys.exit(client.main(parse_ipport(opt.listen or '0.0.0.0:0'),
|
sys.exit(client.main(parse_ipport(opt.listen or '0.0.0.0:0'),
|
||||||
not opt.noserver,
|
opt.ssh_cmd,
|
||||||
remotename,
|
remotename,
|
||||||
(opt.python or "python"),
|
opt.python,
|
||||||
sh,
|
sh,
|
||||||
opt.auto_nets,
|
opt.auto_nets,
|
||||||
parse_subnets(includes),
|
parse_subnets(includes),
|
||||||
|
45
server.py
45
server.py
@ -1,6 +1,7 @@
|
|||||||
import re, struct, socket, select, subprocess, traceback
|
import re, struct, socket, select, traceback
|
||||||
if not globals().get('skip_imports'):
|
if not globals().get('skip_imports'):
|
||||||
import ssnet, helpers, hostwatch
|
import ssnet, helpers, hostwatch
|
||||||
|
import compat.ssubprocess as ssubprocess
|
||||||
from ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
from ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
@ -36,14 +37,18 @@ def _maskbits(netmask):
|
|||||||
if not netmask:
|
if not netmask:
|
||||||
return 32
|
return 32
|
||||||
for i in range(32):
|
for i in range(32):
|
||||||
if netmask[0] & (1<<i):
|
if netmask[0] & _shl(1, i):
|
||||||
return 32-i
|
return 32-i
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _shl(n, bits):
|
||||||
|
return n * int(2**bits)
|
||||||
|
|
||||||
|
|
||||||
def _list_routes():
|
def _list_routes():
|
||||||
argv = ['netstat', '-rn']
|
argv = ['netstat', '-rn']
|
||||||
p = subprocess.Popen(argv, stdout=subprocess.PIPE)
|
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
|
||||||
routes = []
|
routes = []
|
||||||
for line in p.stdout:
|
for line in p.stdout:
|
||||||
cols = re.split(r'\s+', line)
|
cols = re.split(r'\s+', line)
|
||||||
@ -53,7 +58,7 @@ def _list_routes():
|
|||||||
maskw = _ipmatch(cols[2]) # linux only
|
maskw = _ipmatch(cols[2]) # linux only
|
||||||
mask = _maskbits(maskw) # returns 32 if maskw is null
|
mask = _maskbits(maskw) # returns 32 if maskw is null
|
||||||
width = min(ipw[1], mask)
|
width = min(ipw[1], mask)
|
||||||
ip = ipw[0] & (((1<<width)-1) << (32-width))
|
ip = ipw[0] & _shl(_shl(1, width) - 1, 32-width)
|
||||||
routes.append((socket.inet_ntoa(struct.pack('!I', ip)), width))
|
routes.append((socket.inet_ntoa(struct.pack('!I', ip)), width))
|
||||||
rv = p.wait()
|
rv = p.wait()
|
||||||
if rv != 0:
|
if rv != 0:
|
||||||
@ -122,17 +127,26 @@ def main():
|
|||||||
socket.fromfd(sys.stdout.fileno(),
|
socket.fromfd(sys.stdout.fileno(),
|
||||||
socket.AF_INET, socket.SOCK_STREAM))
|
socket.AF_INET, socket.SOCK_STREAM))
|
||||||
handlers.append(mux)
|
handlers.append(mux)
|
||||||
routepkt = ''.join('%s,%d\n' % r
|
routepkt = ''
|
||||||
for r in routes)
|
for r in routes:
|
||||||
|
routepkt += '%s,%d\n' % r
|
||||||
mux.send(0, ssnet.CMD_ROUTES, routepkt)
|
mux.send(0, ssnet.CMD_ROUTES, routepkt)
|
||||||
|
|
||||||
hw = Hostwatch()
|
hw = Hostwatch()
|
||||||
|
hw.leftover = ''
|
||||||
|
|
||||||
def hostwatch_ready():
|
def hostwatch_ready():
|
||||||
assert(hw.pid)
|
assert(hw.pid)
|
||||||
content = hw.sock.recv(4096)
|
content = hw.sock.recv(4096)
|
||||||
if content:
|
if content:
|
||||||
mux.send(0, ssnet.CMD_HOST_LIST, content)
|
lines = (hw.leftover + content).split('\n')
|
||||||
|
if lines[-1]:
|
||||||
|
# no terminating newline: entry isn't complete yet!
|
||||||
|
hw.leftover = lines.pop()
|
||||||
|
lines.append('')
|
||||||
|
else:
|
||||||
|
hw.leftover = ''
|
||||||
|
mux.send(0, ssnet.CMD_HOST_LIST, '\n'.join(lines))
|
||||||
else:
|
else:
|
||||||
raise Fatal('hostwatch process died')
|
raise Fatal('hostwatch process died')
|
||||||
|
|
||||||
@ -156,21 +170,6 @@ def main():
|
|||||||
if rpid:
|
if rpid:
|
||||||
raise Fatal('hostwatch exited unexpectedly: code 0x%04x\n' % rv)
|
raise Fatal('hostwatch exited unexpectedly: code 0x%04x\n' % rv)
|
||||||
|
|
||||||
r = set()
|
ssnet.runonce(handlers, mux)
|
||||||
w = set()
|
|
||||||
x = set()
|
|
||||||
handlers = filter(lambda s: s.ok, handlers)
|
|
||||||
for s in handlers:
|
|
||||||
s.pre_select(r,w,x)
|
|
||||||
debug2('Waiting: %d[%d,%d,%d] (fullness=%d/%d)...\n'
|
|
||||||
% (len(handlers), len(r), len(w), len(x),
|
|
||||||
mux.fullness, mux.too_full))
|
|
||||||
(r,w,x) = select.select(r,w,x)
|
|
||||||
#log('r=%r w=%r x=%r\n' % (r,w,x))
|
|
||||||
ready = set(r) | set(w) | set(x)
|
|
||||||
for s in handlers:
|
|
||||||
#debug2('check: %r: %r\n' % (s, s.socks & ready))
|
|
||||||
if s.socks & ready:
|
|
||||||
s.callback()
|
|
||||||
mux.check_fullness()
|
mux.check_fullness()
|
||||||
mux.callback()
|
mux.callback()
|
||||||
|
41
ssh.py
41
ssh.py
@ -1,4 +1,5 @@
|
|||||||
import sys, os, re, subprocess, socket, zlib
|
import sys, os, re, socket, zlib
|
||||||
|
import compat.ssubprocess as ssubprocess
|
||||||
import helpers
|
import helpers
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
@ -14,25 +15,45 @@ def readfile(name):
|
|||||||
|
|
||||||
|
|
||||||
def empackage(z, filename):
|
def empackage(z, filename):
|
||||||
|
(path,basename) = os.path.split(filename)
|
||||||
content = z.compress(readfile(filename))
|
content = z.compress(readfile(filename))
|
||||||
content += z.flush(zlib.Z_SYNC_FLUSH)
|
content += z.flush(zlib.Z_SYNC_FLUSH)
|
||||||
return '%s\n%d\n%s' % (filename,len(content), content)
|
return '%s\n%d\n%s' % (basename,len(content), content)
|
||||||
|
|
||||||
|
|
||||||
def connect(rhostport, python):
|
def connect(ssh_cmd, rhostport, python):
|
||||||
main_exe = sys.argv[0]
|
main_exe = sys.argv[0]
|
||||||
|
portl = []
|
||||||
|
|
||||||
|
rhostIsIPv6 = False
|
||||||
|
if (rhostport or '').count(':') > 1:
|
||||||
|
rhostIsIPv6 = True
|
||||||
|
if rhostport.count(']') or rhostport.count('['):
|
||||||
|
result = rhostport.split(']')
|
||||||
|
rhost = result[0].strip('[')
|
||||||
|
if len(result) > 1:
|
||||||
|
result[1] = result[1].strip(':')
|
||||||
|
if result[1] is not '':
|
||||||
|
portl = ['-p', str(int(result[1]))]
|
||||||
|
else: # can't disambiguate IPv6 colons and a port number. pass the hostname through.
|
||||||
|
rhost = rhostport
|
||||||
|
else: # IPv4
|
||||||
l = (rhostport or '').split(':', 1)
|
l = (rhostport or '').split(':', 1)
|
||||||
rhost = l[0]
|
rhost = l[0]
|
||||||
portl = []
|
|
||||||
if len(l) > 1:
|
if len(l) > 1:
|
||||||
portl = ['-p', str(int(l[1]))]
|
portl = ['-p', str(int(l[1]))]
|
||||||
|
|
||||||
if rhost == '-':
|
if rhost == '-':
|
||||||
rhost = None
|
rhost = None
|
||||||
|
|
||||||
|
ipv6flag = []
|
||||||
|
if rhostIsIPv6:
|
||||||
|
ipv6flag = ['-6']
|
||||||
|
|
||||||
z = zlib.compressobj(1)
|
z = zlib.compressobj(1)
|
||||||
content = readfile('assembler.py')
|
content = readfile('assembler.py')
|
||||||
content2 = (empackage(z, 'helpers.py') +
|
content2 = (empackage(z, 'helpers.py') +
|
||||||
|
empackage(z, 'compat/ssubprocess.py') +
|
||||||
empackage(z, 'ssnet.py') +
|
empackage(z, 'ssnet.py') +
|
||||||
empackage(z, 'hostwatch.py') +
|
empackage(z, 'hostwatch.py') +
|
||||||
empackage(z, 'server.py') +
|
empackage(z, 'server.py') +
|
||||||
@ -50,16 +71,22 @@ def connect(rhostport, python):
|
|||||||
if not rhost:
|
if not rhost:
|
||||||
argv = [python, '-c', pyscript]
|
argv = [python, '-c', pyscript]
|
||||||
else:
|
else:
|
||||||
argv = ['ssh'] + portl + [rhost, '--', "'%s' -c '%s'" % (python, pyscript)]
|
if ssh_cmd:
|
||||||
|
sshl = ssh_cmd.split(' ')
|
||||||
|
else:
|
||||||
|
sshl = ['ssh']
|
||||||
|
argv = (sshl +
|
||||||
|
portl +
|
||||||
|
ipv6flag +
|
||||||
|
[rhost, '--', "'%s' -c '%s'" % (python, pyscript)])
|
||||||
(s1,s2) = socket.socketpair()
|
(s1,s2) = socket.socketpair()
|
||||||
def setup():
|
def setup():
|
||||||
# runs in the child process
|
# runs in the child process
|
||||||
s2.close()
|
s2.close()
|
||||||
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()
|
||||||
debug2('executing: %r\n' % argv)
|
debug2('executing: %r\n' % argv)
|
||||||
p = subprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup,
|
p = ssubprocess.Popen(argv, stdin=s1a, stdout=s1b, preexec_fn=setup,
|
||||||
close_fds=True)
|
close_fds=True)
|
||||||
os.close(s1a)
|
os.close(s1a)
|
||||||
os.close(s1b)
|
os.close(s1b)
|
||||||
|
223
sshuttle.md
Normal file
223
sshuttle.md
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
% sshuttle(8) Sshuttle 0.42
|
||||||
|
% Avery Pennarun <apenwarr@gmail.com>
|
||||||
|
% 2010-11-09
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
sshuttle - a transparent proxy-based VPN using ssh
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
|
||||||
|
sshuttle [options...] [-r [username@]sshserver[:port]] \<subnets...\>
|
||||||
|
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
sshuttle allows you to create a VPN connection from your
|
||||||
|
machine to any remote server that you can connect to via
|
||||||
|
ssh, as long as that server has python 2.3 or higher.
|
||||||
|
|
||||||
|
To work, you must have root access on the local machine,
|
||||||
|
but you can have a normal account on the server.
|
||||||
|
|
||||||
|
It's valid to run sshuttle more than once simultaneously on
|
||||||
|
a single client machine, connecting to a different server
|
||||||
|
every time, so you can be on more than one VPN at once.
|
||||||
|
|
||||||
|
If run on a router, sshuttle can forward traffic for your
|
||||||
|
entire subnet to the VPN.
|
||||||
|
|
||||||
|
|
||||||
|
# OPTIONS
|
||||||
|
|
||||||
|
\<subnets...\>
|
||||||
|
: a list of subnets to route over the VPN, in the form
|
||||||
|
`a.b.c.d[/width]`. Valid examples are 1.2.3.4 (a
|
||||||
|
single IP address), 1.2.3.4/32 (equivalent to 1.2.3.4),
|
||||||
|
1.2.3.0/24 (a 24-bit subnet, ie. with a 255.255.255.0
|
||||||
|
netmask), and 0/0 ('just route everything through the
|
||||||
|
VPN').
|
||||||
|
|
||||||
|
-l, --listen=*[ip:]port*
|
||||||
|
: use this ip address and port number as the transparent
|
||||||
|
proxy port. By default sshuttle finds an available
|
||||||
|
port automatically, so you don't need to override it.
|
||||||
|
|
||||||
|
-H, --auto-hosts
|
||||||
|
: scan for remote hostnames and update the local /etc/hosts
|
||||||
|
file with matching entries for as long as the VPN is
|
||||||
|
open. This is nicer than changing your system's DNS
|
||||||
|
(/etc/resolv.conf) settings, for several reasons. First,
|
||||||
|
hostnames are added without domain names attached, so
|
||||||
|
you can `ssh thatserver` without worrying if your local
|
||||||
|
domain matches the remote one. Second, if you sshuttle
|
||||||
|
into more than one VPN at a time, it's impossible to
|
||||||
|
use more than one DNS server at once anyway, but
|
||||||
|
sshuttle correctly merges /etc/hosts entries between
|
||||||
|
all running copies. Third, if you're only routing a
|
||||||
|
few subnets over the VPN, you probably would prefer to
|
||||||
|
keep using your local DNS server for everything else.
|
||||||
|
|
||||||
|
-N, --auto-nets
|
||||||
|
: in addition to the subnets provided on the command
|
||||||
|
line, ask the server which subnets it thinks we should
|
||||||
|
route, and route those automatically. The suggestions
|
||||||
|
are taken automatically from the server's routing
|
||||||
|
table.
|
||||||
|
|
||||||
|
--python
|
||||||
|
: specify the name/path of the remote python interpreter.
|
||||||
|
The default is just `python`, which means to use the
|
||||||
|
default python interpreter on the remote system's PATH.
|
||||||
|
|
||||||
|
-r, --remote=*[username@]sshserver[:port]*
|
||||||
|
: the remote hostname and optional username and ssh
|
||||||
|
port number to use for connecting to the remote server.
|
||||||
|
For example, example.com, testuser@example.com,
|
||||||
|
testuser@example.com:2222, or example.com:2244.
|
||||||
|
|
||||||
|
-x, --exclude=*subnet*
|
||||||
|
: explicitly exclude this subnet from forwarding. The
|
||||||
|
format of this option is the same as the `<subnets>`
|
||||||
|
option. To exclude more than one subnet, specify the
|
||||||
|
`-x` option more than once. You can say something like
|
||||||
|
`0/0 -x 1.2.3.0/24` to forward everything except the
|
||||||
|
local subnet over the VPN, for example.
|
||||||
|
|
||||||
|
-v, --verbose
|
||||||
|
: print more information about the session. This option
|
||||||
|
can be used more than once for increased verbosity. By
|
||||||
|
default, sshuttle prints only error messages.
|
||||||
|
|
||||||
|
-e, --ssh-cmd
|
||||||
|
: the command to use to connect to the remote server. The
|
||||||
|
default is just `ssh`. Use this if your ssh client is
|
||||||
|
in a non-standard location or you want to provide extra
|
||||||
|
options to the ssh command, for example, `-e 'ssh -v'`.
|
||||||
|
|
||||||
|
--seed-hosts
|
||||||
|
: a comma-separated list of hostnames to use to
|
||||||
|
initialize the `--auto-hosts` scan algorithm.
|
||||||
|
`--auto-hosts` does things like poll local SMB servers
|
||||||
|
for lists of local hostnames, but can speed things up
|
||||||
|
if you use this option to give it a few names to start
|
||||||
|
from.
|
||||||
|
|
||||||
|
--server
|
||||||
|
: (internal use only) run the sshuttle server on
|
||||||
|
stdin/stdout. This is what the client runs on
|
||||||
|
the remote end.
|
||||||
|
|
||||||
|
--firewall
|
||||||
|
: (internal use only) run the firewall manager. This is
|
||||||
|
the only part of sshuttle that must run as root. If
|
||||||
|
you start sshuttle as a non-root user, it will
|
||||||
|
automatically run `sudo` or `su` to start the firewall
|
||||||
|
manager, but the core of sshuttle still runs as a
|
||||||
|
normal user.
|
||||||
|
|
||||||
|
--hostwatch
|
||||||
|
: (internal use only) run the hostwatch daemon. This
|
||||||
|
process runs on the server side and collects hostnames for
|
||||||
|
the `--auto-hosts` option. Using this option by itself
|
||||||
|
makes it a lot easier to debug and test the `--auto-hosts`
|
||||||
|
feature.
|
||||||
|
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
Test locally by proxying all local connections, without using ssh:
|
||||||
|
|
||||||
|
$ sshuttle -v 0/0
|
||||||
|
|
||||||
|
Starting sshuttle proxy.
|
||||||
|
Listening on ('0.0.0.0', 12300).
|
||||||
|
[local sudo] Password:
|
||||||
|
firewall manager ready.
|
||||||
|
c : connecting to server...
|
||||||
|
s: available routes:
|
||||||
|
s: 192.168.42.0/24
|
||||||
|
c : connected.
|
||||||
|
firewall manager: starting transproxy.
|
||||||
|
c : Accept: '192.168.42.106':50035 -> '192.168.42.121':139.
|
||||||
|
c : Accept: '192.168.42.121':47523 -> '77.141.99.22':443.
|
||||||
|
...etc...
|
||||||
|
^C
|
||||||
|
firewall manager: undoing changes.
|
||||||
|
KeyboardInterrupt
|
||||||
|
c : Keyboard interrupt: exiting.
|
||||||
|
c : SW#8:192.168.42.121:47523: deleting
|
||||||
|
c : SW#6:192.168.42.106:50035: deleting
|
||||||
|
|
||||||
|
Test connection to a remote server, with automatic hostname
|
||||||
|
and subnet guessing:
|
||||||
|
|
||||||
|
$ sshuttle -vNHr example.org
|
||||||
|
|
||||||
|
Starting sshuttle proxy.
|
||||||
|
Listening on ('0.0.0.0', 12300).
|
||||||
|
firewall manager ready.
|
||||||
|
c : connecting to server...
|
||||||
|
s: available routes:
|
||||||
|
s: 77.141.99.0/24
|
||||||
|
c : connected.
|
||||||
|
c : seed_hosts: []
|
||||||
|
firewall manager: starting transproxy.
|
||||||
|
hostwatch: Found: testbox1: 1.2.3.4
|
||||||
|
hostwatch: Found: mytest2: 5.6.7.8
|
||||||
|
hostwatch: Found: domaincontroller: 99.1.2.3
|
||||||
|
c : Accept: '192.168.42.121':60554 -> '77.141.99.22':22.
|
||||||
|
^C
|
||||||
|
firewall manager: undoing changes.
|
||||||
|
c : Keyboard interrupt: exiting.
|
||||||
|
c : SW#6:192.168.42.121:60554: deleting
|
||||||
|
|
||||||
|
|
||||||
|
# DISCUSSION
|
||||||
|
|
||||||
|
When it starts, sshuttle creates an ssh session to the
|
||||||
|
server specified by the `-r` option. If `-r` is omitted,
|
||||||
|
it will start both its client and server locally, which is
|
||||||
|
sometimes useful for testing.
|
||||||
|
|
||||||
|
After connecting to the remote server, sshuttle uploads its
|
||||||
|
(python) source code to the remote end and executes it
|
||||||
|
there. Thus, you don't need to install sshuttle on the
|
||||||
|
remote server, and there are never sshuttle version
|
||||||
|
conflicts between client and server.
|
||||||
|
|
||||||
|
Unlike most VPNs, sshuttle forwards sessions, not packets.
|
||||||
|
That is, it uses kernel transparent proxying (`iptables
|
||||||
|
REDIRECT` rules on Linux, or `ipfw fwd` rules on BSD) to
|
||||||
|
capture outgoing TCP sessions, then creates entirely
|
||||||
|
separate TCP sessions out to the original destination at
|
||||||
|
the other end of the tunnel.
|
||||||
|
|
||||||
|
Packet-level forwarding (eg. using the tun/tap devices on
|
||||||
|
Linux) seems elegant at first, but it results in
|
||||||
|
several problems, notably the 'tcp over tcp' problem. The
|
||||||
|
tcp protocol depends fundamentally on packets being dropped
|
||||||
|
in order to implement its congestion control agorithm; if
|
||||||
|
you pass tcp packets through a tcp-based tunnel (such as
|
||||||
|
ssh), the inner tcp packets will never be dropped, and so
|
||||||
|
the inner tcp stream's congestion control will be
|
||||||
|
completely broken, and performance will be terrible. Thus,
|
||||||
|
packet-based VPNs (such as IPsec and openvpn) cannot use
|
||||||
|
tcp-based encrypted streams like ssh or ssl, and have to
|
||||||
|
implement their own encryption from scratch, which is very
|
||||||
|
complex and error prone.
|
||||||
|
|
||||||
|
sshuttle's simplicity comes from the fact that it can
|
||||||
|
safely use the existing ssh encrypted tunnel without
|
||||||
|
incurring a performance penalty. It does this by letting
|
||||||
|
the client-side kernel manage the incoming tcp stream, and
|
||||||
|
the server-side kernel manage the outgoing tcp stream;
|
||||||
|
there is no need for congestion control to be shared
|
||||||
|
between the two separate streams, so a tcp-based tunnel is
|
||||||
|
fine.
|
||||||
|
|
||||||
|
|
||||||
|
# SEE ALSO
|
||||||
|
|
||||||
|
`ssh`(1), `python`(1)
|
||||||
|
|
136
ssnet.py
136
ssnet.py
@ -2,6 +2,12 @@ import struct, socket, errno, select
|
|||||||
if not globals().get('skip_imports'):
|
if not globals().get('skip_imports'):
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
|
# these don't exist in the socket module in python 2.3!
|
||||||
|
SHUT_RD = 0
|
||||||
|
SHUT_WR = 1
|
||||||
|
SHUT_RDWR = 2
|
||||||
|
|
||||||
|
|
||||||
HDR_LEN = 8
|
HDR_LEN = 8
|
||||||
|
|
||||||
|
|
||||||
@ -9,7 +15,7 @@ CMD_EXIT = 0x4200
|
|||||||
CMD_PING = 0x4201
|
CMD_PING = 0x4201
|
||||||
CMD_PONG = 0x4202
|
CMD_PONG = 0x4202
|
||||||
CMD_CONNECT = 0x4203
|
CMD_CONNECT = 0x4203
|
||||||
CMD_CLOSE = 0x4204
|
# CMD_CLOSE = 0x4204 # never used - removed
|
||||||
CMD_EOF = 0x4205
|
CMD_EOF = 0x4205
|
||||||
CMD_DATA = 0x4206
|
CMD_DATA = 0x4206
|
||||||
CMD_ROUTES = 0x4207
|
CMD_ROUTES = 0x4207
|
||||||
@ -21,7 +27,6 @@ cmd_to_name = {
|
|||||||
CMD_PING: 'PING',
|
CMD_PING: 'PING',
|
||||||
CMD_PONG: 'PONG',
|
CMD_PONG: 'PONG',
|
||||||
CMD_CONNECT: 'CONNECT',
|
CMD_CONNECT: 'CONNECT',
|
||||||
CMD_CLOSE: 'CLOSE',
|
|
||||||
CMD_EOF: 'EOF',
|
CMD_EOF: 'EOF',
|
||||||
CMD_DATA: 'DATA',
|
CMD_DATA: 'DATA',
|
||||||
CMD_ROUTES: 'ROUTES',
|
CMD_ROUTES: 'ROUTES',
|
||||||
@ -31,13 +36,30 @@ cmd_to_name = {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _add(l, elem):
|
||||||
|
if not elem in l:
|
||||||
|
l.append(elem)
|
||||||
|
|
||||||
|
|
||||||
|
def _fds(l):
|
||||||
|
out = []
|
||||||
|
for i in l:
|
||||||
|
try:
|
||||||
|
out.append(i.fileno())
|
||||||
|
except AttributeError:
|
||||||
|
out.append(i)
|
||||||
|
out.sort()
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _nb_clean(func, *args):
|
def _nb_clean(func, *args):
|
||||||
try:
|
try:
|
||||||
return func(*args)
|
return func(*args)
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
if e.errno not in (errno.EWOULDBLOCK, errno.EAGAIN, errno.EPIPE):
|
if e.errno not in (errno.EWOULDBLOCK, errno.EAGAIN):
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
|
debug3('%s: err was: %s\n' % (func.__name__, e))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@ -52,8 +74,12 @@ def _try_peername(sock):
|
|||||||
return 'unknown'
|
return 'unknown'
|
||||||
|
|
||||||
|
|
||||||
|
_swcount = 0
|
||||||
class SockWrapper:
|
class SockWrapper:
|
||||||
def __init__(self, rsock, wsock, connect_to=None, peername=None):
|
def __init__(self, rsock, wsock, connect_to=None, peername=None):
|
||||||
|
global _swcount
|
||||||
|
_swcount += 1
|
||||||
|
debug3('creating new SockWrapper (%d now exist\n)' % _swcount)
|
||||||
self.exc = None
|
self.exc = None
|
||||||
self.rsock = rsock
|
self.rsock = rsock
|
||||||
self.wsock = wsock
|
self.wsock = wsock
|
||||||
@ -64,26 +90,37 @@ class SockWrapper:
|
|||||||
self.try_connect()
|
self.try_connect()
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
debug1('%r: deleting\n' % self)
|
global _swcount
|
||||||
|
_swcount -= 1
|
||||||
|
debug1('%r: deleting (%d remain)\n' % (self, _swcount))
|
||||||
if self.exc:
|
if self.exc:
|
||||||
debug1('%r: error was: %r\n' % (self, self.exc))
|
debug1('%r: error was: %r\n' % (self, self.exc))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'SW:%s' % (self.peername,)
|
if self.rsock == self.wsock:
|
||||||
|
fds = '#%d' % self.rsock.fileno()
|
||||||
|
else:
|
||||||
|
fds = '#%d,%d' % (self.rsock.fileno(), self.wsock.fileno())
|
||||||
|
return 'SW%s:%s' % (fds, self.peername)
|
||||||
|
|
||||||
def seterr(self, e):
|
def seterr(self, e):
|
||||||
if not self.exc:
|
if not self.exc:
|
||||||
self.exc = e
|
self.exc = e
|
||||||
|
|
||||||
def try_connect(self):
|
def try_connect(self):
|
||||||
|
if self.connect_to and self.shut_write:
|
||||||
|
self.noread()
|
||||||
|
self.connect_to = None
|
||||||
if not self.connect_to:
|
if not self.connect_to:
|
||||||
return # already connected
|
return # already connected
|
||||||
self.rsock.setblocking(False)
|
self.rsock.setblocking(False)
|
||||||
|
debug3('%r: trying connect to %r\n' % (self, self.connect_to))
|
||||||
try:
|
try:
|
||||||
self.rsock.connect(self.connect_to)
|
self.rsock.connect(self.connect_to)
|
||||||
# connected successfully (Linux)
|
# connected successfully (Linux)
|
||||||
self.connect_to = None
|
self.connect_to = None
|
||||||
except socket.error, e:
|
except socket.error, e:
|
||||||
|
debug3('%r: connect result: %r\n' % (self, e))
|
||||||
if e.args[0] in [errno.EINPROGRESS, errno.EALREADY]:
|
if e.args[0] in [errno.EINPROGRESS, errno.EALREADY]:
|
||||||
pass # not connected yet
|
pass # not connected yet
|
||||||
elif e.args[0] == errno.EISCONN:
|
elif e.args[0] == errno.EISCONN:
|
||||||
@ -102,14 +139,14 @@ class SockWrapper:
|
|||||||
if not self.shut_read:
|
if not self.shut_read:
|
||||||
debug2('%r: done reading\n' % self)
|
debug2('%r: done reading\n' % self)
|
||||||
self.shut_read = True
|
self.shut_read = True
|
||||||
#self.rsock.shutdown(socket.SHUT_RD) # doesn't do anything anyway
|
#self.rsock.shutdown(SHUT_RD) # doesn't do anything anyway
|
||||||
|
|
||||||
def nowrite(self):
|
def nowrite(self):
|
||||||
if not self.shut_write:
|
if not self.shut_write:
|
||||||
debug2('%r: done writing\n' % self)
|
debug2('%r: done writing\n' % self)
|
||||||
self.shut_write = True
|
self.shut_write = True
|
||||||
try:
|
try:
|
||||||
self.wsock.shutdown(socket.SHUT_WR)
|
self.wsock.shutdown(SHUT_WR)
|
||||||
except socket.error, e:
|
except socket.error, e:
|
||||||
self.seterr(e)
|
self.seterr(e)
|
||||||
|
|
||||||
@ -159,7 +196,7 @@ class SockWrapper:
|
|||||||
wrote = outwrap.write(self.buf[0])
|
wrote = outwrap.write(self.buf[0])
|
||||||
self.buf[0] = self.buf[0][wrote:]
|
self.buf[0] = self.buf[0][wrote:]
|
||||||
while self.buf and not self.buf[0]:
|
while self.buf and not self.buf[0]:
|
||||||
self.buf[0:1] = []
|
self.buf.pop(0)
|
||||||
if not self.buf and self.shut_read:
|
if not self.buf and self.shut_read:
|
||||||
outwrap.nowrite()
|
outwrap.nowrite()
|
||||||
|
|
||||||
@ -167,12 +204,13 @@ class SockWrapper:
|
|||||||
class Handler:
|
class Handler:
|
||||||
def __init__(self, socks = None, callback = None):
|
def __init__(self, socks = None, callback = None):
|
||||||
self.ok = True
|
self.ok = True
|
||||||
self.socks = set(socks or [])
|
self.socks = socks or []
|
||||||
if callback:
|
if callback:
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
|
|
||||||
def pre_select(self, r, w, x):
|
def pre_select(self, r, w, x):
|
||||||
r |= self.socks
|
for i in self.socks:
|
||||||
|
_add(r, i)
|
||||||
|
|
||||||
def callback(self):
|
def callback(self):
|
||||||
log('--no callback defined-- %r\n' % self)
|
log('--no callback defined-- %r\n' % self)
|
||||||
@ -181,7 +219,7 @@ class Handler:
|
|||||||
v = s.recv(4096)
|
v = s.recv(4096)
|
||||||
if not v:
|
if not v:
|
||||||
log('--closed-- %r\n' % self)
|
log('--closed-- %r\n' % self)
|
||||||
self.socks = set()
|
self.socks = []
|
||||||
self.ok = False
|
self.ok = False
|
||||||
|
|
||||||
|
|
||||||
@ -194,20 +232,20 @@ class Proxy(Handler):
|
|||||||
|
|
||||||
def pre_select(self, r, w, x):
|
def pre_select(self, r, w, x):
|
||||||
if self.wrap1.connect_to:
|
if self.wrap1.connect_to:
|
||||||
w.add(self.wrap1.rsock)
|
_add(w, self.wrap1.rsock)
|
||||||
elif self.wrap1.buf:
|
elif self.wrap1.buf:
|
||||||
if not self.wrap2.too_full():
|
if not self.wrap2.too_full():
|
||||||
w.add(self.wrap2.wsock)
|
_add(w, self.wrap2.wsock)
|
||||||
elif not self.wrap1.shut_read:
|
elif not self.wrap1.shut_read:
|
||||||
r.add(self.wrap1.rsock)
|
_add(r, self.wrap1.rsock)
|
||||||
|
|
||||||
if self.wrap2.connect_to:
|
if self.wrap2.connect_to:
|
||||||
w.add(self.wrap2.rsock)
|
_add(w, self.wrap2.rsock)
|
||||||
elif self.wrap2.buf:
|
elif self.wrap2.buf:
|
||||||
if not self.wrap1.too_full():
|
if not self.wrap1.too_full():
|
||||||
w.add(self.wrap1.wsock)
|
_add(w, self.wrap1.wsock)
|
||||||
elif not self.wrap2.shut_read:
|
elif not self.wrap2.shut_read:
|
||||||
r.add(self.wrap2.rsock)
|
_add(r, self.wrap2.rsock)
|
||||||
|
|
||||||
def callback(self):
|
def callback(self):
|
||||||
self.wrap1.try_connect()
|
self.wrap1.try_connect()
|
||||||
@ -216,9 +254,17 @@ class Proxy(Handler):
|
|||||||
self.wrap2.fill()
|
self.wrap2.fill()
|
||||||
self.wrap1.copy_to(self.wrap2)
|
self.wrap1.copy_to(self.wrap2)
|
||||||
self.wrap2.copy_to(self.wrap1)
|
self.wrap2.copy_to(self.wrap1)
|
||||||
|
if self.wrap1.buf and self.wrap2.shut_write:
|
||||||
|
self.wrap1.buf = []
|
||||||
|
self.wrap1.noread()
|
||||||
|
if self.wrap2.buf and self.wrap1.shut_write:
|
||||||
|
self.wrap2.buf = []
|
||||||
|
self.wrap2.noread()
|
||||||
if (self.wrap1.shut_read and self.wrap2.shut_read and
|
if (self.wrap1.shut_read and self.wrap2.shut_read and
|
||||||
not self.wrap1.buf and not self.wrap2.buf):
|
not self.wrap1.buf and not self.wrap2.buf):
|
||||||
self.ok = False
|
self.ok = False
|
||||||
|
self.wrap1.nowrite()
|
||||||
|
self.wrap2.nowrite()
|
||||||
|
|
||||||
|
|
||||||
class Mux(Handler):
|
class Mux(Handler):
|
||||||
@ -247,7 +293,10 @@ class Mux(Handler):
|
|||||||
return self.chani
|
return self.chani
|
||||||
|
|
||||||
def amount_queued(self):
|
def amount_queued(self):
|
||||||
return sum(len(b) for b in self.outbuf)
|
total = 0
|
||||||
|
for b in self.outbuf:
|
||||||
|
total += len(b)
|
||||||
|
return total
|
||||||
|
|
||||||
def check_fullness(self):
|
def check_fullness(self):
|
||||||
if self.fullness > 32768:
|
if self.fullness > 32768:
|
||||||
@ -301,7 +350,11 @@ class Mux(Handler):
|
|||||||
else:
|
else:
|
||||||
raise Exception('got CMD_HOST_LIST without got_host_list?')
|
raise Exception('got CMD_HOST_LIST without got_host_list?')
|
||||||
else:
|
else:
|
||||||
callback = self.channels[channel]
|
callback = self.channels.get(channel)
|
||||||
|
if not callback:
|
||||||
|
log('warning: closed channel %d got cmd=%s len=%d\n'
|
||||||
|
% (channel, cmd_to_name.get(cmd,hex(cmd)), len(data)))
|
||||||
|
else:
|
||||||
callback(cmd, data)
|
callback(cmd, data)
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
@ -346,9 +399,9 @@ class Mux(Handler):
|
|||||||
break
|
break
|
||||||
|
|
||||||
def pre_select(self, r, w, x):
|
def pre_select(self, r, w, x):
|
||||||
r.add(self.rsock)
|
_add(r, self.rsock)
|
||||||
if self.outbuf:
|
if self.outbuf:
|
||||||
w.add(self.wsock)
|
_add(w, self.wsock)
|
||||||
|
|
||||||
def callback(self):
|
def callback(self):
|
||||||
(r,w,x) = select.select([self.rsock], [self.wsock], [], 0)
|
(r,w,x) = select.select([self.rsock], [self.wsock], [], 0)
|
||||||
@ -377,11 +430,19 @@ class MuxWrapper(SockWrapper):
|
|||||||
def noread(self):
|
def noread(self):
|
||||||
if not self.shut_read:
|
if not self.shut_read:
|
||||||
self.shut_read = True
|
self.shut_read = True
|
||||||
|
self.maybe_close()
|
||||||
|
|
||||||
def nowrite(self):
|
def nowrite(self):
|
||||||
if not self.shut_write:
|
if not self.shut_write:
|
||||||
self.shut_write = True
|
self.shut_write = True
|
||||||
self.mux.send(self.channel, CMD_EOF, '')
|
self.mux.send(self.channel, CMD_EOF, '')
|
||||||
|
self.maybe_close()
|
||||||
|
|
||||||
|
def maybe_close(self):
|
||||||
|
if self.shut_read and self.shut_write:
|
||||||
|
# remove the mux's reference to us. The python garbage collector
|
||||||
|
# will then be able to reap our object.
|
||||||
|
self.mux.channels[self.channel] = None
|
||||||
|
|
||||||
def too_full(self):
|
def too_full(self):
|
||||||
return self.mux.too_full
|
return self.mux.too_full
|
||||||
@ -401,10 +462,7 @@ class MuxWrapper(SockWrapper):
|
|||||||
return None # no data available right now
|
return None # no data available right now
|
||||||
|
|
||||||
def got_packet(self, cmd, data):
|
def got_packet(self, cmd, data):
|
||||||
if cmd == CMD_CLOSE:
|
if cmd == CMD_EOF:
|
||||||
self.noread()
|
|
||||||
self.nowrite()
|
|
||||||
elif cmd == CMD_EOF:
|
|
||||||
self.noread()
|
self.noread()
|
||||||
elif cmd == CMD_DATA:
|
elif cmd == CMD_DATA:
|
||||||
self.buf.append(data)
|
self.buf.append(data)
|
||||||
@ -420,3 +478,31 @@ def connect_dst(ip, port):
|
|||||||
return SockWrapper(outsock, outsock,
|
return SockWrapper(outsock, outsock,
|
||||||
connect_to = (ip,port),
|
connect_to = (ip,port),
|
||||||
peername = '%s:%d' % (ip,port))
|
peername = '%s:%d' % (ip,port))
|
||||||
|
|
||||||
|
|
||||||
|
def runonce(handlers, mux):
|
||||||
|
r = []
|
||||||
|
w = []
|
||||||
|
x = []
|
||||||
|
to_remove = filter(lambda s: not s.ok, handlers)
|
||||||
|
for h in to_remove:
|
||||||
|
handlers.remove(h)
|
||||||
|
|
||||||
|
for s in handlers:
|
||||||
|
s.pre_select(r,w,x)
|
||||||
|
debug2('Waiting: %d r=%r w=%r x=%r (fullness=%d/%d)\n'
|
||||||
|
% (len(handlers), _fds(r), _fds(w), _fds(x),
|
||||||
|
mux.fullness, mux.too_full))
|
||||||
|
(r,w,x) = select.select(r,w,x)
|
||||||
|
debug2(' Ready: %d r=%r w=%r x=%r\n'
|
||||||
|
% (len(handlers), _fds(r), _fds(w), _fds(x)))
|
||||||
|
ready = r+w+x
|
||||||
|
did = {}
|
||||||
|
for h in handlers:
|
||||||
|
for s in h.socks:
|
||||||
|
if s in ready:
|
||||||
|
h.callback()
|
||||||
|
did[s] = 1
|
||||||
|
for s in ready:
|
||||||
|
if not s in did:
|
||||||
|
raise Fatal('socket %r was not used by any handler' % s)
|
||||||
|
Reference in New Issue
Block a user