summaryrefslogtreecommitdiff
path: root/src/leap/bitmask/vpn
diff options
context:
space:
mode:
authorKali Kaneko <kali@leap.se>2017-11-01 00:38:56 +0100
committerKali Kaneko <kali@leap.se>2017-11-01 01:40:46 +0100
commitf54676330174c311e2decc21dca92ec3f0a69686 (patch)
tree9cad4207ede97f01649713b442669b3e282d77e9 /src/leap/bitmask/vpn
parent55630c58c072d4f55852629d22cff80aa5835f72 (diff)
[refactor] refactor status object
Diffstat (limited to 'src/leap/bitmask/vpn')
-rw-r--r--src/leap/bitmask/vpn/_state.py98
-rw-r--r--src/leap/bitmask/vpn/_status.py98
-rw-r--r--src/leap/bitmask/vpn/management.py97
-rw-r--r--src/leap/bitmask/vpn/process.py95
-rw-r--r--src/leap/bitmask/vpn/tunnel.py5
5 files changed, 164 insertions, 229 deletions
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 <http://www.gnu.org/licenses/>.
+
+"""
+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 '<State: %s [%s]>' % (
+ 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 '<State: %s [%s]>' % (
- 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):