Snimpy : SNMP & Python
Vincent Bernat
Souvent considéré comme désuet, SNMP est toujours omniprésent pour interagir avec des équipements réseau. Pour la supervision, il permet d’exposer diverses métriques telles que les compteurs liés aux interfaces réseau. Il permet également d’interagir sur la configuration des équipements.
Les variables exposées par les agents SNMP (serveurs) sont contenues dans une base hiérarchique appelée Management Information Base (ou MIB1). Chaque entrée est identifiée par un OID. En interrogeant un OID spécifique, un manager (client) obtient la valeur associée à la variable.
Par exemple, IF-MIB
est une MIB présentée dans la RFC 2863. Elle
contient les objets utilisés pour gérer les interfaces réseau. L’un
d’eux est ifTable
dont chaque ligne représente une interface réseau
logique : son nom, ses caractéristiques et divers compteurs.
ifIndex | ifDescr | ifPhysAddress | ifOperStatus | ifOutOctets |
---|---|---|---|---|
1 | lo | up | 545721741 | |
2 | eth0 | 0:18:f3:3:4e:4 | up | 78875421 |
3 | eth1 | 0:18:f3:3:4e:5 | down | 0 |
ifTable
est indexée par sa première colonne, ifIndex
. Pour obtenir
le statut opérationnel de la seconde interface, il suffit d’interroger
IF-MIB::ifOperStatus.2
que l’on peut traduire vers l’OID
.1.3.6.1.2.1.2.2.1.8.2
à l’aide des informations contenues dans la
définition de la MIB.
Automatiser SNMP#
Un agent SNMP peut fournir de nombreuses informations intéressantes :
- les compteurs des interfaces réseau via l’IF-MIB,
- les adresses IP via l’IP-MIB,
- les tables de routage via l’IP-FORWARD-MIB,
- l’inventaire physique via l’ENTITY-MIB,
- les voisins via la LLDP-MIB.
Des outils tels que snmpget
et snmpwalk
permettent de collecter
manuellement ces informations :
$ snmpwalk -v 2c -c public localhost IF-MIB::ifDescr IF-MIB::ifDescr.1 = STRING: lo IF-MIB::ifDescr.2 = STRING: eth0 IF-MIB::ifDescr.3 = STRING: eth1
Toutefois, il est assez fastidieux de construire des scripts robustes à l’aide de ceux-ci. Par exemple, voici un script pour obtenir les descriptions de toutes les interfaces réseau actives ainsi que le nombre total d’octets transmis :
#!/bin/sh set -e host="${1:-localhost}" community="${2:-public}" args="-v2c -c $community $host" for idx in $(snmpwalk -Ov -OQ $args IF-MIB::ifIndex); do descr=$(snmpget -Ov -OQ $args IF-MIB::ifDescr.$idx) oper=$(snmpget -Ov -OQ $args IF-MIB::ifOperStatus.$idx) in=$(snmpget -Ov -OQ $args IF-MIB::ifInOctets.$idx) out=$(snmpget -Ov -OQ $args IF-MIB::ifOutOctets.$idx) [ x"$descr" != x"lo" ] || continue [ x"$oper" = x"up" ] || continue echo $descr $in $out done
Heureusement, il existe des extensions SNMP pour la plupart des langages. À titre d’exemple, voici comment pourrait être réécrit le script ci-dessus en exploitant l’extension SNMP pour Python livrée avec Net-SNMP :
import argparse import netsnmp parser = argparse.ArgumentParser() parser.add_argument( "host", default="localhost", nargs="?", help="Agent to retrieve variables from") parser.add_argument( "community", default="public", nargs="?", help="Community to query the agent") options = parser.parse_args() args = { "Version": 2, "DestHost": options.host, "Community": options.community } for idx in netsnmp.snmpwalk(netsnmp.Varbind("IF-MIB::ifIndex"), **args): descr, oper, cin, cout = netsnmp.snmpget( netsnmp.Varbind("IF-MIB::ifDescr", idx), netsnmp.Varbind("IF-MIB::ifOperStatus", idx), netsnmp.Varbind("IF-MIB::ifInOctets", idx), netsnmp.Varbind("IF-MIB::ifOutOctets", idx), **args) assert(descr is not None and cin is not None and cout is not None) # ❶ if descr == "lo": continue if oper != "1": # ❷ continue print("{} {} {}".format(descr, cin, cout))
Cette extension a plusieurs défauts importants :
- Tout est présenté sous forme de chaînes de caractères comme on peut le voir en ❷.
- La gestion des erreurs est inexistante. En cas d’erreur sur le nom
d’une variable, le message
snmp_build: unknown failure
s’affiche mais aucune exception n’est levée. Si une variable n’existe pas, les fonctions retournentNone
. Voir en ❶.
L’impossibilité de gérer les erreurs rend cette extension dangereuse. On ne peut imaginer effectuer des modifications importantes sur la base des réponses retournées. Une vérification oubliée et un script peut enchaîner des actions inappropriées !
Snimpy#
N’ayant pas trouvé d’extension fiable pour Python, j’ai donc décidé d’écrire Snimpy avec deux principaux objectifs :
- S’appuyer sur les informations contenues dans les MIB pour fournir une interface pythonique.
- Toute erreur doit se traduire en une exception.
Voici comment le précédent script pourrait être réécrit :
#!/usr/bin/env snimpy import argparse parser = argparse.ArgumentParser() parser.add_argument( "host", default="localhost", nargs="?", help="Agent to retrieve variables from") parser.add_argument( "community", default="public", nargs="?", help="Community to query the agent") options = parser.parse_args() m = M(options.host, options.community, 2) load("IF-MIB") for idx in m.ifDescr: if m.ifDescr[idx] == "lo": continue if m.ifOperStatus[idx] != "up": continue print("{} {} {}".format(m.ifDescr[idx], m.ifInOctets[idx], m.ifOutOctets[idx]))
Une alternative est d’utiliser les listes en compréhension :
load("IF-MIB") print("\n".join([ "{} {} {}".format( m.ifDescr[idx], m.ifInOctets[idx], m.ifOutOctets[idx]) for idx in m.ifDescr if m.ifDescr[idx] != "lo" and m.ifOperStatus[idx] == "up" ]))
Voici un autre exemple pour obtenir la table de routage de l’agent :
load("IP-FORWARD-MIB") m=M("localhost", "public", 2) routes = m.ipCidrRouteNextHop for x in routes: net, netmask, tos, src = x print("{:>15s}/{:<15s} via {:<15s} src {:<15s}".format( net, netmask, routes[x], src))
IP-FORWARD-MIB::ipCidrRouteNextHop
est issue d’une table utilisant
un index composé. Cependant, son utilisation semble toujours
naturelle.
Techniquement, les requêtes SNMP sont gérées par PySNMP et les MIB sont interprétées à l’aide de libsmi2. Snimpy fonctionne à la fois avec Python 2 et Python 3. Pour plus d’informations, jetez un œil sur la documentation de Snimpy.
-
Une MIB est définie à l’aide de SMI, un sous-ensemble de ASN.1. Toutefois, il n’est pas rare d’appeler également « MIB » cette définition. ↩︎
-
À l’heure actuelle, il n’existe malheureusement pas d’analyseur robuste pour SMI écrit en Python. Par exemple, PySNMP s’appuie sur l’outilPySNMP s’appuie désormais sur PySMI. Snimpy exploite libsmi via CFFI. ↩︎smidump
livré avec libsmi.