summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2013-06-14 14:06:22 -0300
committerTomás Touceda <chiiph@leap.se>2013-06-14 14:06:22 -0300
commit625a2d672885f5c86b1908c0b5a147188a82bb5d (patch)
tree48c5b8f86c1543fdb10ec5d4f7488432bb18fd00
parent614bb802c11cee04de8a2e42c6666fa899ca0b33 (diff)
parentfdda95c92d22b4ede61bfb7587320ddb36da5cd7 (diff)
Merge remote-tracking branch 'kali/feature/terminate_openvpn_rev1' into develop
-rw-r--r--changes/feature_terminate_openvpn2
-rw-r--r--src/leap/app.py9
-rw-r--r--src/leap/gui/mainwindow.py40
-rw-r--r--src/leap/services/eip/vpnprocess.py141
4 files changed, 143 insertions, 49 deletions
diff --git a/changes/feature_terminate_openvpn b/changes/feature_terminate_openvpn
new file mode 100644
index 00000000..e7a4b724
--- /dev/null
+++ b/changes/feature_terminate_openvpn
@@ -0,0 +1,2 @@
+ o Cleanly terminate openvpn process, sending SIGTERM and SIGKILL after a while.
+ Closes #2753
diff --git a/src/leap/app.py b/src/leap/app.py
index 05b54d1f..cb9951c1 100644
--- a/src/leap/app.py
+++ b/src/leap/app.py
@@ -138,11 +138,14 @@ def main():
app.setApplicationName("leap")
app.setOrganizationDomain("leap.se")
+ # XXX ---------------------------------------------------------
+ # In quarantine, looks like we don't need it anymore.
# This dummy timer ensures that control is given to the outside
# loop, so we can hook our sigint handler.
- timer = QtCore.QTimer()
- timer.start(500)
- timer.timeout.connect(lambda: None)
+ #timer = QtCore.QTimer()
+ #timer.start(500)
+ #timer.timeout.connect(lambda: None)
+ # XXX ---------------------------------------------------------
window = MainWindow(
lambda: twisted_main.quit(app),
diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py
index 80876c27..51b96463 100644
--- a/src/leap/gui/mainwindow.py
+++ b/src/leap/gui/mainwindow.py
@@ -185,11 +185,11 @@ class MainWindow(QtGui.QMainWindow):
self._eip_bootstrapper.download_client_certificate.connect(
self._finish_eip_bootstrap)
- self._soledad_bootstrapper = SoledadBootstrapper()
- self._soledad_bootstrapper.download_config.connect(
- self._soledad_intermediate_stage)
- self._soledad_bootstrapper.gen_key.connect(
- self._soledad_bootstrapped_stage)
+ #self._soledad_bootstrapper = SoledadBootstrapper()
+ #self._soledad_bootstrapper.download_config.connect(
+ #self._soledad_intermediate_stage)
+ #self._soledad_bootstrapper.gen_key.connect(
+ #self._soledad_bootstrapped_stage)
self._smtp_bootstrapper = SMTPBootstrapper()
self._smtp_bootstrapper.download_config.connect(
@@ -808,11 +808,12 @@ class MainWindow(QtGui.QMainWindow):
self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX)
- self._soledad_bootstrapper.run_soledad_setup_checks(
- self._provider_config,
- self._login_widget.get_user(),
- self._login_widget.get_password(),
- download_if_needed=True)
+ # XXX disabling soledad for now
+ #self._soledad_bootstrapper.run_soledad_setup_checks(
+ #self._provider_config,
+ #self._login_widget.get_user(),
+ #self._login_widget.get_password(),
+ #download_if_needed=True)
self._download_eip_config()
@@ -992,19 +993,21 @@ class MainWindow(QtGui.QMainWindow):
self._status_panel.set_startstop_enabled(True)
def _stop_eip(self):
+ """
+ Stops vpn process and makes gui adjustments to reflect
+ the change of state.
+ """
self._vpn.terminate()
+
self._status_panel.set_eip_status(self.tr("Off"))
self._status_panel.set_eip_status_icon("error")
-
self._status_panel.eip_stopped()
-
self._action_eip_startstop.setText(self.tr("Turn ON"))
self._action_eip_startstop.disconnect(self)
self._action_eip_startstop.triggered.connect(
self._start_eip)
self._already_started_eip = False
self._settings.set_defaultprovider(None)
-
if self._logged_user:
self._status_panel.set_provider(
"%s@%s" % (self._logged_user,
@@ -1202,12 +1205,12 @@ class MainWindow(QtGui.QMainWindow):
"""
logger.debug('About to quit, doing cleanup...')
- logger.debug('Killing vpn')
- self._vpn.terminate()
-
logger.debug('Cleaning pidfiles')
self._cleanup_pidfiles()
+ logger.debug('Terminating vpn')
+ self._vpn.terminate()
+
def quit(self):
"""
Cleanup and tidely close the main window before quitting.
@@ -1221,13 +1224,14 @@ class MainWindow(QtGui.QMainWindow):
if self._logger_window:
self._logger_window.close()
- self.close()
-
if self._login_defer:
self._login_defer.cancel()
+ self.close()
+
if self._quit_callback:
self._quit_callback()
+
logger.debug('Bye.')
diff --git a/src/leap/services/eip/vpnprocess.py b/src/leap/services/eip/vpnprocess.py
index eae8aadd..162dc7f0 100644
--- a/src/leap/services/eip/vpnprocess.py
+++ b/src/leap/services/eip/vpnprocess.py
@@ -20,6 +20,8 @@ VPN Manager, spawned in a custom processProtocol.
import logging
import os
import psutil
+import socket
+import time
from PySide import QtCore
@@ -63,6 +65,9 @@ class VPN(object):
opened by the openvpn process, executing commands over that interface on
demand.
"""
+ TERMINATE_MAXTRIES = 10
+ TERMINATE_WAIT = 1 # secs
+
def __init__(self):
"""
Instantiate empty attributes and get a copy
@@ -94,6 +99,10 @@ class VPN(object):
# start the main vpn subprocess
vpnproc = VPNProcess(*args, **kwargs)
+ # XXX Should stop if already running -------
+ if vpnproc.get_openvpn_process():
+ logger.warning("Another vpnprocess is running!")
+
cmd = vpnproc.getCommand()
env = os.environ
for key, val in vpnproc.vpn_env.items():
@@ -103,7 +112,7 @@ class VPN(object):
self._vpnproc = vpnproc
# add pollers for status and state
- # XXX this could be extended to a collection of
+ # this could be extended to a collection of
# generic watchers
poll_list = [LoopingCall(vpnproc.pollStatus),
@@ -111,15 +120,50 @@ class VPN(object):
self._pollers.extend(poll_list)
self._start_pollers()
+ def _kill_if_left_alive(self, tries=0):
+ """
+ Check if the process is still alive, and sends a
+ SIGKILL after a timeout period.
+
+ :param tries: counter of tries, used in recursion
+ :type tries: int
+ """
+ from twisted.internet import reactor
+ while tries < self.TERMINATE_MAXTRIES:
+ if self._vpnproc.transport.pid is None:
+ logger.debug("Process has been happily terminated.")
+ return
+ else:
+ logger.debug("Process did not die, waiting...")
+ tries += 1
+ reactor.callLater(self.TERMINATE_WAIT,
+ self._kill_if_left_alive, tries)
+
+ # after running out of patience, we try a killProcess
+ logger.debug("Process did not died. Sending a SIGKILL.")
+ self._vpnproc.killProcess()
+
def terminate(self):
"""
Stops the openvpn subprocess.
+
+ Attempts to send a SIGTERM first, and after a timeout
+ it sends a SIGKILL.
"""
+ from twisted.internet import reactor
self._stop_pollers()
- # XXX we should leave a KILL as a last resort.
- # First we should try to send a SIGTERM
+
+ # First we try to be polite and send a SIGTERM...
if self._vpnproc:
- self._vpnproc.killProcess()
+ self._sentterm = True
+ self._vpnproc.terminate_openvpn()
+
+ # ...but we also trigger a countdown to be unpolite
+ # if strictly needed.
+ reactor.callLater(
+ self.TERMINATE_WAIT, self._kill_if_left_alive)
+
+ # TODO: should also cleanup tempfiles!!!
def _start_pollers(self):
"""
@@ -148,6 +192,10 @@ class VPNManager(object):
A copy of a QObject containing signals as attributes is passed along
upon initialization, and we use that object to emit signals to qt-land.
+
+ For more info about management methods::
+
+ zcat `dpkg -L openvpn | grep management`
"""
# Timers, in secs
@@ -183,15 +231,15 @@ class VPNManager(object):
def qtsigs(self):
return self._qtsigs
- def _disconnect(self):
+ def _seek_to_eof(self):
"""
- Disconnects the telnet connection to the openvpn process.
+ Read as much as available. Position seek pointer to end of stream
"""
- logger.debug('Closing socket')
- self._tn.write("quit\n")
- self._tn.read_all()
- self._tn.close()
- self._tn = None
+ try:
+ self._tn.read_eager()
+ except EOFError:
+ logger.debug("Could not read from socket. Assuming it died.")
+ return
def _send_command(self, command, until=b"END"):
"""
@@ -208,12 +256,24 @@ class VPNManager(object):
:rtype: list
"""
leap_assert(self._tn, "We need a tn connection!")
+
try:
self._tn.write("%s\n" % (command,))
buf = self._tn.read_until(until, 2)
- self._tn.read_eager()
- lines = buf.split("\n")
- return lines
+ self._seek_to_eof()
+ blist = buf.split('\r\n')
+ if blist[-1].startswith(until):
+ del blist[-1]
+ return blist
+ else:
+ return []
+
+ except socket.error:
+ # XXX should get a counter and repeat only
+ # after mod X times.
+ logger.warning('socket error')
+ self._close_management_socket(announce=False)
+ return []
# XXX should move this to a errBack!
except Exception as e:
@@ -221,9 +281,21 @@ class VPNManager(object):
(command, e))
return []
- def _connect(self, socket_host, socket_port):
+ def _close_management_socket(self, announce=True):
+ """
+ Close connection to openvpn management interface.
+ """
+ logger.debug('closing socket')
+ if announce:
+ self._tn.write("quit\n")
+ self._tn.read_all()
+ self._tn.get_socket().close()
+ del self._tn
+
+ def _connect_management(self, socket_host, socket_port):
"""
- Connects to the specified socket_host socket_port.
+ Connects to the management interface on the specified
+ socket_host socket_port.
:param socket_host: either socket path (unix) or socket IP
:type socket_host: str
@@ -232,6 +304,9 @@ class VPNManager(object):
socket, or port otherwise
:type socket_port: str
"""
+ if self.is_connected():
+ self._close_management_socket()
+
try:
self._tn = UDSTelnet(socket_host, socket_port)
@@ -268,7 +343,7 @@ class VPNManager(object):
"""
logger.warning(failure)
- def connect(self, host, port):
+ def connect_to_management(self, host, port):
"""
Connect to a management interface.
@@ -280,7 +355,8 @@ class VPNManager(object):
:returns: a deferred
"""
- self.connectd = defer.maybeDeferred(self._connect, host, port)
+ self.connectd = defer.maybeDeferred(
+ self._connect_management, host, port)
self.connectd.addCallbacks(self._connectCb, self._connectErr)
return self.connectd
@@ -293,7 +369,7 @@ class VPNManager(object):
"""
return True if self._tn else False
- def try_to_connect(self, retry=0):
+ def try_to_connect_to_management(self, retry=0):
"""
Attempts to connect to a management interface, and retries
after CONNECTION_RETRY_TIME if not successful.
@@ -304,9 +380,10 @@ class VPNManager(object):
# TODO decide about putting a max_lim to retries and signaling
# an error.
if not self.is_connected():
- self.connect(self._socket_host, self._socket_port)
+ self.connect_to_management(self._socket_host, self._socket_port)
self._reactor.callLater(
- self.CONNECTION_RETRY_TIME, self.try_to_connect, retry + 1)
+ self.CONNECTION_RETRY_TIME,
+ self.try_to_connect_to_management, retry + 1)
def _parse_state_and_notify(self, output):
"""
@@ -405,9 +482,17 @@ class VPNManager(object):
"""
return self._launcher.get_vpn_env(self._providerconfig)
+ def terminate_openvpn(self):
+ """
+ Attempts to terminate openvpn by sending a SIGTERM.
+ """
+ if self.is_connected():
+ self._send_command("signal SIGTERM")
+
+ # ---------------------------------------------------
# XXX old methods, not adapted to twisted process yet
- def _get_openvpn_process(self):
+ def get_openvpn_process(self):
"""
Looks for openvpn instances running.
@@ -421,7 +506,7 @@ class VPNManager(object):
# we should check that cmdline BEGINS
# with openvpn or with our wrapper
# (pkexec / osascript / whatever)
- if self._launcher.OPENVPN_BIN in ' '.join(p.cmdline):
+ if "openvpn" in ' '.join(p.cmdline):
openvpn_process = p
break
except psutil.error.AccessDenied:
@@ -434,10 +519,10 @@ class VPNManager(object):
:return: True if stopped, False otherwise
"""
-
+ # TODO cleanup this
process = self._get_openvpn_process()
if process:
- logger.debug("OpenVPN is already running, trying to stop it")
+ logger.debug("OpenVPN is already running, trying to stop it...")
cmdline = process.cmdline
manag_flag = "--management"
@@ -448,11 +533,11 @@ class VPNManager(object):
port = cmdline[index + 2]
logger.debug("Trying to connect to %s:%s"
% (host, port))
- self._connect(host, port)
+ self._connect_to_management(host, port)
self._send_command("signal SIGTERM")
self._tn.close()
self._tn = None
- #self._disconnect()
+ #self._disconnect_management()
except Exception as e:
logger.warning("Problem trying to terminate OpenVPN: %r"
% (e,))
@@ -518,7 +603,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):
.. seeAlso: `http://twistedmatrix.com/documents/13.0.0/api/twisted.internet.protocol.ProcessProtocol.html` # noqa
"""
- self.try_to_connect()
+ self.try_to_connect_to_management()
def outReceived(self, data):
"""