summaryrefslogtreecommitdiff
path: root/src/leap/base
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/base')
-rw-r--r--src/leap/base/checks.py107
-rw-r--r--src/leap/base/constants.py2
-rw-r--r--src/leap/base/exceptions.py23
-rw-r--r--src/leap/base/network.py73
-rw-r--r--src/leap/base/tests/test_checks.py116
5 files changed, 321 insertions, 0 deletions
diff --git a/src/leap/base/checks.py b/src/leap/base/checks.py
new file mode 100644
index 00000000..a775e162
--- /dev/null
+++ b/src/leap/base/checks.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+
+import logging
+import platform
+
+import ping
+import requests
+
+from leap.base import constants
+from leap.base import exceptions
+
+logger = logging.getLogger(name=__name__)
+
+class LeapNetworkChecker(object):
+ """
+ all network related checks
+ """
+ # TODO eventually, use a more portable solution
+ # like psutil
+
+ def run_all(self, checker=None):
+ if not checker:
+ checker = self
+ self.error = None # ?
+
+ # for MVS
+ checker.check_tunnel_default_interface()
+ checker.check_internet_connection()
+ checker.is_internet_up()
+ checker.ping_gateway()
+
+ def check_internet_connection(self):
+ try:
+ # XXX remove this hardcoded random ip
+ requests.get('http://216.172.161.165')
+ except (requests.HTTPError, requests.RequestException) as e:
+ raise exceptions.NoInternetConnection(e.message)
+ except requests.ConnectionError as e:
+ error = "Unidentified Connection Error"
+ if e.message == "[Errno 113] No route to host":
+ if not self.is_internet_up():
+ error = "No valid internet connection found."
+ else:
+ error = "Provider server appears to be down."
+ raise exceptions.NoInternetConnection(error)
+ logger.debug('Network appears to be up.')
+
+ def is_internet_up(self):
+ iface, gateway = self.get_default_interface_gateway()
+ self.ping_gateway(self)
+
+ def check_tunnel_default_interface(self):
+ """
+ Raises an TunnelNotDefaultRouteError
+ (including when no routes are present)
+ """
+ if not platform.system() == "Linux":
+ raise NotImplementedError
+
+ f = open("/proc/net/route")
+ route_table = f.readlines()
+ f.close()
+ #toss out header
+ route_table.pop(0)
+
+ if not route_table:
+ raise exceptions.TunnelNotDefaultRouteError()
+
+ line = route_table.pop(0)
+ iface, destination = line.split('\t')[0:2]
+ if not destination == '00000000' or not iface == 'tun0':
+ raise exceptions.TunnelNotDefaultRouteError()
+
+
+ def get_default_interface_gateway(self):
+ """only impletemented for linux so far."""
+ if not platform.system() == "Linux":
+ raise NotImplementedError
+
+ f = open("/proc/net/route")
+ route_table = f.readlines()
+ f.close()
+ #toss out header
+ route_table.pop(0)
+
+ default_iface = None
+ gateway = None
+ while route_table:
+ line = route_table.pop(0)
+ iface, destination, gateway = line.split('\t')[0:3]
+ if destination == '00000000':
+ default_iface = iface
+ break
+
+ if not default_iface:
+ raise exceptions.NoDefaultInterfaceFoundError
+
+ if default_iface not in netifaces.interfaces():
+ raise exceptions.InterfaceNotFoundError
+
+ return default_iface, gateway
+
+ def ping_gateway(self, gateway):
+ #TODO: Discuss how much packet loss (%) is acceptable.
+ packet_loss = ping.quiet_ping(gateway)[0]
+ if packet_loss > constants.MAX_ICMP_PACKET_LOSS:
+ raise exceptions.NoConnectionToGateway
diff --git a/src/leap/base/constants.py b/src/leap/base/constants.py
index 7a1415fb..8a76b6b4 100644
--- a/src/leap/base/constants.py
+++ b/src/leap/base/constants.py
@@ -28,3 +28,5 @@ DEFAULT_PROVIDER_DEFINITION = {
u'version': u'0.1.0'}
MAX_ICMP_PACKET_LOSS = 10
+
+ROUTE_CHECK_INTERVAL = 120
diff --git a/src/leap/base/exceptions.py b/src/leap/base/exceptions.py
index 9c4aa77b..48d827f5 100644
--- a/src/leap/base/exceptions.py
+++ b/src/leap/base/exceptions.py
@@ -4,3 +4,26 @@ class MissingConfigFileError(Exception):
class ImproperlyConfigured(Exception):
pass
+
+
+class NoDefaultInterfaceFoundError(Exception):
+ message = "no default interface found"
+ usermessage = "Looks like your computer is not connected to the internet"
+
+
+class InterfaceNotFoundError(Exception):
+ # XXX should take iface arg on init maybe?
+ message = "interface not found"
+
+
+class NoConnectionToGateway(Exception):
+ message = "no connection to gateway"
+ usermessage = "Looks like there are problems with your internet connection"
+
+
+class NoInternetConnection(Exception):
+ message = "No Internet connection found"
+
+
+class TunnelNotDefaultRouteError(Exception):
+ message = "VPN Maybe be down."
diff --git a/src/leap/base/network.py b/src/leap/base/network.py
new file mode 100644
index 00000000..a1e7c880
--- /dev/null
+++ b/src/leap/base/network.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+from __future__ import (print_function)
+import logging
+import threading
+
+from leap.base.checks import LeapNetworkChecker
+from leap.base.constants import ROUTE_CHECK_INTERVAL
+from leap.base.exceptions import TunnelNotDefaultRouteError
+from leap.util.coroutines import (launch_thread_no_daemon, process_events)
+
+from time import sleep
+
+logger = logging.getLogger(name=__name__)
+
+
+class NetworkChecker(object):
+ """
+ Manages network checking thread that makes sure we have a working network
+ connection.
+ """
+ def __init__(self, *args, **kwargs):
+ self.status_signals = kwargs.pop('status_signals', None)
+ self.watcher_cb = kwargs.pop('status_signals', None)
+ self.excp_logger = lambda exc: logger.error("%s", exc.message)
+ self.checker = LeapNetworkChecker()
+
+ def start(self):
+ self.process_handle = self._launch_recurrent_network_checks((self.excp_logger,))
+
+ def stop(self):
+ #TODO: Thread still not being stopped when openvpn is stopped.
+ logger.debug("stopping network checker...")
+ self.process_handle._Thread__stop()
+ logger.debug("network checked stopped.")
+
+ def run_checks(self):
+ pass
+
+ #private methods
+
+ #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:
+ try:
+ self.checker.check_tunnel_default_interface()
+ break
+ except TunnelNotDefaultRouteError:
+ sleep(1)
+
+ observer_dict = dict(((
+ observer, process_events(observer)) for observer in fail_callbacks))
+ while True:
+ try:
+ self.checker.check_tunnel_default_interface()
+ self.checker.check_internet_connection()
+ sleep(ROUTE_CHECK_INTERVAL)
+ except Exception as exc:
+ for obs in observer_dict:
+ observer_dict[obs].send(exc)
+ sleep(ROUTE_CHECK_INTERVAL)
+
+
+ def _launch_recurrent_network_checks(self, fail_callbacks):
+ #we need to wrap the fail callback in a turple
+ watcher = launch_thread_no_daemon(
+ self._network_checks_thread,
+ (fail_callbacks,))
+ return watcher
+
+
diff --git a/src/leap/base/tests/test_checks.py b/src/leap/base/tests/test_checks.py
new file mode 100644
index 00000000..30746991
--- /dev/null
+++ b/src/leap/base/tests/test_checks.py
@@ -0,0 +1,116 @@
+try:
+ import unittest2 as unittest
+except ImportError:
+ import unittest
+import os
+
+from mock import (patch, Mock)
+from StringIO import StringIO
+
+import ping
+import requests
+
+from leap.base import checks
+from leap.base import exceptions
+from leap.testing.basetest import BaseLeapTest
+
+_uid = os.getuid()
+
+
+class LeapNetworkCheckTest(BaseLeapTest):
+ __name__ = "leap_network_check_tests"
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def test_checker_should_implement_check_methods(self):
+ checker = checks.LeapNetworkChecker()
+
+ self.assertTrue(hasattr(checker, "check_internet_connection"),
+ "missing meth")
+ self.assertTrue(hasattr(checker, "check_tunnel_default_interface"),
+ "missing meth")
+ self.assertTrue(hasattr(checker, "is_internet_up"),
+ "missing meth")
+ self.assertTrue(hasattr(checker, "ping_gateway"),
+ "missing meth")
+
+ def test_checker_should_actually_call_all_tests(self):
+ checker = checks.LeapNetworkChecker()
+
+ mc = Mock()
+ checker.run_all(checker=mc)
+ self.assertTrue(mc.check_internet_connection.called, "not called")
+ 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")
+
+ def test_get_default_interface_no_interface(self):
+ checker = checks.LeapNetworkChecker()
+ with patch('leap.base.checks.open', create=True) as mock_open:
+ with self.assertRaises(exceptions.NoDefaultInterfaceFoundError):
+ mock_open.return_value = StringIO(
+ "Iface\tDestination Gateway\t"
+ "Flags\tRefCntd\tUse\tMetric\t"
+ "Mask\tMTU\tWindow\tIRTT")
+ checker.get_default_interface_gateway()
+
+ def test_check_tunnel_default_interface(self):
+ checker = checks.LeapNetworkChecker()
+ with patch('leap.base.checks.open', create=True) as mock_open:
+ with self.assertRaises(exceptions.TunnelNotDefaultRouteError):
+ mock_open.return_value = StringIO(
+ "Iface\tDestination Gateway\t"
+ "Flags\tRefCntd\tUse\tMetric\t"
+ "Mask\tMTU\tWindow\tIRTT")
+ checker.check_tunnel_default_interface()
+
+ with patch('leap.base.checks.open', create=True) as mock_open:
+ with self.assertRaises(exceptions.TunnelNotDefaultRouteError):
+ mock_open.return_value = StringIO(
+ "Iface\tDestination Gateway\t"
+ "Flags\tRefCntd\tUse\tMetric\t"
+ "Mask\tMTU\tWindow\tIRTT\n"
+ "wlan0\t00000000\t0102A8C0\t0003\t0\t0\t0\t00000000\t0\t0\t0")
+ checker.check_tunnel_default_interface()
+
+ with patch('leap.base.checks.open', create=True) as mock_open:
+ mock_open.return_value = StringIO(
+ "Iface\tDestination Gateway\t"
+ "Flags\tRefCntd\tUse\tMetric\t"
+ "Mask\tMTU\tWindow\tIRTT\n"
+ "tun0\t00000000\t01002A0A\t0003\t0\t0\t0\t00000080\t0\t0\t0")
+ checker.check_tunnel_default_interface()
+
+ def test_ping_gateway_fail(self):
+ checker = checks.LeapNetworkChecker()
+ with patch.object(ping, "quiet_ping") as mocked_ping:
+ with self.assertRaises(exceptions.NoConnectionToGateway):
+ mocked_ping.return_value = [11, "", ""]
+ checker.ping_gateway("4.2.2.2")
+
+ def test_check_internet_connection_failures(self):
+ checker = checks.LeapNetworkChecker()
+ with patch.object(requests, "get") as mocked_get:
+ mocked_get.side_effect = requests.HTTPError
+ with self.assertRaises(exceptions.NoInternetConnection):
+ checker.check_internet_connection()
+
+ with patch.object(requests, "get") as mocked_get:
+ mocked_get.side_effect = requests.RequestException
+ with self.assertRaises(exceptions.NoInternetConnection):
+ checker.check_internet_connection()
+
+ #TODO: Mock possible errors that can be raised by is_internet_up
+ with patch.object(requests, "get") as mocked_get:
+ mocked_get.side_effect = requests.ConnectionError
+ with self.assertRaises(exceptions.NoInternetConnection):
+ checker.check_internet_connection()
+
+ @unittest.skipUnless(_uid == 0, "root only")
+ def test_ping_gateway(self):
+ checker = checks.LeapNetworkChecker()
+ checker.ping_gateway("4.2.2.2")