diff options
Diffstat (limited to 'src/leap/base/checks.py')
-rw-r--r-- | src/leap/base/checks.py | 198 |
1 files changed, 136 insertions, 62 deletions
diff --git a/src/leap/base/checks.py b/src/leap/base/checks.py index 84f9dd46..0bf44f59 100644 --- a/src/leap/base/checks.py +++ b/src/leap/base/checks.py @@ -1,122 +1,171 @@ # -*- coding: utf-8 -*- import logging import platform +import re +import socket import netifaces -import ping -import requests +import sh from leap.base import constants from leap.base import exceptions logger = logging.getLogger(name=__name__) +_platform = platform.system() + +#EVENTS OF NOTE +EVENT_CONNECT_REFUSED = "[ECONNREFUSED]: Connection refused (code=111)" + +ICMP_TARGET = "8.8.8.8" class LeapNetworkChecker(object): """ all network related checks """ - # #718 - # XXX get provider gateway as a parameter - # for constructor. - # def __init__(self, *args, **kwargs): - # ... - # provider_gw = kwargs.pop('provider_gw', None) - # self.provider_gateway = provider_gw + def __init__(self, *args, **kwargs): + provider_gw = kwargs.pop('provider_gw', None) + self.provider_gateway = provider_gw def run_all(self, checker=None): if not checker: checker = self - self.error = None # ? + #self.error = None # ? # for MVS checker.check_tunnel_default_interface() checker.check_internet_connection() checker.is_internet_up() - # XXX We are pinging the default gateway for our connection right? - # kali: 2012-10-05 20:59 -- I think we should get - # also the default gateway and ping it instead. - checker.ping_gateway() + if self.provider_gateway: + checker.ping_gateway(self.provider_gateway) - # something like: ? - # see __init__ above - # 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 - # ping leap.se or eip provider instead...? - 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 _platform == "Linux": + try: + output = sh.ping("-c", "5", "-w", "5", ICMP_TARGET) + # XXX should redirect this to netcheck logger. + # and don't clutter main log. + logger.debug('Network appears to be up.') + except sh.ErrorReturnCode_1 as e: + packet_loss = re.findall("\d+% packet loss", e.message)[0] + logger.debug("Unidentified Connection Error: " + packet_loss) if not self.is_internet_up(): error = "No valid internet connection found." else: error = "Provider server appears to be down." - logger.error(error) - 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) + logger.error(error) + raise exceptions.NoInternetConnection(error) - def check_tunnel_default_interface(self): - """ - Raises an TunnelNotDefaultRouteError - (including when no routes are present) - """ - if not platform.system() == "Linux": + else: raise NotImplementedError + def is_internet_up(self): + iface, gateway = self.get_default_interface_gateway() + try: + self.ping_gateway(self.provider_gateway) + except exceptions.NoConnectionToGateway: + return False + return True + + def _get_route_table_linux(self): + # do not use context manager, tests pass a StringIO 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() + raise exceptions.NoDefaultInterfaceFoundError + return route_table + def _get_def_iface_osx(self): + default_iface = None + #gateway = None + routes = list(sh.route('-n', 'get', ICMP_TARGET, _iter=True)) + iface = filter(lambda l: "interface" in l, routes) + if not iface: + return None, None + def_ifacel = re.findall('\w+\d', iface[0]) + default_iface = def_ifacel[0] if def_ifacel else None + if not default_iface: + return None, None + _gw = filter(lambda l: "gateway" in l, routes) + gw = re.findall('\d+\.\d+\.\d+\.\d+', _gw[0])[0] + return default_iface, gw + + def _get_tunnel_iface_linux(self): + # XXX review. + # valid also when local router has a default entry? + route_table = self._get_route_table_linux() line = route_table.pop(0) iface, destination = line.split('\t')[0:2] if not destination == '00000000' or not iface == 'tun0': raise exceptions.TunnelNotDefaultRouteError() + return True - def get_default_interface_gateway(self): - """only impletemented for linux so far.""" - if not platform.system() == "Linux": + def check_tunnel_default_interface(self): + """ + Raises an TunnelNotDefaultRouteError + if tun0 is not the chosen default route + (including when no routes are present) + """ + #logger.debug('checking tunnel default interface...') + + if _platform == "Linux": + valid = self._get_tunnel_iface_linux() + return valid + elif _platform == "Darwin": + default_iface, gw = self._get_def_iface_osx() + #logger.debug('iface: %s', default_iface) + if default_iface != "tun0": + logger.debug('tunnel not default route! gw: %s', default_iface) + # XXX should catch this and act accordingly... + # but rather, this test should only be launched + # when we have successfully completed a connection + # ... TRIGGER: Connection stablished (or whatever it is) + # in the logs + raise exceptions.TunnelNotDefaultRouteError + else: + #logger.debug('PLATFORM !!! %s', _platform) raise NotImplementedError - # XXX use psutil - f = open("/proc/net/route") - route_table = f.readlines() - f.close() - #toss out header - route_table.pop(0) - + def _get_def_iface_linux(self): default_iface = None gateway = None + + route_table = self._get_route_table_linux() while route_table: line = route_table.pop(0) iface, destination, gateway = line.split('\t')[0:3] if destination == '00000000': default_iface = iface break + return default_iface, gateway + + def get_default_interface_gateway(self): + """ + gets the interface we are going thru. + (this should be merged with check tunnel default interface, + imo...) + """ + if _platform == "Linux": + default_iface, gw = self._get_def_iface_linux() + elif _platform == "Darwin": + default_iface, gw = self._get_def_iface_osx() + else: + raise NotImplementedError if not default_iface: raise exceptions.NoDefaultInterfaceFoundError if default_iface not in netifaces.interfaces(): raise exceptions.InterfaceNotFoundError - - return default_iface, gateway + logger.debug('-- default iface %s', default_iface) + return default_iface, gw def ping_gateway(self, gateway): # TODO: Discuss how much packet loss (%) is acceptable. @@ -125,15 +174,40 @@ class LeapNetworkChecker(object): # -- is it a valid ip? (there's something in util) # -- is it a domain? # -- can we resolve? -- raise NoDNSError if not. - packet_loss = ping.quiet_ping(gateway)[0] + + # XXX -- sh.ping implemtation needs review! + try: + output = sh.ping("-c", "10", gateway).stdout + except sh.ErrorReturnCode_1 as e: + output = e.message + finally: + packet_loss = int(re.findall("(\d+)% packet loss", output)[0]) + + logger.debug('packet loss %s%%' % packet_loss) if packet_loss > constants.MAX_ICMP_PACKET_LOSS: raise exceptions.NoConnectionToGateway - # XXX check for name resolution servers - # dunno what's the best way to do this... - # check for etc/resolv entries or similar? - # just try to resolve? - # is there something in psutil? + def check_name_resolution(self, domain_name): + try: + socket.gethostbyname(domain_name) + return True + except socket.gaierror: + raise exceptions.CannotResolveDomainError - # def check_name_resolution(self): - # pass + 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() |