diff options
| -rw-r--r-- | src/leap/bitmask/cli/command.py | 6 | ||||
| -rw-r--r-- | src/leap/bitmask/vpn/_control.py | 54 | ||||
| -rw-r--r-- | src/leap/bitmask/vpn/_management.py | 32 | ||||
| -rw-r--r-- | src/leap/bitmask/vpn/_observer.py | 73 | ||||
| -rw-r--r-- | src/leap/bitmask/vpn/_status.py | 77 | ||||
| -rw-r--r-- | src/leap/bitmask/vpn/eip.py | 23 | ||||
| -rw-r--r-- | src/leap/bitmask/vpn/fw/firewall.py | 19 | ||||
| -rw-r--r-- | src/leap/bitmask/vpn/manager.py | 33 | ||||
| -rw-r--r-- | src/leap/bitmask/vpn/process.py | 40 | ||||
| -rw-r--r-- | src/leap/bitmask/vpn/service.py | 10 | ||||
| -rw-r--r-- | src/leap/bitmask/vpn/status.py | 42 | ||||
| -rw-r--r-- | src/leap/bitmask/vpn/utils.py | 1 | 
12 files changed, 177 insertions, 233 deletions
| diff --git a/src/leap/bitmask/cli/command.py b/src/leap/bitmask/cli/command.py index ff213a35..a4757f80 100644 --- a/src/leap/bitmask/cli/command.py +++ b/src/leap/bitmask/cli/command.py @@ -44,7 +44,11 @@ def default_dict_printer(result):      for key, value in result.items():          if value is None:              value = str(value) -        print(Fore.RESET + key.ljust(10) + Fore.GREEN + value + Fore.RESET) +        if value in ('OFF', 'OFFLINE', 'ABORTED'): +            color = Fore.RED +        else: +            color = Fore.GREEN +        print(Fore.RESET + key.ljust(10) + color + value + Fore.RESET)  class Command(object): diff --git a/src/leap/bitmask/vpn/_control.py b/src/leap/bitmask/vpn/_control.py index 6e942f48..e05aeed1 100644 --- a/src/leap/bitmask/vpn/_control.py +++ b/src/leap/bitmask/vpn/_control.py @@ -1,5 +1,5 @@ -  import os +import subprocess  from twisted.internet.task import LoopingCall  from twisted.internet import reactor @@ -18,7 +18,7 @@ POLL_TIME = 2.5 if IS_MAC else 1.0  class VPNControl(object):      """ -    This is the high-level object that the service is dealing with. +    This is the high-level object that the service knows about.      It exposes the start and terminate methods.      On start, it spawns a VPNProcess instance that will use a vpnlauncher @@ -37,10 +37,8 @@ class VPNControl(object):          self._vpnproc = None          self._pollers = [] -        self._signaler = kwargs['signaler']          # self._openvpn_verb = flags.OPENVPN_VERBOSITY          self._openvpn_verb = None -          self._user_stopped = False          self._remotes = kwargs['remotes'] @@ -58,7 +56,6 @@ class VPNControl(object):          self._user_stopped = False          self._stop_pollers()          kwargs['openvpn_verb'] = self._openvpn_verb -        kwargs['signaler'] = self._signaler          kwargs['remotes'] = self._remotes          # start the main vpn subprocess @@ -72,15 +69,7 @@ class VPNControl(object):          # errors here are catched, since we currently handle them          # at the frontend layer. This *should* move to be handled entirely          # in the backend. -        # exception is indeed technically catched in backend, then converted -        # into a signal, that is catched in the eip_status widget in the -        # frontend, and converted into a signal to abort the connection that is -        # sent to the backend again. - -        # the whole exception catching should be done in the backend, without -        # the ping-pong to the frontend, and without adding any logical checks -        # in the frontend. We should just communicate UI changes to frontend, -        # and abstract us away from anything else. +          try:              cmd = vpnproc.getCommand()          except Exception as e: @@ -103,9 +92,14 @@ class VPNControl(object):          self._pollers.extend(poll_list)          self._start_pollers() +    @property +    def status(self): +        if not self._vpnproc: +            return 'OFFLINE' +        return self._vpnproc.status + -    # TODO -- rename to stop ?? -    def terminate(self, shutdown=False, restart=False): +    def stop(self, shutdown=False, restart=False):          """          Stops the openvpn subprocess. @@ -136,20 +130,6 @@ class VPNControl(object):          else:              logger.debug("VPN is not running.") - -    # TODO should this be public?? -    def killit(self): -        """ -        Sends a kill signal to the process. -        """ -        self._stop_pollers() -        if self._vpnproc is None: -            logger.debug("There's no vpn process running to kill.") -        else: -            self._vpnproc.aborted = True -            self._vpnproc.killProcess() - -      def bitmask_root_vpn_down(self):          """          Bring openvpn down using the privileged wrapper. @@ -164,6 +144,18 @@ class VPNControl(object):                                      BM_ROOT, "openvpn", "stop"])          return True if exitCode is 0 else False +    def _killit(self): +        """ +        Sends a kill signal to the process. +        """ +        self._stop_pollers() +        if self._vpnproc is None: +            logger.debug("There's no vpn process running to kill.") +        else: +            self._vpnproc.aborted = True +            self._vpnproc.killProcess() + +      def _kill_if_left_alive(self, tries=0):          """ @@ -188,7 +180,7 @@ class VPNControl(object):          # after running out of patience, we try a killProcess          logger.debug("Process did not died. Sending a SIGKILL.")          try: -            self.killit() +            self._killit()          except OSError:              logger.error("Could not kill process!") diff --git a/src/leap/bitmask/vpn/_management.py b/src/leap/bitmask/vpn/_management.py index 920bd9d1..905372b1 100644 --- a/src/leap/bitmask/vpn/_management.py +++ b/src/leap/bitmask/vpn/_management.py @@ -45,16 +45,8 @@ class VPNManagement(object):      # Timers, in secs      CONNECTION_RETRY_TIME = 1 -    def __init__(self, signaler=None): -        """ -        Initializes the VPNManager. - -        :param signaler: Signaler object used to send notifications to the -                         backend -        :type signaler: backend.Signaler -        """ +    def __init__(self):          self._tn = None -        self._signaler = signaler          self.aborted = False      def _seek_to_eof(self): @@ -227,8 +219,8 @@ class VPNManagement(object):      def _parse_state_and_notify(self, output):          """ -        Parses the output of the state command and emits state_changed -        signal when the state changes. +        Parses the output of the state command, and trigger a state transition +        when the state changes.          :param output: list of lines that the state command printed as                         its output @@ -248,9 +240,9 @@ class VPNManagement(object):              state = status_step              if state != self._last_state: -                if self._signaler is not None: -                    self._signaler.signal( -                        self._signaler.eip_state_changed, state) +                # XXX this status object is the vpn status observer +                if self._status: +                    self._status.set_status(state, None)                  self._last_state = state      def _parse_status_and_notify(self, output): @@ -286,12 +278,12 @@ class VPNManagement(object):              elif text == "TUN/TAP write bytes":                  tun_tap_write = value  # upload -        status = (tun_tap_read, tun_tap_write) -        if status != self._last_status: -            if self._signaler is not None: -                self._signaler.signal( -                    self._signaler.eip_status_changed, status) -            self._last_status = status +        traffic_status = (tun_tap_read, tun_tap_write) +        if traffic_status != self._last_status: +            # XXX this status object is the vpn status observer +            if self._status: +                self._status.set_traffic_status(traffic_status) +            self._last_status = traffic_status      def get_state(self):          """ diff --git a/src/leap/bitmask/vpn/_observer.py b/src/leap/bitmask/vpn/_observer.py deleted file mode 100644 index c50a50d9..00000000 --- a/src/leap/bitmask/vpn/_observer.py +++ /dev/null @@ -1,73 +0,0 @@ -from itertools import chain, repeat -from twisted.logger import Logger - -logger = Logger() - - -class VPNObserver(object): -    """ -    A class containing different patterns in the openvpn output that -    we can react upon. -    """ - -    _events = { -        'NETWORK_UNREACHABLE': ( -            'Network is unreachable (code=101)',), -        'PROCESS_RESTART_TLS': ( -            "SIGTERM[soft,tls-error]",), -        'PROCESS_RESTART_PING': ( -            "SIGTERM[soft,ping-restart]",), -        'INITIALIZATION_COMPLETED': ( -            "Initialization Sequence Completed",), -    } - -    def __init__(self, signaler=None): -        self._signaler = signaler - -    def watch(self, line): -        """ -        Inspects line searching for the different patterns. If a match -        is found, try to emit the corresponding signal. - -        :param line: a line of openvpn output -        :type line: str -        """ -        chained_iter = chain(*[ -            zip(repeat(key, len(l)), l) -            for key, l in self._events.iteritems()]) -        for event, pattern in chained_iter: -            if pattern in line: -                logger.debug('pattern matched! %s' % pattern) -                break -        else: -            return - -        sig = self._get_signal(event) -        if sig is not None: -            if self._signaler is not None: -                self._signaler.signal(sig) -            return -        else: -            logger.debug('We got %s event from openvpn output but we could ' -                         'not find a matching signal for it.' % event) - -    def _get_signal(self, event): -        """ -        Tries to get the matching signal from the eip signals -        objects based on the name of the passed event (in lowercase) - -        :param event: the name of the event that we want to get a signal for -        :type event: str -        :returns: a Signaler signal or None -        :rtype: str or None -        """ -        if self._signaler is None: -            return -        sig = self._signaler -        signals = { -            "network_unreachable": sig.eip_network_unreachable, -            "process_restart_tls": sig.eip_process_restart_tls, -            "process_restart_ping": sig.eip_process_restart_ping, -            "initialization_completed": sig.eip_connected -        } -        return signals.get(event.lower()) diff --git a/src/leap/bitmask/vpn/_status.py b/src/leap/bitmask/vpn/_status.py new file mode 100644 index 00000000..11d564fa --- /dev/null +++ b/src/leap/bitmask/vpn/_status.py @@ -0,0 +1,77 @@ +from itertools import chain, repeat +from twisted.logger import Logger + +logger = Logger() + + +# TODO implement a state machine in this class + + +class VPNStatus(object): +    """ +    A class containing different patterns in the openvpn output that +    we can react upon. +    """ + +    _events = { +        'NETWORK_UNREACHABLE': ( +            'Network is unreachable (code=101)',), +        'PROCESS_RESTART_TLS': ( +            "SIGTERM[soft,tls-error]",), +        'PROCESS_RESTART_PING': ( +            "SIGTERM[soft,ping-restart]",), +        'INITIALIZATION_COMPLETED': ( +            "Initialization Sequence Completed",), +    } + +    def __init__(self): +        self.status = 'OFFLINE' +        self._traffic_down = None +        self._traffic_up = None + +    def watch(self, line): +        """ +        Inspects line searching for the different patterns. If a match +        is found, try to emit the corresponding signal. + +        :param line: a line of openvpn output +        :type line: str +        """ +        chained_iter = chain(*[ +            zip(repeat(key, len(l)), l) +            for key, l in self._events.iteritems()]) +        for event, pattern in chained_iter: +            if pattern in line: +                break +        else: +            return + +        status, errcode = self._status_codes(event) +        self.set_status(status, errcode) + + +    def set_status(self, status, errcode): +        self.status = status +        self.errcode = errcode + +    def set_traffic_status(self, status): +        down, up = status +        self._traffic_up = up +        self._traffic_down = down + +    def get_traffic_status(self): +        # XXX return Human readable too +        return {'down': self._traffic_down, +                'up': self._traffic_up} + +    def _status_codes(self, event): +        # TODO check good transitions +        # TODO check valid states + +        _table = { +            "network_unreachable": ('OFFLINE', 'network unreachable'), +            "process_restart_tls": ('RESTARTING', 'restart tls'), +            "process_restart_ping": ('CONNECTING', None), +            "initialization_completed": ('ONLINE', None) +        } +        return _table.get(event.lower()) diff --git a/src/leap/bitmask/vpn/eip.py b/src/leap/bitmask/vpn/eip.py index b080aa65..c2aa4fb3 100644 --- a/src/leap/bitmask/vpn/eip.py +++ b/src/leap/bitmask/vpn/eip.py @@ -20,19 +20,15 @@ from colorama import Fore  from leap.bitmask.vpn.manager import VPNManager  from leap.bitmask.vpn.fw.firewall import FirewallManager -from leap.bitmask.vpn.status import StatusQueue -from leap.bitmask.vpn.zmq_pub import ZMQPublisher  class EIPManager(object):      def __init__(self, remotes, cert, key, ca, flags): +        self._vpn = VPNManager( +            remotes, cert, key, ca, flags)          self._firewall = FirewallManager(remotes) -        self._status_queue = StatusQueue() -        self._pub = ZMQPublisher(self._status_queue) -        self._vpn = VPNManager(remotes, cert, key, ca, flags, -                               self._status_queue)      def start(self):          """ @@ -40,7 +36,6 @@ class EIPManager(object):          This may raise exceptions, see errors.py          """ -        # self._pub.start()          print(Fore.BLUE + "Firewall: starting..." + Fore.RESET)          fw_ok = self._firewall.start()          if not fw_ok: @@ -59,10 +54,6 @@ class EIPManager(object):          print(Fore.GREEN + "VPN: started" + Fore.RESET)      def stop(self): -        """ -        Stop EIP service -        """ -        # self._pub.stop()          print(Fore.BLUE + "Firewall: stopping..." + Fore.RESET)          fw_ok = self._firewall.stop() @@ -81,8 +72,10 @@ class EIPManager(object):          print(Fore.GREEN + "VPN: stopped." + Fore.RESET)          return True -    def get_state(self): -        pass -      def get_status(self): -        pass +        vpn_status = self._vpn.status +        fw_status = self._firewall.status +        result = {'EIP': vpn_status, +                  'firewall': fw_status} +        return result + diff --git a/src/leap/bitmask/vpn/fw/firewall.py b/src/leap/bitmask/vpn/fw/firewall.py index 4335b8e9..5eace20a 100644 --- a/src/leap/bitmask/vpn/fw/firewall.py +++ b/src/leap/bitmask/vpn/fw/firewall.py @@ -13,7 +13,7 @@  # GNU General Public License for more details.  #  # You should have received a copy of the GNU General Public License -# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# alng with this program.  If not, see <http://www.gnu.org/licenses/>.  """  Firewall Manager @@ -44,6 +44,7 @@ class FirewallManager(object):          :param remotes: the gateway(s) that we will allow          :type remotes: list          """ +        self.status = 'OFF'          self._remotes = remotes      def start(self, restart=False): @@ -65,7 +66,13 @@ class FirewallManager(object):          # FIXME -- use a processprotocol          exitCode = subprocess.call(cmd + gateways) -        return True if exitCode is 0 else False + +        if exitCode == 0: +            self.status = 'ON' +            return True +        else: +            self.status = 'OFF' +            return False      # def tear_down_firewall(self):      def stop(self): @@ -78,9 +85,13 @@ class FirewallManager(object):          exitCode = subprocess.call(["pkexec", self.BITMASK_ROOT,                                      "firewall", "stop"]) -        return True if exitCode is 0 else False +        if exitCode == 0: +            self.status = 'OFF' +            return True +        else: +            self.status = 'ON' +            return False -    # def is_fw_down(self):      def is_up(self):          """          Return whether the firewall is up or not. diff --git a/src/leap/bitmask/vpn/manager.py b/src/leap/bitmask/vpn/manager.py index a27789c3..04e88097 100644 --- a/src/leap/bitmask/vpn/manager.py +++ b/src/leap/bitmask/vpn/manager.py @@ -33,8 +33,7 @@ from .constants import IS_WIN  class VPNManager(object): -    def __init__(self, remotes, cert_path, key_path, ca_path, extra_flags, -                 mock_signaler): +    def __init__(self, remotes, cert_path, key_path, ca_path, extra_flags):          """          Initialize the VPNManager object. @@ -51,8 +50,7 @@ class VPNManager(object):          self._eipconfig = _TempEIPConfig(extra_flags, cert_path, ports)          self._providerconfig = _TempProviderConfig(domain, ca_path) -        signaler = None  # XXX handle signaling somehow... -        self._vpn = VPNControl(remotes=remotes, signaler=signaler) +        self._vpn = VPNControl(remotes=remotes)      def start(self): @@ -82,34 +80,15 @@ class VPNManager(object):          :returns: True if succeeded, False otherwise.          :rtype: bool          """ -        self._vpn.terminate(False, False)  # TODO review params -          # TODO how to return False if this fails +        self._vpn.stop(False, False)  # TODO review params          return True -    def is_up(self): -        """ -        Return whether the VPN is up or not. - -        :rtype: bool -        """ -        pass - -    def kill(self): -        """ -        Sends a kill signal to the openvpn process. -        """ -        pass -        # self._vpn.killit() -    def terminate(self): -        """ -        Stop the openvpn subprocess. +    @property +    def status(self): +        return self._vpn.status -        Attempts to send a SIGTERM first, and after a timeout it sends a -        SIGKILL. -        """ -        pass      def _get_management_location(self):          """ diff --git a/src/leap/bitmask/vpn/process.py b/src/leap/bitmask/vpn/process.py index 813025d7..185ba624 100644 --- a/src/leap/bitmask/vpn/process.py +++ b/src/leap/bitmask/vpn/process.py @@ -17,6 +17,7 @@  """  VPN Process management. +  A custom processProtocol launches the VPNProcess and connects to its management  interface.  """ @@ -39,7 +40,7 @@ from leap.bitmask.vpn.utils import first, force_eval  from leap.bitmask.vpn.utils import get_vpn_launcher  from leap.bitmask.vpn.launchers import linux  from leap.bitmask.vpn._telnet import UDSTelnet -from leap.bitmask.vpn import _observer +from leap.bitmask.vpn import _status  from leap.bitmask.vpn import _management  logger = Logger() @@ -57,7 +58,7 @@ class VPNProcess(protocol.ProcessProtocol, _management.VPNManagement):      """      def __init__(self, eipconfig, providerconfig, socket_host, socket_port, -                 signaler, openvpn_verb, remotes): +                 openvpn_verb, remotes):          """          :param eipconfig: eip configuration object          :type eipconfig: EIPConfig @@ -72,15 +73,11 @@ class VPNProcess(protocol.ProcessProtocol, _management.VPNManagement):                              socket, or port otherwise          :type socket_port: str -        :param signaler: Signaler object used to receive notifications to the -                         backend -        :type signaler: backend.Signaler -          :param openvpn_verb: the desired level of verbosity in the                               openvpn invocation          :type openvpn_verb: int          """ -        _management.VPNManagement.__init__(self, signaler=signaler) +        _management.VPNManagement.__init__(self)          self._eipconfig = eipconfig          self._providerconfig = providerconfig @@ -97,11 +94,15 @@ class VPNProcess(protocol.ProcessProtocol, _management.VPNManagement):          # the parameter around.          self._openvpn_verb = openvpn_verb -        self._vpn_observer = _observer.VPNObserver(signaler) +        self._status = _status.VPNStatus()          self.is_restart = False          self._remotes = remotes +    @property +    def status(self): +        return self._status.status +      # processProtocol methods      def connectionMade(self): @@ -125,20 +126,27 @@ class VPNProcess(protocol.ProcessProtocol, _management.VPNManagement):          # truncate the newline          line = data[:-1]          logger.info(line) -        self._vpn_observer.watch(line) +        self._status.watch(line) -    def processExited(self, reason): +    def processExited(self, failure):          """          Called when the child process exits.          .. seeAlso: `http://twistedmatrix.com/documents/13.0.0/api/twisted.internet.protocol.ProcessProtocol.html` # noqa          """ -        exit_code = reason.value.exitCode -        if isinstance(exit_code, int): -            logger.debug("processExited, status %d" % (exit_code,)) -        if self._signaler is not None: -            self._signaler.signal( -                self._signaler.eip_process_finished, exit_code) +        err = failure.trap( +            internet_error.ProcessDone, internet_error.ProcessTerminated) + +        if err == internet_error.ProcessDone: +            status, errmsg = 'OFFLINE', None +        elif err == internet_error.ProcessTerminated: +            status, errmsg = 'ABORTED', failure.value.exitCode +            if errmsg: +                logger.debug("processExited, status %d" % (errmsg,)) +            else: +                logger.warn('%r' % failure.value) + +        self._status.set_status(status, errmsg)          self._alive = False      def processEnded(self, reason): diff --git a/src/leap/bitmask/vpn/service.py b/src/leap/bitmask/vpn/service.py index 3edae352..170c5afd 100644 --- a/src/leap/bitmask/vpn/service.py +++ b/src/leap/bitmask/vpn/service.py @@ -45,6 +45,7 @@ class EIPService(HookableService):          super(EIPService, self).__init__()          self._started = False +        self._eip = None          if basepath is None:              self._basepath = get_path_prefix() @@ -74,8 +75,11 @@ class EIPService(HookableService):              return {'result': 'stopped'}      def do_status(self): -        # TODO -- get status from a dedicated STATUS CLASS -        return {'result': 'running'} +        if self._eip: +            status = self._eip.get_status() +        else: +            status = {'EIP': 'OFF'} +        return status      def do_check(self):          """Check whether the EIP Service is properly configured, @@ -100,7 +104,7 @@ class EIPService(HookableService):              os.makedirs(cert_dir, mode=0700)          with open(cert_path, 'w') as outf:              outf.write(cert_str) -        check_and_fix_urw_only(cert_path) +        heck_and_fix_urw_only(cert_path)          defer.returnValue({'get_cert': 'ok'})      def do_install(self): diff --git a/src/leap/bitmask/vpn/status.py b/src/leap/bitmask/vpn/status.py deleted file mode 100644 index ff7f3111..00000000 --- a/src/leap/bitmask/vpn/status.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Queue used to store status changes on EIP/VPN/Firewall and to be checked for -any app using this vpn library. - -This should be considered a temporary code meant to replace the signaling -system that announces events inside of vpn code and is catched on the bitmask -client. -""" - -import Queue - - -class StatusQueue(object): -    def __init__(self): -        self._status = Queue.Queue() - -        # this attributes serve to simulate events in the old signaler used -        self.eip_network_unreachable = "network_unreachable" -        self.eip_process_restart_tls = "process_restart_tls" -        self.eip_process_restart_ping = "process_restart_ping" -        self.eip_connected = "initialization_completed" -        self.eip_status_changed = "status_changed"  # has parameter -        self.eip_state_changed = "state_changed"  # has parameter -        self.eip_process_finished = "process_finished"  # has parameter - -    def get_noblock(self): -        s = None -        try: -            s = self._status.get(False) -        except Queue.Empty: -            pass - -        return s - -    def get(self): -        return self._status.get(timeout=1) - -    def signal(self, status, data=None): -        self._status.put({'status': status, 'data': data}) diff --git a/src/leap/bitmask/vpn/utils.py b/src/leap/bitmask/vpn/utils.py index b0ffa128..8fa51783 100644 --- a/src/leap/bitmask/vpn/utils.py +++ b/src/leap/bitmask/vpn/utils.py @@ -19,7 +19,6 @@  Common utils  """ -import os  def force_eval(items):      """ | 
