summaryrefslogtreecommitdiff
path: root/src/leap/bitmask/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/bitmask/backend')
-rw-r--r--src/leap/bitmask/backend/api.py6
-rw-r--r--src/leap/bitmask/backend/backend.py40
-rw-r--r--src/leap/bitmask/backend/backend_proxy.py67
-rw-r--r--src/leap/bitmask/backend/components.py7
-rw-r--r--src/leap/bitmask/backend/leapbackend.py10
-rw-r--r--src/leap/bitmask/backend/leapsignaler.py1
-rw-r--r--src/leap/bitmask/backend/utils.py3
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(...)