2011-01-26 11:34:12 +01:00
|
|
|
import re, struct, socket, select, traceback, time
|
2010-05-05 05:21:16 +02:00
|
|
|
if not globals().get('skip_imports'):
|
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
|
|
|
import ssnet, helpers, hostwatch
|
2010-10-01 21:06:56 +02:00
|
|
|
import compat.ssubprocess as ssubprocess
|
2010-05-05 05:21:16 +02:00
|
|
|
from ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper
|
|
|
|
from helpers import *
|
2010-05-02 06:52:06 +02:00
|
|
|
|
|
|
|
|
2010-05-08 02:02:04 +02:00
|
|
|
def _ipmatch(ipstr):
|
|
|
|
if ipstr == 'default':
|
|
|
|
ipstr = '0.0.0.0/0'
|
|
|
|
m = re.match(r'^(\d+(\.\d+(\.\d+(\.\d+)?)?)?)(?:/(\d+))?$', ipstr)
|
|
|
|
if m:
|
|
|
|
g = m.groups()
|
|
|
|
ips = g[0]
|
|
|
|
width = int(g[4] or 32)
|
|
|
|
if g[1] == None:
|
|
|
|
ips += '.0.0.0'
|
|
|
|
width = min(width, 8)
|
|
|
|
elif g[2] == None:
|
|
|
|
ips += '.0.0'
|
|
|
|
width = min(width, 16)
|
|
|
|
elif g[3] == None:
|
|
|
|
ips += '.0'
|
|
|
|
width = min(width, 24)
|
|
|
|
return (struct.unpack('!I', socket.inet_aton(ips))[0], width)
|
|
|
|
|
|
|
|
|
|
|
|
def _ipstr(ip, width):
|
|
|
|
if width >= 32:
|
|
|
|
return ip
|
|
|
|
else:
|
|
|
|
return "%s/%d" % (ip, width)
|
|
|
|
|
|
|
|
|
|
|
|
def _maskbits(netmask):
|
|
|
|
if not netmask:
|
|
|
|
return 32
|
|
|
|
for i in range(32):
|
2010-10-03 00:26:29 +02:00
|
|
|
if netmask[0] & _shl(1, i):
|
2010-05-08 02:02:04 +02:00
|
|
|
return 32-i
|
|
|
|
return 0
|
2010-10-01 23:46:34 +02:00
|
|
|
|
|
|
|
|
|
|
|
def _shl(n, bits):
|
|
|
|
return n * int(2**bits)
|
2010-05-08 02:02:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
def _list_routes():
|
|
|
|
argv = ['netstat', '-rn']
|
2010-10-01 21:06:56 +02:00
|
|
|
p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE)
|
2010-05-08 02:02:04 +02:00
|
|
|
routes = []
|
|
|
|
for line in p.stdout:
|
|
|
|
cols = re.split(r'\s+', line)
|
|
|
|
ipw = _ipmatch(cols[0])
|
|
|
|
if not ipw:
|
|
|
|
continue # some lines won't be parseable; never mind
|
|
|
|
maskw = _ipmatch(cols[2]) # linux only
|
|
|
|
mask = _maskbits(maskw) # returns 32 if maskw is null
|
|
|
|
width = min(ipw[1], mask)
|
2010-10-01 23:46:34 +02:00
|
|
|
ip = ipw[0] & _shl(_shl(1, width) - 1, 32-width)
|
2010-05-08 02:02:04 +02:00
|
|
|
routes.append((socket.inet_ntoa(struct.pack('!I', ip)), width))
|
|
|
|
rv = p.wait()
|
|
|
|
if rv != 0:
|
2010-09-04 20:29:11 +02:00
|
|
|
log('WARNING: %r returned %d\n' % (argv, rv))
|
|
|
|
log('WARNING: That prevents --auto-nets from working.\n')
|
2010-05-08 02:02:04 +02:00
|
|
|
return routes
|
|
|
|
|
|
|
|
|
|
|
|
def list_routes():
|
|
|
|
for (ip,width) in _list_routes():
|
|
|
|
if not ip.startswith('0.') and not ip.startswith('127.'):
|
|
|
|
yield (ip,width)
|
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
|
|
|
|
|
|
|
|
|
|
|
def _exc_dump():
|
|
|
|
exc_info = sys.exc_info()
|
|
|
|
return ''.join(traceback.format_exception(*exc_info))
|
|
|
|
|
|
|
|
|
|
|
|
def start_hostwatch(seed_hosts):
|
|
|
|
s1,s2 = socket.socketpair()
|
|
|
|
pid = os.fork()
|
|
|
|
if not pid:
|
|
|
|
# child
|
|
|
|
rv = 99
|
|
|
|
try:
|
2010-05-10 19:58:52 +02:00
|
|
|
try:
|
|
|
|
s2.close()
|
|
|
|
os.dup2(s1.fileno(), 1)
|
|
|
|
os.dup2(s1.fileno(), 0)
|
|
|
|
s1.close()
|
|
|
|
rv = hostwatch.hw_main(seed_hosts) or 0
|
|
|
|
except Exception, e:
|
|
|
|
log('%s\n' % _exc_dump())
|
|
|
|
rv = 98
|
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
|
|
|
finally:
|
|
|
|
os._exit(rv)
|
|
|
|
s1.close()
|
|
|
|
return pid,s2
|
|
|
|
|
|
|
|
|
|
|
|
class Hostwatch:
|
|
|
|
def __init__(self):
|
|
|
|
self.pid = 0
|
|
|
|
self.sock = None
|
2010-05-08 02:02:04 +02:00
|
|
|
|
|
|
|
|
2011-01-26 11:00:19 +01:00
|
|
|
class DnsProxy(Handler):
|
|
|
|
def __init__(self, mux, chan, request):
|
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
|
|
Handler.__init__(self, [sock])
|
2011-01-26 11:34:12 +01:00
|
|
|
self.timeout = time.time()+30
|
2011-01-26 11:00:19 +01:00
|
|
|
self.mux = mux
|
|
|
|
self.chan = chan
|
2011-04-06 18:30:12 +02:00
|
|
|
self.tries = 0
|
|
|
|
self.peer = None
|
|
|
|
self.request = request
|
|
|
|
self.sock = sock
|
2011-01-26 11:00:19 +01:00
|
|
|
self.sock.setsockopt(socket.SOL_IP, socket.IP_TTL, 42)
|
2011-04-06 18:30:12 +02:00
|
|
|
self.try_send()
|
|
|
|
|
|
|
|
def try_send(self):
|
|
|
|
if self.tries >= 3:
|
|
|
|
return
|
|
|
|
self.tries += 1
|
|
|
|
self.peer = resolvconf_random_nameserver()
|
|
|
|
self.sock.connect((self.peer, 53))
|
|
|
|
debug2('DNS: sending to %r\n' % self.peer)
|
|
|
|
try:
|
|
|
|
self.sock.send(self.request)
|
|
|
|
except socket.error, e:
|
2011-05-03 22:32:25 +02:00
|
|
|
if e.args[0] in ssnet.NET_ERRS:
|
2011-04-06 18:30:12 +02:00
|
|
|
# might have been spurious; try again.
|
|
|
|
# Note: these errors sometimes are reported by recv(),
|
|
|
|
# and sometimes by send(). We have to catch both.
|
|
|
|
debug2('DNS send to %r: %s\n' % (self.peer, e))
|
|
|
|
self.try_send()
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
log('DNS send to %r: %s\n' % (self.peer, e))
|
|
|
|
return
|
2011-01-26 11:00:19 +01:00
|
|
|
|
|
|
|
def callback(self):
|
2011-03-20 06:48:15 +01:00
|
|
|
try:
|
|
|
|
data = self.sock.recv(4096)
|
|
|
|
except socket.error, e:
|
2011-05-03 22:32:25 +02:00
|
|
|
if e.args[0] in ssnet.NET_ERRS:
|
2011-04-06 18:30:12 +02:00
|
|
|
# might have been spurious; try again.
|
|
|
|
# Note: these errors sometimes are reported by recv(),
|
|
|
|
# and sometimes by send(). We have to catch both.
|
|
|
|
debug2('DNS recv from %r: %s\n' % (self.peer, e))
|
|
|
|
self.try_send()
|
|
|
|
return
|
2011-03-20 06:48:15 +01:00
|
|
|
else:
|
2011-04-06 18:30:12 +02:00
|
|
|
log('DNS recv from %r: %s\n' % (self.peer, e))
|
|
|
|
return
|
2011-01-26 11:34:12 +01:00
|
|
|
debug2('DNS response: %d bytes\n' % len(data))
|
2011-01-26 11:00:19 +01:00
|
|
|
self.mux.send(self.chan, ssnet.CMD_DNS_RESPONSE, data)
|
2011-01-26 11:34:12 +01:00
|
|
|
self.ok = False
|
2011-01-26 11:00:19 +01:00
|
|
|
|
|
|
|
|
2010-05-02 06:52:06 +02:00
|
|
|
def main():
|
2010-05-02 08:23:42 +02:00
|
|
|
if helpers.verbose >= 1:
|
|
|
|
helpers.logprefix = ' s: '
|
|
|
|
else:
|
|
|
|
helpers.logprefix = 'server: '
|
2011-01-26 06:07:01 +01:00
|
|
|
debug1('latency control setting = %r\n' % latency_control)
|
2010-05-08 02:02:04 +02:00
|
|
|
|
|
|
|
routes = list(list_routes())
|
|
|
|
debug1('available routes:\n')
|
|
|
|
for r in routes:
|
|
|
|
debug1(' %s/%d\n' % r)
|
2010-05-05 05:21:16 +02:00
|
|
|
|
|
|
|
# synchronization header
|
2011-05-03 22:59:25 +02:00
|
|
|
sys.stdout.write('\0\0SSHUTTLE0001')
|
2010-05-05 05:21:16 +02:00
|
|
|
sys.stdout.flush()
|
|
|
|
|
2010-05-02 06:52:06 +02:00
|
|
|
handlers = []
|
|
|
|
mux = Mux(socket.fromfd(sys.stdin.fileno(),
|
|
|
|
socket.AF_INET, socket.SOCK_STREAM),
|
|
|
|
socket.fromfd(sys.stdout.fileno(),
|
|
|
|
socket.AF_INET, socket.SOCK_STREAM))
|
|
|
|
handlers.append(mux)
|
2010-10-01 20:55:45 +02:00
|
|
|
routepkt = ''
|
|
|
|
for r in routes:
|
|
|
|
routepkt += '%s,%d\n' % r
|
2010-05-08 02:02:04 +02:00
|
|
|
mux.send(0, ssnet.CMD_ROUTES, routepkt)
|
2010-05-02 06:52:06 +02:00
|
|
|
|
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
|
|
|
hw = Hostwatch()
|
2010-10-04 11:45:21 +02:00
|
|
|
hw.leftover = ''
|
|
|
|
|
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
|
|
|
def hostwatch_ready():
|
|
|
|
assert(hw.pid)
|
|
|
|
content = hw.sock.recv(4096)
|
|
|
|
if content:
|
2010-10-04 11:45:21 +02:00
|
|
|
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))
|
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
|
|
|
else:
|
|
|
|
raise Fatal('hostwatch process died')
|
|
|
|
|
|
|
|
def got_host_req(data):
|
|
|
|
if not hw.pid:
|
|
|
|
(hw.pid,hw.sock) = start_hostwatch(data.strip().split())
|
|
|
|
handlers.append(Handler(socks = [hw.sock],
|
|
|
|
callback = hostwatch_ready))
|
|
|
|
mux.got_host_req = got_host_req
|
|
|
|
|
2010-05-02 06:52:06 +02:00
|
|
|
def new_channel(channel, data):
|
|
|
|
(dstip,dstport) = data.split(',', 1)
|
|
|
|
dstport = int(dstport)
|
|
|
|
outwrap = ssnet.connect_dst(dstip,dstport)
|
|
|
|
handlers.append(Proxy(MuxWrapper(mux, channel), outwrap))
|
|
|
|
mux.new_channel = new_channel
|
|
|
|
|
2011-01-26 11:00:19 +01:00
|
|
|
dnshandlers = {}
|
|
|
|
def dns_req(channel, data):
|
2011-01-26 11:34:12 +01:00
|
|
|
debug2('Incoming DNS request.\n')
|
2011-01-26 11:00:19 +01:00
|
|
|
h = DnsProxy(mux, channel, data)
|
|
|
|
handlers.append(h)
|
|
|
|
dnshandlers[channel] = h
|
|
|
|
mux.got_dns_req = dns_req
|
|
|
|
|
2010-05-02 06:52:06 +02:00
|
|
|
while mux.ok:
|
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
|
|
|
if hw.pid:
|
2011-01-01 07:54:07 +01:00
|
|
|
assert(hw.pid > 0)
|
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
|
|
|
(rpid, rv) = os.waitpid(hw.pid, os.WNOHANG)
|
|
|
|
if rpid:
|
|
|
|
raise Fatal('hostwatch exited unexpectedly: code 0x%04x\n' % rv)
|
|
|
|
|
2010-10-02 02:36:09 +02:00
|
|
|
ssnet.runonce(handlers, mux)
|
2011-01-26 06:07:01 +01:00
|
|
|
if latency_control:
|
|
|
|
mux.check_fullness()
|
2010-05-02 11:39:17 +02:00
|
|
|
mux.callback()
|
2011-01-26 11:34:12 +01:00
|
|
|
|
|
|
|
if dnshandlers:
|
|
|
|
now = time.time()
|
|
|
|
for channel,h in dnshandlers.items():
|
|
|
|
if h.timeout < now or not h.ok:
|
|
|
|
del dnshandlers[channel]
|
|
|
|
h.ok = False
|