From 29e74283077fd45641d858c8ee718c8bb74ae747 Mon Sep 17 00:00:00 2001 From: Andrzej Jankowski Date: Thu, 14 Aug 2014 10:34:04 +0200 Subject: [PATCH] snmp_lldp plugin added --- src/ralph/discovery/snmp.py | 12 ++ src/ralph/scan/plugins/snmp_lldp.py | 122 +++++++++++++++ .../scan/tests/plugins/test_snmp_lldp.py | 145 ++++++++++++++++++ src/ralph/settings.py | 9 ++ 4 files changed, 288 insertions(+) create mode 100644 src/ralph/scan/plugins/snmp_lldp.py create mode 100644 src/ralph/scan/tests/plugins/test_snmp_lldp.py diff --git a/src/ralph/discovery/snmp.py b/src/ralph/discovery/snmp.py index 931a86ca2f..6190ef9d9b 100644 --- a/src/ralph/discovery/snmp.py +++ b/src/ralph/discovery/snmp.py @@ -63,6 +63,18 @@ def snmp_command( return vars +def snmp_walk( + hostname, community, oid, snmp_version='2c', timeout=1, attempts=3, + priv_protocol=cmdgen.usmDESPrivProtocol +): + transport = cmdgen.UdpTransportTarget((hostname, 161), attempts, timeout) + data = user_data(community, snmp_version, priv_protocol=priv_protocol) + gen = cmdgen.CommandGenerator() + error, status, index, values = gen.nextCmd(data, transport, oid) + if not error: + return values + + def snmp_bulk( hostname, community, oid, snmp_version='2c', timeout=1, attempts=3 ): diff --git a/src/ralph/scan/plugins/snmp_lldp.py b/src/ralph/scan/plugins/snmp_lldp.py new file mode 100644 index 0000000000..4c0b7acf60 --- /dev/null +++ b/src/ralph/scan/plugins/snmp_lldp.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from django.conf import settings + +from ralph.discovery.models import ConnectionType +from ralph.scan.errors import Error +from ralph.scan.plugins import get_base_result_template +from ralph.discovery.snmp import snmp_walk + + +SETTINGS = settings.SCAN_PLUGINS.get(__name__, {}) + + +def _get_device_interfaces_map(ip_address, snmp_community, snmp_version): + items = snmp_walk( + ip_address, + snmp_community, + [int(chunk) for chunk in "1.0.8802.1.1.2.1.3.7.1.3".split(".")], + snmp_version + ) + interfaces_map = {} + for item in items: + interfaces_map[unicode(item[0][0]).split(".")[-1]] = unicode( + item[0][1] + ) + return interfaces_map + + +def _get_remote_mac_addresses_map(ip_address, snmp_community, snmp_version): + items = snmp_walk( + ip_address, + snmp_community, + [int(chunk) for chunk in "1.0.8802.1.1.2.1.4.1.1.5".split(".")], + snmp_version + ) + mac_addresses_map = {} + for item in items: + mac_addresses_map[unicode(item[0][0]).split(".")[-2]] = "".join( + "{:0>2}".format(str(hex(ord(chunk)))[2:]) for chunk in item[0][1] + ).upper() + return mac_addresses_map + + +def _get_remote_connected_ports_map(ip_address, snmp_community, snmp_version): + items = snmp_walk( + ip_address, + snmp_community, + [int(chunk) for chunk in "1.0.8802.1.1.2.1.4.1.1.7".split(".")], + snmp_version + ) + ports_map = {} + for item in items: + ports_map[unicode(item[0][0]).split(".")[-2]] = unicode(item[0][1]) + return ports_map + + +def _snmp_lldp( + ip_address, snmp_community, snmp_version, messages=[] +): + try: + interfaces_map = _get_device_interfaces_map( + ip_address, snmp_community, snmp_version + ) + mac_addresses_map = _get_remote_mac_addresses_map( + ip_address, snmp_community, snmp_version + ) + ports_map = _get_remote_connected_ports_map( + ip_address, snmp_community, snmp_version + ) + except TypeError: + raise Error("No answer.") + except IndexError: + raise Error("Incorrect answer.") + connections = [] + for item_id, mac_address in mac_addresses_map.iteritems(): + details = {} + if interfaces_map.get(item_id): + details['outbound_port'] = interfaces_map.get(item_id) + if ports_map.get(item_id): + details['inbound_port'] = ports_map.get(item_id) + connection = { + 'connection_type': ConnectionType.network.name, + 'connected_device_mac_addresses': mac_address + } + if details: + connection['details'] = details + connections.append(connection) + return { + 'system_ip_addresses': [ip_address], + 'connections': connections + } + + +def scan_address(ip_address, **kwargs): + snmp_version = kwargs.get('snmp_version', '2c') or '2c' + if snmp_version == '3': + snmp_community = SETTINGS['snmp_v3_auth'] + else: + snmp_community = str(kwargs['snmp_community']) + messages = [] + result = get_base_result_template('snmp_macs', messages) + try: + info = _snmp_lldp( + ip_address, + snmp_community, + snmp_version, + messages, + ) + except Error as e: + messages.append(unicode(e)) + result['status'] = "error" + else: + result.update({ + 'status': 'success', + 'device': info, + }) + return result diff --git a/src/ralph/scan/tests/plugins/test_snmp_lldp.py b/src/ralph/scan/tests/plugins/test_snmp_lldp.py new file mode 100644 index 0000000000..a43112b3cf --- /dev/null +++ b/src/ralph/scan/tests/plugins/test_snmp_lldp.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import mock + +from django.test import TestCase + +from ralph.scan.plugins.snmp_lldp import ( + _get_device_interfaces_map, + _get_remote_connected_ports_map, + _get_remote_mac_addresses_map, + _snmp_lldp, +) + + +class SnmpLldpPluginTest(TestCase): + + def test_get_device_interfaces_map(self): + with mock.patch( + 'ralph.scan.plugins.snmp_lldp.snmp_walk', + ) as snmp_walk: + snmp_walk.side_effect = [ + [ + (("1.0.8802.1.1.2.1.3.7.1.3.1", "Gi0/1"), None), + (("1.0.8802.1.1.2.1.3.7.1.3.2", "Gi0/2"), None), + (("1.0.8802.1.1.2.1.3.7.1.3.3", "Gi0/3"), None), + (("1.0.8802.1.1.2.1.3.7.1.3.4", "Gi0/4"), None), + ] + ] + self.assertEqual( + _get_device_interfaces_map("10.10.10.10", "public", "2c"), + { + "1": "Gi0/1", + "2": "Gi0/2", + "3": "Gi0/3", + "4": "Gi0/4", + } + ) + + def test_get_remote_mac_addresses_map(self): + with mock.patch( + 'ralph.scan.plugins.snmp_lldp.snmp_walk', + ) as snmp_walk: + snmp_walk.side_effect = [ + [ + ( + ( + "1.0.8802.1.1.2.1.3.7.1.3.6.1", + ['\xff', '\xfe', '\xfd', '\xfc', '\xfb', '\x02'] + ), + None + ), + ] + ] + self.assertEqual( + _get_remote_mac_addresses_map("10.10.10.10", "public", "2c"), + {"6": "FFFEFDFCFB02"} + ) + + def test_get_remote_connected_ports_map(self): + with mock.patch( + 'ralph.scan.plugins.snmp_lldp.snmp_walk', + ) as snmp_walk: + snmp_walk.side_effect = [ + [ + ( + ( + "1.0.8802.1.1.2.1.4.1.1.7.0.33.1", + "Eth0" + ), + None + ), + ( + ( + "1.0.8802.1.1.2.1.4.1.1.7.0.42.2", + "Eth1" + ), + None + ), + ] + ] + self.assertEqual( + _get_remote_connected_ports_map("10.10.10.10", "public", "2c"), + {'33': 'Eth0', '42': 'Eth1'}, + ) + + def test_snmp_lldp(self): + with mock.patch( + 'ralph.scan.plugins.snmp_lldp._get_device_interfaces_map', + ) as get_device_interfaces_map_mock, mock.patch( + 'ralph.scan.plugins.snmp_lldp._get_remote_mac_addresses_map', + ) as get_remote_mac_addresses_map_mock, mock.patch( + 'ralph.scan.plugins.snmp_lldp._get_remote_connected_ports_map', + ) as get_remote_connected_ports_map_mock: + get_device_interfaces_map_mock.return_value = { + "1": "Gi0/1", + "2": "Gi0/2", + "3": "Gi0/3", + "4": "Gi0/4", + "5": "Gi0/5", + "6": "Gi0/6", + "7": "Gi0/7", + "8": "Gi0/8", + } + get_remote_mac_addresses_map_mock.return_value = { + "2": "FFFEFDFCFB03", + "3": "FFFEFDFCFB04", + "9": "FFFEFDFCFBFF", + } + get_remote_connected_ports_map_mock.return_value = { + '2': 'Eth0', + '3': 'Eth1', + } + self.assertEqual( + _snmp_lldp("10.10.10.11", "public", "2c"), + { + 'connections': [ + { + 'connected_device_mac_addresses': 'FFFEFDFCFBFF', + 'connection_type': 'network', + }, + { + 'connected_device_mac_addresses': 'FFFEFDFCFB04', + 'connection_type': 'network', + 'details': { + 'inbound_port': 'Eth1', + 'outbound_port': 'Gi0/3' + } + }, + { + 'connected_device_mac_addresses': 'FFFEFDFCFB03', + 'connection_type': 'network', + 'details': { + 'inbound_port': 'Eth0', + 'outbound_port': 'Gi0/2' + } + } + ], + 'system_ip_addresses': ['10.10.10.11'] + } + ) diff --git a/src/ralph/settings.py b/src/ralph/settings.py index 0adede3549..f4cd589b4b 100644 --- a/src/ralph/settings.py +++ b/src/ralph/settings.py @@ -426,6 +426,14 @@ 'serial_number': 20, }, }, + 'ralph.scan.plugins.snmp_lldp': { + 'communities': SNMP_PLUGIN_COMMUNITIES, + 'snmp_v3_auth': (SNMP_V3_USER, SNMP_V3_AUTH_KEY, SNMP_V3_PRIV_KEY), + 'results_priority': { + 'connections': 55, + 'system_ip_addresses': 50, + }, + }, 'ralph.scan.plugins.idrac': { 'user': IDRAC_USER, 'password': IDRAC_PASSWORD, @@ -456,6 +464,7 @@ 'system_cores_count': 20, 'processors': 15, 'memory': 15, + 'connections': 25, }, }, 'ralph.scan.plugins.puppet': {