diff options
Diffstat (limited to 'src/leap/bitmask/backend')
| -rw-r--r-- | src/leap/bitmask/backend/api.py | 6 | ||||
| -rw-r--r-- | src/leap/bitmask/backend/backend.py | 40 | ||||
| -rw-r--r-- | src/leap/bitmask/backend/backend_proxy.py | 67 | ||||
| -rw-r--r-- | src/leap/bitmask/backend/components.py | 7 | ||||
| -rw-r--r-- | src/leap/bitmask/backend/leapbackend.py | 10 | ||||
| -rw-r--r-- | src/leap/bitmask/backend/leapsignaler.py | 1 | ||||
| -rw-r--r-- | src/leap/bitmask/backend/utils.py | 3 | 
7 files changed, 119 insertions, 15 deletions
diff --git a/src/leap/bitmask/backend/api.py b/src/leap/bitmask/backend/api.py index b8533f36..3f6c0ad1 100644 --- a/src/leap/bitmask/backend/api.py +++ b/src/leap/bitmask/backend/api.py @@ -18,10 +18,14 @@  Backend available API and SIGNALS definition.  """  STOP_REQUEST = "stop" +PING_REQUEST = "PING" + +# XXX this needs documentation. What is it used for?  API = (      STOP_REQUEST,  # this method needs to be defined in order to support the                     # backend stop action +    PING_REQUEST,      "eip_can_start",      "eip_cancel_setup", @@ -54,6 +58,7 @@ API = (      "soledad_close",      "soledad_load_offline",      "tear_fw_down", +    "bitmask_root_vpn_down",      "user_cancel_login",      "user_change_password",      "user_get_logged_in_status", @@ -95,6 +100,7 @@ SIGNALS = (      "eip_status_changed",      "eip_stopped",      "eip_tear_fw_down", +    "eip_bitmask_root_vpn_down",      "eip_uninitialized_provider",      "eip_vpn_launcher_exception",      "imap_stopped", diff --git a/src/leap/bitmask/backend/backend.py b/src/leap/bitmask/backend/backend.py index 833f4368..37535f37 100644 --- a/src/leap/bitmask/backend/backend.py +++ b/src/leap/bitmask/backend/backend.py @@ -14,16 +14,23 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# FIXME this is missing module documentation. It would be fine to say a couple +# of lines about the whole backend architecture. +# TODO use txzmq bindings instead. +  import json  import threading  import time +import psutil +  from twisted.internet import defer, reactor, threads  import zmq  from zmq.auth.thread import ThreadAuthenticator -from leap.bitmask.backend.api import API +from leap.bitmask.backend.api import API, PING_REQUEST  from leap.bitmask.backend.utils import get_backend_certificates  from leap.bitmask.backend.signaler import Signaler @@ -36,15 +43,23 @@ class Backend(object):      Backend server.      Receives signals from backend_proxy and emit signals if needed.      """ +    # XXX this should not be hardcoded. Make it configurable.      PORT = '5556' + +    # XXX we might want to make this configurable per-platform, +    # and use the most performant socket type on each one.      BIND_ADDR = "tcp://127.0.0.1:%s" % PORT -    def __init__(self): +    PING_INTERVAL = 2  # secs + +    def __init__(self, frontend_pid=None):          """          Backend constructor, create needed instances.          """          self._signaler = Signaler() +        self._frontend_pid = frontend_pid +          self._do_work = threading.Event()  # used to stop the worker thread.          self._zmq_socket = None @@ -61,6 +76,7 @@ class Backend(object):          # Start an authenticator for this context.          auth = ThreadAuthenticator(context)          auth.start() +        # XXX do not hardcode this here.          auth.allow('127.0.0.1')          # Tell authenticator to use the certificate in a directory @@ -81,6 +97,8 @@ class Backend(object):          Note: we use a simple while since is less resource consuming than a          Twisted's LoopingCall.          """ +        pid = self._frontend_pid +        check_wait = 0          while self._do_work.is_set():              # Wait for next request from client              try: @@ -93,6 +111,20 @@ class Backend(object):                      raise              time.sleep(0.01) +            check_wait += 0.01 +            if pid is not None and check_wait > self.PING_INTERVAL: +                check_wait = 0 +                self._check_frontend_alive() + +    def _check_frontend_alive(self): +        """ +        Check if the frontend is alive and stop the backend if it is not. +        """ +        pid = self._frontend_pid +        if pid is not None and not psutil.pid_exists(pid): +            logger.critical("The frontend is down!") +            self.stop() +      def _stop_reactor(self):          """          Stop the Twisted reactor, but first wait a little for some threads to @@ -146,6 +178,10 @@ class Backend(object):          :param request_json: a json specification of a request.          :type request_json: str          """ +        if request_json == PING_REQUEST: +            # do not process request if it's just a ping +            return +          try:              # request = zmq.utils.jsonapi.loads(request_json)              # We use stdlib's json to ensure that we get unicode strings diff --git a/src/leap/bitmask/backend/backend_proxy.py b/src/leap/bitmask/backend/backend_proxy.py index f683e465..e2611251 100644 --- a/src/leap/bitmask/backend/backend_proxy.py +++ b/src/leap/bitmask/backend/backend_proxy.py @@ -18,6 +18,8 @@  The BackendProxy handles calls from the GUI and forwards (through ZMQ)  to the backend.  """ +# XXX should document the relationship to the API here. +  import functools  import Queue  import threading @@ -25,7 +27,7 @@ import time  import zmq -from leap.bitmask.backend.api import API, STOP_REQUEST +from leap.bitmask.backend.api import API, STOP_REQUEST, PING_REQUEST  from leap.bitmask.backend.utils import get_backend_certificates  import logging @@ -37,9 +39,15 @@ class BackendProxy(object):      The BackendProxy handles calls from the GUI and forwards (through ZMQ)      to the backend.      """ +      PORT = '5556'      SERVER = "tcp://localhost:%s" % PORT +    POLL_TIMEOUT = 4000  # ms +    POLL_TRIES = 3 + +    PING_INTERVAL = 2  # secs +      def __init__(self):          self._socket = None @@ -62,6 +70,9 @@ class BackendProxy(object):          socket.connect(self.SERVER)          self._socket = socket +        self._ping_at = 0 +        self.online = False +          self._call_queue = Queue.Queue()          self._worker_caller = threading.Thread(target=self._worker)          self._worker_caller.start() @@ -82,9 +93,26 @@ class BackendProxy(object):              except Queue.Empty:                  pass              time.sleep(0.01) +            self._ping()          logger.debug("BackendProxy worker stopped.") +    def _reset_ping(self): +        """ +        Reset the ping timeout counter. +        This is called for every ping and request. +        """ +        self._ping_at = time.time() + self.PING_INTERVAL + +    def _ping(self): +        """ +        Heartbeat helper. +        Sends a PING request just to know that the server is alive. +        """ +        if time.time() > self._ping_at: +            self._send_request(PING_REQUEST) +            self._reset_ping() +      def _api_call(self, *args, **kwargs):          """          Call the `api_method` method in backend (through zmq). @@ -134,16 +162,35 @@ class BackendProxy(object):          # logger.debug("Sending request to backend: {0}".format(request))          self._socket.send(request) -        try: -            # Get the reply. -            self._socket.recv() -            # response = self._socket.recv() -            # msg = "Received reply for '{0}' -> '{1}'" -            # msg = msg.format(request, response) -            # logger.debug(msg) -        except zmq.error.Again as e: -            msg = "Timeout error contacting backend. {0!r}".format(e) +        poll = zmq.Poller() +        poll.register(self._socket, zmq.POLLIN) + +        reply = None +        tries = 0 + +        while True: +            socks = dict(poll.poll(self.POLL_TIMEOUT)) +            if socks.get(self._socket) == zmq.POLLIN: +                reply = self._socket.recv() +                break + +            tries += 1 +            if tries < self.POLL_TRIES: +                logger.warning('Retrying receive... {0}/{1}'.format( +                    tries, self.POLL_TRIES)) +            else: +                break + +        if reply is None: +            msg = "Timeout error contacting backend."              logger.critical(msg) +            self.online = False +        else: +            # msg = "Received reply for '{0}' -> '{1}'".format(request, reply) +            # logger.debug(msg) +            self.online = True +            # request received, no ping needed for other interval. +            self._reset_ping()      def __getattribute__(self, name):          """ diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index b372db89..f721086b 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -69,7 +69,6 @@ class ILEAPComponent(zope.interface.Interface):      """      Interface that every component for the backend should comply to      """ -      key = zope.interface.Attribute("Key id for this component") @@ -552,6 +551,12 @@ class EIP(object):          """          self._vpn.tear_down_firewall() +    def bitmask_root_vpn_down(self): +        """ +        Bring openvpn down, using bitmask-root helper. +        """ +        self._vpn.bitmask_root_vpn_down() +      def get_gateways_list(self, domain):          """          Signal a list of gateways for the given provider. diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py index d3c4fcda..3b023563 100644 --- a/src/leap/bitmask/backend/leapbackend.py +++ b/src/leap/bitmask/backend/leapbackend.py @@ -36,11 +36,11 @@ class LeapBackend(Backend):      """      Backend server subclass, used to implement the API methods.      """ -    def __init__(self, bypass_checks=False): +    def __init__(self, bypass_checks=False, frontend_pid=None):          """          Constructor for the backend.          """ -        Backend.__init__(self) +        Backend.__init__(self, frontend_pid)          self._settings = Settings() @@ -317,6 +317,12 @@ class LeapBackend(Backend):          """          self._eip.tear_fw_down() +    def bitmask_root_vpn_down(self): +        """ +        Signal the need to bring vpn down. +        """ +        self._eip.bitmask_root_vpn_down() +      def user_login(self, provider, username, password):          """          Execute the whole authentication process for a user diff --git a/src/leap/bitmask/backend/leapsignaler.py b/src/leap/bitmask/backend/leapsignaler.py index a36e6fdc..c0fdffdc 100644 --- a/src/leap/bitmask/backend/leapsignaler.py +++ b/src/leap/bitmask/backend/leapsignaler.py @@ -58,6 +58,7 @@ class LeapSignaler(SignalerQt):      eip_status_changed = QtCore.Signal(dict)      eip_stopped = QtCore.Signal()      eip_tear_fw_down = QtCore.Signal(object) +    eip_bitmask_root_vpn_down = QtCore.Signal(object)      eip_uninitialized_provider = QtCore.Signal()      eip_vpn_launcher_exception = QtCore.Signal() diff --git a/src/leap/bitmask/backend/utils.py b/src/leap/bitmask/backend/utils.py index 54a16fd7..65bf6753 100644 --- a/src/leap/bitmask/backend/utils.py +++ b/src/leap/bitmask/backend/utils.py @@ -19,6 +19,7 @@ Backend utilities to handle ZMQ certificates.  """  import os  import shutil +import stat  import zmq.auth @@ -36,6 +37,8 @@ def generate_certificates():      if os.path.exists(KEYS_DIR):          shutil.rmtree(KEYS_DIR)      mkdir_p(KEYS_DIR) +    # set permissions to: 0700 (U:rwx G:--- O:---) +    os.chmod(KEYS_DIR, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)      # create new keys in certificates dir      # public_file, secret_file = create_certificates(...)  | 
