PySNMP est un module open-source Python prenant en charge toutes les versions du protocole SNMP et permettant d'agir autant comme manager que comme agent.
Méthode standard, depuis l'environnement virtuel on utilise pip:
pip3 install pysnmp
L'opération SNMP GET permet de récupérer la valeur d'un objet de la MIB exposée par l'agent SNMP. Depuis une script Python, il nous faut importer le module pysnmp, instancier un contrôleur et manipuler ce contrôleur à la fois pour envoyer des requêtes SNMP et pour obtenir les résultats retournés par la cible.
Pour appréhender le fonctionnement de pysnmp on peut utiliser le mode interactif:
from pysnmp import hlapi def get(target, oids, credentials, port=161, engine=hlapi.SnmpEngine(), context=hlapi.ContextData()): handler = hlapi.getCmd( engine, credentials, hlapi.UdpTransportTarget((target, port)), context, *construct_object_types(oids) ) return fetch(handler, 1)[0]
Dans le code ci-dessus on définit une fonction get() avec 3 arguments obligatoires:
Via les paramètres optionnels on peut:
Il peut être utile d'utiliser un même moteur pour récupérer plusieurs OIDs sur une même cible.
Via l'appel de hlapi.getCmd(), la fonction instancie le contrôleur (handler) en charge de la session SNMP. C'est également par l'intermédiaire du contrôleur qu'on peut récupérer la réponse retournée par l'agent.
Notons que le paramètre oids n'est pas directement transmis à la fonction hlapi.getCmd() il est traité par une fonction qui transforme la liste de chaînes de caractères en une liste d'objets attendus de type hlapi.ObjectType
.
Ci dessous le code de la fonction:
def construct_object_types(list_of_oids): object_types = [] for oid in list_of_oids: object_types.append(hlapi.ObjectType(hlapi.ObjectIdentity(oid))) return object_types
La fonction construct_object_types() retourne une liste qui est développée lors de l'appel par l'usage de l'opérateur '*'.
La fonction fetch() est la pièce angulaire du script Python proposé ici. Elle est pensée pour pouvoir être réutilisée avec d'autre fonctions SNMP disponible via le handler telle que get-bulk(). Elle permet de boucler sur le contrôleur autant de fois que l'indique la valeur du paramètre count. Si elle rencontre une erreur, elle stoppe sont éxecution et lève une exception de type RuntimeError
. Sinon les données sont stockées dans une liste de dictionnaires.
def fetch(handler, count): result = [] for i in range(count): try: error_indication, error_status, error_index, var_binds = next(handler) if not error_indication and not error_status: items = {} for var_bind in var_binds: items[str(var_bind[0])] = cast(var_bind[1]) result.append(items) else: raise RuntimeError('Got SNMP error: {0}'.format(error_indication)) except StopIteration: break return result
Le bloc try … except StopIteration
est nécessaire pour le cas ou la valeur de count spécifiée par l'utilisateur est supérieure au nombre d'objets retournés.. Dans ce cas on capture l'exception, on arrête simplement la boucle et on retourne la liste de résultats récupérée.
Les dictionnaires sont nécessaires car chaque opération GET peut contenir de multiples OIDs. Chaque dictionnaire a ainsi comme clé l'OID et comme valeur la valeur retournée par la cible.
Le résultat retourné par fetch() est une liste. Dans le cas de l'appel de la commande SNMP GET la valeur est retournée une seule fois. Cependant si l'on fait appel à la commande SNMP BULK nous allons demander la meme information plusieurs fois sur différentes instances. Cela peut être utile par exemple si on souhaite remonter le compteur d'erreurs sur l'ensemble des interfaces de l'agent (la requête demande un meme compteur d'erreur mais il y a de multiples instances: une par interface).
Notons enfin que la fonction fetch() utilise la fonction cast() qui se charge de convertir les chaînes de caractères reçues de PySNMP en types Python int, float ou string.
def cast(value): try: return int(value) except (ValueError, TypeError): try: return float(value) except (ValueError, TypeError): try: return str(value) except (ValueError, TypeError): pass return value