Java:SNMP

De WIKI.minetti.org
Révision de 7 avril 2016 à 17:36 par Jp (discussion | contributions) (Page créée avec « == But == Le Simple Network Management Protocol (SNMP) est un protocole réseau permettant la supervision et l'administration d'applications. A ce titre, il peut être com... »)

(diff) ← Version précédente | Voir la version courante (diff) | Version suivante → (diff)
Aller à : navigation, rechercher

But

Le Simple Network Management Protocol (SNMP) est un protocole réseau permettant la supervision et l'administration d'applications. A ce titre, il peut être complémentaire de JMX. En effet, l'intégration d'un adaptateur JMX fournissant un service SNMP peut devenir très intéressant puisqu'il nous permettrait l'utilisation d'un grand nombre de logiciels pour faire de la supervision, tel que:

API SNMP

On utilisera joeSNMP pour mettre en oeuvre le protocole SNMP (téléchargeable ici).

Service SNMP

Tout d'abord nous devons créer une classe qui sera chargée de traiter nos requêtes SNMP. Elle aura le gabaris suivant:

public final class SnmpAgentHandlerImpl implements SnmpAgentHandler
{

   public void SnmpAgentSessionError (final SnmpAgentSession session, final int error, final Object ref)
   {

      // Traitement des erreurs

   }

   public void snmpReceivedPdu (final SnmpAgentSession session, final InetAddress manager, final int port, final SnmpOctetString community,
            final SnmpPduPacket pdu)
   {
      if (pdu instanceof SnmpPduBulk) {
         final SnmpPduRequest response = new SnmpPduRequest (SnmpPduPacket.RESPONSE);
         response.setRequestId (pdu.getRequestId ());
         doBulk (pdu.toVarBindArray (), (SnmpPduBulk) pdu, response);
         try {
            session.send (new SnmpPeer (manager, port), response);
         }
         catch (final AsnEncodingException e) {
            final SnmpPduRequest errorResponse = new SnmpPduRequest (SnmpPduPacket.RESPONSE);
            errorResponse.setRequestId (pdu.getRequestId ());
            errorResponse.setErrorStatus (SnmpPduPacket.ErrTooBig);
            errorResponse.setErrorIndex (0);

            // Traitement des erreurs de type ErrTooBig (errorStatus)
            ...

         }
         catch (final Throwable e) {
            final SnmpPduRequest errorResponse = new SnmpPduRequest (SnmpPduPacket.RESPONSE);
            errorResponse.setRequestId (pdu.getRequestId ());
            errorResponse.setErrorStatus (SnmpPduPacket.ErrGenError);
            errorResponse.setErrorIndex (0);

            // Traitement des autres type d'erreurs
            ...

         }
      }
   }

   public SnmpPduRequest snmpReceivedGet (final SnmpPduPacket pdu, final boolean getNext)
   {
      final SnmpPduRequest response = new SnmpPduRequest (SnmpPduPacket.RESPONSE);
      response.setRequestId (pdu.getRequestId ());
      final SnmpVarBind[] binds = pdu.toVarBindArray ();

      // SNMP v1
      if (pdu instanceof SnmpPduRequest) {
         int resultIndex = 1;                // pour connaître le ErrorIndex en cas d'erreur
         for (final SnmpVarBind varBind : binds) {
            final SnmpObjectId oid = varBind.getName ();
            if (getNext) {

               // Traitement d'une requête GET NEXT
               SnmpObjectId nextOid = ...    // recherche l'OID du métrique positionné après oid
               SnmpSyntax nextValue = ...    // recherche la valeur de ce métrique

               // Si fin de la MIB atteint, renvoyer l'erreur SnmpPduPacket.ErrNoSuchName
               ...

               response.addVarBind (new SnmpVarBind (nextOid, nextValue));
            }
            else {

               // Traitement d'une requête GET
               SnmpSyntax value = ...        // recherche de la valeur d'un métrique à la position oid

               // Si la valeur n'a pas été trouvée, renvoyer l'erreur SnmpPduPacket.ErrNoSuchName
               ...

               varBind.setValue (value);
               response.addVarBind (varBind);
            }
            resultIndex++;                   // pour connaître le ErrorIndex en cas d'erreur
         }
      }

      // SNMP v2
      else if (pdu instanceof SnmpPduBulk) {
        doBulk (binds, (SnmpPduBulk) pdu, response);
      }

      return response; 
   }

   private void doBulk (final SnmpVarBind binds[], final SnmpPduBulk bulk, final SnmpPduRequest response)
   {
      int resultIndex = 1;                   // pour connaître le ErrorIndex en cas d'erreur
      for (final SnmpVarBind varBind : binds) {
         final SnmpObjectId oid = varBind.getName ();
         if (i < bulk.getNonRepeaters ()) {

            // Traitement d'une requête GET NEXT
            SnmpObjectId nextOid = ...       // recherche l'OID du métrique positionné après oid
            SnmpSyntax nextValue = ...       // recherche la valeur de ce métrique

            // Si un métrique a été trouvé:
            response.addVarBind (new SnmpVarBind (nextOid, nextValue));

            // Si la fin de la MIB a été atteinte:
            response.addVarBind (new SnmpVarBind (oid, new SnmpEndOfMibView ()));

         }
         else {
            final int maxRepititions = bulk.getMaxRepititions ();

            // Traitement d'une requête WALK
            while ... {                      // pour les N métriques positionnés après oid (N <= maxRepititions)

               SnmpObjectId nextOid = ...    // OID du métrique
               SnmpSyntax nextValue = ...    // valeur du métrique

               // Si un métrique a été trouvé:
               response.addVarBind (new SnmpVarBind (nextOid, nextValue));

               // Si la fin de la MIB a été atteinte (N < maxRepititions):
               response.addVarBind (new SnmpVarBind (".1.9", new SnmpEndOfMibView ()));

            }

            // Si aucun métrique trouvé:
            response.addVarBind (new SnmpVarBind (oid, new SnmpEndOfMibView ()));

         }
         resultIndex++;                      // pour connaître le ErrorIndex en cas d'erreur
      }
   }

   public SnmpPduRequest snmpReceivedSet (final SnmpPduPacket pdu)
   {
      final SnmpPduRequest response = new SnmpPduRequest (SnmpPduPacket.RESPONSE);
      response.setRequestId (pdu.getRequestId ());
      int resultIndex = 1;                   // pour connaître le ErrorIndex en cas d'erreur
      for (final SnmpVarBind varBind : pdu.toVarBindArray ()) {
         SnmpObjectId oid = varBind.getName ();
         SnmpSyntax newValue = varBind.getValue ();

         // Traitement d'une requête SET
         ...

         // Si le métrique n'existe pas, renvoyer l'erreur SnmpPduPacket.ErrNoSuchName
         ...

         response.addVarBind (varBind);
         resultIndex++;                      // pour connaître le ErrorIndex en cas d'erreur
      }
     return response;
   }

}

Nous pouvons voir qu'il y a 2 façons de rechercher un métrique:

  • soit on le recherche à une position bien précise (cas de la requête GET)
  • soit on le recherche à une position suivante d'une position donnnée (cas des requêtes GET NEXT et WALK)

Partons dans l'hypothèse que nos métriques sont stockés dans un TreeMap<SnmpObjectId, SnmpSyntax> nommé mibMap. Le code suivant permet de trouver les métriques suivants à oid jusqu'au dernier:

final SnmpObjectId baseOid = new SnmpObjectId (oid);
baseOid.append (new int []{0});
final SortedMap<SnmpObjectId, SnmpSyntax> followsMap = mibMap.tailMap (baseOid);

Les valeurs des métriques sont stockés dans des objets de type:

  • SnmpNull pour une valeur nulle,
  • SnmpInt32 pour une valeur entière signée de 32 bits (int),
  • SnmpUInt32 pour une valeur entière non signée de 32 bits (long),
  • SnmpGauge32 pour une gauge de 32 bits (long),
  • SnmpCounter32 pour un compteur de 32 bits (long),
  • SnmpCounter64 pour un compteur de 64 bits (java.math.BigInteger ou long),
  • SnmpTimeTicks pour un temps écoulé en 100ème de secondes (long),
  • SnmpOctetString pour un texte (byte[]),
  • SnmpIPAddress pour une adresse IP (java.net.InetAddress),
  • SnmpObjectId pour un OID (int[]),
  • SnmpOpaque pour autre chose (byte[]).

Tous ces types héritent de SnmpSyntax.

Le traitement des requêtes SNMP peut provoquer les erreurs suivantes (toutes énumérées dans la classe SnmpPduPacket):

  • ErrNoError pour aucune erreur,
  • ErrTooBig pour une requête dont la réponse serait trop importante,
  • ErrNoSuchName pour un métrique non trouvé,
  • ErrBadValue pour une valeur envoyée avec un mauvais type ou une mauvaise longueur (par exemple lors d'une exception SnmpBadConversionException),
  • ErrReadOnly pour une tentative de modification d'un métrique non modifiable,
  • ErrGenError pour une erreur générale,
  • ErrNoAccess,
  • ErrWrongType pour un métrique avec un mauvais type de données,
  • ErrWrongLength,
  • ErrWrongEncoding,
  • ErrWrongValue,
  • ErrNoCreation,
  • ErrInconsistentValue,
  • ErrResourceUnavailable pour une ressource non disponible (outOfMemory),
  • ErrCommitFailed,
  • ErrUndoFailed,
  • ErrAuthorizationError pour un accès à un métrique refusé,
  • ErrNotWritable,
  • ErrInconsistentName.

Ces erreurs sont renvoyées dans la réponse de la requête SNMP avec l'index de l'élément où s'est produit l'erreur:

response.setErrorStatus (SnmpPduPacket.ErrGenError);
response.setErrorIndex (resultIndex);

Une fois la classe SnmpAgentHandlerImpl implémentée, nous pouvons ouvrir une session d'écoute sur le port UDP 161:

final SnmpPeer peer = new SnmpPeer (InetAddress.getByName ("localhost"), 161);
final SnmpParameters params = peer.getParameters ();
params.setVersion (SnmpSMI.SNMPV1);
params.setReadCommunity ("public");
params.setWriteCommunity ("private");
final SnmpAgentSession session = new SnmpAgentSession (new SnmpAgentHandlerImpl (), peer);

Ici, nous utilisons la version 1 du protocole SNMP. Mais nous pouvons tout aussi bien utiliser la version 2c:

params.setVersion (SnmpSMI.SNMPV2);

Pour fermer la session:

session.close ();

Pour tester il faut installer un client qui va interroger notre service SNMP. Avec Net-SNMP on tapera la commande:

snmpget -v2c -c public localhost .1.3.6.1.2.1.1.1.0

ou bien:

snmpwalk -v2c -c public localhost .1.3.6.1.4.1.690

Envoi de traps SNMP

Comme pour le service SNMP, nous devons créer une classe mais qui sera chargée de traiter les réponses à nos requêtes SNMP, notamment les erreurs:

public final class SnmpHandlerImpl implements SnmpHandler
{

   public void snmpInternalError (final SnmpSession snmpSession, final int err, final SnmpSyntax pdu)
   {

      // Traitement des erreurs

   }

   public void snmpTimeoutError (final SnmpSession snmpSession, final SnmpSyntax pdu)
   {

      // Traitement des erreurs

   }

   public void snmpReceivedPdu (final SnmpSession snmpSession, final int cmd, final SnmpPduPacket pdu)
   {
      // NOP
   }

}

Avant d'envoyer une trap nous devons établir une connexion vers l'agent SNMP:

SnmpPeer peer = new SnmpPeer (InetAddress.getByName ("localhost"));
peer.setPort (162);
SnmpParameters parameters = peer.getParameters ();
parameters.setReadCommunity ("public");
parameters.setVersion (SnmpSMI.SNMPV1);
SnmpSession session = new SnmpSession (peer);

Une fois la connexion établie, nous pouvons construire notre requête SNMP:

final SnmpPduTrap trapPdu = new SnmpPduTrap ();
trapPdu.setTimeStamp ( ... );                                 // Temps écoulés en 100ème de seconde depuis l'initialisation de l'application
trapPdu.setEnterprise ( ... );                                // OID indexant la source de la trap
trapPdu.setAgentAddress ( ... );                              // Adresse IP de la machine hébergeant l'application
trapPdu.setGeneric (SnmpPduTrap.GenericEnterpriseSpecific);   // Pour une trap de type spécifique
trapPdu.setSpecific ( ... );                                  // et le numéro de cette trap spécifique

Y embarquer d'autres données (indexées par un OID unique):

trapPdu.addVarBind (new SnmpVarBind (oid, value));

Et l'envoyer à l'agent chargé de traiter les traps SNMP:

session.send (trapPdu, new SnmpHandlerImpl ( ... ));

On n'oubli pas de fermer notre connexion:

session.close ();

Pour tester, nous devons installer un agent chargé de traiter les traps SNMP. Avec Net-SNMP on démarrera le démon snmptrapd:

service snmptrapd start

Mais avant, nous devons:

traphandle default /usr/sbin/snmptt
disableAuthorization yes
  • paramétrer les options du démon (/etc/snmp/snmptrapd.options):
OPTIONS="-On -p /var/run/snmptrapd.pid"
  • configurer snmptt (/etc/snmp/snmptt.ini):
...
mode = standalone
...
strip_domain_list = <<END
lan.minetti.org
minetti.org
END
...
snmptt_conf_files = <<END
/etc/minetti/snmptt/test.conf
END
  • paramétrer la gestion de chaque traps (/etc/minetti/snmptt/test.conf):
EVENT TEST_APP_1 .1.3.6.1.4.1.1000.1.* "Test application 1" Normal
FORMAT $2
EXEC echo "$r app1.$1 $S \"$2\"" >> /var/log/minetti/traps.log

EVENT TEST_APP_2 .1.3.6.1.4.1.1000.2.* "Test application 2" Normal
FORMAT $2
EXEC echo "$r app2.$1 $S \"$2\"" >> /var/log/minetti/traps.log

Ici, toutes nos traps seront traçées dans le fichier /var/log/minetti/traps.log.

A propos des OID

Le choix des OID doit se faire dans la branche private enterprises (.1.3.6.1.4.1) qui sont attribués par l'IANA.

En principe, les services SNMP doivent renvoyer les informations suivantes:

  • 1.3.6.1.2.1.1.1.0 (system.sysDescr.0) pour renvoyer une description de notre application,
  • 1.3.6.1.2.1.1.2.0 (system.sysObjectID.0) pour renvoyer l'OID racine où se trouve tous nos métriques,
  • 1.3.6.1.2.1.1.3.0 (system.sysUpTime.0) pour renvoyer le temps écoulés en 100ème de seconde depuis le dernier démarrage de notre application,
  • 1.3.6.1.2.1.1.4.0 (system.sysContact.0) pour renvoyer le nom et l'adresse e-mail de la personne responsable de l'application,
  • 1.3.6.1.2.1.1.5.0 (system.sysName.0) pour renvoyer le nom de notre application,
  • 1.3.6.1.2.1.1.6.0 (system.sysLocation.0) pour renvoyer la localisation de notre application.

Voir aussi