PageRenderTime 57ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/src/leap/eip/eipconnection.py

https://gitlab.com/mcnair/mcnair-leap_client-fork
Python | 405 lines | 237 code | 48 blank | 120 comment | 34 complexity | 35fc1e7889fb97efbb9c5029d3022474 MD5 | raw file
Possible License(s): GPL-3.0
  1. """
  2. EIP Connection Class
  3. """
  4. from __future__ import (absolute_import,)
  5. import logging
  6. import Queue
  7. import sys
  8. import time
  9. from dateutil.parser import parse as dateparse
  10. from leap.eip.checks import ProviderCertChecker
  11. from leap.eip.checks import EIPConfigChecker
  12. from leap.eip import config as eipconfig
  13. from leap.eip import exceptions as eip_exceptions
  14. from leap.eip.openvpnconnection import OpenVPNConnection
  15. logger = logging.getLogger(name=__name__)
  16. class StatusMixIn(object):
  17. # a bunch of methods related with querying the connection
  18. # state/status and displaying useful info.
  19. # Needs to get clear on what is what, and
  20. # separate functions.
  21. # Should separate EIPConnectionStatus (self.status)
  22. # from the OpenVPN state/status command and parsing.
  23. ERR_CONNREFUSED = False
  24. def connection_state(self):
  25. """
  26. returns the current connection state
  27. """
  28. return self.status.current
  29. def get_icon_name(self):
  30. """
  31. get icon name from status object
  32. """
  33. return self.status.get_state_icon()
  34. def get_leap_status(self):
  35. return self.status.get_leap_status()
  36. def poll_connection_state(self):
  37. """
  38. """
  39. try:
  40. state = self.get_connection_state()
  41. except eip_exceptions.ConnectionRefusedError:
  42. # connection refused. might be not ready yet.
  43. if not self.ERR_CONNREFUSED:
  44. logger.warning('connection refused')
  45. self.ERR_CONNREFUSED = True
  46. return
  47. if not state:
  48. #logger.debug('no state')
  49. return
  50. (ts, status_step,
  51. ok, ip, remote) = state
  52. self.status.set_vpn_state(status_step)
  53. status_step = self.status.get_readable_status()
  54. return (ts, status_step, ok, ip, remote)
  55. def make_error(self):
  56. """
  57. capture error and wrap it in an
  58. understandable format
  59. """
  60. # mostly a hack to display errors in the debug UI
  61. # w/o breaking the polling.
  62. #XXX get helpful error codes
  63. self.with_errors = True
  64. now = int(time.time())
  65. return '%s,LAUNCHER ERROR,ERROR,-,-' % now
  66. def state(self):
  67. """
  68. Sends OpenVPN command: state
  69. """
  70. state = self._send_command("state")
  71. if not state:
  72. return None
  73. if isinstance(state, str):
  74. return state
  75. if isinstance(state, list):
  76. if len(state) == 1:
  77. return state[0]
  78. else:
  79. return state[-1]
  80. def vpn_status(self):
  81. """
  82. OpenVPN command: status
  83. """
  84. status = self._send_command("status")
  85. return status
  86. def vpn_status2(self):
  87. """
  88. OpenVPN command: last 2 statuses
  89. """
  90. return self._send_command("status 2")
  91. #
  92. # parse info as the UI expects
  93. #
  94. def get_status_io(self):
  95. status = self.vpn_status()
  96. if isinstance(status, str):
  97. lines = status.split('\n')
  98. if isinstance(status, list):
  99. lines = status
  100. try:
  101. (header, when, tun_read, tun_write,
  102. tcp_read, tcp_write, auth_read) = tuple(lines)
  103. except ValueError:
  104. return None
  105. when_ts = dateparse(when.split(',')[1]).timetuple()
  106. sep = ','
  107. # XXX clean up this!
  108. tun_read = tun_read.split(sep)[1]
  109. tun_write = tun_write.split(sep)[1]
  110. tcp_read = tcp_read.split(sep)[1]
  111. tcp_write = tcp_write.split(sep)[1]
  112. auth_read = auth_read.split(sep)[1]
  113. # XXX this could be a named tuple. prettier.
  114. return when_ts, (tun_read, tun_write, tcp_read, tcp_write, auth_read)
  115. def get_connection_state(self):
  116. state = self.state()
  117. if state is not None:
  118. ts, status_step, ok, ip, remote = state.split(',')
  119. ts = time.gmtime(float(ts))
  120. # XXX this could be a named tuple. prettier.
  121. return ts, status_step, ok, ip, remote
  122. class EIPConnection(OpenVPNConnection, StatusMixIn):
  123. """
  124. Aka conductor.
  125. Manages the execution of the OpenVPN process, auto starts, monitors the
  126. network connection, handles configuration, fixes leaky hosts, handles
  127. errors, etc.
  128. Status updates (connected, bandwidth, etc) are signaled to the GUI.
  129. """
  130. # XXX change name to EIPConductor ??
  131. def __init__(self,
  132. provider_cert_checker=ProviderCertChecker,
  133. config_checker=EIPConfigChecker,
  134. *args, **kwargs):
  135. #self.settingsfile = kwargs.get('settingsfile', None)
  136. #self.logfile = kwargs.get('logfile', None)
  137. self.provider = kwargs.pop('provider', None)
  138. self._providercertchecker = provider_cert_checker
  139. self._configchecker = config_checker
  140. self.error_queue = Queue.Queue()
  141. status_signals = kwargs.pop('status_signals', None)
  142. self.status = EIPConnectionStatus(callbacks=status_signals)
  143. checker_signals = kwargs.pop('checker_signals', None)
  144. self.checker_signals = checker_signals
  145. self.init_checkers()
  146. host = eipconfig.get_socket_path()
  147. kwargs['host'] = host
  148. super(EIPConnection, self).__init__(*args, **kwargs)
  149. def connect(self, **kwargs):
  150. """
  151. entry point for connection process
  152. """
  153. # in OpenVPNConnection
  154. self.try_openvpn_connection()
  155. def disconnect(self, shutdown=False):
  156. """
  157. disconnects client
  158. """
  159. self.terminate_openvpn_connection(shutdown=shutdown)
  160. self.status.change_to(self.status.DISCONNECTED)
  161. def has_errors(self):
  162. return True if self.error_queue.qsize() != 0 else False
  163. def init_checkers(self):
  164. """
  165. initialize checkers
  166. """
  167. self.provider_cert_checker = self._providercertchecker(
  168. domain=self.provider)
  169. self.config_checker = self._configchecker(domain=self.provider)
  170. def set_provider_domain(self, domain):
  171. """
  172. sets the provider domain.
  173. used from the first run wizard when we launch the run_checks
  174. and connect process after having initialized the conductor.
  175. """
  176. # This looks convoluted, right.
  177. # We have to reinstantiate checkers cause we're passing
  178. # the domain param that we did not know at the beginning
  179. # (only for the firstrunwizard case)
  180. self.provider = domain
  181. self.init_checkers()
  182. def run_checks(self, skip_download=False, skip_verify=False):
  183. """
  184. run all eip checks previous to attempting a connection
  185. """
  186. logger.debug('running conductor checks')
  187. def push_err(exc):
  188. # keep the original traceback!
  189. exc_traceback = sys.exc_info()[2]
  190. self.error_queue.put((exc, exc_traceback))
  191. try:
  192. # network (1)
  193. if self.checker_signals:
  194. for signal in self.checker_signals:
  195. signal('checking encryption keys')
  196. self.provider_cert_checker.run_all(skip_verify=skip_verify)
  197. except Exception as exc:
  198. push_err(exc)
  199. try:
  200. if self.checker_signals:
  201. for signal in self.checker_signals:
  202. signal('checking provider config')
  203. self.config_checker.run_all(skip_download=skip_download)
  204. except Exception as exc:
  205. push_err(exc)
  206. try:
  207. self.run_openvpn_checks()
  208. except Exception as exc:
  209. push_err(exc)
  210. class EIPConnectionStatus(object):
  211. """
  212. Keep track of client (gui) and openvpn
  213. states.
  214. These are the OpenVPN states:
  215. CONNECTING -- OpenVPN's initial state.
  216. WAIT -- (Client only) Waiting for initial response
  217. from server.
  218. AUTH -- (Client only) Authenticating with server.
  219. GET_CONFIG -- (Client only) Downloading configuration options
  220. from server.
  221. ASSIGN_IP -- Assigning IP address to virtual network
  222. interface.
  223. ADD_ROUTES -- Adding routes to system.
  224. CONNECTED -- Initialization Sequence Completed.
  225. RECONNECTING -- A restart has occurred.
  226. EXITING -- A graceful exit is in progress.
  227. We add some extra states:
  228. DISCONNECTED -- GUI initial state.
  229. UNRECOVERABLE -- An unrecoverable error has been raised
  230. while invoking openvpn service.
  231. """
  232. CONNECTING = 1
  233. WAIT = 2
  234. AUTH = 3
  235. GET_CONFIG = 4
  236. ASSIGN_IP = 5
  237. ADD_ROUTES = 6
  238. CONNECTED = 7
  239. RECONNECTING = 8
  240. EXITING = 9
  241. # gui specific states:
  242. UNRECOVERABLE = 11
  243. DISCONNECTED = 0
  244. def __init__(self, callbacks=None):
  245. """
  246. EIPConnectionStatus is initialized with a tuple
  247. of signals to be triggered.
  248. :param callbacks: a tuple of (callable) observers
  249. :type callbacks: tuple
  250. """
  251. self.current = self.DISCONNECTED
  252. self.previous = None
  253. # (callbacks to connect to signals in Qt-land)
  254. self.callbacks = callbacks
  255. def get_readable_status(self):
  256. # XXX DRY status / labels a little bit.
  257. # think we'll want to i18n this.
  258. human_status = {
  259. 0: 'disconnected',
  260. 1: 'connecting',
  261. 2: 'waiting',
  262. 3: 'authenticating',
  263. 4: 'getting config',
  264. 5: 'assigning ip',
  265. 6: 'adding routes',
  266. 7: 'connected',
  267. 8: 'reconnecting',
  268. 9: 'exiting',
  269. 11: 'unrecoverable error',
  270. }
  271. return human_status[self.current]
  272. def get_leap_status(self):
  273. # XXX improve nomenclature
  274. leap_status = {
  275. 0: 'disconnected',
  276. 1: 'connecting to gateway',
  277. 2: 'connecting to gateway',
  278. 3: 'authenticating',
  279. 4: 'establishing network encryption',
  280. 5: 'establishing network encryption',
  281. 6: 'establishing network encryption',
  282. 7: 'connected',
  283. 8: 'reconnecting',
  284. 9: 'exiting',
  285. 11: 'unrecoverable error',
  286. }
  287. return leap_status[self.current]
  288. def get_state_icon(self):
  289. """
  290. returns the high level icon
  291. for each fine-grain openvpn state
  292. """
  293. connecting = (self.CONNECTING,
  294. self.WAIT,
  295. self.AUTH,
  296. self.GET_CONFIG,
  297. self.ASSIGN_IP,
  298. self.ADD_ROUTES)
  299. connected = (self.CONNECTED,)
  300. disconnected = (self.DISCONNECTED,
  301. self.UNRECOVERABLE)
  302. # this can be made smarter,
  303. # but it's like it'll change,
  304. # so +readability.
  305. if self.current in connecting:
  306. return "connecting"
  307. if self.current in connected:
  308. return "connected"
  309. if self.current in disconnected:
  310. return "disconnected"
  311. def set_vpn_state(self, status):
  312. """
  313. accepts a state string from the management
  314. interface, and sets the internal state.
  315. :param status: openvpn STATE (uppercase).
  316. :type status: str
  317. """
  318. if hasattr(self, status):
  319. self.change_to(getattr(self, status))
  320. def set_current(self, to):
  321. """
  322. setter for the 'current' property
  323. :param to: destination state
  324. :type to: int
  325. """
  326. self.current = to
  327. def change_to(self, to):
  328. """
  329. :param to: destination state
  330. :type to: int
  331. """
  332. if to == self.current:
  333. return
  334. changed = False
  335. from_ = self.current
  336. self.current = to
  337. # We can add transition restrictions
  338. # here to ensure no transitions are
  339. # allowed outside the fsm.
  340. self.set_current(to)
  341. changed = True
  342. #trigger signals (as callbacks)
  343. #print('current state: %s' % self.current)
  344. if changed:
  345. self.previous = from_
  346. if self.callbacks:
  347. for cb in self.callbacks:
  348. if callable(cb):
  349. cb(self)