diff options
Diffstat (limited to 'Sshuttle VPN.app/Contents/Resources/sshuttle/server.py')
-rw-r--r-- | Sshuttle VPN.app/Contents/Resources/sshuttle/server.py | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/Sshuttle VPN.app/Contents/Resources/sshuttle/server.py b/Sshuttle VPN.app/Contents/Resources/sshuttle/server.py new file mode 100644 index 0000000..24dd462 --- /dev/null +++ b/Sshuttle VPN.app/Contents/Resources/sshuttle/server.py @@ -0,0 +1,176 @@ +import re, struct, socket, select, traceback +if not globals().get('skip_imports'): + import ssnet, helpers, hostwatch + import compat.ssubprocess as ssubprocess + from ssnet import SockWrapper, Handler, Proxy, Mux, MuxWrapper + from helpers import * + + +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): + if netmask[0] & _shl(1, i): + return 32-i + return 0 + + +def _shl(n, bits): + return n * int(2**bits) + + +def _list_routes(): + argv = ['netstat', '-rn'] + p = ssubprocess.Popen(argv, stdout=ssubprocess.PIPE) + 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) + ip = ipw[0] & _shl(_shl(1, width) - 1, 32-width) + routes.append((socket.inet_ntoa(struct.pack('!I', ip)), width)) + rv = p.wait() + if rv != 0: + log('WARNING: %r returned %d\n' % (argv, rv)) + log('WARNING: That prevents --auto-nets from working.\n') + return routes + + +def list_routes(): + for (ip,width) in _list_routes(): + if not ip.startswith('0.') and not ip.startswith('127.'): + yield (ip,width) + + +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: + 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 + finally: + os._exit(rv) + s1.close() + return pid,s2 + + +class Hostwatch: + def __init__(self): + self.pid = 0 + self.sock = None + + +def main(): + if helpers.verbose >= 1: + helpers.logprefix = ' s: ' + else: + helpers.logprefix = 'server: ' + + routes = list(list_routes()) + debug1('available routes:\n') + for r in routes: + debug1(' %s/%d\n' % r) + + # synchronization header + sys.stdout.write('SSHUTTLE0001') + sys.stdout.flush() + + 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) + routepkt = '' + for r in routes: + routepkt += '%s,%d\n' % r + mux.send(0, ssnet.CMD_ROUTES, routepkt) + + hw = Hostwatch() + hw.leftover = '' + + def hostwatch_ready(): + assert(hw.pid) + content = hw.sock.recv(4096) + if 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: + 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 + + 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 + + while mux.ok: + if hw.pid: + assert(hw.pid > 0) + (rpid, rv) = os.waitpid(hw.pid, os.WNOHANG) + if rpid: + raise Fatal('hostwatch exited unexpectedly: code 0x%04x\n' % rv) + + ssnet.runonce(handlers, mux) + mux.check_fullness() + mux.callback() |