summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKali Kaneko (leap communications) <kali@leap.se>2017-02-04 17:01:25 +0100
committerKali Kaneko (leap communications) <kali@leap.se>2017-02-23 00:40:37 +0100
commit701fe5ebc70fb49bb32e81e6d6605f27ad09925b (patch)
tree0763df29131f3abe4604fc3626f51ed4360b5ff1
parent409a4c663ec3c0b4a394fcaa6d4b1c6b527f8522 (diff)
[feature] parse status
- simple status parsing - add separate firewall status - set status for abnormal termination
-rw-r--r--src/leap/bitmask/cli/command.py6
-rw-r--r--src/leap/bitmask/vpn/_control.py54
-rw-r--r--src/leap/bitmask/vpn/_management.py32
-rw-r--r--src/leap/bitmask/vpn/_observer.py73
-rw-r--r--src/leap/bitmask/vpn/_status.py77
-rw-r--r--src/leap/bitmask/vpn/eip.py23
-rw-r--r--src/leap/bitmask/vpn/fw/firewall.py19
-rw-r--r--src/leap/bitmask/vpn/manager.py33
-rw-r--r--src/leap/bitmask/vpn/process.py40
-rw-r--r--src/leap/bitmask/vpn/service.py10
-rw-r--r--src/leap/bitmask/vpn/status.py42
-rw-r--r--src/leap/bitmask/vpn/utils.py1
12 files changed, 177 insertions, 233 deletions
diff --git a/src/leap/bitmask/cli/command.py b/src/leap/bitmask/cli/command.py
index ff213a3..a4757f8 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 6e942f4..e05aeed 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 920bd9d..905372b 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 c50a50d..0000000
--- 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 0000000..11d564f
--- /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 b080aa6..c2aa4fb 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 4335b8e..5eace20 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 a27789c..04e8809 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 813025d..185ba62 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 3edae35..170c5af 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 ff7f311..0000000
--- 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 b0ffa12..8fa5178 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):
"""