diff options
| -rw-r--r-- | src/leap/base/checks.py | 23 | ||||
| -rw-r--r-- | src/leap/base/network.py | 17 | ||||
| -rw-r--r-- | src/leap/base/tests/test_checks.py | 38 | ||||
| -rw-r--r-- | src/leap/baseapp/eip.py | 6 | ||||
| -rw-r--r-- | src/leap/baseapp/log.py | 6 | ||||
| -rw-r--r-- | src/leap/baseapp/network.py | 24 | ||||
| -rw-r--r-- | src/leap/eip/eipconnection.py | 6 | ||||
| -rw-r--r-- | src/leap/eip/openvpnconnection.py | 30 | 
8 files changed, 127 insertions, 23 deletions
| diff --git a/src/leap/base/checks.py b/src/leap/base/checks.py index 4d4a5d8b..e5767018 100644 --- a/src/leap/base/checks.py +++ b/src/leap/base/checks.py @@ -12,6 +12,9 @@ from leap.base import exceptions  logger = logging.getLogger(name=__name__) +#EVENTS OF NOTE +EVENT_CONNECT_REFUSED = "[ECONNREFUSED]: Connection refused (code=111)" +  class LeapNetworkChecker(object):      """ @@ -34,6 +37,8 @@ class LeapNetworkChecker(object):          if self.provider_gateway:              checker.ping_gateway(self.provider_gateway) +        checker.parse_log_and_react([], ()) +      def check_internet_connection(self):          try:              # XXX remove this hardcoded random ip @@ -136,3 +141,21 @@ class LeapNetworkChecker(object):              return True          except socket.gaierror:              raise exceptions.CannotResolveDomainError + +    def parse_log_and_react(self, log, error_matrix=None): +        """ +        compares the recent openvpn status log to +        strings passed in and executes the callbacks passed in. +        @param log: openvpn log +        @type log: list of strings +        @param error_matrix: tuples of strings and tuples of callbacks +        @type error_matrix: tuples strings and call backs +        """ +        for line in log: +            # we could compile a regex here to save some cycles up -- kali +            for each in error_matrix: +                error, callbacks = each +                if error in line: +                    for cb in callbacks: +                        if callable(cb): +                            cb() diff --git a/src/leap/base/network.py b/src/leap/base/network.py index 765d8ea0..d841e692 100644 --- a/src/leap/base/network.py +++ b/src/leap/base/network.py @@ -21,8 +21,8 @@ class NetworkCheckerThread(object):      connection.      """      def __init__(self, *args, **kwargs): +          self.status_signals = kwargs.pop('status_signals', None) -        #self.watcher_cb = kwargs.pop('status_signals', None)          self.error_cb = kwargs.pop(              'error_cb',              lambda exc: logger.error("%s", exc.message)) @@ -48,6 +48,7 @@ class NetworkCheckerThread(object):              (self.error_cb,))      def stop(self): +        self.process_handle.join(timeout=0.1)          self.shutdown.set()          logger.debug("network checked stopped.") @@ -59,6 +60,7 @@ class NetworkCheckerThread(object):      #here all the observers in fail_callbacks expect one positional argument,      #which is exception so we can try by passing a lambda with logger to      #check it works. +      def _network_checks_thread(self, fail_callbacks):          #TODO: replace this with waiting for a signal from openvpn          while True: @@ -69,11 +71,15 @@ class NetworkCheckerThread(object):                  # XXX ??? why do we sleep here???                  # aa: If the openvpn isn't up and running yet,                  # let's give it a moment to breath. +                #logger.error('NOT DEFAULT ROUTE!----') +                # Instead of this, we should flag when the +                # iface IS SUPPOSED to be up imo. -- kali                  sleep(1)          fail_observer_dict = dict(((              observer,              process_events(observer)) for observer in fail_callbacks)) +          while not self.shutdown.is_set():              try:                  self.checker.check_tunnel_default_interface() @@ -83,11 +89,18 @@ class NetworkCheckerThread(object):                  for obs in fail_observer_dict:                      fail_observer_dict[obs].send(exc)                  sleep(ROUTE_CHECK_INTERVAL) +          #reset event +        # I see a problem with this. You cannot stop it, it +        # resets itself forever. -- kali + +        # XXX use QTimer for the recurrent triggers, +        # and ditch the sleeps. +        logger.debug('resetting event')          self.shutdown.clear()      def _launch_recurrent_network_checks(self, fail_callbacks): -        #we need to wrap the fail callback in a tuple +        # XXX reimplement using QTimer -- kali          watcher = launch_thread(              self._network_checks_thread,              (fail_callbacks,)) diff --git a/src/leap/base/tests/test_checks.py b/src/leap/base/tests/test_checks.py index 7a694f89..645e615c 100644 --- a/src/leap/base/tests/test_checks.py +++ b/src/leap/base/tests/test_checks.py @@ -37,6 +37,8 @@ class LeapNetworkCheckTest(BaseLeapTest):                          "missing meth")          self.assertTrue(hasattr(checker, "ping_gateway"),                          "missing meth") +        self.assertTrue(hasattr(checker, "parse_log_and_react"), +                        "missing meth")      def test_checker_should_actually_call_all_tests(self):          checker = checks.LeapNetworkChecker() @@ -45,6 +47,7 @@ class LeapNetworkCheckTest(BaseLeapTest):          self.assertTrue(mc.check_internet_connection.called, "not called")          self.assertTrue(mc.check_tunnel_default_interface.called, "not called")          self.assertTrue(mc.is_internet_up.called, "not called") +        self.assertTrue(mc.parse_log_and_react.called, "not called")          # ping gateway only called if we pass provider_gw          checker = checks.LeapNetworkChecker(provider_gw="0.0.0.0") @@ -54,6 +57,7 @@ class LeapNetworkCheckTest(BaseLeapTest):          self.assertTrue(mc.check_tunnel_default_interface.called, "not called")          self.assertTrue(mc.ping_gateway.called, "not called")          self.assertTrue(mc.is_internet_up.called, "not called") +        self.assertTrue(mc.parse_log_and_react.called, "not called")      def test_get_default_interface_no_interface(self):          checker = checks.LeapNetworkChecker() @@ -134,6 +138,40 @@ class LeapNetworkCheckTest(BaseLeapTest):                      mock_ping.side_effect = exceptions.NoConnectionToGateway                      checker.check_internet_connection() +    def test_parse_log_and_react(self): +        checker = checks.LeapNetworkChecker() +        to_call = Mock() +        log = [("leap.openvpn - INFO - Mon Nov 19 13:36:24 2012 " +                "read UDPv4 [ECONNREFUSED]: Connection refused (code=111)"] +        err_matrix = [(checks.EVENT_CONNECT_REFUSED, (to_call, ))] +        checker.parse_log_and_react(log, err_matrix) +        self.assertTrue(to_call.called) + +        log = [("2012-11-19 13:36:26,177 - leap.openvpn - INFO - " +                "Mon Nov 19 13:36:24 2012 ERROR: Linux route delete command " +                "failed: external program exited"), +               ("2012-11-19 13:36:26,178 - leap.openvpn - INFO - " +                "Mon Nov 19 13:36:24 2012 ERROR: Linux route delete command " +                "failed: external program exited"), +               ("2012-11-19 13:36:26,180 - leap.openvpn - INFO - " +                "Mon Nov 19 13:36:24 2012 ERROR: Linux route delete command " +                "failed: external program exited"), +               ("2012-11-19 13:36:26,181 - leap.openvpn - INFO - " +                "Mon Nov 19 13:36:24 2012 /sbin/ifconfig tun0 0.0.0.0"), +               ("2012-11-19 13:36:26,182 - leap.openvpn - INFO - " +                "Mon Nov 19 13:36:24 2012 Linux ip addr del failed: external " +                "program exited with error stat"), +               ("2012-11-19 13:36:26,183 - leap.openvpn - INFO - " +                "Mon Nov 19 13:36:26 2012 SIGTERM[hard,] received, process" +                "exiting"), ] +        to_call.reset_mock() +        checker.parse_log_and_react(log, err_matrix) +        self.assertFalse(to_call.called) + +        to_call.reset_mock() +        checker.parse_log_and_react([], err_matrix) +        self.assertFalse(to_call.called) +      @unittest.skipUnless(_uid == 0, "root only")      def test_ping_gateway(self):          checker = checks.LeapNetworkChecker() diff --git a/src/leap/baseapp/eip.py b/src/leap/baseapp/eip.py index 41f4c541..4fcbee3f 100644 --- a/src/leap/baseapp/eip.py +++ b/src/leap/baseapp/eip.py @@ -9,6 +9,7 @@ from leap.baseapp.dialogs import ErrorDialog  from leap.baseapp import constants  from leap.eip import exceptions as eip_exceptions  from leap.eip.eipconnection import EIPConnection +from leap.base.checks import EVENT_CONNECT_REFUSED  logger = logging.getLogger(name=__name__) @@ -174,6 +175,11 @@ class EIPConductorAppMixin(object):              self.tun_read_bytes.setText(tun_read)              self.tun_write_bytes.setText(tun_write) +        # connection information via management interface +        log = self.conductor.get_log() +        error_matrix = [(EVENT_CONNECT_REFUSED, (self.start_or_stopVPN, ))] +        self.network_checker.checker.parse_log_and_react(log, error_matrix) +      @QtCore.pyqtSlot()      def start_or_stopVPN(self):          """ diff --git a/src/leap/baseapp/log.py b/src/leap/baseapp/log.py index 95cfc918..e6a767fb 100644 --- a/src/leap/baseapp/log.py +++ b/src/leap/baseapp/log.py @@ -11,6 +11,7 @@ class LogPaneMixin(object):      a simple log pane      that writes new lines as they come      """ +    EXCLUDES = ('MANAGEMENT',)      def createLogBrowser(self):          """ @@ -60,6 +61,7 @@ class LogPaneMixin(object):          simple slot: writes new line to logger Pane.          """          msg = line[:-1] -        if self.debugmode: +        if self.debugmode and all(map(lambda w: w not in msg, +                                      LogPaneMixin.EXCLUDES)):              self.logbrowser.append(msg) -        vpnlogger.info(msg) +            vpnlogger.info(msg) diff --git a/src/leap/baseapp/network.py b/src/leap/baseapp/network.py index a33265e5..a67f6340 100644 --- a/src/leap/baseapp/network.py +++ b/src/leap/baseapp/network.py @@ -17,6 +17,8 @@ class NetworkCheckerAppMixin(object):      initialize an instance of the Network Checker,      which gathers error and passes them on.      """ +    ERR_NETERR = False +      def __init__(self, *args, **kwargs):          provider = kwargs.pop('provider', None)          if provider: @@ -41,11 +43,19 @@ class NetworkCheckerAppMixin(object):          slot that receives a network exceptions          and raises a user error message          """ -        logger.debug('handling network exception') -        logger.error(exc.message) -        dialog = ErrorDialog(parent=self) +        # FIXME this should not HANDLE anything after +        # the network check thread has been stopped. -        if exc.critical: -            dialog.criticalMessage(exc.usermessage, "network error") -        else: -            dialog.warningMessage(exc.usermessage, "network error") +        logger.debug('handling network exception') +        if not self.ERR_NETERR: +            self.ERR_NETERR = True + +            logger.error(exc.message) +            dialog = ErrorDialog(parent=self) +            if exc.critical: +                dialog.criticalMessage(exc.usermessage, "network error") +            else: +                dialog.warningMessage(exc.usermessage, "network error") + +            self.start_or_stopVPN() +            self.network_checker.stop() diff --git a/src/leap/eip/eipconnection.py b/src/leap/eip/eipconnection.py index 540e7558..20b45e36 100644 --- a/src/leap/eip/eipconnection.py +++ b/src/leap/eip/eipconnection.py @@ -27,6 +27,8 @@ class StatusMixIn(object):      # Should separate EIPConnectionStatus (self.status)      # from the OpenVPN state/status command and parsing. +    ERR_CONNREFUSED = False +      def connection_state(self):          """          returns the current connection state @@ -49,7 +51,9 @@ class StatusMixIn(object):              state = self.get_connection_state()          except eip_exceptions.ConnectionRefusedError:              # connection refused. might be not ready yet. -            logger.warning('connection refused') +            if not self.ERR_CONNREFUSED: +                logger.warning('connection refused') +                self.ERR_CONNREFUSED = True              return          if not state:              #logger.debug('no state') diff --git a/src/leap/eip/openvpnconnection.py b/src/leap/eip/openvpnconnection.py index b36b0b16..a36d99de 100644 --- a/src/leap/eip/openvpnconnection.py +++ b/src/leap/eip/openvpnconnection.py @@ -92,14 +92,17 @@ class OpenVPNManagement(object):              logger.error('socket error')              self._close_management_socket(announce=False)              return [] -        buf = self.tn.read_until(b"END", 2) -        self._seek_to_eof() -        blist = buf.split('\r\n') -        if blist[-1].startswith('END'): -            del blist[-1] -            return blist -        else: -            return [] +        try: +            buf = self.tn.read_until(b"END", 2) +            self._seek_to_eof() +            blist = buf.split('\r\n') +            if blist[-1].startswith('END'): +                del blist[-1] +                return blist +            else: +                return [] +        except socket.error as exc: +            logger.debug('socket error: %s' % exc.message)      def _send_short_command(self, cmd):          """ @@ -329,12 +332,12 @@ to be triggered for each one of them.          #use _only_ signal_maps instead          logger.debug('_launch_openvpn called') -        logger.debug('watcher_cb: %s' % self.watcher_cb)          if self.watcher_cb is not None:              linewrite_callback = self.watcher_cb          else:              #XXX get logger instead -            linewrite_callback = lambda line: logger.debug('watcher: %s' % line) +            linewrite_callback = lambda line: logger.debug( +                    'watcher: %s' % line)          # the partial is not          # being applied now because we're not observing the process @@ -342,7 +345,8 @@ to be triggered for each one of them.          # here since it will be handy for observing patterns in the          # thru-the-manager updates (with regex)          observers = (linewrite_callback, -                     partial(lambda con_status, line: linewrite_callback, self.status)) +                     partial(lambda con_status, +                             line: linewrite_callback, self.status))          subp, watcher = spawn_and_watch_process(              self.command,              self.args, @@ -400,3 +404,7 @@ to be triggered for each one of them.              if process.name == "openvpn":                  return process          return None + +    def get_log(self, lines=1): +        log = self._send_command("log %s" % lines) +        return log | 
