diff --git a/netdiff/__init__.py b/netdiff/__init__.py index 9a1cd40..0b498a2 100644 --- a/netdiff/__init__.py +++ b/netdiff/__init__.py @@ -5,4 +5,5 @@ from .parsers.bmx6 import Bmx6Parser # noqa from .parsers.netjson import NetJsonParser # noqa from .parsers.cnml import CnmlParser # noqa +from .parsers.openvpn import OpenvpnParser #noqa from .utils import diff # noqa diff --git a/netdiff/parsers/openvpn.py b/netdiff/parsers/openvpn.py new file mode 100644 index 0000000..d1504c4 --- /dev/null +++ b/netdiff/parsers/openvpn.py @@ -0,0 +1,45 @@ +import networkx +from openvpn_status import parse_status + +from .base import BaseParser + + +class OpenvpnParser(BaseParser): + """ OpenVPN status log parser """ + protocol = 'OpenVPN Status Log' + version = '1' + metric = 'static' + # for internal use only + _server_common_name = 'openvpn-server' + + def to_python(self, data): + return parse_status(data) + + def parse(self, data): + """ + Converts a OpenVPN JSON to a NetworkX Graph object + which is then returned. + """ + # initialize graph and list of aggregated nodes + graph = networkx.Graph() + server = self._server_common_name + # add server (central node) to graph + graph.add_node(server) + # add clients in graph as nodes + for common_name, client in data.client_list.items(): + client_properties = { + 'real_address': str(client.real_address.host), + 'port': client.real_address.port, + 'connected_since': client.connected_since.strftime('%Y-%m-%dT%H:%M:%SZ'), + 'bytes_received': client.bytes_received, + 'bytes_sent': client.bytes_sent + } + if common_name in data.routing_table: + client_properties['local_addresses'] = [ + str(data.routing_table.get(common_name).virtual_address) + ] + graph.add_node(common_name, **client_properties) + # add links in routing table to graph + for common_name, link in data.routing_table.items(): + graph.add_edge(server, common_name, weight=1) + return graph diff --git a/requirements.txt b/requirements.txt index 6b05f5a..c93eee0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ networkx<2.0 requests<3.0 six libcnml<0.10.0 +openvpn-status<0.2 diff --git a/tests/static/openvpn-2-links.txt b/tests/static/openvpn-2-links.txt new file mode 100644 index 0000000..6662f21 --- /dev/null +++ b/tests/static/openvpn-2-links.txt @@ -0,0 +1,12 @@ +OpenVPN CLIENT LIST +Updated,Thu Jun 18 08:12:15 2015 +Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since +node1,87.18.10.87:49502,334948,1973012,Thu Jun 18 04:23:03 2015 +node2,93.40.230.50:64169,1817262,28981224,Thu Jun 18 04:08:39 2015 +ROUTING TABLE +Virtual Address,Common Name,Real Address,Last Ref +192.168.255.134,node1,87.18.10.87:49502,Thu Jun 18 08:12:09 2015 +192.168.255.126,node2,93.40.230.50:64169,Thu Jun 18 08:11:55 2015 +GLOBAL STATS +Max bcast/mcast queue length,0 +END diff --git a/tests/test_openvpn.py b/tests/test_openvpn.py new file mode 100644 index 0000000..57a3dfd --- /dev/null +++ b/tests/test_openvpn.py @@ -0,0 +1,33 @@ +import os + +import networkx +import six +from netdiff import OpenvpnParser +from netdiff.exceptions import ParserError, TopologyRetrievalError +from netdiff.tests import TestCase + +CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) +links2 = open('{0}/static/openvpn-2-links.txt'.format(CURRENT_DIR)).read() + + +class TestOpenvpnParser(TestCase): + + def test_parse(self): + p = OpenvpnParser(links2) + self.assertIsInstance(p.graph, networkx.Graph) + self.assertEqual(p.version, '1') + self.assertEqual(p.metric, 'static') + + def test_json_dict(self): + p = OpenvpnParser(links2) + data = p.json(dict=True) + self.assertIsInstance(data, dict) + self.assertEqual(data['type'], 'NetworkGraph') + self.assertEqual(data['protocol'], 'OpenVPN Status Log') + self.assertEqual(data['version'], '1') + self.assertEqual(data['revision'], None) + self.assertEqual(data['metric'], 'static') + self.assertIsInstance(data['nodes'], list) + self.assertIsInstance(data['links'], list) + self.assertEqual(len(data['nodes']), 3) + self.assertEqual(len(data['links']), 2)