Makina Blog
Initiation à SNMP avec Python : PySNMP (Partie 3) - Les tables
SNMP est un protocole de supervision réseau universellement répandu. C'est le standard utilisé par la quasi totalité des équipements réseaux. Il permet de superviser (interrogation et modification) tous les types de matériels, allant du routeur à l'imprimante, et même certaines machines à café connectées. Cette troisième partie du tutoriel vous présente comment gérer les données de type tables.
Introduction
Ce tutoriel vous propose de découvrir la librairie Python PySNMP permettant de dialoguer avec tout matériel compatible avec le protocole du même nom.
Dans cette troisième partie nous vous présentons comment consulter des données de type "tableau" depuis la ligne de commandes et avec la librairie PySNMP.
Si vous ne connaissez pas le protocole SNMP nous vous invitons à lire auparavant la première partie de ce tutoriel qui vous présentera le protocole SNMP, ses qualités et défauts et vous fournira les outils de base pour commencer à l'utiliser et vous l'approprier. Sans ces connaissances préliminaires, ce troisième chapitre risque fort de vous sembler bien difficile à appréhender.
Si vous n'avez jamais utilisé la librairie PySNMP nous vous invitons aussi à lire la seconde partie de ce tutoriel qui vous donnera les bases pour utiliser le protocole SNMP en Python.
A la fin de cette troisième partie vous serez normalement capable :
- D'interroger des tables de données en ligne de commande
- De lire des tables de données SNMP avec Python
Les tables
Certaines informations des MIBs sont fournies sous forme de tableaux.
Par exemple les interfaces réseau d'un serveur (eth0, wlan0, loopback).
Ces tableaux sont organisés sous forme d'enregistrements. Chaque enregistrement ou ligne du tableau contient plusieurs informations.
Ces enregistrements sont accessibles individuellement via leur index.
Prenons l'exemple de l'entrée « ifTable », dont l'OID est :
-
iso . org . dod . internet . mgmt . mib-2 . interfaces . IfTable
-
1.3.6.1.2.1.2.2
Sa définition ASN.1 est la suivante :
-- the Interfaces table -- The Interfaces table contains information on the entity's -- interfaces. Each sub-layer below the internetwork-layer -- of a network interface is considered to be an interface. ifTable OBJECT-TYPE SYNTAX SEQUENCE OF IfEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "A list of interface entries. The number of entries is given by the value of ifNumber." ::= { interfaces 2 } ifEntry OBJECT-TYPE SYNTAX IfEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "An entry containing management information applicable to a particular interface." INDEX { ifIndex } ::= { ifTable 1 } IfEntry ::= SEQUENCE { ifIndex InterfaceIndex, ifDescr DisplayString, ifType IANAifType, ifMtu Integer32, ifSpeed Gauge32, ifPhysAddress PhysAddress, ifAdminStatus INTEGER, ifOperStatus INTEGER, ifLastChange TimeTicks, ifInOctets Counter32, ifInUcastPkts Counter32, ifInNUcastPkts Counter32, -- deprecated ifInDiscards Counter32, ifInErrors Counter32, ifInUnknownProtos Counter32, ifOutOctets Counter32, ifOutUcastPkts Counter32, ifOutNUcastPkts Counter32, -- deprecated ifOutDiscards Counter32, ifOutErrors Counter32, ifOutQLen Gauge32, -- deprecated ifSpecific OBJECT IDENTIFIER -- deprecated }
On y lit que l'élément « ifTable » est une séquence d'éléments « ifEntry » et que chaque élément « ifEntry » est indexé par un élément « ifIndex »
Enfin, « ifEntry » est lui même une séquence de nombreux éléments comme « ifDescr », « ifType », « ifSpeed » etc.
En d'autres termes, « ifTable » est un tableau de lignes de type « ifEntry » possédant plusieurs colonnes « ifDescr », « ifType » et caetera.
Représentation de la table « ifTable », source « etutorials.org »
Nous pouvons regarder cela de plus près avec la commande SNMPWALK :
$ snmpwalk -v2c -c public demo.snmplabs.com ifTable IF-MIB::ifIndex.1 = INTEGER: 1 IF-MIB::ifIndex.2 = INTEGER: 2 IF-MIB::ifDescr.1 = STRING: eth0 IF-MIB::ifDescr.2 = STRING: eth1 IF-MIB::ifType.1 = INTEGER: ethernetCsmacd(6) IF-MIB::ifType.2 = INTEGER: ethernetCsmacd(6) IF-MIB::ifMtu.1 = INTEGER: 1500 IF-MIB::ifMtu.2 = INTEGER: 1500 IF-MIB::ifSpeed.1 = Gauge32: 100000000 IF-MIB::ifSpeed.2 = Gauge32: 100000000 IF-MIB::ifPhysAddress.1 = STRING: 0:12:79:62:f9:40 IF-MIB::ifPhysAddress.2 = STRING: 0:12:79:62:f9:41 IF-MIB::ifAdminStatus.1 = INTEGER: up(1) IF-MIB::ifAdminStatus.2 = INTEGER: up(1) IF-MIB::ifOperStatus.1 = INTEGER: up(1)
Il est possible de retrouver les différents enregistrements du tableau grâce à l'index « ifIndex » des entrées « ifEntry ». Ici nous avons 2 valeurs pour l'index : 1 et 2, donc 2 enregistrements.
Chaque colonne de l'enregistrement « ifEntry » sera donc associée à son Index :
- « ifDescr.1 » et « ifType.1 » pour le premier enregistrement.
- « ifDescr.2 » et « ifType.2 » pour le second, et ainsi de suite.
Il est ainsi possible de reconstituer tous les enregistrements à l'aide de la commande « snmpwalk » et des valeurs des index. Mais c'est laborieux.
La commande « snmptable » réalise cela automatiquement pour vous :
$ snmptable -v2c -c public demo.snmplabs.com ifTable SNMP table: IF-MIB::ifTable ifIndex ifDescr ifType ifMtu ifSpeed ifPhysAddress 1 eth0 ethernetCsmacd 1500 100000000 0:12:79:62:f9:40 2 eth1 ethernetCsmacd 1500 100000000 0:12:79:62:f9:41
Cela peut se compliquer lorsque qu'une entrée de table est indexée par plusieurs index.
Vous aurez remarqué, que la valeur de l'index se retrouve dans l'OID de chaque « colonne ». Il en est de même quand il y a plusieurs index, ils sont chaînés dans l'OID, je vous laisse lire la documentation de PySNMP sur les tables qui explique cela plutôt bien.
Les tables avec PySNMP
PySNMP ne propose pas d'équivalent à la commande « snmptable » pour lire une table.
Mais elle peut le faire très simplement avec la commande « nextCmd » si vous la configurez convenablement.
Avant de regarder comment réaliser cette lecture avec les bonnes pratiques nous allons essayer de parcourir nous-mêmes les différents éléments de la MIB « Interface » OID (IF-MIB, ifTable) manuellement et de retourner une liste de dictionnaires contenant chacun les valeurs de chacune des colonnes du nœud "ifEntry".
Ceci nous permettra de mieux comprendre la mécanique utilisée par PySNMP et nous apprendra à manipuler les itérations successives de « nextCmd ».
Une fois que nous aurons mis en place une solution fonctionnelle, nous pourrons appliquer quelques paramétrages supplémentaires pour simplifier la création de notre liste d'enregistrements.
A l'aide de la commande suivante nous allons parcourir tous les éléments de l'entrée (IF-MIB, ifTable) :
g = nextCmd(SnmpEngine() , CommunityData('public', mpModel=1) , UdpTransportTarget(('demo.snmplabs.com', 161) , ContextData() , ObjectType(ObjectIdentity('IF-MIB', 'ifTable')) , lexicographicMode=False)
Ici nous avons tout de même ajouté l'option « lexicographiqueMode=False » qui indique à la commande getNext de ne pas continuer à parcourir les éléments en dehors de ceux sous cet OID.
Pour construire les éléments « ifEntry » nous utilisons la commande « getNext » comme à l'habitude :
print("\nWalking table") data = [] dIndex = {} for errorIndication, errorStatus, errorIndex, varBinds in g: if errorIndication: print(errorIndication) elif errorStatus: print('%s at %s' % ( errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex) - 1][0] or '?' ) ) else: for varBind in varBinds: print(varBind) oid = varBind[0] value = varBind[1] label = oid.getLabel() print("Label:", label) print("Mib Symbol:", oid.getMibSymbol())
Ceci nous donnera une sortie semblable à celle-ci :
Walking table
IF-MIB::ifIndex.1 = 1
Label: ('iso', 'org', 'dod', 'internet', 'mgmt', 'mib-2', 'interfaces', 'ifTable', 'ifEntry', 'ifIndex')
Mib Symbol: ('IF-MIB', 'ifIndex', (InterfaceIndex(1),))
IF-MIB::ifIndex.2 = 2
Label: ('iso', 'org', 'dod', 'internet', 'mgmt', 'mib-2', 'interfaces', 'ifTable', 'ifEntry', 'ifIndex')
Mib Symbol: ('IF-MIB', 'ifIndex', (InterfaceIndex(2),))
IF-MIB::ifDescr.1 = eth0
Label: ('iso', 'org', 'dod', 'internet', 'mgmt', 'mib-2', 'interfaces', 'ifTable', 'ifEntry', 'ifDescr')
Mib Symbol: ('IF-MIB', 'ifDescr', (InterfaceIndex(1),))
IF-MIB::ifDescr.2 = eth1
Ce listing nous montre 2 choses :
-
Le numéro de l'ifEntry à laquelle appartient chaque propriété est fourni par le dernier élément du tuple « Mib Symbol » InterfaceIndex(1) ou InterfaceIndex(2)
-
Le nom de la propriété est fourni par le dernier élément du Label
('iso', 'org', 'dod', 'internet', 'mgmt', 'mib-2', 'interfaces', 'ifTable', 'ifEntry', 'ifDescr')
Nous pouvons donc construire une liste stockant chaque élément "ifEntry" dans un dictionnaire, comme suit :
key = str(oid.getMibSymbol()[-1][0]) if not key in dIndex: dIndex[key] = {} data.append(dIndex[key]) dIndex[key][label[-1]] = str(value.prettyPrint())
Le code final devient le suivant :
from pysnmp.hlapi import * from pysnmp.smi.view import MibViewController se = SnmpEngine() mvc = se.getUserContext('mibViewController') if not mvc: mvc = MibViewController(se.getMibBuilder()) # # # 1.3.6.1.2.1.2.2 ('iso', 'org', 'dod', 'internet', 'mgmt', 'mib-2', 'interfaces', 'ifTable') oid_iftable = ObjectIdentity('IF-MIB', 'ifTable') oid_iftable.resolveWithMib(mvc) print("Root oid") print(oid_iftable.getOid()) print(oid_iftable.getLabel()) print(oid_iftable.getMibNode()) print(oid_iftable.getMibSymbol()) g = nextCmd(SnmpEngine() , CommunityData('public', mpModel=1) , UdpTransportTarget(('demo.snmplabs.com', 161)) , ContextData() , ObjectType(ObjectIdentity('IF-MIB', 'ifTable')) , lexicographicMode=False) print("\nWalking table") data = [] dIndex = {} for errorIndication, errorStatus, errorIndex, varBinds in g: if errorIndication: print(errorIndication) elif errorStatus: print('%s at %s' % ( errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex) - 1][0] or '?' ) ) else: for varBind in varBinds: print(varBind) oid = varBind[0] value = varBind[1] label = oid.getLabel() print("Label:", label) print("Mib Symbol:", oid.getMibSymbol()) key = str(oid.getMibSymbol()[-1][0]) if not key in dIndex: dIndex[key] = {} data.append(dIndex[key]) dIndex[key][label[-1]] = str(value.prettyPrint()) print("\nPrinting items") for ind, item in enumerate(data): print("Item %s" % (str(ind + 1), )) for key, value in item.items(): print(" %s: %s"% (key, value))
Avec la sortie finale suivante pour les éléments "ifEntry":
Printing items Item 1 ifOutUcastPkts: 11277302 ifInErrors: 0 ifInUcastPkts: 22554581 ifOutErrors: 0 ifOutDiscards: 0 ifInNUcastPkts: 2506064 ifType: 'ethernetCsmacd' ifSpecific: SNMPv2-SMI::zeroDotZero ifSpeed: 100000000 ifPhysAddress: 00:12:79:62:f9:40 ifOutNUcastPkts: 1253033 ifOperStatus: 'up' ifIndex: 1 ifInOctets: 275667056 ifDescr: eth0 ifLastChange: 150603818 ifInDiscards: 0 ifOutOctets: 125303333 ifAdminStatus: 'up' ifInUnknownProtos: 125301 ifMtu: 1500 ifOutQLen: 0 Item 2 ifOutUcastPkts: 11277305 ifInErrors: 0 ifInUcastPkts: 22554583 ifOutErrors: 0 ifOutDiscards: 0 ifInNUcastPkts: 2506065 ifType: 'ethernetCsmacd' ifSpecific: SNMPv2-SMI::zeroDotZero ifSpeed: 100000000 ifPhysAddress: 00:12:79:62:f9:41 ifOutNUcastPkts: 1253034 ifOperStatus: 'up' ifIndex: 2 ifInOctets: 250606431 ifDescr: eth1 ifLastChange: 249303192 ifInDiscards: 0 ifOutOctets: 125303346 ifAdminStatus: 'up' ifInUnknownProtos: 125304 ifMtu: 1500 ifOutQLen: 0
Cette technique est intéressante pour comprendre comment sont organisés les enregistrements dans une table SNMP et pour les reconstituer manuellement.
Mais la commande « getNext » permet de les récupérer beaucoup plus simplement en lui indiquant quelles informations nous souhaitons lire dans les entrées "ifEntry" à chacun de ses appels au lieu de les lire une par une, comme précédemment.
Pour cela il suffit de lui passer les OID qui nous intéressent en 1 seule fois :
nextCmd(SnmpEngine(), CommunityData('public', mpModel=0), UdpTransportTarget(('demo.snmplabs.com', 161)), ContextData(), ObjectType(ObjectIdentity('IF-MIB', 'ifDescr')), ObjectType(ObjectIdentity('IF-MIB', 'ifType')), ObjectType(ObjectIdentity('IF-MIB', 'ifMtu')), ObjectType(ObjectIdentity('IF-MIB', 'ifSpeed')), ObjectType(ObjectIdentity('IF-MIB', 'ifType')), ObjectType(ObjectIdentity('IF-MIB', 'ifPhysAddress')), lexicographicMode=False)
Notre programme devient alors :
from pysnmp.hlapi import * item = 0 for errorIndication, \ errorStatus, \ errorIndex, \ varBinds in nextCmd(SnmpEngine(), CommunityData('public', mpModel=0), UdpTransportTarget(('demo.snmplabs.com', 161)), ContextData(), ObjectType(ObjectIdentity('IF-MIB', 'ifDescr')), ObjectType(ObjectIdentity('IF-MIB', 'ifType')), ObjectType(ObjectIdentity('IF-MIB', 'ifMtu')), ObjectType(ObjectIdentity('IF-MIB', 'ifSpeed')), ObjectType(ObjectIdentity('IF-MIB', 'ifType')), ObjectType(ObjectIdentity('IF-MIB', 'ifPhysAddress')), lexicographicMode=False): print("Item ", item) item += 1 if errorIndication: print(errorIndication) break elif errorStatus: print('%s at %s' % ( errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex)-1][0] or '?' ) ) break else: for varBind in varBinds: print(' = '.join([ x.prettyPrint() for x in varBind ]))
Avec la sortie suivante :
Item 0 IF-MIB::ifDescr.1 = eth0 IF-MIB::ifType.1 = 'ethernetCsmacd' IF-MIB::ifMtu.1 = 1500 IF-MIB::ifSpeed.1 = 100000000 IF-MIB::ifType.1 = 'ethernetCsmacd' IF-MIB::ifPhysAddress.1 = 00:12:79:62:f9:40 Item 1 IF-MIB::ifDescr.2 = eth1 IF-MIB::ifType.2 = 'ethernetCsmacd' IF-MIB::ifMtu.2 = 1500 IF-MIB::ifSpeed.2 = 100000000 IF-MIB::ifType.2 = 'ethernetCsmacd' IF-MIB::ifPhysAddress.2 = 00:12:79:62:f9:41
Conclusion
Nous avons vu dans cette troisième partie comment manipuler des tableaux SNMP avec la ligne de commande ou la librairie Python.
PySNMP ne possèdant pas de commande spéciale pour parcourir une table, nous oblige à lister tous ses éléments via des "getNext".
Heureusement il est possible de récupérer toutes les propriétés d'une entrée au travers d'un seul appel à "getNext" ce qui permet de grandement simplifier la reconstitution des enregistrements du tableau.
Dans la quatrième partie de ce tutoriel nous apprendrons à créer des agents.
Formations associées
Formations IA / Data Science
Formation Python scientifique
Nantes Du 27 au 21 janvier 2025
Voir la formationActualités en lien
Migrer une application de Python 2 à Python 3
Le support de Python 2 est officiellement terminé, c'est le moment de passer vos applications à Python 3 ! Voici quelques conseils pour réussir cette migration.
Utilisation de la vision par ordinateur pour redresser des images
Dans un module de comparaison d'images, lorsque deux photographies ne sont pas cadrées de la même manière, non-superposable, c'est frustrant. On vous propose ici d'y remédier avec du redressement d'images par homographie.
Machine Learning : classer automatiquement vos données à l'import
Comment utiliser des algorithmes de machine learning pour importer correctement des données dans vos projets de DataScience ?