Browse Source

move node+vpn handling logic into BaseStorage

the ApiServer was not the right place to manipulate the storage
Helge Jung 9 years ago
parent
commit
3cfbc6ffa5
4 changed files with 336 additions and 229 deletions
  1. 1 0
      batcave.py
  2. 254 0
      ffstatus/basestorage.py
  3. 15 0
      ffstatus/exceptions.py
  4. 66 229
      ffstatus/server.py

+ 1 - 0
batcave.py

@@ -57,6 +57,7 @@ if args.no_detach:
 logger.info('Starting up')
 
 storage = Storage(args.storage_dir)
+storage.open()
 logger.info('Storage: ' + str(storage))
 
 a = AlfredParser()

+ 254 - 0
ffstatus/basestorage.py

@@ -2,6 +2,26 @@
 # -*- coding: utf-8 -*-
 
 from __future__ import print_function, unicode_literals
+import logging
+import re
+import time
+
+import ffstatus
+from .exceptions import VpnKeyFormatError
+
+
+def sanitize_node(data, include_raw_data=False):
+    """
+    Filters potentially harmful entries from the node's data.
+    """
+    export = ffstatus.dict_merge({}, data)
+
+    # remove fields from output: __RAW__
+    if '__RAW__' in export and not include_raw_data:
+        del export['__RAW__']
+
+    return export
+
 
 class BaseStorage(object):
     """
@@ -10,6 +30,9 @@ class BaseStorage(object):
     to a file, database, whatever.
     """
 
+    DATAKEY_VPN = '__VPN__'
+    FIELDKEY_UPDATED = '__UPDATED__'
+
     data = None
 
     def open(self):
@@ -33,3 +56,234 @@ class BaseStorage(object):
         """
         pass
 
+    def get_nodes(self, sortby=None, include_raw_data=False):
+        """Gets a list of all known nodes."""
+
+        sorted_ids = self.data.keys()
+        if not sortby is None:
+            if sortby == 'name':
+                sortkey = lambda x: self.data[x]['hostname'].lower()
+                sorted_ids = sorted(self.data, key=sortkey)
+            elif sortby == 'id':
+                sorted_ids = sorted(self.data)
+
+        result = []
+        for nodeid in sorted_ids:
+            if nodeid.startswith('__'):
+                continue
+            node = sanitize_node(self.data[nodeid], include_raw_data)
+            result.append(node)
+
+        return result
+
+    def find_node(self, rawid):
+        """
+        Fetch node data by given id.
+        If necessary, look through node aliases.
+        """
+
+        # if we have a direct hit, return it immediately
+        if rawid in self.data:
+            return sanitize_node(self.data[rawid])
+
+        # no direct hit -> search via aliases
+        nodeid = rawid
+        for nid in self.data:
+            node = self.data[nid]
+            if 'aliases' in node and rawid in node['aliases']:
+                nodeid = nid
+
+        # return found node
+        if nodeid in self.data:
+            return sanitize_node(self.data[nodeid])
+        else:
+            return None
+
+    def find_node_by_mac(self, mac):
+        """Fetch node data by given MAC address."""
+
+        needle = mac.lower()
+
+        # iterate over all nodes
+        for nodeid in self.data:
+            if nodeid.startswith('__'):
+                continue
+            node = self.data[nodeid]
+
+            # check node's primary MAC
+            if 'mac' in node and needle == node['mac'].lower():
+                return sanitize_node(node)
+
+            # check alias MACs
+            if 'macs' in node:
+                haystack = [x.lower() for x in node['macs']]
+                if mac in haystack:
+                    return sanitize_node(node)
+
+        # MAC address not found
+        return None
+
+    def get_nodestatus(self, rawid):
+        """Determine node's status."""
+
+        # search node by the given id
+        node = self.find_node(rawid)
+
+        # handle unknown nodes
+        if node is None:
+            return None
+
+        # check that the last batadv update is noted in the data
+        updated = node.get(self.FIELDKEY_UPDATED, None)
+        if updated is None or not 'batadv' in updated:
+            return 'unknown'
+
+        # make decision based on time of last batadv update
+        diff = time.time() - updated['batadv']
+        if diff < 150:
+            return 'active'
+        elif diff < 300:
+            return 'stale'
+        else:
+            return 'offline'
+
+    def resolve_vpn_remotes(self):
+        if not self.DATAKEY_VPN in self.data:
+            return
+
+        vpn = self.data[self.DATAKEY_VPN]
+        init_vpn_cache = {}
+        for key in vpn:
+            if not isinstance(vpn[key], dict):
+                continue
+
+            for mode in vpn[key]:
+                if not isinstance(vpn[key][mode], dict):
+                    continue
+
+                for gateway in vpn[key][mode]:
+                    if not isinstance(vpn[key][mode][gateway], dict):
+                        continue
+
+                    item = vpn[key][mode][gateway]
+                    if 'remote' in item and not 'remote_raw' in item:
+                        item['remote_raw'] = item['remote']
+                        resolved = None
+                        if item['remote'] in init_vpn_cache:
+                            resolved = init_vpn_cache[item['remote']]
+                        else:
+                            resolved = ffstatus.resolve_ipblock(item['remote'])
+                            init_vpn_cache[item['remote']] = resolved
+                            if not resolved is None:
+                                logging.info(
+                                    'Resolved VPN entry \'%s\' to net \'%s\'.',
+                                    item['remote'],
+                                    resolved['name'],
+                                )
+
+                        if not resolved is None:
+                            item['remote'] = resolved
+
+        self.save()
+
+    def __get_vpn_item(self, key, create=False):
+        if key is None or re.match(r'^[a-fA-F0-9]+$', key) is None:
+            raise VpnKeyFormatError(key)
+            return
+
+        if not self.DATAKEY_VPN in self.data:
+            if not create:
+                return None
+            self.data[self.DATAKEY_VPN] = {}
+
+        if not key in self.data[self.DATAKEY_VPN]:
+            if not create:
+                return None
+            self.data[self.DATAKEY_VPN][key] = {'active': {}, 'last': {}}
+
+        return self.data[self.DATAKEY_VPN][key]
+
+    def get_vpn_gateways(self):
+        if not self.DATAKEY_VPN in self.data:
+            return []
+
+        gateways = set()
+        vpn = self.data[self.DATAKEY_VPN]
+        for key in vpn:
+            for conntype in vpn[key]:
+                for gateway in vpn[key][conntype]:
+                    gateways.add(gateway)
+
+        return sorted(gateways)
+
+    def get_vpn_connections(self):
+        if not self.DATAKEY_VPN in self.data:
+            return []
+
+        conntypes = ['active', 'last']
+        result = []
+        vpn = self.data[self.DATAKEY_VPN]
+        for key in vpn:
+            vpn_entry = vpn[key]
+            if not isinstance(vpn_entry, dict):
+                continue
+
+            item = {
+                'key': key,
+                'count': {},
+                'remote': {},
+            }
+            names = set()
+            for conntype in conntypes:
+                item['count'][conntype] = 0
+                item['remote'][conntype] = {}
+                if conntype in vpn_entry:
+                    for gateway in vpn_entry[conntype]:
+                        if 'remote' in vpn_entry[conntype][gateway]:
+                            remote = vpn_entry[conntype][gateway]['remote']
+                            if isinstance(remote, basestring) and len(remote) == 0:
+                                continue
+
+                            item['count'][conntype] += 1
+                            item['remote'][conntype][gateway] = remote
+                        if 'peer' in vpn_entry[conntype][gateway]:
+                            names.add(vpn_entry[conntype][gateway]['peer'])
+
+            item['names'] = sorted(names)
+            item['online'] = item['count']['active'] > 0
+            result.append(item)
+
+        return result
+
+    def log_vpn_connect(self, key, peername, remote, gateway, timestamp):
+        item = self.__get_vpn_item(key, create=True)
+
+        # resolve remote addr to its netblock
+        remote_raw = remote
+        remote_resolved = None
+        if remote is not None:
+            remote_resolved = ffstatus.resolve_ipblock(remote)
+            if not remote_resolved is None:
+                logging.debug('Resolved IP \'{0}\' to block \'{1}\'.'.format(
+                    remote, remote_resolved['name'],
+                ))
+                remote = remote_resolved
+
+        # store connection info
+        item['active'][gateway] = {
+            'establish': timestamp,
+            'peer': peername,
+            'remote': remote,
+            'remote_raw': remote_raw,
+        }
+
+    def log_vpn_disconnect(self, key, gateway, timestamp):
+        item = self.__get_vpn_item(key, create=True)
+
+        active = {}
+        if gateway in item['active']:
+            active = item['active'][gateway]
+            del item['active'][gateway]
+
+        active['disestablish'] = timestamp
+        item['last'][gateway] = active

+ 15 - 0
ffstatus/exceptions.py

@@ -0,0 +1,15 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""
+Contains custom Exceptions used in ffstatus.
+"""
+
+class VpnKeyFormatError(Exception):
+    """Thrown by BaseStorage on invalid VPN keys."""
+
+    def __init__(self, key):
+        Exception.__init__(self)
+        self.key = key
+
+    def __str__(self):
+        return 'The VPN key has an invalid format: ' + repr(self.key)

+ 66 - 229
ffstatus/server.py

@@ -1,14 +1,12 @@
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 
-from __future__ import print_function
+from __future__ import print_function, unicode_literals
 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
 
 import cgi
-from storage import Storage
 import json
 import logging
-import pygeoip
 import re
 import socket
 from SocketServer import ThreadingMixIn
@@ -17,8 +15,6 @@ import time
 import ffstatus
 
 class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
-    DATAKEY_VPN = '__VPN__'
-    FIELDKEY_UPDATED = '__UPDATED__'
 
     def __init__(self, request, client_address, server):
         self.logger = logging.getLogger('API')
@@ -162,7 +158,6 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
     def respond_list(self, query):
         """List stored data."""
 
-        storage = self.server.storage
         self.send_headers()
 
         self.wfile.write('<!DOCTYPE html><html>\n')
@@ -174,67 +169,17 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
         self.wfile.write('<thead><tr><th>ID</th><th>Name</th></tr></thead>\n')
         self.wfile.write('<tbody>\n')
 
-        data = storage.data
-        if 'sort' in query:
-            if query['sort'] == 'name':
-                sorteddata = sorted(data, key=lambda x: data[x]['hostname'].lower())
-                data = sorteddata
-            elif query['sort'] == 'id':
-                sorteddata = sorted(data)
-                data = sorteddata
-
-        for nodeid in data:
-            if nodeid.startswith('__'):
-                continue
-            nodename = storage.data[nodeid]['hostname'] if 'hostname' in storage.data[nodeid] else '&lt;?&gt;'
+        sortkey = query['sort'] if 'sort' in query else None
+        data = self.server.storage.get_nodes(sortby=sortkey)
+        for node in data:
+            nodeid = node['node_id']
+            nodename = node['hostname'] if 'hostname' in node else '&lt;?&gt;'
+
             self.wfile.write('<tr><td><a href="/node/' + nodeid + '.json">' + nodeid + '</a></td><td>' + nodename + '</td></tr>')
 
         self.wfile.write('</tbody>\n')
         self.wfile.write('</table>\n')
 
-    def find_node(self, rawid):
-        """Fetch node data from storage by given id, if necessary looking thorugh node aliases."""
-
-        storage = self.server.storage
-
-        # if we have a direct hit, return it immediately
-        if rawid in storage.data:
-            return storage.data[rawid]
-
-        # no direct hit -> search via aliases
-        nodeid = rawid
-        for nid in storage.data:
-            if 'aliases' in storage.data[nid] and rawid in storage.data[nid]['aliases']:
-                nodeid = nid
-
-        # return found node
-        return storage.data[nodeid] if nodeid in storage.data else None
-
-    def find_node_by_mac(self, mac):
-        """Fetch node data from storage by given MAC address."""
-
-        storage = self.server.storage
-        needle = mac.lower()
-
-        # iterate over all nodes
-        for nodeid in storage.data:
-            if nodeid.startswith('__'):
-                continue
-            node = storage.data[nodeid]
-
-            # check node's primary MAC
-            if 'mac' in node and needle == node['mac'].lower():
-                return node
-
-            # check alias MACs
-            if 'macs' in node:
-                haystack = [x.lower() for x in node['macs']]
-                if mac in haystack:
-                    return node
-
-        # MAC address not found
-        return None
-
     def respond_node(self, rawid):
         """Display node data."""
 
@@ -249,50 +194,21 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
             return
 
         # search node by the given id
-        node = self.find_node(rawid)
+        node = self.server.storage.find_node(rawid)
 
         # handle unknown nodes
         if node is None:
             self.send_error(404, 'No node with id \'' + rawid + '\' present.')
             return
 
-        # remove fields from output: __RAW__
-        export = ffstatus.dict_merge({}, node)
-        if '__RAW__' in export:
-            del export['__RAW__']
-
         # dump node data as JSON
         self.send_headers('text/json')
-        self.wfile.write(json.dumps(export))
-
-    def get_nodestatus(self, rawid):
-        """Determine node's status."""
-
-        # search node by the given id
-        node = self.find_node(rawid)
-
-        # handle unknown nodes
-        if node is None:
-            return None
-
-        # check that the last batadv update is noted in the data
-        updated = node[self.FIELDKEY_UPDATED] if self.FIELDKEY_UPDATED in node else None
-        if updated is None or not 'batadv' in updated:
-            return 'unknown'
-
-        # make decision based on time of last batadv update
-        diff = time.time() - updated['batadv']
-        if diff < 150:
-            return 'active'
-        elif diff < 300:
-            return 'stale'
-        else:
-            return 'offline'
+        self.wfile.write(json.dumps(node))
 
     def respond_nodestatus(self, rawid):
         """Display node status."""
 
-        status = self.get_nodestatus(rawid)
+        status = self.server.storage.get_nodestatus(rawid)
 
         if status is None:
             self.send_error(404, 'No node with id \'' + rawid + '\' present.')
@@ -305,14 +221,14 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
 
         self.send_headers('text/plain')
         for nodeid in ids:
-            node = self.find_node(nodeid) if not ':' in nodeid else self.find_node_by_mac(nodeid)
+            node = self.server.storage.find_node(nodeid) if not ':' in nodeid else self.server.storage.find_node_by_mac(nodeid)
             nodename = node['hostname'] if (not node is None) and 'hostname' in node else nodeid
             self.wfile.write('{0}={1}\n'.format(nodeid, nodename))
 
     def respond_nodedetail(self, nodeid, field):
         """Return a field from the given node - a string is returned as text, all other as JSON."""
 
-        node = self.find_node(nodeid)
+        node = self.server.storage.find_node(nodeid)
         if node is None:
             self.send_error(404, 'No node with id \'' + nodeid + '\' present.')
             return
@@ -358,44 +274,25 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
                 self.send_error(400, 'Missing value for ' + str(k))
                 return
 
-        if key is None or re.match(r'^[a-fA-F0-9]+$', key) is None:
-            self.logger.error('VPN peer \'{0}\' {1}: bad key \'{2}\''.format(peername, action, key))
-            self.send_error(400, 'Bad key.')
-            return
+        try:
+            if action == 'establish':
+                self.server.storage.log_vpn_connect(
+                    key, peername, remote, gw, ts)
 
-        if not self.DATAKEY_VPN in storage.data:
-            storage.data[self.DATAKEY_VPN] = {}
-        if not key in storage.data[self.DATAKEY_VPN]:
-            storage.data[self.DATAKEY_VPN][key] = {'active': {}, 'last': {}}
-        item = storage.data[self.DATAKEY_VPN][key]
-
-        # resolve remote addr to its netblock
-        remote_raw = remote
-        remote_resolved = None
-        if not remote is None:
-            remote_resolved = ffstatus.resolve_ipblock(remote)
-            if not remote_resolved is None:
-                self.logger.debug('Resolved IP \'{0}\' to block \'{1}\'.'.format(remote, remote_resolved['name']))
-                remote = remote_resolved
-
-        if action == 'establish':
-            item['active'][gw] = {
-                'establish': ts,
-                'peer': peername,
-                'remote': remote,
-                'remote_raw': remote_raw,
-            }
-
-        elif action == 'disestablish':
-            active = {}
-            if gw in item['active']:
-                active = item['active'][gw]
-                del item['active'][gw]
-            active['disestablish'] = ts
-            item['last'][gw] = active
+            elif action == 'disestablish':
+                self.server.storage.log_vpn_connect(key, gw, ts)
 
-        else:
-            self.send_error(500, 'Unknown action not filtered: ' + str(action))
+            else:
+                self.logger.error('Unknown VPN action \'%s\' not filtered.',
+                                  action)
+                self.send_error(500)
+                return
+
+        except ffstatus.exceptions.VpnKeyFormatError:
+            self.logger.error('VPN peer \'{0}\' {1}: bad key \'{2}\''.format(
+                peername, action, key,
+            ))
+            self.send_error(400, 'Bad key.')
             return
 
         self.send_headers('text/plain')
@@ -404,10 +301,6 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
         storage.save()
 
     def respond_vpnlist(self):
-        storage = self.server.storage
-
-        gateways = ['gw01', 'gw02', 'gw03', 'gw04', 'gw05', 'gw06']
-
         self.send_headers()
         self.wfile.write('<!DOCTYPE html>\n')
         self.wfile.write('<html><head><title>BATCAVE - VPN LIST</title></head>\n')
@@ -419,39 +312,30 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
         self.wfile.write('table tbody tr.offline { background-color: #FCC; }\n')
         self.wfile.write('</style>\n')
         self.wfile.write('<table>\n<thead>\n')
+        gateways = self.server.storage.get_vpn_gateways()
         self.wfile.write('<tr><th rowspan="2">names (key)</th><th colspan="' + str(len(gateways)) + '">active</th><th colspan="' + str(len(gateways)) + '">last</th></tr>\n')
         self.wfile.write('<tr><th>' + '</th><th>'.join(gateways) + '</th><th>' + '</th><th>'.join(gateways) + '</th></tr>\n')
         self.wfile.write('</thead>\n')
 
-        if self.DATAKEY_VPN in storage.data:
-            for key in storage.data[self.DATAKEY_VPN]:
-                item = storage.data[self.DATAKEY_VPN][key]
-                if not isinstance(item, dict):
-                    continue
-
-                names = set()
-                count = {}
-                for t in [ 'active', 'last' ]:
-                    count[t] = 0
-                    if t in item:
-                        for gw in item[t]:
-                            if 'remote' in item[t][gw] and len(item[t][gw]['remote']) > 0:
-                                count[t] += 1
-                            if 'peer' in item[t][gw]:
-                                names.add(item[t][gw]['peer'])
-
-                self.wfile.write('<tr class="online">' if count['active'] > 0 else '<tr class="offline">')
-                self.wfile.write('<td title="' + str(key) + '">' + (' / '.join(names) if len(names) > 0 else '?') + '</td>')
-                for t in [ 'active', 'last' ]:
-                    for gw in gateways:
-                        ip = ''
-                        if t in item and gw in item[t]:
-                            ip = item[t][gw]['remote'] if 'remote' in item[t][gw] else ''
-                            if isinstance(ip, dict):
-                                ip = ip['name']
-                        self.wfile.write('<td title="' + ip + '">' + ('&check;' if len(ip) > 0 else '&times;') + '</td>')
-
-                self.wfile.write('</tr>\n')
+        for item in self.server.storage.get_vpn_connections():
+            self.wfile.write('<tr class="{0}">'.format('online' if item['online'] else 'offline'))
+            self.wfile.write('<td title="{0}">{1}</td>'.format(
+                item['key'],
+                ' / '.join(item['names']) if len(item['names']) > 0 else '?',
+            ))
+
+            for conntype in ['active', 'last']:
+                for gateway in gateways:
+                    remote = ''
+                    if conntype in item['remote'] and gateway in item['remote'][conntype]:
+                        remote = item['remote'][conntype][gateway]
+                        if isinstance(remote, dict):
+                            remote = remote['name']
+                    symbol = '&check;' if len(remote) > 0 else '&times;'
+                    self.wfile.write('<td title="{0}">{1}</td>'.format(
+                        remote, symbol))
+
+            self.wfile.write('</tr>\n')
 
         self.wfile.write('</table>\n')
         self.wfile.write('</body>')
@@ -460,49 +344,32 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
     def respond_providers(self, query):
         """Return a summary of providers."""
 
-        vpn = self.server.storage.data[self.DATAKEY_VPN]
         outputformat = query['format'].lower() if 'format' in query else 'html'
 
         isps = {}
         ispblocks = {}
-        vpnstorage_updated = False
-        vpnstorage_update_allowed = 'update' in query and query['update'] == 'allowed'
-        for key in vpn:
-            if key is None:
-                continue
-            item = vpn[key]
-            if not isinstance(item, dict):
-                continue
-            if not 'active' in item:
+        for item in self.server.storage.get_vpn_connections():
+            if item['count']['active'] == 0:
                 continue
 
-            ips = []
-            for gw in item['active']:
-                if 'remote' in item['active'][gw]:
-                    ip = item['active'][gw]['remote']
-                    if vpnstorage_update_allowed and not isinstance(ip, dict):
-                        # try to resolve ip now
-                        resolved = ffstatus.resolve_ipblock(ip)
-                        if not resolved is None:
-                            self.logger.debug('Resolved IP \'{0}\' to block \'{1}\'.'.format(ip, resolved))
-                            item['active'][gw]['remote'] = resolved
-                            vpnstorage_updated = True
-                            ip = resolved
-                        else:
-                            self.logger.debug('Failed to resolve IP \'{0}\'.'.format(ip))
-                    ips.append(ip)
-
-            if len(ips) == 0:
-                # no active dialins -> no need to process this key any further
+            remotes = []
+            for gateway in item['remote']['active']:
+                remote = item['remote']['active'][gateway]
+                remotes.append(remote)
+
+            if len(remotes) == 0:
+                self.logger.warn(
+                    'VPN key \'%s\' is marked with active remotes but 0 found?',
+                    item['key'])
                 continue
 
             item_isps = set()
-            for ip in ips:
+            for remote in remotes:
                 isp = "UNKNOWN"
-                ispblock = ip
-                if isinstance(ip, dict):
-                    ispblock = ip['name']
-                    desc_lines = ip['description'].split('\n')
+                ispblock = remote
+                if isinstance(remote, dict):
+                    ispblock = remote['name']
+                    desc_lines = remote['description'].split('\n')
                     isp = desc_lines[0].strip()
 
                     # normalize name: strip company indication
@@ -538,9 +405,6 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
 
         isps_sum = sum([isps[x] for x in isps])
 
-        if vpnstorage_updated:
-            self.server.storage.save()
- 
         if outputformat == 'csv':
             self.send_headers('text/csv')
 
@@ -586,6 +450,7 @@ class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
         else:
             self.send_error(400, 'Unknown output format.')
 
+
 class ApiServer(ThreadingMixIn, HTTPServer):
     def __init__(self, endpoint, storage):
         if ':' in endpoint[0]:
@@ -593,34 +458,6 @@ class ApiServer(ThreadingMixIn, HTTPServer):
         HTTPServer.__init__(self, endpoint, BatcaveHttpRequestHandler)
         self.storage = storage
 
-        # check all entries for a proper 'remote' entry
-        vpn = storage.data[BatcaveHttpRequestHandler.DATAKEY_VPN] if BatcaveHttpRequestHandler.DATAKEY_VPN in storage.data else {}
-        init_vpn_cache = {}
-        for key in vpn:
-            if not isinstance(vpn[key], dict):
-                continue
-            for mode in vpn[key]:
-                if not isinstance(vpn[key][mode], dict):
-                    continue
-                for gw in vpn[key][mode]:
-                    if not isinstance(vpn[key][mode][gw], dict):
-                        continue
-                    item = vpn[key][mode][gw]
-                    if 'remote' in item and not 'remote_raw' in item:
-                        item['remote_raw'] = item['remote']
-                        resolved = None
-                        if item['remote'] in init_vpn_cache:
-                            resolved = init_vpn_cache[item['remote']]
-                        else:
-                            resolved = ffstatus.resolve_ipblock(item['remote'])
-                            init_vpn_cache[item['remote']] = resolved
-                            if not resolved is None:
-                                logging.info('Startup: resolved VPN entry \'{0}\' to net \'{1}\'.'.format(item['remote'], resolved['name']))
-                        if not resolved is None:
-                            item['remote'] = resolved
-        storage.save()
-
-
     def __str__(self):
         return 'ApiServer on {0}'.format(self.server_address)