Use Python and SNMP (LLDP) to find the neighbors of the switch

introduce About SNMP SNMP stands for simple network management protocol. This is a way for servers to share infor...
introduce

About SNMP

SNMP stands for simple network management protocol. This is a way for servers to share information about their current status and a channel for administrators to modify predefined values. SNMP is a protocol implemented on the application layer of the network stack (click here to learn about the network layer). The protocol was created to collect information from very different systems in a consistent manner.

You can read more about SNMP, OID, and SNMP methods in the links above. To summarize, the script uses:

  • snmp version 2c
  • snmp walk is the main method to obtain data from snmp
  • The data obtained from the device is obtained from a specific OID (more lldp OIDs can be found in here (found)

About LLDP

Link Layer Discovery Protocol(   LLDP  ) It is a vendor neutral layer 2 protocol. Sites connected to a specific LAN segment can use this protocol to announce their identity and functions, and receive the same information from physically adjacent layer 2 peers.

Using python to combine SNMP and LLDP

The purpose of my program is to return the neighbor data (local and remote ports + neighbor name) of all switches in the file by using Python 3.6 and providing a switch data file (community string, snmp port and switch ip).

Sample profile:

community_string1, snmp_port1, ip1 community_string2, snmp_port2, ip2 community_string3, snmp_port3, ip3

Sample output:

[ { "name1": { "ip": "ip1", "neighbours": [ { "neighbour_name1": "neighbour_name1", "local_port1": "local_port1", "remote_port1": "remote_port1" }, { "neighbour_name2": "neighbour_name2", "local_port2": "local_port2", "remote_port2": "remote_port2" }, { "neighbour_name3": "neighbour_name3", "local_port3": "local_port3", "remote_port3": "remote_port3" }, ] }, "name2": , "name3": , } ]

Interpretation output

  • name1 represents the switch name in the first line of the configuration file (retrieve PARENT_NAME_OID by executing snmp walk for)
  • ip1   The first line from the configuration file represents the IP of the switch (this is obtained from the configuration file)
  • Neighbors are retrieved through snmp using a specific OID (see the following code).

I think this JSON output format is the most relevant, but if you have a better idea, I'd like to hear it.

code

Now, the code is a little messy, but it uses pysnmp You can use pip   It receives the configuration file as a CLI parameter, parses it, and processes the information in it.

import argparse import itertools import os import pprint from pysnmp.hlapi import * NEIGHBOUR_PORT_OID = '1.0.8802.1.1.2.1.4.1.1.8.0' NEIGHBOUR_NAME_OID = '1.0.8802.1.1.2.1.4.1.1.9' PARENT_NAME_OID = '1.0.8802.1.1.2.1.3.3' class MissingOidParameter(Exception): """ Custom exception used when the OID is missing. """ pass def is_file_valid(filepath): """ Check if a file exists or not. Args: filepath (str): Path to the switches file Returns: filepath or raise exception if invalid """ if not os.path.exists(filepath): raise ValueError('Invalid filepath') return filepath def get_cli_arguments(): """ Simple command line parser function. """ parser = argparse.ArgumentParser(description="") parser.add_argument( '-f', '--file', type=is_file_valid, help='Path to the switches file' ) return parser def get_switches_from_file(): """Return data as a list from a file. The file format is the following: community_string1, snmp_port1, ip1 community_string2, snmp_port2, ip2 community_string3, snmp_port3, ip3 The output: [ {"community": "community_string1", "snmp_port": "snmp_port1", "ip": "ip1"}, {"community": "community_string2", "snmp_port": "snmp_port2", "ip": "ip2"}, {"community": "community_string3", "snmp_port": "snmp_port3", "ip": "ip3"}, ] """ args = get_cli_arguments().parse_args() switches_info = [] with open(args.file) as switches_info_fp: for line in switches_info_fp: line = line.rstrip().split(',') switches_info.append({ 'community': line[0].strip(), 'snmp_port': line[1].strip(), 'ip': line[2].strip(), }) return switches_info def parse_neighbours_ports_result(result): """ One line of result looks like this: result = iso.0.8802.1.1.2.1.4.1.1.8.0.2.3 = 2 Where the last "2" from the OID is the local port and the value after '=' is the remote port (2) """ if not result: raise MissingOidParameter('No OID provided.') value = result.split(' = ') if not value: return 'Missing entire value for OID={}'.format(result) else: oid, port = value local_port = re.search(r'{}\.(\d+)'.format(NEIGHBOUR_PORT_OID[2:]), oid).group(1) if port: remote_port = re.search(r'(\d+)', port).group(1) else: remote_port = 'Unknown' return 'local_port', local_port, 'remote_port', remote_port def parse_parent_name(result): """ One line of result looks like this: result = iso.0.8802.1.1.2.1.3.3.0 = Switch01 The name of the parent is "Switch01" """ if not result: raise MissingOidParameter('No OID provided.') value = result.split(' = ') if not value: return 'Missing entire value for OID={}'.format(result) else: return 'Unknown' if not value[-1] else value[-1] def parse_neighbour_names_results(result): """ One line of result looks like this: result = iso.0.8802.1.1.2.1.4.1.1.9.0.2.3 = HP-2920-24G The name of the parent is "Switch01" """ if not result: raise MissingOidParameter('No OID provided.') value = result.split(' = ') if not value: return 'Missing entire value for OID={}'.format(result) else: return ('name', 'Unknown') if not value[-1] else ('name', value[-1]) def main(): data = {} switches_filedata = get_switches_from_file() for switch in switches_filedata: community = switch.get('community') snmp_port = switch.get('snmp_port') ip = switch.get('ip') name = '' for (error_indication, error_status, error_index, var_binds) in nextCmd( SnmpEngine(), CommunityData(community), UdpTransportTarget((ip, snmp_port)), ContextData(), ObjectType(ObjectIdentity(PARENT_NAME_OID)), lexicographicMode=False ): # this should always return one result name = parse_parent_name(str(var_binds[0])) if not name: print('Could not retrieve name of switch. Moving to the next one...') continue neighbour_names = [] neighbour_local_remote_ports = [] for (error_indication, error_status, error_index, var_binds) in nextCmd( SnmpEngine(), CommunityData(community), UdpTransportTarget((ip, snmp_port)), ContextData(), ObjectType(ObjectIdentity(NEIGHBOUR_NAME_OID)), lexicographicMode=False ): for var_bind in var_binds: neighbour_names.append( parse_neighbour_names_results(str(var_bind)) ) for (error_indication, error_status, error_index, var_binds) in nextCmd( SnmpEngine(), CommunityData(community), UdpTransportTarget((ip, snmp_port)), ContextData(), ObjectType(ObjectIdentity(NEIGHBOUR_PORT_OID)), lexicographicMode=False ): for var_bind in var_binds: neighbour_local_remote_ports.append( parse_neighbours_ports_result(str(var_bind)) ) neighbours = [] for a, b in itertools.zip_longest( neighbour_names, neighbour_local_remote_ports, fillvalue='unknown' ): neighbours.append({ a[0]: a[1], b[0]: b[1], b[2]: b[3] }) data[name] = { 'ip': ip, 'neighbors': neighbours } return data if __name__ == '__main__': all_data = main() pprint.pprint(all_data, indent=4)

snmp-lldp.py -f snmp-lldp.txt

17 November 2021, 00:12 | Views: 3666

Add new comment

For adding a comment, please log in
or create account

0 comments