summaryrefslogtreecommitdiff
path: root/src/leap/eip/conductor.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/eip/conductor.py')
-rw-r--r--src/leap/eip/conductor.py272
1 files changed, 272 insertions, 0 deletions
diff --git a/src/leap/eip/conductor.py b/src/leap/eip/conductor.py
new file mode 100644
index 00000000..e3adadc4
--- /dev/null
+++ b/src/leap/eip/conductor.py
@@ -0,0 +1,272 @@
+"""
+stablishes a vpn connection and monitors its state
+"""
+from __future__ import (division, unicode_literals, print_function)
+#import threading
+from functools import partial
+import logging
+
+from leap.utils.coroutines import spawn_and_watch_process
+from leap.baseapp.config import get_config, get_vpn_stdout_mockup
+from leap.eip.vpnwatcher import EIPConnectionStatus, status_watcher
+from leap.eip.vpnmanager import OpenVPNManager, ConnectionRefusedError
+
+logger = logging.getLogger(name=__name__)
+
+
+# TODO Move exceptions to their own module
+
+
+class ConnectionError(Exception):
+ """
+ generic connection error
+ """
+ pass
+
+
+class EIPClientError(Exception):
+ """
+ base EIPClient exception
+ """
+ def __str__(self):
+ if len(self.args) >= 1:
+ return repr(self.args[0])
+ else:
+ return ConnectionError
+
+
+class UnrecoverableError(EIPClientError):
+ """
+ we cannot do anything about it, sorry
+ """
+ pass
+
+
+class OpenVPNConnection(object):
+ """
+ All related to invocation
+ of the openvpn binary
+ """
+ # Connection Methods
+
+ def __init__(self, config_file=None, watcher_cb=None):
+ #XXX FIXME
+ #change watcher_cb to line_observer
+ """
+ :param config_file: configuration file to read from
+ :param watcher_cb: callback to be \
+called for each line in watched stdout
+ :param signal_map: dictionary of signal names and callables \
+to be triggered for each one of them.
+ :type config_file: str
+ :type watcher_cb: function
+ :type signal_map: dict
+ """
+ # XXX get host/port from config
+ self.manager = OpenVPNManager()
+
+ self.config_file = config_file
+ self.watcher_cb = watcher_cb
+ #self.signal_maps = signal_maps
+
+ self.subp = None
+ self.watcher = None
+
+ self.server = None
+ self.port = None
+ self.proto = None
+
+ self.autostart = True
+
+ self._get_config()
+
+ def _set_command_mockup(self):
+ """
+ sets command and args for a command mockup
+ that just mimics the output from the real thing
+ """
+ command, args = get_vpn_stdout_mockup()
+ self.command, self.args = command, args
+
+ def _get_config(self):
+ """
+ retrieves the config options from defaults or
+ home file, or config file passed in command line.
+ """
+ config = get_config(config_file=self.config_file)
+ self.config = config
+
+ if config.has_option('openvpn', 'command'):
+ commandline = config.get('openvpn', 'command')
+ if commandline == "mockup":
+ self._set_command_mockup()
+ return
+ command_split = commandline.split(' ')
+ command = command_split[0]
+ if len(command_split) > 1:
+ args = command_split[1:]
+ else:
+ args = []
+ self.command = command
+ #print("debug: command = %s" % command)
+ self.args = args
+ else:
+ self._set_command_mockup()
+
+ if config.has_option('openvpn', 'autostart'):
+ autostart = config.get('openvpn', 'autostart')
+ self.autostart = autostart
+
+ def _launch_openvpn(self):
+ """
+ invocation of openvpn binaries in a subprocess.
+ """
+ #XXX TODO:
+ #deprecate watcher_cb,
+ #use _only_ signal_maps instead
+
+ if self.watcher_cb is not None:
+ linewrite_callback = self.watcher_cb
+ else:
+ #XXX get logger instead
+ linewrite_callback = lambda line: print('watcher: %s' % line)
+
+ observers = (linewrite_callback,
+ partial(status_watcher, self.status))
+ subp, watcher = spawn_and_watch_process(
+ self.command,
+ self.args,
+ observers=observers)
+ self.subp = subp
+ self.watcher = watcher
+
+ conn_result = self.status.CONNECTED
+ return conn_result
+
+ def _try_connection(self):
+ """
+ attempts to connect
+ """
+ if self.subp is not None:
+ print('cowardly refusing to launch subprocess again')
+ return
+ self._launch_openvpn()
+
+ def cleanup(self):
+ """
+ terminates child subprocess
+ """
+ if self.subp:
+ self.subp.terminate()
+
+
+class EIPConductor(OpenVPNConnection):
+ """
+ Manages the execution of the OpenVPN process, auto starts, monitors the
+ network connection, handles configuration, fixes leaky hosts, handles
+ errors, etc.
+ Preferences will be stored via the Storage API. (TBD)
+ Status updates (connected, bandwidth, etc) are signaled to the GUI.
+ """
+
+ def __init__(self, *args, **kwargs):
+ self.settingsfile = kwargs.get('settingsfile', None)
+ self.logfile = kwargs.get('logfile', None)
+ self.error_queue = []
+ self.desired_con_state = None # ???
+
+ status_signals = kwargs.pop('status_signals', None)
+ self.status = EIPConnectionStatus(callbacks=status_signals)
+
+ super(EIPConductor, self).__init__(*args, **kwargs)
+
+ def connect(self):
+ """
+ entry point for connection process
+ """
+ self.manager.forget_errors()
+ self._try_connection()
+ # XXX should capture errors?
+
+ def disconnect(self):
+ """
+ disconnects client
+ """
+ self._disconnect()
+ self.status.change_to(self.status.DISCONNECTED)
+ pass
+
+ def shutdown(self):
+ """
+ shutdown and quit
+ """
+ self.desired_con_state = self.status.DISCONNECTED
+
+ def connection_state(self):
+ """
+ returns the current connection state
+ """
+ return self.status.current
+
+ def desired_connection_state(self):
+ """
+ returns the desired_connection state
+ """
+ return self.desired_con_state
+
+ def poll_connection_state(self):
+ """
+ """
+ try:
+ state = self.manager.get_connection_state()
+ except ConnectionRefusedError:
+ # connection refused. might be not ready yet.
+ return
+ if not state:
+ return
+ (ts, status_step,
+ ok, ip, remote) = state
+ self.status.set_vpn_state(status_step)
+ status_step = self.status.get_readable_status()
+ return (ts, status_step, ok, ip, remote)
+
+ def get_icon_name(self):
+ """
+ get icon name from status object
+ """
+ return self.status.get_state_icon()
+
+ #
+ # private methods
+ #
+
+ def _disconnect(self):
+ """
+ private method for disconnecting
+ """
+ if self.subp is not None:
+ self.subp.terminate()
+ self.subp = None
+ # XXX signal state changes! :)
+
+ def _is_alive(self):
+ """
+ don't know yet
+ """
+ pass
+
+ def _connect(self):
+ """
+ entry point for connection cascade methods.
+ """
+ #conn_result = ConState.DISCONNECTED
+ try:
+ conn_result = self._try_connection()
+ except UnrecoverableError as except_msg:
+ logger.error("FATAL: %s" % unicode(except_msg))
+ conn_result = self.status.UNRECOVERABLE
+ except Exception as except_msg:
+ self.error_queue.append(except_msg)
+ logger.error("Failed Connection: %s" %
+ unicode(except_msg))
+ return conn_result