2010-05-02 05:14:42 +02:00
|
|
|
import struct, socket, errno, select
|
2010-05-05 05:21:16 +02:00
|
|
|
if not globals().get('skip_imports'):
|
|
|
|
from helpers import *
|
2011-02-02 11:32:46 +01:00
|
|
|
|
|
|
|
MAX_CHANNEL = 65535
|
2010-10-03 00:24:04 +02:00
|
|
|
|
|
|
|
# these don't exist in the socket module in python 2.3!
|
|
|
|
SHUT_RD = 0
|
|
|
|
SHUT_WR = 1
|
|
|
|
SHUT_RDWR = 2
|
|
|
|
|
2010-05-02 02:45:59 +02:00
|
|
|
|
2010-05-02 05:14:42 +02:00
|
|
|
HDR_LEN = 8
|
|
|
|
|
2010-05-02 07:18:55 +02:00
|
|
|
|
2010-05-02 05:14:42 +02:00
|
|
|
CMD_EXIT = 0x4200
|
|
|
|
CMD_PING = 0x4201
|
|
|
|
CMD_PONG = 0x4202
|
|
|
|
CMD_CONNECT = 0x4203
|
2011-01-01 06:58:02 +01:00
|
|
|
CMD_STOP_SENDING = 0x4204
|
2010-05-02 05:14:42 +02:00
|
|
|
CMD_EOF = 0x4205
|
|
|
|
CMD_DATA = 0x4206
|
2010-05-08 02:02:04 +02:00
|
|
|
CMD_ROUTES = 0x4207
|
Added new --auto-hosts and --seed-hosts options to the client.
Now if you use --auto-hosts (-H), the client will ask the server to spawn a
hostwatcher to add names. That, in turn, will send names back to the
server, which sends them back to the client, which sends them to the
firewall subprocess, which will write them to /etc/hosts. Whew!
Only the firewall process can write to /etc/hosts, of course, because only
he's running as root.
Since the name discovery process is kind of slow, we cache the names in
~/.sshuttle.hosts on the remote server.
Right now, most of the names are discovered using nmblookup and smbclient,
as well as by reading the existing entries in /etc/hosts. What would really
be nice would be to query active directory or mdns somehow... but I don't
really know how those work, so this is what you get for now :) It's pretty
neat, at least.
2010-05-08 09:03:12 +02:00
|
|
|
CMD_HOST_REQ = 0x4208
|
|
|
|
CMD_HOST_LIST = 0x4209
|
2011-01-26 11:00:19 +01:00
|
|
|
CMD_DNS_REQ = 0x420a
|
|
|
|
CMD_DNS_RESPONSE = 0x420b
|
2010-05-02 05:14:42 +02:00
|
|
|
|
2010-05-02 07:18:55 +02:00
|
|
|
cmd_to_name = {
|
|
|
|
CMD_EXIT: 'EXIT',
|
|
|
|
CMD_PING: 'PING',
|
|
|
|
CMD_PONG: 'PONG',
|
|
|
|
CMD_CONNECT: 'CONNECT',
|
2011-01-01 06:58:02 +01:00
|
|
|
CMD_STOP_SENDING: 'STOP_SENDING',
|
2010-05-02 07:18:55 +02:00
|
|
|
CMD_EOF: 'EOF',
|
|
|
|
CMD_DATA: 'DATA',
|
2010-05-08 02:02:04 +02:00
|
|
|
CMD_ROUTES: 'ROUTES',
|
Added new --auto-hosts and --seed-hosts options to the client.
Now if you use --auto-hosts (-H), the client will ask the server to spawn a
hostwatcher to add names. That, in turn, will send names back to the
server, which sends them back to the client, which sends them to the
firewall subprocess, which will write them to /etc/hosts. Whew!
Only the firewall process can write to /etc/hosts, of course, because only
he's running as root.
Since the name discovery process is kind of slow, we cache the names in
~/.sshuttle.hosts on the remote server.
Right now, most of the names are discovered using nmblookup and smbclient,
as well as by reading the existing entries in /etc/hosts. What would really
be nice would be to query active directory or mdns somehow... but I don't
really know how those work, so this is what you get for now :) It's pretty
neat, at least.
2010-05-08 09:03:12 +02:00
|
|
|
CMD_HOST_REQ: 'HOST_REQ',
|
|
|
|
CMD_HOST_LIST: 'HOST_LIST',
|
2011-01-26 11:00:19 +01:00
|
|
|
CMD_DNS_REQ: 'DNS_REQ',
|
|
|
|
CMD_DNS_RESPONSE: 'DNS_RESPONSE',
|
2010-05-02 07:18:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-05-02 05:14:42 +02:00
|
|
|
|
2010-10-01 23:23:27 +02:00
|
|
|
def _add(l, elem):
|
|
|
|
if not elem in l:
|
|
|
|
l.append(elem)
|
|
|
|
|
|
|
|
|
2010-10-02 02:36:09 +02:00
|
|
|
def _fds(l):
|
|
|
|
out = []
|
|
|
|
for i in l:
|
|
|
|
try:
|
|
|
|
out.append(i.fileno())
|
|
|
|
except AttributeError:
|
|
|
|
out.append(i)
|
|
|
|
out.sort()
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
2010-05-02 02:45:59 +02:00
|
|
|
def _nb_clean(func, *args):
|
|
|
|
try:
|
|
|
|
return func(*args)
|
2010-05-02 07:18:55 +02:00
|
|
|
except OSError, e:
|
2010-10-02 03:25:03 +02:00
|
|
|
if e.errno not in (errno.EWOULDBLOCK, errno.EAGAIN):
|
2010-05-02 06:52:06 +02:00
|
|
|
raise
|
|
|
|
else:
|
2010-10-02 02:36:09 +02:00
|
|
|
debug3('%s: err was: %s\n' % (func.__name__, e))
|
2010-05-02 02:45:59 +02:00
|
|
|
return None
|
2010-05-02 06:52:06 +02:00
|
|
|
|
|
|
|
|
|
|
|
def _try_peername(sock):
|
|
|
|
try:
|
2010-05-02 07:52:05 +02:00
|
|
|
pn = sock.getpeername()
|
|
|
|
if pn:
|
|
|
|
return '%s:%s' % (pn[0], pn[1])
|
2010-05-02 06:52:06 +02:00
|
|
|
except socket.error, e:
|
2010-05-02 07:18:55 +02:00
|
|
|
if e.args[0] not in (errno.ENOTCONN, errno.ENOTSOCK):
|
2010-05-02 06:52:06 +02:00
|
|
|
raise
|
2010-05-02 07:52:05 +02:00
|
|
|
return 'unknown'
|
2010-05-02 02:45:59 +02:00
|
|
|
|
|
|
|
|
2010-12-10 03:31:41 +01:00
|
|
|
_swcount = 0
|
2010-05-02 02:45:59 +02:00
|
|
|
class SockWrapper:
|
2010-05-02 08:43:10 +02:00
|
|
|
def __init__(self, rsock, wsock, connect_to=None, peername=None):
|
2010-12-10 03:31:41 +01:00
|
|
|
global _swcount
|
|
|
|
_swcount += 1
|
|
|
|
debug3('creating new SockWrapper (%d now exist\n)' % _swcount)
|
2010-05-02 07:52:05 +02:00
|
|
|
self.exc = None
|
2010-05-02 05:32:30 +02:00
|
|
|
self.rsock = rsock
|
|
|
|
self.wsock = wsock
|
2010-05-02 02:45:59 +02:00
|
|
|
self.shut_read = self.shut_write = False
|
|
|
|
self.buf = []
|
2010-05-02 08:43:10 +02:00
|
|
|
self.connect_to = connect_to
|
2010-05-02 07:52:05 +02:00
|
|
|
self.peername = peername or _try_peername(self.rsock)
|
2010-05-02 08:43:10 +02:00
|
|
|
self.try_connect()
|
2010-05-02 02:45:59 +02:00
|
|
|
|
|
|
|
def __del__(self):
|
2010-12-10 03:31:41 +01:00
|
|
|
global _swcount
|
|
|
|
_swcount -= 1
|
|
|
|
debug1('%r: deleting (%d remain)\n' % (self, _swcount))
|
2010-05-02 07:52:05 +02:00
|
|
|
if self.exc:
|
2010-05-02 08:14:20 +02:00
|
|
|
debug1('%r: error was: %r\n' % (self, self.exc))
|
2010-05-02 02:45:59 +02:00
|
|
|
|
|
|
|
def __repr__(self):
|
2010-10-02 03:22:36 +02:00
|
|
|
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)
|
2010-05-02 07:52:05 +02:00
|
|
|
|
|
|
|
def seterr(self, e):
|
|
|
|
if not self.exc:
|
|
|
|
self.exc = e
|
2011-01-01 06:58:02 +01:00
|
|
|
self.nowrite()
|
|
|
|
self.noread()
|
2010-05-02 02:45:59 +02:00
|
|
|
|
2010-05-02 08:43:10 +02:00
|
|
|
def try_connect(self):
|
2010-10-02 03:22:36 +02:00
|
|
|
if self.connect_to and self.shut_write:
|
|
|
|
self.noread()
|
|
|
|
self.connect_to = None
|
2010-05-02 08:43:10 +02:00
|
|
|
if not self.connect_to:
|
|
|
|
return # already connected
|
|
|
|
self.rsock.setblocking(False)
|
2010-10-02 03:22:36 +02:00
|
|
|
debug3('%r: trying connect to %r\n' % (self, self.connect_to))
|
2010-05-02 08:43:10 +02:00
|
|
|
try:
|
|
|
|
self.rsock.connect(self.connect_to)
|
2010-05-05 00:24:43 +02:00
|
|
|
# connected successfully (Linux)
|
2010-05-02 08:43:10 +02:00
|
|
|
self.connect_to = None
|
|
|
|
except socket.error, e:
|
2010-10-02 03:22:36 +02:00
|
|
|
debug3('%r: connect result: %r\n' % (self, e))
|
2010-05-02 08:43:10 +02:00
|
|
|
if e.args[0] in [errno.EINPROGRESS, errno.EALREADY]:
|
|
|
|
pass # not connected yet
|
2010-05-05 00:24:43 +02:00
|
|
|
elif e.args[0] == errno.EISCONN:
|
|
|
|
# connected successfully (BSD)
|
|
|
|
self.connect_to = None
|
2010-05-07 18:30:03 +02:00
|
|
|
elif e.args[0] in [errno.ECONNREFUSED, errno.ETIMEDOUT,
|
|
|
|
errno.EHOSTUNREACH, errno.ENETUNREACH,
|
|
|
|
errno.EACCES, errno.EPERM]:
|
2010-05-02 08:43:10 +02:00
|
|
|
# a "normal" kind of error
|
2010-05-02 09:52:46 +02:00
|
|
|
self.connect_to = None
|
2010-05-02 08:43:10 +02:00
|
|
|
self.seterr(e)
|
|
|
|
else:
|
|
|
|
raise # error we've never heard of?! barf completely.
|
|
|
|
|
2010-05-02 02:45:59 +02:00
|
|
|
def noread(self):
|
|
|
|
if not self.shut_read:
|
2010-05-02 08:14:20 +02:00
|
|
|
debug2('%r: done reading\n' % self)
|
2010-05-02 02:45:59 +02:00
|
|
|
self.shut_read = True
|
2010-10-03 00:24:04 +02:00
|
|
|
#self.rsock.shutdown(SHUT_RD) # doesn't do anything anyway
|
2010-05-02 02:45:59 +02:00
|
|
|
|
|
|
|
def nowrite(self):
|
|
|
|
if not self.shut_write:
|
2010-05-02 08:14:20 +02:00
|
|
|
debug2('%r: done writing\n' % self)
|
2010-05-02 02:45:59 +02:00
|
|
|
self.shut_write = True
|
2010-05-02 06:52:06 +02:00
|
|
|
try:
|
2010-10-03 00:24:04 +02:00
|
|
|
self.wsock.shutdown(SHUT_WR)
|
2010-05-02 07:52:05 +02:00
|
|
|
except socket.error, e:
|
2011-01-12 18:18:17 +01:00
|
|
|
self.seterr('nowrite: %s' % e)
|
2010-05-02 05:14:42 +02:00
|
|
|
|
2010-05-02 12:17:43 +02:00
|
|
|
def too_full(self):
|
|
|
|
return False # fullness is determined by the socket's select() state
|
|
|
|
|
2010-05-02 05:14:42 +02:00
|
|
|
def uwrite(self, buf):
|
2010-05-02 08:43:10 +02:00
|
|
|
if self.connect_to:
|
|
|
|
return 0 # still connecting
|
2010-05-02 05:32:30 +02:00
|
|
|
self.wsock.setblocking(False)
|
2010-05-02 06:52:06 +02:00
|
|
|
try:
|
2010-05-02 07:18:55 +02:00
|
|
|
return _nb_clean(os.write, self.wsock.fileno(), buf)
|
2010-05-02 07:52:05 +02:00
|
|
|
except OSError, e:
|
2011-01-12 18:19:43 +01:00
|
|
|
if e.errno == errno.EPIPE:
|
|
|
|
debug1('%r: uwrite: got EPIPE\n' % self)
|
|
|
|
self.nowrite()
|
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
# unexpected error... stream is dead
|
|
|
|
self.seterr('uwrite: %s' % e)
|
|
|
|
return 0
|
2010-05-02 02:45:59 +02:00
|
|
|
|
|
|
|
def write(self, buf):
|
|
|
|
assert(buf)
|
2010-05-02 05:14:42 +02:00
|
|
|
return self.uwrite(buf)
|
2010-05-02 02:45:59 +02:00
|
|
|
|
2010-05-02 05:14:42 +02:00
|
|
|
def uread(self):
|
2010-05-02 08:43:10 +02:00
|
|
|
if self.connect_to:
|
|
|
|
return None # still connecting
|
2010-05-02 02:45:59 +02:00
|
|
|
if self.shut_read:
|
|
|
|
return
|
2010-05-02 05:32:30 +02:00
|
|
|
self.rsock.setblocking(False)
|
2010-05-02 06:52:06 +02:00
|
|
|
try:
|
2010-05-02 07:18:55 +02:00
|
|
|
return _nb_clean(os.read, self.rsock.fileno(), 65536)
|
2010-05-02 07:52:05 +02:00
|
|
|
except OSError, e:
|
2011-01-12 18:18:17 +01:00
|
|
|
self.seterr('uread: %s' % e)
|
2010-05-02 06:52:06 +02:00
|
|
|
return '' # unexpected error... we'll call it EOF
|
2010-05-02 05:14:42 +02:00
|
|
|
|
|
|
|
def fill(self):
|
|
|
|
if self.buf:
|
|
|
|
return
|
|
|
|
rb = self.uread()
|
2010-05-02 02:45:59 +02:00
|
|
|
if rb:
|
|
|
|
self.buf.append(rb)
|
|
|
|
if rb == '': # empty string means EOF; None means temporarily empty
|
|
|
|
self.noread()
|
|
|
|
|
|
|
|
def copy_to(self, outwrap):
|
|
|
|
if self.buf and self.buf[0]:
|
2010-05-02 05:14:42 +02:00
|
|
|
wrote = outwrap.write(self.buf[0])
|
2010-05-02 02:45:59 +02:00
|
|
|
self.buf[0] = self.buf[0][wrote:]
|
|
|
|
while self.buf and not self.buf[0]:
|
2010-10-02 03:22:36 +02:00
|
|
|
self.buf.pop(0)
|
2010-05-02 02:45:59 +02:00
|
|
|
if not self.buf and self.shut_read:
|
|
|
|
outwrap.nowrite()
|
|
|
|
|
|
|
|
|
|
|
|
class Handler:
|
|
|
|
def __init__(self, socks = None, callback = None):
|
|
|
|
self.ok = True
|
2010-10-01 23:23:27 +02:00
|
|
|
self.socks = socks or []
|
2010-05-02 02:45:59 +02:00
|
|
|
if callback:
|
|
|
|
self.callback = callback
|
|
|
|
|
|
|
|
def pre_select(self, r, w, x):
|
2010-10-01 23:23:27 +02:00
|
|
|
for i in self.socks:
|
|
|
|
_add(r, i)
|
2010-05-02 02:45:59 +02:00
|
|
|
|
|
|
|
def callback(self):
|
|
|
|
log('--no callback defined-- %r\n' % self)
|
|
|
|
(r,w,x) = select.select(self.socks, [], [], 0)
|
|
|
|
for s in r:
|
|
|
|
v = s.recv(4096)
|
|
|
|
if not v:
|
|
|
|
log('--closed-- %r\n' % self)
|
2010-10-01 23:23:27 +02:00
|
|
|
self.socks = []
|
2010-05-02 02:45:59 +02:00
|
|
|
self.ok = False
|
|
|
|
|
|
|
|
|
|
|
|
class Proxy(Handler):
|
|
|
|
def __init__(self, wrap1, wrap2):
|
2010-05-02 05:32:30 +02:00
|
|
|
Handler.__init__(self, [wrap1.rsock, wrap1.wsock,
|
|
|
|
wrap2.rsock, wrap2.wsock])
|
2010-05-02 02:45:59 +02:00
|
|
|
self.wrap1 = wrap1
|
|
|
|
self.wrap2 = wrap2
|
|
|
|
|
|
|
|
def pre_select(self, r, w, x):
|
2011-01-01 06:58:02 +01:00
|
|
|
if self.wrap1.shut_write: self.wrap2.noread()
|
|
|
|
if self.wrap2.shut_write: self.wrap1.noread()
|
|
|
|
|
2010-05-02 08:43:10 +02:00
|
|
|
if self.wrap1.connect_to:
|
2010-10-01 23:23:27 +02:00
|
|
|
_add(w, self.wrap1.rsock)
|
2010-05-02 08:43:10 +02:00
|
|
|
elif self.wrap1.buf:
|
2010-05-02 12:17:43 +02:00
|
|
|
if not self.wrap2.too_full():
|
2010-10-01 23:23:27 +02:00
|
|
|
_add(w, self.wrap2.wsock)
|
2010-05-02 02:45:59 +02:00
|
|
|
elif not self.wrap1.shut_read:
|
2010-10-01 23:23:27 +02:00
|
|
|
_add(r, self.wrap1.rsock)
|
2010-05-02 08:43:10 +02:00
|
|
|
|
|
|
|
if self.wrap2.connect_to:
|
2010-10-01 23:23:27 +02:00
|
|
|
_add(w, self.wrap2.rsock)
|
2010-05-02 08:43:10 +02:00
|
|
|
elif self.wrap2.buf:
|
2010-05-02 12:17:43 +02:00
|
|
|
if not self.wrap1.too_full():
|
2010-10-01 23:23:27 +02:00
|
|
|
_add(w, self.wrap1.wsock)
|
2010-05-02 02:45:59 +02:00
|
|
|
elif not self.wrap2.shut_read:
|
2010-10-01 23:23:27 +02:00
|
|
|
_add(r, self.wrap2.rsock)
|
2010-05-02 02:45:59 +02:00
|
|
|
|
|
|
|
def callback(self):
|
2010-05-02 08:43:10 +02:00
|
|
|
self.wrap1.try_connect()
|
|
|
|
self.wrap2.try_connect()
|
2010-05-02 05:14:42 +02:00
|
|
|
self.wrap1.fill()
|
|
|
|
self.wrap2.fill()
|
2010-05-02 02:45:59 +02:00
|
|
|
self.wrap1.copy_to(self.wrap2)
|
|
|
|
self.wrap2.copy_to(self.wrap1)
|
2010-10-02 03:22:36 +02:00
|
|
|
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()
|
2010-05-02 02:45:59 +02:00
|
|
|
if (self.wrap1.shut_read and self.wrap2.shut_read and
|
|
|
|
not self.wrap1.buf and not self.wrap2.buf):
|
|
|
|
self.ok = False
|
2010-12-09 00:02:10 +01:00
|
|
|
self.wrap1.nowrite()
|
|
|
|
self.wrap2.nowrite()
|
2010-05-02 02:45:59 +02:00
|
|
|
|
|
|
|
|
2010-05-02 05:14:42 +02:00
|
|
|
class Mux(Handler):
|
2010-05-02 05:32:30 +02:00
|
|
|
def __init__(self, rsock, wsock):
|
|
|
|
Handler.__init__(self, [rsock, wsock])
|
|
|
|
self.rsock = rsock
|
|
|
|
self.wsock = wsock
|
2011-01-26 11:00:19 +01:00
|
|
|
self.new_channel = self.got_dns_req = self.got_routes = None
|
Added new --auto-hosts and --seed-hosts options to the client.
Now if you use --auto-hosts (-H), the client will ask the server to spawn a
hostwatcher to add names. That, in turn, will send names back to the
server, which sends them back to the client, which sends them to the
firewall subprocess, which will write them to /etc/hosts. Whew!
Only the firewall process can write to /etc/hosts, of course, because only
he's running as root.
Since the name discovery process is kind of slow, we cache the names in
~/.sshuttle.hosts on the remote server.
Right now, most of the names are discovered using nmblookup and smbclient,
as well as by reading the existing entries in /etc/hosts. What would really
be nice would be to query active directory or mdns somehow... but I don't
really know how those work, so this is what you get for now :) It's pretty
neat, at least.
2010-05-08 09:03:12 +02:00
|
|
|
self.got_host_req = self.got_host_list = None
|
2010-05-02 05:14:42 +02:00
|
|
|
self.channels = {}
|
|
|
|
self.chani = 0
|
|
|
|
self.want = 0
|
|
|
|
self.inbuf = ''
|
|
|
|
self.outbuf = []
|
2010-05-02 11:39:17 +02:00
|
|
|
self.fullness = 0
|
2010-05-02 11:06:51 +02:00
|
|
|
self.too_full = False
|
2010-05-02 05:14:42 +02:00
|
|
|
self.send(0, CMD_PING, 'chicken')
|
|
|
|
|
|
|
|
def next_channel(self):
|
|
|
|
# channel 0 is special, so we never allocate it
|
|
|
|
for timeout in xrange(1024):
|
|
|
|
self.chani += 1
|
2011-02-02 11:32:46 +01:00
|
|
|
if self.chani > MAX_CHANNEL:
|
2010-05-02 05:14:42 +02:00
|
|
|
self.chani = 1
|
|
|
|
if not self.channels.get(self.chani):
|
|
|
|
return self.chani
|
2010-05-02 11:06:51 +02:00
|
|
|
|
|
|
|
def amount_queued(self):
|
2010-10-01 20:55:45 +02:00
|
|
|
total = 0
|
|
|
|
for b in self.outbuf:
|
|
|
|
total += len(b)
|
|
|
|
return total
|
2010-05-02 05:14:42 +02:00
|
|
|
|
2010-05-02 11:06:51 +02:00
|
|
|
def check_fullness(self):
|
2010-05-02 12:17:43 +02:00
|
|
|
if self.fullness > 32768:
|
|
|
|
if not self.too_full:
|
|
|
|
self.send(0, CMD_PING, 'rttest')
|
2010-05-02 11:39:17 +02:00
|
|
|
self.too_full = True
|
|
|
|
#ob = []
|
|
|
|
#for b in self.outbuf:
|
|
|
|
# (s1,s2,c) = struct.unpack('!ccH', b[:4])
|
|
|
|
# ob.append(c)
|
|
|
|
#log('outbuf: %d %r\n' % (self.amount_queued(), ob))
|
2010-05-02 11:06:51 +02:00
|
|
|
|
2010-05-02 05:14:42 +02:00
|
|
|
def send(self, channel, cmd, data):
|
|
|
|
data = str(data)
|
|
|
|
assert(len(data) <= 65535)
|
|
|
|
p = struct.pack('!ccHHH', 'S', 'S', channel, cmd, len(data)) + data
|
|
|
|
self.outbuf.append(p)
|
2010-05-02 11:39:17 +02:00
|
|
|
debug2(' > channel=%d cmd=%s len=%d (fullness=%d)\n'
|
2010-05-08 02:02:04 +02:00
|
|
|
% (channel, cmd_to_name.get(cmd,hex(cmd)),
|
|
|
|
len(data), self.fullness))
|
2010-05-02 11:39:17 +02:00
|
|
|
self.fullness += len(data)
|
2010-05-02 05:14:42 +02:00
|
|
|
|
|
|
|
def got_packet(self, channel, cmd, data):
|
2010-05-02 08:14:20 +02:00
|
|
|
debug2('< channel=%d cmd=%s len=%d\n'
|
2010-05-08 02:02:04 +02:00
|
|
|
% (channel, cmd_to_name.get(cmd,hex(cmd)), len(data)))
|
2010-05-02 05:14:42 +02:00
|
|
|
if cmd == CMD_PING:
|
2010-05-02 06:52:06 +02:00
|
|
|
self.send(0, CMD_PONG, data)
|
|
|
|
elif cmd == CMD_PONG:
|
2010-05-02 08:14:20 +02:00
|
|
|
debug2('received PING response\n')
|
2010-05-02 11:39:17 +02:00
|
|
|
self.too_full = False
|
|
|
|
self.fullness = 0
|
2010-05-02 05:14:42 +02:00
|
|
|
elif cmd == CMD_EXIT:
|
|
|
|
self.ok = False
|
2010-05-02 06:52:06 +02:00
|
|
|
elif cmd == CMD_CONNECT:
|
|
|
|
assert(not self.channels.get(channel))
|
|
|
|
if self.new_channel:
|
|
|
|
self.new_channel(channel, data)
|
2011-01-26 11:00:19 +01:00
|
|
|
elif cmd == CMD_DNS_REQ:
|
|
|
|
assert(not self.channels.get(channel))
|
|
|
|
if self.got_dns_req:
|
|
|
|
self.got_dns_req(channel, data)
|
2010-05-08 02:02:04 +02:00
|
|
|
elif cmd == CMD_ROUTES:
|
|
|
|
if self.got_routes:
|
|
|
|
self.got_routes(data)
|
|
|
|
else:
|
Added new --auto-hosts and --seed-hosts options to the client.
Now if you use --auto-hosts (-H), the client will ask the server to spawn a
hostwatcher to add names. That, in turn, will send names back to the
server, which sends them back to the client, which sends them to the
firewall subprocess, which will write them to /etc/hosts. Whew!
Only the firewall process can write to /etc/hosts, of course, because only
he's running as root.
Since the name discovery process is kind of slow, we cache the names in
~/.sshuttle.hosts on the remote server.
Right now, most of the names are discovered using nmblookup and smbclient,
as well as by reading the existing entries in /etc/hosts. What would really
be nice would be to query active directory or mdns somehow... but I don't
really know how those work, so this is what you get for now :) It's pretty
neat, at least.
2010-05-08 09:03:12 +02:00
|
|
|
raise Exception('got CMD_ROUTES without got_routes?')
|
|
|
|
elif cmd == CMD_HOST_REQ:
|
|
|
|
if self.got_host_req:
|
|
|
|
self.got_host_req(data)
|
|
|
|
else:
|
|
|
|
raise Exception('got CMD_HOST_REQ without got_host_req?')
|
|
|
|
elif cmd == CMD_HOST_LIST:
|
|
|
|
if self.got_host_list:
|
|
|
|
self.got_host_list(data)
|
|
|
|
else:
|
|
|
|
raise Exception('got CMD_HOST_LIST without got_host_list?')
|
2010-05-02 05:14:42 +02:00
|
|
|
else:
|
2010-12-12 02:27:12 +01:00
|
|
|
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)
|
2010-05-02 05:14:42 +02:00
|
|
|
|
|
|
|
def flush(self):
|
2010-05-02 05:32:30 +02:00
|
|
|
self.wsock.setblocking(False)
|
2010-05-02 05:14:42 +02:00
|
|
|
if self.outbuf and self.outbuf[0]:
|
2010-05-02 07:18:55 +02:00
|
|
|
wrote = _nb_clean(os.write, self.wsock.fileno(), self.outbuf[0])
|
2010-10-01 08:25:08 +02:00
|
|
|
debug2('mux wrote: %r/%d\n' % (wrote, len(self.outbuf[0])))
|
2010-05-02 05:14:42 +02:00
|
|
|
if wrote:
|
|
|
|
self.outbuf[0] = self.outbuf[0][wrote:]
|
|
|
|
while self.outbuf and not self.outbuf[0]:
|
2010-05-02 07:18:55 +02:00
|
|
|
self.outbuf[0:1] = []
|
2010-05-02 05:14:42 +02:00
|
|
|
|
|
|
|
def fill(self):
|
2010-05-02 05:32:30 +02:00
|
|
|
self.rsock.setblocking(False)
|
2010-05-03 02:53:29 +02:00
|
|
|
try:
|
|
|
|
b = _nb_clean(os.read, self.rsock.fileno(), 32768)
|
|
|
|
except OSError, e:
|
|
|
|
raise Fatal('other end: %r' % e)
|
2010-05-02 07:18:55 +02:00
|
|
|
#log('<<< %r\n' % b)
|
2010-05-02 05:14:42 +02:00
|
|
|
if b == '': # EOF
|
2010-05-02 06:52:06 +02:00
|
|
|
self.ok = False
|
2010-05-02 05:14:42 +02:00
|
|
|
if b:
|
|
|
|
self.inbuf += b
|
|
|
|
|
|
|
|
def handle(self):
|
2010-05-02 06:52:06 +02:00
|
|
|
self.fill()
|
2010-05-02 07:18:55 +02:00
|
|
|
#log('inbuf is: (%d,%d) %r\n'
|
|
|
|
# % (self.want, len(self.inbuf), self.inbuf))
|
2010-05-02 06:52:06 +02:00
|
|
|
while 1:
|
|
|
|
if len(self.inbuf) >= (self.want or HDR_LEN):
|
|
|
|
(s1,s2,channel,cmd,datalen) = \
|
|
|
|
struct.unpack('!ccHHH', self.inbuf[:HDR_LEN])
|
|
|
|
assert(s1 == 'S')
|
|
|
|
assert(s2 == 'S')
|
|
|
|
self.want = datalen + HDR_LEN
|
|
|
|
if self.want and len(self.inbuf) >= self.want:
|
|
|
|
data = self.inbuf[HDR_LEN:self.want]
|
|
|
|
self.inbuf = self.inbuf[self.want:]
|
|
|
|
self.want = 0
|
|
|
|
self.got_packet(channel, cmd, data)
|
|
|
|
else:
|
|
|
|
break
|
2010-05-02 05:14:42 +02:00
|
|
|
|
|
|
|
def pre_select(self, r, w, x):
|
2010-10-01 23:23:27 +02:00
|
|
|
_add(r, self.rsock)
|
2010-05-02 05:14:42 +02:00
|
|
|
if self.outbuf:
|
2010-10-01 23:23:27 +02:00
|
|
|
_add(w, self.wsock)
|
2010-05-02 05:14:42 +02:00
|
|
|
|
|
|
|
def callback(self):
|
2010-05-02 05:32:30 +02:00
|
|
|
(r,w,x) = select.select([self.rsock], [self.wsock], [], 0)
|
|
|
|
if self.rsock in r:
|
2010-05-02 05:14:42 +02:00
|
|
|
self.handle()
|
2010-05-02 05:32:30 +02:00
|
|
|
if self.outbuf and self.wsock in w:
|
2010-05-02 05:14:42 +02:00
|
|
|
self.flush()
|
|
|
|
|
|
|
|
|
|
|
|
class MuxWrapper(SockWrapper):
|
|
|
|
def __init__(self, mux, channel):
|
2010-05-02 05:32:30 +02:00
|
|
|
SockWrapper.__init__(self, mux.rsock, mux.wsock)
|
2010-05-02 05:14:42 +02:00
|
|
|
self.mux = mux
|
|
|
|
self.channel = channel
|
2010-05-02 06:52:06 +02:00
|
|
|
self.mux.channels[channel] = self.got_packet
|
2010-05-02 11:06:51 +02:00
|
|
|
self.socks = []
|
2010-05-02 08:14:20 +02:00
|
|
|
debug2('new channel: %d\n' % channel)
|
2010-05-02 05:14:42 +02:00
|
|
|
|
2010-05-02 06:52:06 +02:00
|
|
|
def __del__(self):
|
|
|
|
self.nowrite()
|
|
|
|
SockWrapper.__del__(self)
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return 'SW%r:Mux#%d' % (self.peername,self.channel)
|
|
|
|
|
2010-05-02 05:14:42 +02:00
|
|
|
def noread(self):
|
|
|
|
if not self.shut_read:
|
|
|
|
self.shut_read = True
|
2011-01-01 06:58:02 +01:00
|
|
|
self.mux.send(self.channel, CMD_STOP_SENDING, '')
|
2010-12-09 00:02:10 +01:00
|
|
|
self.maybe_close()
|
2010-05-02 05:14:42 +02:00
|
|
|
|
|
|
|
def nowrite(self):
|
|
|
|
if not self.shut_write:
|
|
|
|
self.shut_write = True
|
|
|
|
self.mux.send(self.channel, CMD_EOF, '')
|
2010-12-09 00:02:10 +01:00
|
|
|
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
|
2010-05-02 05:14:42 +02:00
|
|
|
|
2010-05-02 12:17:43 +02:00
|
|
|
def too_full(self):
|
|
|
|
return self.mux.too_full
|
|
|
|
|
2010-05-02 05:14:42 +02:00
|
|
|
def uwrite(self, buf):
|
2010-05-02 11:06:51 +02:00
|
|
|
if self.mux.too_full:
|
|
|
|
return 0 # too much already enqueued
|
|
|
|
if len(buf) > 2048:
|
|
|
|
buf = buf[:2048]
|
2010-05-02 05:14:42 +02:00
|
|
|
self.mux.send(self.channel, CMD_DATA, buf)
|
|
|
|
return len(buf)
|
|
|
|
|
|
|
|
def uread(self):
|
|
|
|
if self.shut_read:
|
|
|
|
return '' # EOF
|
|
|
|
else:
|
|
|
|
return None # no data available right now
|
|
|
|
|
|
|
|
def got_packet(self, cmd, data):
|
2010-12-10 04:09:30 +01:00
|
|
|
if cmd == CMD_EOF:
|
2010-05-02 05:14:42 +02:00
|
|
|
self.noread()
|
2011-01-01 06:58:02 +01:00
|
|
|
elif cmd == CMD_STOP_SENDING:
|
|
|
|
self.nowrite()
|
2010-05-02 05:14:42 +02:00
|
|
|
elif cmd == CMD_DATA:
|
|
|
|
self.buf.append(data)
|
|
|
|
else:
|
|
|
|
raise Exception('unknown command %d (%d bytes)'
|
|
|
|
% (cmd, len(data)))
|
2010-05-02 06:52:06 +02:00
|
|
|
|
|
|
|
|
|
|
|
def connect_dst(ip, port):
|
2010-05-02 08:14:20 +02:00
|
|
|
debug2('Connecting to %s:%d\n' % (ip, port))
|
2010-05-02 06:52:06 +02:00
|
|
|
outsock = socket.socket()
|
2010-05-05 00:24:43 +02:00
|
|
|
outsock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
|
2010-05-02 08:43:10 +02:00
|
|
|
return SockWrapper(outsock, outsock,
|
|
|
|
connect_to = (ip,port),
|
|
|
|
peername = '%s:%d' % (ip,port))
|
2010-10-02 02:36:09 +02:00
|
|
|
|
|
|
|
|
|
|
|
def runonce(handlers, mux):
|
|
|
|
r = []
|
|
|
|
w = []
|
|
|
|
x = []
|
2010-12-08 06:23:30 +01:00
|
|
|
to_remove = filter(lambda s: not s.ok, handlers)
|
|
|
|
for h in to_remove:
|
|
|
|
handlers.remove(h)
|
|
|
|
|
2010-10-02 02:36:09 +02:00
|
|
|
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
|
2010-10-02 03:22:36 +02:00
|
|
|
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)
|