O guia definitivo para os iniciantes em Net-SNMP (7)

Introdução

No post anterior usamos o aplicativo mib2c para gerar o esqueleto de código da nossa MIB. Neste post discutiremos como “rechear” o esqueleto de código gerado. No fundo, a quantidade de código necessária é bem pequena quando se usa o modelo escalar de geração, facilitando bastante uma primeira implementação do agente. Recomendo que leiam depois o tutorial disponível no site do Net-SNMP para maiores esclarecimento e dicas. Também recomendo que olhem a implementação de outras módulos para aprenderem um pouco mais através de exemplos (os fontes do Net-SNMP podem ajudar).

Preenchendo os callabcks da MIB exemplo

Se você observar, vai notar que foram criadas cinco funções dentro de exemplo.c (ao final do texto). Em cada uma delas você irá preencher um pequeno pedaço. A primeira função é a inicialização da MIB, chamada na partida do snmpd, e que deve conter o que for necessário para inicializar o seu módulo:

void init_exemplo()

Já as outras quatro são os callbacks dos OIDs presentes na MIB propriamente ditos, e que devem ser preenchidos adequadamente, de acordo com o esperado para o tipo de dado.

int handle_capsLock()
int handle_numLock()
int handle_scrollLock()
int handle_runTime()

A seguir, vamos dar uma descrição sumária sobre as funções geradas e o seu preenchimento.

Inicializando a MIB

Para cada módulo gerado com o mib2c usando a opção escalar, será gerada uma função de inicialização com o protótipo abaixo, chamada apenas uma vez, na partida.

void init_nomedomodulo(void)

Dentro desta função você deve inicializar os dados que pretende usar posteriormente e registrar cada OID que tenha um callback associado com ajuda da função “netsnmp_register_scalar“. No nosso caso, foi feita a seguinte inicialização e registro (o código foi generosamente criado pelo nosso revisor e colaborador, Alan Carvalho):

#include <time.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/XKBlib.h>

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "exemplo.h"

#define CAPSLOCK 1
#define NUMLOCK 2
#define SCRLOCK 4

static int capsLockLED = 0;
static int numLockLED = 0;
static int scrollLockLED = 0;

unsigned int kbd;
Display *disp = NULL;

/** Initializes the exemplo module */
void
init_exemplo(void)
{
    static oid capsLock_oid[] = { 1,3,6,1,4,1,54321,0,0,0 };
    static oid numLock_oid[] = { 1,3,6,1,4,1,54321,0,0,1 };
    static oid scrollLock_oid[] = { 1,3,6,1,4,1,54321,0,0,2 };
    static oid runTime_oid[] = { 1,3,6,1,4,1,54321,0,1,3 };

    DEBUGMSGTL(("exemplo", "Initializing\n"));

    /* Open X Display to read keyboard status later */
    disp = XOpenDisplay(NULL);

    netsnmp_register_scalar(
        netsnmp_create_handler_registration("capsLock", handle_capsLock,
                               capsLock_oid, OID_LENGTH(capsLock_oid),
                               HANDLER_CAN_RONLY
        ));
    netsnmp_register_scalar(
        netsnmp_create_handler_registration("numLock", handle_numLock,
                               numLock_oid, OID_LENGTH(numLock_oid),
                               HANDLER_CAN_RONLY
        ));
    netsnmp_register_scalar(
        netsnmp_create_handler_registration("scrollLock", handle_scrollLock,
                               scrollLock_oid, OID_LENGTH(scrollLock_oid),
                               HANDLER_CAN_RWRITE
        ));
    netsnmp_register_scalar(
        netsnmp_create_handler_registration("runTime", handle_runTime,
                               runTime_oid, OID_LENGTH(runTime_oid),
                               HANDLER_CAN_RONLY
        ));
}

Em azul está a parte efetivamente criada pelo Alan, para que vocês tenham um noção do trabalho. Dá pra perceber que ficou a cargo do programador basicamente a inicialização da variável disp e de variáveis internas do módulo.

Criando callbacks somente de leitura

Callbacks somente de leitura também são simples de serem feitos, bastando implementar o método GET. O mib2c já gera o esqueleto e você precisa apenas preencher  adequadamente a área indicada pelo ponteiro passado na requisição, respeitando o tipo de dado. Abaixo, o callback para o caps lock, com comentários logo depois.

int
handle_capsLock(netsnmp_mib_handler *handler,
                          netsnmp_handler_registration *reginfo,
                          netsnmp_agent_request_info   *reqinfo,
                          netsnmp_request_info         *requests)
{
    /* We are never called for a GETNEXT if it's registered as a
       "instance", as it's "magically" handled for us.  */
     if (disp)     {      XkbGetIndicatorState(disp, XkbUseCoreKbd, &kbd);      capsLockLED = (kbd & CAPSLOCK) == CAPSLOCK;     }

    /* a instance handler also only hands us one request at a time, so
       we don't need to loop over a list of requests; we'll only get one. */
    switch(reqinfo->mode) {

        case MODE_GET:
            snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER,
                                     (u_char *) &capsLockLED, sizeof(capsLockLED));
            break;

        default:
            /* we should never get here, so this is a really bad error */
            snmp_log(LOG_ERR, "unknown mode (%d) in handle_capsLock\n", reqinfo->mode );
            return SNMP_ERR_GENERR;
    }

    return SNMP_ERR_NOERROR;
}

O switch permite identificar o tipo de requisição via “reqinfo->mode” e a função snmp_set_var_typed_value() realiza a cópia do valor desejado (conteúdo da variável capsLockLED) dentro do buffer de resposta. Se você for investigar a estrutura netsnmp_request_info, vai notar que requestvb, do tipo netsnmp_variable_list, possui o campo de dados (val) definido como uma união dos diversos tipos de dados do Net-SNMP. É a estratégia para tratar tipos diferentes de dados com uma mesma interface (union). Estratégia, aliás, bastante comum em C.

Criando callbacks de leitura e escrita

No caso de leitura e escrita, existe trabalho adicional. Isto acontece porque o Net-SNMP assume que, ao pedir a escrita de um OID (método SET), ele deve fazer isto da maneira mais segura possível. Antes de fazer a escrita propriamente dita, ele pede que o agente verifique o tipo de dado a ser gravado (estado RESERVE1 da máquina de estados abaixo, retirada do site do Net-SNMP), aloque espaço para ele (estado RESERVE2) e só então requisita a escrita (estado ACTION). O valor só é efetivado posteriormente, com o estado COMMIT (qualquer semelhança com uma base de dados não é mera coincidência).  Perceba que existem estados para tratar as falhas também, como no caso de não existir espaço para o recurso (saída pelo estado FREE) ou em falha da escrita (estado UNDO, que desfaz tudo e retorna ao valor anterior).

Felizmente o nosso caso é bastante simples e é possível ignorar a maioria dos estados e implementar apenas o estado ACTION para o método SET. Obviamente, também é necessário implementar o método GET. Veja como ficou então o callback para o scroll lock:

int
handle_scrollLock(netsnmp_mib_handler *handler,
                          netsnmp_handler_registration *reginfo,
                          netsnmp_agent_request_info   *reqinfo,
                          netsnmp_request_info         *requests)
{
    int ret;
    XKeyboardControl values;

    /* We are never called for a GETNEXT if it's registered as a
       "instance", as it's "magically" handled for us.  */

    if (disp)
    {
     XkbGetIndicatorState(disp, XkbUseCoreKbd, &kbd);
     scrollLockLED = (kbd & SCRLOCK) == SCRLOCK;
    }

    /* a instance handler also only hands us one request at a time, so
       we don't need to loop over a list of requests; we'll only get one. */

    switch(reqinfo->mode) {

        case MODE_GET:
            snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER,
                                     (u_char *) &scrollLockLED, sizeof(scrollLockLED));
            break;

        /*
         * SET REQUEST
         *
         * multiple states in the transaction.  See:
         * http://www.net-snmp.org/tutorial-5/toolkit/mib_module/set-actions.jpg
         */
        case MODE_SET_RESERVE1:
                /* or you could use netsnmp_check_vb_type_and_size instead */
            ret = netsnmp_check_vb_type(requests->requestvb, ASN_INTEGER);
            if ( ret != SNMP_ERR_NOERROR ) {
                netsnmp_set_request_error(reqinfo, requests, ret );
            }
            break;

        case MODE_SET_RESERVE2:
            /* XXX malloc "undo" storage buffer */
            break;

        case MODE_SET_FREE:
            /* XXX: free resources allocated in RESERVE1 and/or
               RESERVE2.  Something failed somewhere, and the states
               below won't be called. */
            break;

        case MODE_SET_ACTION:
            /* XXX: perform the value change here */

            /* Setup ScrollLock LED */

            /* SCROLL LOCK is value 3 in X Graphic mode */
            values.led = SCRLOCK - 1;
            /* led_mode = 0 is LED OFF. led_mode = 1 is LED ON. */
            values.led_mode = *(requests->requestvb->val.integer);

            /* Change status of ScrollLock LED */
            XChangeKeyboardControl(disp, KBLed | KBLedMode, &values);
            /* Syncronize imediately */
            XSync(disp, 0);

            break;

        case MODE_SET_COMMIT:
            /* XXX: delete temporary storage */
            break;

        case MODE_SET_UNDO:
            /* XXX: UNDO and return to previous value for the object */
            break;

        default:
            /* we should never get here, so this is a really bad error */
            snmp_log(LOG_ERR, "unknown mode (%d) in handle_scrollLock\n", reqinfo->mode );
            return SNMP_ERR_GENERR;
    }

    return SNMP_ERR_NOERROR;
}

Moleza, nhein ? Basicamente código para acesso ao led, sendo que o valor recebido veio através da união citada anteriormente (requests->requestvb->val.integer).

Listagem final

A seguir estão os arquivos exemplo.c e exemplo.h, para referência. No próximo post vamos recompilar o Net-SNMP usando estes arquivos. Aproveite para ler um pouco a wiki do Net-SNMP. Ela é “levemente” confusa mas com muita informação útil. Até lá !

exemplo.h

/*
 * Note: this file originally auto-generated by mib2c using
 *        : mib2c.scalar.conf 11805 2005-01-07 09:37:18Z dts12 $
 */
#ifndef EXEMPLO_H
#define EXEMPLO_H

/* function declarations */
void init_exemplo(void);
Netsnmp_Node_Handler handle_capsLock;
Netsnmp_Node_Handler handle_numLock;
Netsnmp_Node_Handler handle_scrollLock;
Netsnmp_Node_Handler handle_runTime;

#endif /* EXEMPLO_H */

exemplo.c

/*
 * Note: this file originally auto-generated by mib2c using
 *        : mib2c.scalar.conf 11805 2005-01-07 09:37:18Z dts12 $
 */

#include <time.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/XKBlib.h>

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "exemplo.h"

#define CAPSLOCK 1
#define NUMLOCK 2
#define SCRLOCK 4

static int capsLockLED = 0;
static int numLockLED = 0;
static int scrollLockLED = 0;

unsigned int kbd;
Display *disp = NULL;

/** Initializes the exemplo module */
void
init_exemplo(void)
{
    static oid capsLock_oid[] = { 1,3,6,1,4,1,54321,0,0,0 };
    static oid numLock_oid[] = { 1,3,6,1,4,1,54321,0,0,1 };
    static oid scrollLock_oid[] = { 1,3,6,1,4,1,54321,0,0,2 };
    static oid runTime_oid[] = { 1,3,6,1,4,1,54321,0,1,3 };

    DEBUGMSGTL(("exemplo", "Initializing\n"));

    /* Open X Display to read keyboard status later */
    disp = XOpenDisplay(NULL);

    netsnmp_register_scalar(
        netsnmp_create_handler_registration("capsLock", handle_capsLock,
                               capsLock_oid, OID_LENGTH(capsLock_oid),
                               HANDLER_CAN_RONLY
        ));
    netsnmp_register_scalar(
        netsnmp_create_handler_registration("numLock", handle_numLock,
                               numLock_oid, OID_LENGTH(numLock_oid),
                               HANDLER_CAN_RONLY
        ));
    netsnmp_register_scalar(
        netsnmp_create_handler_registration("scrollLock", handle_scrollLock,
                               scrollLock_oid, OID_LENGTH(scrollLock_oid),
                               HANDLER_CAN_RWRITE
        ));
    netsnmp_register_scalar(
        netsnmp_create_handler_registration("runTime", handle_runTime,
                               runTime_oid, OID_LENGTH(runTime_oid),
                               HANDLER_CAN_RONLY
        ));
}

int
handle_capsLock(netsnmp_mib_handler *handler,
                          netsnmp_handler_registration *reginfo,
                          netsnmp_agent_request_info   *reqinfo,
                          netsnmp_request_info         *requests)
{
    /* We are never called for a GETNEXT if it's registered as a
       "instance", as it's "magically" handled for us.  */

    if (disp)
    {
    XkbGetIndicatorState(disp, XkbUseCoreKbd, &kbd);
    capsLockLED = (kbd & CAPSLOCK) == CAPSLOCK;
    }

    /* a instance handler also only hands us one request at a time, so
       we don't need to loop over a list of requests; we'll only get one. */
    switch(reqinfo->mode) {

        case MODE_GET:
            snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER,
                                     (u_char *) &capsLockLED, sizeof(capsLockLED));
            break;

        default:
            /* we should never get here, so this is a really bad error */
            snmp_log(LOG_ERR, "unknown mode (%d) in handle_capsLock\n", reqinfo->mode );
            return SNMP_ERR_GENERR;
    }

    return SNMP_ERR_NOERROR;
}
int
handle_numLock(netsnmp_mib_handler *handler,
                          netsnmp_handler_registration *reginfo,
                          netsnmp_agent_request_info   *reqinfo,
                          netsnmp_request_info         *requests)
{
    /* We are never called for a GETNEXT if it's registered as a
       "instance", as it's "magically" handled for us.  */

    if (disp)
    {
    XkbGetIndicatorState(disp, XkbUseCoreKbd, &kbd);
    numLockLED = (kbd & NUMLOCK) == NUMLOCK;
    }

    /* a instance handler also only hands us one request at a time, so
       we don't need to loop over a list of requests; we'll only get one. */

    switch(reqinfo->mode) {

        case MODE_GET:
            snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER,
                                     (u_char *) &numLockLED, sizeof(numLockLED));
            break;

        default:
            /* we should never get here, so this is a really bad error */
            snmp_log(LOG_ERR, "unknown mode (%d) in handle_numLock\n", reqinfo->mode );
            return SNMP_ERR_GENERR;
    }

    return SNMP_ERR_NOERROR;
}
int
handle_scrollLock(netsnmp_mib_handler *handler,
                          netsnmp_handler_registration *reginfo,
                          netsnmp_agent_request_info   *reqinfo,
                          netsnmp_request_info         *requests)
{
    int ret;
    XKeyboardControl values;

    /* We are never called for a GETNEXT if it's registered as a
       "instance", as it's "magically" handled for us.  */

    if (disp)
    {
    XkbGetIndicatorState(disp, XkbUseCoreKbd, &kbd);
    scrollLockLED = (kbd & SCRLOCK) == SCRLOCK;
    }

    /* a instance handler also only hands us one request at a time, so
       we don't need to loop over a list of requests; we'll only get one. */

    switch(reqinfo->mode) {

        case MODE_GET:
            snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER,
                                     (u_char *) &scrollLockLED, sizeof(scrollLockLED));
            break;

        /*
         * SET REQUEST
         *
         * multiple states in the transaction.  See:
         * http://www.net-snmp.org/tutorial-5/toolkit/mib_module/set-actions.jpg
         */
        case MODE_SET_RESERVE1:
                /* or you could use netsnmp_check_vb_type_and_size instead */
            ret = netsnmp_check_vb_type(requests->requestvb, ASN_INTEGER);
            if ( ret != SNMP_ERR_NOERROR ) {
                netsnmp_set_request_error(reqinfo, requests, ret );
            }
            break;

        case MODE_SET_RESERVE2:
            /* XXX malloc "undo" storage buffer */
            break;

        case MODE_SET_FREE:
            /* XXX: free resources allocated in RESERVE1 and/or
               RESERVE2.  Something failed somewhere, and the states
               below won't be called. */
            break;

        case MODE_SET_ACTION:
            /* XXX: perform the value change here */

            /* Setup ScrollLock LED */

            /* SCROLL LOCK is value 3 in X Graphic mode */
            values.led = SCRLOCK - 1;
            /* led_mode = 0 is LED OFF. led_mode = 1 is LED ON. */
            values.led_mode = *(requests->requestvb->val.integer);

            /* Change status of ScrollLock LED */
            XChangeKeyboardControl(disp, KBLed | KBLedMode, &values);
            /* Syncronize imediately */
            XSync(disp, 0);

            break;

        case MODE_SET_COMMIT:
            /* XXX: delete temporary storage */
            break;

        case MODE_SET_UNDO:
            /* XXX: UNDO and return to previous value for the object */
            break;

        default:
            /* we should never get here, so this is a really bad error */
            snmp_log(LOG_ERR, "unknown mode (%d) in handle_scrollLock\n", reqinfo->mode );
            return SNMP_ERR_GENERR;
    }

    return SNMP_ERR_NOERROR;
}
int
handle_runTime(netsnmp_mib_handler *handler,
                          netsnmp_handler_registration *reginfo,
                          netsnmp_agent_request_info   *reqinfo,
                          netsnmp_request_info         *requests)
{
    /* We are never called for a GETNEXT if it's registered as a
       "instance", as it's "magically" handled for us.  */

    time_t now = 0;

    /* a instance handler also only hands us one request at a time, so
       we don't need to loop over a list of requests; we'll only get one. */

    now = time(NULL);

    switch(reqinfo->mode) {

        case MODE_GET:
            snmp_set_var_typed_value(requests->requestvb, ASN_COUNTER,
                                     (u_char *) now, sizeof(now));
            break;

        default:
            /* we should never get here, so this is a really bad error */
            snmp_log(LOG_ERR, "unknown mode (%d) in handle_runTime\n", reqinfo->mode );
            return SNMP_ERR_GENERR;
    }

    return SNMP_ERR_NOERROR;
}
About these ads

, ,

  1. #1 por acassis em março 24, 2012 - 10:37 am

    Marcelo,

    A linha destacada em azul no inicio do seu post:

    “if (disp) { XkbGetIndicatorState(disp, XkbUseCoreKbd, &kbd); capsLockLED = (kbd & CAPSLOCK) == CAPSLOCK; }”

    deveria ter quebra de linha, ficou tudo na mesma linha.

    []‘s

    Alan

  2. #2 por Marcelo Barros em março 24, 2012 - 12:17 pm

    Pois é, pena que o wordpress não programa em C. Eu altero, ele acerta. Depois ele vai e muda sozinho se edito o post novamente. Tem um bug nele. Não sei bem o que fazer neste caso …. :/

  3. #3 por Thiago Neves em julho 27, 2012 - 9:13 pm

    Oi Marcelo, descobri a pouco tempo esse seu blog e estou achando bastante útil para aprender a mexer no net-snmp… Existem planos para um post 8? Abcs.

  4. #4 por Marcelo Barros em julho 28, 2012 - 12:23 am

    Sim, mais uns dois ou três. Aguarde que o oitavo tá semi pronto.

  5. #5 por Thiago Neves em julho 30, 2012 - 8:05 pm

    Caro Marcelo, uma dúvida. Nesse último post você mostrou como gerar uma MIB com a estrutura SMIv2 e gerar um código C relativo a essa estrutura. Esse código em C possui funções GET e SET. Minha dúvida é, onde eu incorporaria essas funções GET e SET? Imagino que seja em algum programa que eu queira que seja visível para o gerente snmp. É isso mesmo? Abcs.

  6. #7 por Marcelo Barros em julho 31, 2012 - 12:04 am

    Thiago,
    Sim, você vai ter que “alimentar” estes GET e SET de algum lugar. Existem algumas formas, que vão depender de como pretende fazer a sua integração com o net-snmp. A mais simples é o tratamento entrar dentro do código final do net-snmp, ao escrevê-lo dentro do arquivo gerado pelo net-snmp, algo que pretendo mostrar em breve. Mas existem outras formas. Você pode implementar uma lib que é carregada pelo net-snmp e que é usada para fornecer os valores ou ainda é possível usar comunicação entre processos. Nestes casos, a complexidade cresce bastante. Eu acho mais simples deixar parte do seu código dentro do net-snmp mesmo e ler/setar coisas em arquivos do /proc. O /proc seria uma espécie de gateway entre a manipulação do get/set e o grosso do seu programa. Isto pode não ser bom, dependendo das licenças que pretenda usar, daí a comunicação entre processos é o caminho. Tem exemplos disso no site do net-snmp.

  7. #8 por Marcelo Barros em julho 31, 2012 - 12:08 am

    Obrigado pela dica, Cleiton. Vou usá-la daqui pra diante !

  8. #9 por Renan Carlos em agosto 23, 2012 - 6:26 pm

    Olá, quando sai a próxima parte do tutorial? Abcs.

  9. #10 por Marcelo Barros™ (@marcelobarros) em agosto 23, 2012 - 7:48 pm

    Nos próximos dias, texto praticamente pronto, preciso apenas validar ;)

  10. #11 por joao em abril 27, 2013 - 12:14 pm

    Ola. aonde consigo mais destas informações ? estou aguardando o proximo post, porem estou em fase de implementação e preciso fazer funcionar.
    muito grato. joao

  11. #12 por lasacochehomme.com em junho 30, 2013 - 2:44 pm

    An outstanding share! I’ve just forwarded this onto a co-worker who was conducting a little research on this. And he actually bought me lunch due to the fact that I discovered it for him… lol. So allow me to reword this…. Thank YOU for the meal!! But yeah, thanx for spending some time to talk about this topic here on your website.

  1. O guia definitivo para os iniciantes em Net-SNMP (6) | Jedizone
  2. O guia definitivo para os iniciantes em Net-SNMP (8) | Jedizone

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.

Junte-se a 508 outros seguidores

%d blogueiros gostam disto: