summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changes/feature_2790-autoselect_vpn_gateway_based_on_timezone1
-rw-r--r--src/leap/crypto/srpauth.py22
-rw-r--r--src/leap/gui/mainwindow.py3
-rw-r--r--src/leap/gui/statuspanel.py2
-rw-r--r--src/leap/services/abstractbootstrapper.py7
-rw-r--r--src/leap/services/eip/eipconfig.py91
-rw-r--r--src/leap/services/eip/vpnlaunchers.py12
-rw-r--r--src/leap/util/leap_log_handler.py9
8 files changed, 128 insertions, 19 deletions
diff --git a/changes/feature_2790-autoselect_vpn_gateway_based_on_timezone b/changes/feature_2790-autoselect_vpn_gateway_based_on_timezone
new file mode 100644
index 00000000..9f378b79
--- /dev/null
+++ b/changes/feature_2790-autoselect_vpn_gateway_based_on_timezone
@@ -0,0 +1 @@
+ o Autoselect VPN gateway based on timezone. Closes #2790.
diff --git a/src/leap/crypto/srpauth.py b/src/leap/crypto/srpauth.py
index bcd24de3..0e95ae64 100644
--- a/src/leap/crypto/srpauth.py
+++ b/src/leap/crypto/srpauth.py
@@ -24,6 +24,7 @@ import json
#this error is raised from requests
from simplejson.decoder import JSONDecodeError
+from functools import partial
from PySide import QtCore
from twisted.internet import threads
@@ -321,6 +322,9 @@ class SRPAuth(QtCore.QObject):
self.set_session_id(session_id)
+ def _threader(self, cb, res, *args, **kwargs):
+ return threads.deferToThread(cb, res, *args, **kwargs)
+
def authenticate(self, username, password):
"""
Executes the whole authentication process for a user
@@ -341,10 +345,17 @@ class SRPAuth(QtCore.QObject):
username=username,
password=password)
- d.addCallback(self._start_authentication, username=username,
- password=password)
- d.addCallback(self._process_challenge, username=username)
- d.addCallback(self._verify_session)
+ d.addCallback(
+ partial(self._threader,
+ self._start_authentication),
+ username=username,
+ password=password)
+ d.addCallback(
+ partial(self._threader,
+ self._process_challenge),
+ username=username)
+ d.addCallback(partial(self._threader,
+ self._verify_session))
return d
@@ -459,7 +470,8 @@ class SRPAuth(QtCore.QObject):
:type failure: twisted.python.failure.Failure
"""
logger.error("Error logging in %s" % (failure,))
- self.authentication_finished.emit(False, "%s" % (failure,))
+ self.authentication_finished.emit(False, "%s" % (failure.value,))
+ failure.trap(Exception)
def get_session_id(self):
return self.__instance.get_session_id()
diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py
index c58cd4e3..80876c27 100644
--- a/src/leap/gui/mainwindow.py
+++ b/src/leap/gui/mainwindow.py
@@ -1149,7 +1149,8 @@ class MainWindow(QtGui.QMainWindow):
"""
passed = data[self._provider_bootstrapper.PASSED_KEY]
if not passed:
- self._set_status(data[self._provider_bootstrapper.ERROR_KEY])
+ self._login_widget.set_status(
+ data[self._provider_bootstrapper.ERROR_KEY])
self._already_started_eip = False
def _eip_finished(self, exitCode):
diff --git a/src/leap/gui/statuspanel.py b/src/leap/gui/statuspanel.py
index 53c19e86..554903d8 100644
--- a/src/leap/gui/statuspanel.py
+++ b/src/leap/gui/statuspanel.py
@@ -189,7 +189,7 @@ class StatusPanelWidget(QtGui.QWidget):
"it's already "
"running.")))
else:
- self._set_eip_status(status)
+ self.set_eip_status(status)
def set_eip_status_icon(self, status):
"""
diff --git a/src/leap/services/abstractbootstrapper.py b/src/leap/services/abstractbootstrapper.py
index 2cbd56bc..f0937197 100644
--- a/src/leap/services/abstractbootstrapper.py
+++ b/src/leap/services/abstractbootstrapper.py
@@ -22,6 +22,8 @@ import logging
import requests
+from functools import partial
+
from PySide import QtCore
from twisted.internet import threads
from leap.common.check import leap_assert, leap_assert_type
@@ -128,6 +130,9 @@ class AbstractBootstrapper(QtCore.QObject):
logger.debug("Emitting %s" % (signal,))
signal.emit({self.PASSED_KEY: True, self.ERROR_KEY: ""})
+ def _callback_threader(self, cb, res, *args, **kwargs):
+ return threads.deferToThread(cb, res, *args, **kwargs)
+
def addCallbackChain(self, callbacks):
"""
Creates a callback/errback chain on another thread using
@@ -148,7 +153,7 @@ class AbstractBootstrapper(QtCore.QObject):
if d is None:
d = threads.deferToThread(cb)
else:
- d.addCallback(cb)
+ d.addCallback(partial(self._callback_threader, cb))
d.addErrback(self._errback, signal=sig)
d.addCallback(self._gui_notify, signal=sig)
d.addErrback(self._gui_errback)
diff --git a/src/leap/services/eip/eipconfig.py b/src/leap/services/eip/eipconfig.py
index 0a7d2b23..f7d03963 100644
--- a/src/leap/services/eip/eipconfig.py
+++ b/src/leap/services/eip/eipconfig.py
@@ -21,6 +21,8 @@ Provider configuration
import logging
import os
import re
+import datetime
+import time
import ipaddr
@@ -32,6 +34,79 @@ from leap.services.eip.eipspec import eipservice_config_spec
logger = logging.getLogger(__name__)
+class VPNGatewaySelector(object):
+ """
+ VPN Gateway selector.
+ """
+
+ def __init__(self, eipconfig):
+ '''
+ Constructor for VPNGatewaySelector.
+
+ :param eipconfig: a valid EIP Configuration.
+ :type eipconfig: EIPConfig
+ '''
+ leap_assert_type(eipconfig, EIPConfig)
+ self._local_offset = 0 # defaults to GMT
+ self._local_timezone = None
+ self._set_local_offset()
+ self._eipconfig = eipconfig
+
+ def _get_best_gateway(self):
+ """
+ Returns index of the closest gateway, using timezones offsets.
+
+ :rtype: int
+ """
+ best_gateway = (-1, 99) # gateway, distance
+ locations = self._eipconfig.get_locations()
+ gateways = self._eipconfig.get_gateways()
+ for idx, gateway in enumerate(gateways):
+ gateway_offset = int(locations[gateway['location']]['timezone'])
+ gateway_distance = self._get_timezone_distance(gateway_offset)
+ if gateway_distance < best_gateway[1]:
+ best_gateway = (idx, gateway_distance)
+
+ return best_gateway[0]
+
+ def get_best_gateway_ip(self):
+ """
+ Returns the ip of the best possible gateway.
+
+ :rtype: An IPv4Address or IPv6Address object.
+ """
+ best_gateway = self._get_best_gateway()
+ gateway_ip = self._eipconfig.get_gateway_ip(best_gateway)
+
+ return gateway_ip
+
+ def _get_timezone_distance(self, offset):
+ '''
+ Returns the distance between the local timezone and
+ the one with offset 'offset'.
+
+ :param offset: the distance of a timezone to GMT.
+ :type offset: int
+ :returns: distance between local offset and param offset.
+ :rtype: int
+ '''
+ delta1 = datetime.timedelta(hours=offset)
+ delta2 = self._local_offset
+ diff = abs(delta1 - delta2)
+ hours = diff.seconds / (60 * 60)
+ return hours
+
+ def _set_local_offset(self):
+ '''
+ Sets the distance between GMT and the local timezone.
+ '''
+ local_offset = time.timezone
+ if time.daylight:
+ local_offset = time.altzone
+
+ self._local_offset = datetime.timedelta(seconds=-local_offset)
+
+
class EIPConfig(BaseConfig):
"""
Provider configuration abstraction class
@@ -56,6 +131,14 @@ class EIPConfig(BaseConfig):
# TODO: create an abstraction for gateways
return self._safe_get_value("gateways")
+ def get_locations(self):
+ '''
+ Returns a list of locations
+
+ :rtype: dict
+ '''
+ return self._safe_get_value("locations")
+
def get_openvpn_configuration(self):
"""
Returns a dictionary containing the openvpn configuration
@@ -63,8 +146,8 @@ class EIPConfig(BaseConfig):
These are sanitized with alphanumeric whitelist.
- @returns: openvpn configuration dict
- @rtype: C{dict}
+ :returns: openvpn configuration dict
+ :rtype: C{dict}
"""
ovpncfg = self._safe_get_value("openvpn_configuration")
config = {}
@@ -84,7 +167,9 @@ class EIPConfig(BaseConfig):
def get_gateway_ip(self, index=0):
"""
- Returns the ip of the gateway
+ Returns the ip of the gateway.
+
+ :rtype: An IPv4Address or IPv6Address object.
"""
gateways = self.get_gateways()
leap_assert(len(gateways) > 0, "We don't have any gateway!")
diff --git a/src/leap/services/eip/vpnlaunchers.py b/src/leap/services/eip/vpnlaunchers.py
index 6c2ff006..fa2989bc 100644
--- a/src/leap/services/eip/vpnlaunchers.py
+++ b/src/leap/services/eip/vpnlaunchers.py
@@ -34,7 +34,7 @@ from functools import partial
from leap.common.check import leap_assert, leap_assert_type
from leap.common.files import which
from leap.config.providerconfig import ProviderConfig
-from leap.services.eip.eipconfig import EIPConfig
+from leap.services.eip.eipconfig import EIPConfig, VPNGatewaySelector
logger = logging.getLogger(__name__)
@@ -228,7 +228,8 @@ class LinuxVPNLauncher(VPNLauncher):
# TODO: handle verbosity
- gateway_ip = str(eipconfig.get_gateway_ip(0))
+ gateway_selector = VPNGatewaySelector(eipconfig)
+ gateway_ip = gateway_selector.get_best_gateway_ip()
logger.debug("Using gateway ip %s" % (gateway_ip,))
@@ -391,7 +392,9 @@ class DarwinVPNLauncher(VPNLauncher):
# TODO: handle verbosity
- gateway_ip = str(eipconfig.get_gateway_ip(0))
+ gateway_selector = VPNGatewaySelector(eipconfig)
+ gateway_ip = gateway_selector.get_best_gateway_ip()
+
logger.debug("Using gateway ip %s" % (gateway_ip,))
args += [
@@ -530,7 +533,8 @@ class WindowsVPNLauncher(VPNLauncher):
# TODO: handle verbosity
- gateway_ip = str(eipconfig.get_gateway_ip(0))
+ gateway_selector = VPNGatewaySelector(eipconfig)
+ gateway_ip = gateway_selector.get_best_gateway_ip()
logger.debug("Using gateway ip %s" % (gateway_ip,))
diff --git a/src/leap/util/leap_log_handler.py b/src/leap/util/leap_log_handler.py
index 5b8ae789..e5bc87e1 100644
--- a/src/leap/util/leap_log_handler.py
+++ b/src/leap/util/leap_log_handler.py
@@ -31,16 +31,16 @@ class LogHandler(logging.Handler):
MESSAGE_KEY = 'message'
RECORD_KEY = 'record'
- # TODO This is going to eat lots of memory after some time.
- # Should be pruned at some moment.
- _log_history = []
-
def __init__(self, qtsignal):
"""
LogHander initialization.
Calls parent method and keeps a reference to the qtsignal
that will be used to fire the gui update.
"""
+ # TODO This is going to eat lots of memory after some time.
+ # Should be pruned at some moment.
+ self._log_history = []
+
logging.Handler.__init__(self)
self._qtsignal = qtsignal
@@ -85,6 +85,7 @@ class LogHandler(logging.Handler):
self._set_format(logRecord.levelname)
log = self.format(logRecord)
log_item = {self.RECORD_KEY: logRecord, self.MESSAGE_KEY: log}
+ self._log_history.append(log_item)
self._qtsignal(log_item)