2
0

basestorage.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. from __future__ import print_function, unicode_literals
  4. import logging
  5. import re
  6. import time
  7. import ffstatus
  8. from .exceptions import VpnKeyFormatError
  9. def sanitize_node(data, include_raw_data=False):
  10. """
  11. Filters potentially harmful entries from the node's data.
  12. """
  13. export = ffstatus.dict_merge({}, data)
  14. # remove fields from output: __RAW__
  15. if '__RAW__' in export and not include_raw_data:
  16. del export['__RAW__']
  17. return export
  18. class BaseStorage(object):
  19. """
  20. Provides operations on the storage data.
  21. This class gets subclassed to actually write the data
  22. to a file, database, whatever.
  23. """
  24. DATAKEY_VPN = '__VPN__'
  25. FIELDKEY_UPDATED = '__UPDATED__'
  26. __data = None
  27. @property
  28. def data(self):
  29. """Contains the data handled by this storage."""
  30. return self.__data
  31. @data.setter
  32. def data(self, value):
  33. """setter for data property"""
  34. logging.debug('Setting new storage data (old=%d/new=%d items).',
  35. len(self.__data) if self.__data is not None else 0,
  36. len(value) if value is not None else 0)
  37. self.__data = value
  38. def open(self):
  39. """
  40. When overridden in a subclass,
  41. closes the persistent storage.
  42. """
  43. pass
  44. def save(self):
  45. """
  46. When overriden in a subclass,
  47. stores the data to a persistent storage.
  48. """
  49. pass
  50. def close(self):
  51. """
  52. When overridden in a subclass,
  53. closes the persistent storage.
  54. """
  55. pass
  56. def get_nodes(self, sortby=None, include_raw_data=False):
  57. """Gets a list of all known nodes."""
  58. sorted_ids = self.data.keys()
  59. if not sortby is None:
  60. if sortby == 'name':
  61. sortkey = lambda x: self.data[x]['hostname'].lower()
  62. sorted_ids = sorted(self.data, key=sortkey)
  63. elif sortby == 'id':
  64. sorted_ids = sorted(self.data)
  65. result = []
  66. for nodeid in sorted_ids:
  67. if nodeid.startswith('__'):
  68. continue
  69. node = sanitize_node(self.data[nodeid], include_raw_data)
  70. result.append(node)
  71. return result
  72. def find_node(self, rawid):
  73. """
  74. Fetch node data by given id.
  75. If necessary, look through node aliases.
  76. """
  77. # if we have a direct hit, return it immediately
  78. if rawid in self.data:
  79. return sanitize_node(self.data[rawid])
  80. # no direct hit -> search via aliases
  81. nodeid = rawid
  82. for nid in self.data:
  83. node = self.data[nid]
  84. if 'aliases' in node and rawid in node['aliases']:
  85. nodeid = nid
  86. # return found node
  87. if nodeid in self.data:
  88. return sanitize_node(self.data[nodeid])
  89. else:
  90. return None
  91. def find_node_by_mac(self, mac):
  92. """Fetch node data by given MAC address."""
  93. needle = mac.lower()
  94. # iterate over all nodes
  95. for nodeid in self.data:
  96. if nodeid.startswith('__'):
  97. continue
  98. node = self.data[nodeid]
  99. # check node's primary MAC
  100. if 'mac' in node and needle == node['mac'].lower():
  101. return sanitize_node(node)
  102. # check alias MACs
  103. if 'macs' in node:
  104. haystack = [x.lower() for x in node['macs']]
  105. if mac in haystack:
  106. return sanitize_node(node)
  107. # MAC address not found
  108. return None
  109. def get_nodestatus(self, rawid):
  110. """Determine node's status."""
  111. # search node by the given id
  112. node = self.find_node(rawid)
  113. # handle unknown nodes
  114. if node is None:
  115. return None
  116. # check that the last batadv update is noted in the data
  117. updated = node.get(self.FIELDKEY_UPDATED, None)
  118. if updated is None or not 'batadv' in updated:
  119. return 'unknown'
  120. # make decision based on time of last batadv update
  121. diff = time.time() - updated['batadv']
  122. if diff < 150:
  123. return 'active'
  124. elif diff < 300:
  125. return 'stale'
  126. else:
  127. return 'offline'
  128. def resolve_vpn_remotes(self):
  129. if not self.DATAKEY_VPN in self.data:
  130. return
  131. vpn = self.data[self.DATAKEY_VPN]
  132. init_vpn_cache = {}
  133. for key in vpn:
  134. if not isinstance(vpn[key], dict):
  135. continue
  136. for mode in vpn[key]:
  137. if not isinstance(vpn[key][mode], dict):
  138. continue
  139. for gateway in vpn[key][mode]:
  140. if not isinstance(vpn[key][mode][gateway], dict):
  141. continue
  142. item = vpn[key][mode][gateway]
  143. if 'remote' in item and not 'remote_raw' in item:
  144. item['remote_raw'] = item['remote']
  145. resolved = None
  146. if item['remote'] in init_vpn_cache:
  147. resolved = init_vpn_cache[item['remote']]
  148. else:
  149. resolved = ffstatus.resolve_ipblock(item['remote'])
  150. init_vpn_cache[item['remote']] = resolved
  151. if not resolved is None:
  152. logging.info(
  153. 'Resolved VPN entry \'%s\' to net \'%s\'.',
  154. item['remote'],
  155. resolved['name'],
  156. )
  157. if not resolved is None:
  158. item['remote'] = resolved
  159. self.save()
  160. def __get_vpn_item(self, key, create=False):
  161. if key is None or re.match(r'^[a-fA-F0-9]+$', key) is None:
  162. raise VpnKeyFormatError(key)
  163. return
  164. if not self.DATAKEY_VPN in self.data:
  165. if not create:
  166. return None
  167. self.data[self.DATAKEY_VPN] = {}
  168. if not key in self.data[self.DATAKEY_VPN]:
  169. if not create:
  170. return None
  171. self.data[self.DATAKEY_VPN][key] = {'active': {}, 'last': {}}
  172. return self.data[self.DATAKEY_VPN][key]
  173. def get_vpn_gateways(self):
  174. if not self.DATAKEY_VPN in self.data:
  175. return []
  176. gateways = set()
  177. vpn = self.data[self.DATAKEY_VPN]
  178. for key in vpn:
  179. for conntype in vpn[key]:
  180. for gateway in vpn[key][conntype]:
  181. gateways.add(gateway)
  182. return sorted(gateways)
  183. def get_vpn_connections(self):
  184. if not self.DATAKEY_VPN in self.data:
  185. return []
  186. conntypes = ['active', 'last']
  187. result = []
  188. vpn = self.data[self.DATAKEY_VPN]
  189. for key in vpn:
  190. vpn_entry = vpn[key]
  191. if not isinstance(vpn_entry, dict):
  192. continue
  193. item = {
  194. 'key': key,
  195. 'count': {},
  196. 'remote': {},
  197. }
  198. names = set()
  199. for conntype in conntypes:
  200. item['count'][conntype] = 0
  201. item['remote'][conntype] = {}
  202. if conntype in vpn_entry:
  203. for gateway in vpn_entry[conntype]:
  204. if 'remote' in vpn_entry[conntype][gateway]:
  205. remote = vpn_entry[conntype][gateway]['remote']
  206. if isinstance(remote, basestring) and len(remote) == 0:
  207. continue
  208. item['count'][conntype] += 1
  209. item['remote'][conntype][gateway] = remote
  210. if 'peer' in vpn_entry[conntype][gateway]:
  211. names.add(vpn_entry[conntype][gateway]['peer'])
  212. item['names'] = sorted(names)
  213. item['online'] = item['count']['active'] > 0
  214. result.append(item)
  215. return result
  216. def log_vpn_connect(self, key, peername, remote, gateway, timestamp):
  217. item = self.__get_vpn_item(key, create=True)
  218. # resolve remote addr to its netblock
  219. remote_raw = remote
  220. remote_resolved = None
  221. if remote is not None:
  222. remote_resolved = ffstatus.resolve_ipblock(remote)
  223. if not remote_resolved is None:
  224. logging.debug('Resolved IP \'{0}\' to block \'{1}\'.'.format(
  225. remote, remote_resolved['name'],
  226. ))
  227. remote = remote_resolved
  228. # store connection info
  229. item['active'][gateway] = {
  230. 'establish': timestamp,
  231. 'peer': peername,
  232. 'remote': remote,
  233. 'remote_raw': remote_raw,
  234. }
  235. def log_vpn_disconnect(self, key, gateway, timestamp):
  236. item = self.__get_vpn_item(key, create=True)
  237. active = {}
  238. if gateway in item['active']:
  239. active = item['active'][gateway]
  240. del item['active'][gateway]
  241. active['disestablish'] = timestamp
  242. item['last'][gateway] = active