basestorage.py 8.7 KB

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