From f54676330174c311e2decc21dca92ec3f0a69686 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 1 Nov 2017 00:38:56 +0100 Subject: [refactor] refactor status object --- src/leap/bitmask/vpn/_state.py | 98 ++++++++++++++++++++++++++++++++++++++ src/leap/bitmask/vpn/_status.py | 98 -------------------------------------- src/leap/bitmask/vpn/management.py | 97 ++----------------------------------- src/leap/bitmask/vpn/process.py | 95 ++++++++++++++++++++++-------------- src/leap/bitmask/vpn/tunnel.py | 5 +- 5 files changed, 164 insertions(+), 229 deletions(-) create mode 100644 src/leap/bitmask/vpn/_state.py delete mode 100644 src/leap/bitmask/vpn/_status.py (limited to 'src/leap') diff --git a/src/leap/bitmask/vpn/_state.py b/src/leap/bitmask/vpn/_state.py new file mode 100644 index 00000000..53877735 --- /dev/null +++ b/src/leap/bitmask/vpn/_state.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# _state.py +# Copyright (C) 2017 LEAP Encryption Access Project +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# 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 . + +""" +VPN State. +""" + +import time + + +class State(object): + + """ + Possible States in an OpenVPN connection, according to the + OpenVPN Management documentation. + """ + + CONNECTING = 'CONNECTING' + WAIT = 'WAIT' + AUTH = 'AUTH' + GET_CONFIG = 'GET_CONFIG' + ASSIGN_IP = 'ASSIGN_IP' + ADD_ROUTES = 'ADD_ROUTES' + CONNECTED = 'CONNECTED' + RECONNECTING = 'RECONNECTING' + EXITING = 'EXITING' + + OFF = 'OFF' + ON = 'ON' + STARTING = 'STARTING' + STOPPING = 'STOPPING' + FAILED = 'FAILED' + + _legend = { + 'CONNECTING': 'Connecting to remote server', + 'WAIT': 'Waiting from initial response from server', + 'AUTH': 'Authenticating with server', + 'GET_CONFIG': 'Downloading configuration options from server', + 'ASSIGN_IP': 'Assigning IP address to virtual network interface', + 'ADD_ROUTES': 'Adding routes to system', + 'CONNECTED': 'Initialization Sequence Completed', + 'RECONNECTING': 'A restart has occurred', + 'EXITING': 'A graceful exit is in progress', + 'OFF': 'Disconnected', + 'FAILED': 'A failure has occurred', + } + + _simple = { + 'CONNECTING': STARTING, + 'WAIT': STARTING, + 'AUTH': STARTING, + 'GET_CONFIG': STARTING, + 'ASSIGN_IP': STARTING, + 'ADD_ROUTES': STARTING, + 'CONNECTED': ON, + 'RECONNECTING': STARTING, + 'EXITING': STOPPING, + 'OFF': OFF, + 'FAILED': OFF + } + + def __init__(self, state, timestamp): + self.state = state + self.timestamp = timestamp + + @classmethod + def get_legend(cls, state): + return cls._legend.get(state) + + @classmethod + def get_simple(cls, state): + return cls._simple.get(state) + + @property + def simple(self): + return self.get_simple(self.state) + + @property + def legend(self): + return self.get_legend(self.state) + + def __repr__(self): + return '' % ( + self.state, time.ctime(int(self.timestamp))) diff --git a/src/leap/bitmask/vpn/_status.py b/src/leap/bitmask/vpn/_status.py deleted file mode 100644 index 4bf8d004..00000000 --- a/src/leap/bitmask/vpn/_status.py +++ /dev/null @@ -1,98 +0,0 @@ -from itertools import chain, repeat - -from leap.common.events import catalog, emit_async - -from leap.bitmask.vpn._human import bytes2human - -# TODO implement a more explicit state machine -# TODO check good transitions -# TODO check valid states - - -class VPNStatus(object): - """ - A class containing different patterns in the openvpn output that - we can react upon. The patterns drive an internal state machine that can be - queried through the ``status`` property. - """ - - _events = { - 'NETWORK_UNREACHABLE': ( - 'Network is unreachable (code=101)',), - 'PROCESS_RESTART_TLS': ( - "SIGTERM[soft,tls-error]",), - 'INITIALIZATION_COMPLETED': ( - "Initialization Sequence Completed",), - } - _STARTING = ('AUTH', 'WAIT', 'CONNECTING', 'GET_CONFIG', - 'ASSIGN_IP', 'ADD_ROUTES', 'RECONNECTING') - _STOPPING = ("EXITING",) - _CONNECTED = ("CONNECTED",) - - def __init__(self): - self._status = 'off' - self.errcode = None - self._traffic_down = None - self._traffic_up = None - self._chained_iter = chain(*[ - zip(repeat(key, len(l)), l) - for key, l in self._events.iteritems()]) - - def _status_codes(self, event): - - _table = { - "network_unreachable": ('off', 'network unreachable'), - "process_restart_tls": ('starting', 'restart tls'), - "initialization_completed": ('on', None) - } - return _table.get(event.lower()) - - def watch(self, line): - for event, pattern in self._chained_iter: - if pattern in line: - break - else: - return - - status, errcode = self._status_codes(event) - self.set_status(status, errcode) - - @property - def status(self): - status = self.get_traffic_status() - status.update({ - 'status': self._status, - 'error': self.errcode - }) - return status - - def set_status(self, status, errcode): - if not status: - return - - if status in self._STARTING: - status = "starting" - elif status in self._STOPPING: - status = "stopping" - elif status in self._CONNECTED: - status = "on" - - if self._status != status: - self._status = status - self.errcode = errcode - emit_async(catalog.VPN_STATUS_CHANGED, status.upper()) - - def get_traffic_status(self): - down = None - up = None - if self._traffic_down: - down = bytes2human(self._traffic_down) - if self._traffic_up: - up = bytes2human(self._traffic_up) - return {'down': down, 'up': up} - - def set_traffic_status(self, up, down): - if up != self._traffic_up or down != self._traffic_down: - self._traffic_up = up - self._traffic_down = down - emit_async(catalog.VPN_STATUS_CHANGED) diff --git a/src/leap/bitmask/vpn/management.py b/src/leap/bitmask/vpn/management.py index 9b081114..3ab230a6 100644 --- a/src/leap/bitmask/vpn/management.py +++ b/src/leap/bitmask/vpn/management.py @@ -27,15 +27,9 @@ from twisted.protocols.basic import LineReceiver from twisted.internet.defer import Deferred from twisted.logger import Logger -from zope.interface import Interface - from _human import bytes2human - -class IStateListener(Interface): - - def change_state(self, state): - pass +from ._state import State class ManagementProtocol(LineReceiver): @@ -53,23 +47,16 @@ class ManagementProtocol(LineReceiver): self.pid = None self._defs = [] - self._statelog = OrderedDict() self._linebuf = [] self._state_listeners = set([]) def addStateListener(self, listener): """ - A Listener must implement change_state method, + A Listener must implement changeState method, and it will be called with a State object. """ self._state_listeners.add(listener) - # TODO -- this needs to be exposed by the API - # The UI needs this feature. - - def getStateHistory(self): - return self._statelog - def lineReceived(self, line): if self.verbose: if int(self.verbose) > 1: @@ -169,11 +156,9 @@ class ManagementProtocol(LineReceiver): return if state != self.state: - now = time.time() stateobj = State(state, ts) - self._statelog[now] = stateobj for listener in self._state_listeners: - listener.change_state(stateobj) + listener.changeState(stateobj) self.state = stateobj self.remote = remote self.rport = rport @@ -233,91 +218,15 @@ class ManagementProtocol(LineReceiver): return d def getInfo(self): - state = self._statelog.values()[-1] return { 'remote': self.remote, 'rport': self.rport, - 'state': state.state, - 'state_simple': state.simple, - 'state_legend': state.legend, 'openvpn_version': self.openvpn_version, 'pid': self.pid, 'traffic_down_total': self.traffic.down, 'traffic_up_total': self.traffic.up} -class State(object): - - """ - Possible States in an OpenVPN connection, according to the - OpenVPN Management documentation. - """ - - CONNECTING = 'CONNECTING' - WAIT = 'WAIT' - AUTH = 'AUTH' - GET_CONFIG = 'GET_CONFIG' - ASSIGN_IP = 'ASSIGN_IP' - ADD_ROUTES = 'ADD_ROUTES' - CONNECTED = 'CONNECTED' - RECONNECTING = 'RECONNECTING' - EXITING = 'EXITING' - - OFF = 'OFF' - ON = 'ON' - STARTING = 'STARTING' - STOPPING = 'STOPPING' - FAILED = 'FAILED' - - _legend = { - 'CONNECTING': 'Connecting to remote server', - 'WAIT': 'Waiting from initial response from server', - 'AUTH': 'Authenticating with server', - 'GET_CONFIG': 'Downloading configuration options from server', - 'ASSIGN_IP': 'Assigning IP address to virtual network interface', - 'ADD_ROUTES': 'Adding routes to system', - 'CONNECTED': 'Initialization Sequence Completed', - 'RECONNECTING': 'A restart has occurred', - 'EXITING': 'A graceful exit is in progress' - } - - _simple = { - 'CONNECTING': STARTING, - 'WAIT': STARTING, - 'AUTH': STARTING, - 'GET_CONFIG': STARTING, - 'ASSIGN_IP': STARTING, - 'ADD_ROUTES': STARTING, - 'CONNECTED': ON, - 'RECONNECTING': STARTING, - 'EXITING': STOPPING - } - - def __init__(self, state, timestamp): - self.state = state - self.timestamp = timestamp - - @classmethod - def get_legend(cls, state): - return cls._legend.get(state) - - @classmethod - def get_simple(cls, state): - return cls._simple.get(state) - - @property - def simple(self): - return self.get_simple(self.state) - - @property - def legend(self): - return self.get_legend(self.state) - - def __repr__(self): - return '' % ( - self.state, time.ctime(int(self.timestamp))) - - class TrafficCounter(object): CAPACITY = 60 diff --git a/src/leap/bitmask/vpn/process.py b/src/leap/bitmask/vpn/process.py index 862215f8..92a2eedc 100644 --- a/src/leap/bitmask/vpn/process.py +++ b/src/leap/bitmask/vpn/process.py @@ -25,6 +25,8 @@ interface. import os import shutil import sys +import time +from collections import OrderedDict from twisted.internet import protocol, reactor, defer from twisted.internet import error as internet_error @@ -33,26 +35,35 @@ from twisted.logger import Logger from zope.interface import implementer from leap.bitmask.vpn.utils import get_vpn_launcher -from leap.bitmask.vpn.management import ManagementProtocol, IStateListener +from leap.bitmask.vpn.management import ManagementProtocol from leap.bitmask.vpn.launchers import darwin from leap.bitmask.vpn.constants import IS_MAC, IS_LINUX - from leap.common.events import catalog, emit_async +from zope.interface import Interface + +from ._state import State + OPENVPN_VERBOSITY = 4 -@implementer(IStateListener) -class VPNStateListener(object): +class IStateListener(Interface): - # TODO we should move the state history to this class - # and make VPNProcess the implementer itself - or the service. + def changeState(self, state): + pass - def change_state(self, state): - emit_async(catalog.VPN_STATUS_CHANGED, state.simple) + def appendToStateLog(self, state): + pass + + def getState(sef): + pass + + def getStateHistory(self): + pass +@implementer(IStateListener) class _VPNProcess(protocol.ProcessProtocol): """ @@ -102,14 +113,39 @@ class _VPNProcess(protocol.ProcessProtocol): self._providerconfig = providerconfig self._launcher = get_vpn_launcher() self._restartfun = restartfun - self._status = 'off' self.restarting = True self.failed = False self.errmsg = None self.proto = None self._remotes = remotes - self._listener = VPNStateListener() + self._statelog = OrderedDict() + self._turn_state_off() + + def _turn_state_off(self): + ts = time.time() + off = State('OFF', ts) + self.changeState(off) + + # IStateListener methods + + def changeState(self, state): + self.appendToStateLog(state) + emit_async(catalog.VPN_STATUS_CHANGED, state.simple) + + def appendToStateLog(self, state): + ts = state.timestamp + self._statelog[ts] = state + + def getState(self): + if self._statelog.values(): + return self._statelog.values()[-1] + else: + return None + + # TODO -- expose on the API, wanted by UI + def getStateHistory(self): + return self._statelog # processProtocol methods @@ -121,7 +157,7 @@ class _VPNProcess(protocol.ProcessProtocol): @defer.inlineCallbacks def _got_management_protocol(self, proto): self.proto = proto - proto.addStateListener(self._listener) + proto.addStateListener(self) try: yield proto.logOn() @@ -175,6 +211,7 @@ class _VPNProcess(protocol.ProcessProtocol): # TODO: need to exit properly! self.errmsg = None self.proto = None + self._turn_state_off() def processEnded(self, reason): """ @@ -213,32 +250,20 @@ class _VPNProcess(protocol.ProcessProtocol): def status(self): if self.failed: return {'status': 'failed', 'error': self.errmsg} - - # FIXME - hack, doesn't belong here ---------------------------------- - # needs to go with implementing the history within the VPNProcess. - # this only will work before the UI is pulling the state and therefore - # checking this condition as a side-effect of reading the property. - - # TODO trigger off transition when proto dissapears (at processExited) - if not self.proto: - OFF = 'off' - if self._status != OFF: - self._status = OFF - class _state(object): - simple = 'OFF' - off = _state() - self._listener.change_state(off) - return {'status': OFF, 'error': None} - # -------------------------------------------------------------------- - try: - self._status = self.proto.state.simple.lower() - status = {'status': self._status, 'error': None} + state = self.getState() + if state: + _status = state.simple.lower() + status = {'status': _status, 'error': None} except AttributeError: - # glitch due to proto.state transition? - status = {'status': self._status, 'error': None} - - if self.proto.traffic: + raise + # BUG -- glitch due to proto.state transition? + #state = self.getState() + #if state: + # _status = state.simple.lower() + #status = {'status': _status, 'error': None} + + if self.proto and self.proto.traffic: remote = self.proto.remote rport = self.proto.rport status['remote'] = '%s:%s' % (remote, rport) diff --git a/src/leap/bitmask/vpn/tunnel.py b/src/leap/bitmask/vpn/tunnel.py index f6080b85..b003a33c 100644 --- a/src/leap/bitmask/vpn/tunnel.py +++ b/src/leap/bitmask/vpn/tunnel.py @@ -118,8 +118,9 @@ class ConfiguredTunnel(object): vpnproc.pid = running.pid defer.returnValue(True) except Exception as exc: - self._vpnproc.failed = True - self._vpnproc.errmsg = exc.message + if self._vpnproc: + self._vpnproc.failed = True + self._vpnproc.errmsg = exc.message raise def __start_pre_up(self, proc): -- cgit v1.2.3