Makina Blog

Le blog Makina-corpus

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 formation

Formations Python

Formation Python

À distance (FOAD) Du 16 au 20 décembre 2024

Voir la formation

Formations Python

Formation Python avancé

Toulouse Du 2 au 6 décembre 2024

Voir la formation

Actualités en lien

Image
Python_logo_3
08/07/2020

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.

Voir l'article
Image
Redressement d'image
02/04/2019

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.

Voir l'article
Image
machine-learnin-données-à-l'import.
22/02/2018

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 ?

Voir l'article

Inscription à la newsletter

Nous vous avons convaincus