OoO En  cette matinée pluvieuse du  samedi 03 mars 2012,  vers 10:53, je

> Hi!
> I am hit by this annoying bug:

> I am  trying to fix  this. The situation  seems quite simple. Here  is a
> pseudo-trace:

>  - snmp_async_send()
>  - snmp_sess_async_send()
>  - _sess_async_send()
>  - snmpv3_engineID_probe()
>  - sptr->probe_engineid() and sptr->post_probe_engineid()

> Those two callbacks can be:
>  - snmpv3_probe_contextEngineID_rfc5343()
>  - usm_discover_engineid()
>  - usm_create_user_from_session_hook()

> The last one seems OK.

> The two first ones  call snmp_sess_synch_response(). The problem is that
> if  we want  to  fix  this, we  need  to change  the  semantics for  the
> callbacks. Is  it OK  to change the  first callback  to an async  one? I
> don't know if the semantics of the second one needs any adaptation.

I have tried  to fix this but  this is harder than I  thought.  There is
also a path from snmp_open() that could lead to snmpv3_engineID_probe().
This  path  is  disabled  by  default.  The  other  difficulty  is  when
receiving  the  Report message.  The  original  PDU  cannot be  sent  in
response  to  this  message  because  NetSNMP  did  not  yet  write  the
appropriate engineBoots/engineUptime to the  session. This is done after
the  callback   handling  the  Report  message   has  been  successfully

I have  fixed my  problem right  into my application  because I  was too
incomfortable with NetSNMP.  I only corrected the snmp_sess_async_send()
path and only with USM.

I have written a wrapper around snmp_async_send():

  /* Copy the version from the session if needed. */
  if (pdu->version == SNMP_DEFAULT_VERSION)
    pdu->version = session->version;

  /* Do we need probing? */
  if (pdu->version != SNMP_VERSION_3 ||
      session->securityEngineIDLen != 0 ||
      (0 != (session->flags & SNMP_FLAGS_DONT_PROBE))) {
    /* No, we can just call snmp_async_send() */
    int ret = snmp_async_send(session, pdu, cb, arg);
    if (!ret) log_snmp_error(seat);
    return ret;

  /* Allocate some "magic" structure to remember PDU, callback and argument*/
  struct magic *magic = calloc(1, sizeof(struct magic));
  if (!magic)
    return 0;
  magic->pdu  = pdu;
  magic->cb   = cb;
  magic->arg = arg;
  magic->session = session;

  netsnmp_pdu *probe = NULL;
  if (snmpv3_build_probe_pdu(&probe) != 0) {
    return 0;

  /* Send it. */
  session->flags |= SNMP_FLAGS_DONT_PROBE; /* prevent recursion */
  if (!snmp_async_send(session, probe,
                       probe_engine_step1_cb, magic)) {
    session->flags &= ~SNMP_FLAGS_DONT_PROBE;
    return 0;

  return 1;

The snmpv3_build_probe_pdu is stolen  from NetSNMP (either in snmp_api.c
or in snmpusm.c).

Then, I have this function to handle the received probe answer:

static int
probe_engine_step1_cb(int operation,
                      struct snmp_session *sp,
                      int reqid,
                      struct snmp_pdu *pdu,
                      void *arg) {
  struct magic *magic = arg;
  struct timeval tv = {0, 0};
  int ret;

  /* Did we receive the appropriate Report message? */
      pdu && pdu->command == SNMP_MSG_REPORT) {
    /* We need to execute the remaining operations outside of this
       callback because the appropriate information are not put in the
       session, yet. I am using libevent! */
    magic->next = evtimer_new(magic->cfg->base, probe_engine_step2_cb, magic);
    if (!magic->next) goto probe_failed;
    if (evtimer_add(magic->next, &tv) == -1) goto probe_failed;
    return 1;

  sp->flags &= ~SNMP_FLAGS_DONT_PROBE;
                  sp, reqid, pdu, magic->arg);
  if (magic->next) event_free(magic->next);
  return ret;

To resend the  PDU outside the callback, I am using  my libevent loop to
schedule the original  PDU to be sent immediatly. I have  no idea on how
to   do   this   with    NetSNMP.   This   is   necessary   because   in
_sess_process_packet(), the code is like this:

      if (callback == NULL
                      pdu->reqid, pdu, magic) == 1) {
        if (pdu->command == SNMP_MSG_REPORT) {
          if (sp->s_snmp_errno == SNMPERR_NOT_IN_TIME_WINDOW ||
              snmpv3_get_report_type(pdu) ==
             * trigger immediate retry on recoverable Reports 
             * * (notInTimeWindow), incr_retries == TRUE to prevent
             * * inifinite resend                      
            if (rp->retries <= sp->retries) {
              snmp_resend_request(slp, rp, TRUE);
          } else {

           * Handle engineID discovery.  
          if (!sp->securityEngineIDLen && pdu->securityEngineIDLen) {
            sp->securityEngineID =
              (u_char *) malloc(pdu->securityEngineIDLen);
            if (sp->securityEngineID == NULL) {
               * TODO FIX: recover after message callback *?
               * return -1;
            memcpy(sp->securityEngineID, pdu->securityEngineID,
            sp->securityEngineIDLen = pdu->securityEngineIDLen;
            if (!sp->contextEngineIDLen) {
              sp->contextEngineID =
                (u_char *) malloc(pdu->
              if (sp->contextEngineID == NULL) {
                 * TODO FIX: recover after message callback *?
                 * return -1;
              sp->contextEngineIDLen =

And my last step is where I send the original PDU back:

static void
probe_engine_step2_cb(evutil_socket_t fd, short what, void *arg) {
  (void)what; (void)fd;
  struct magic *magic = arg;
  struct snmp_session *session = magic->session;

  /* We don't need magic->next anymore. */
  event_free(magic->next); magic->next = NULL;

  if (session->securityEngineIDLen == 0)
    goto probe_failed2;

  /* Create the appropriate user from data from session */
  if (create_user_from_session(session) != SNMPERR_SUCCESS)
    goto probe_failed2;

  /* We can now send the original PDU */
  if (!snmp_async_send(session, magic->pdu,
                       magic->cb, magic->arg))
    goto probe_failed2;

  session->flags &= ~SNMP_FLAGS_DONT_PROBE;
            session, 0, magic->pdu, magic->arg);

This  works fine  but  to propose  a  patch for  NetSNMP,  I have  three

 1. snmp_open()  calling snmpv3_engineID_probe() is a pain.  There is no
    "async"  version  of  this  function  because I  suppose  that  this
    function should never block. Could  we just remove this path? A user
    could    trigger   such    a    path   by    unsetting   the    flag
    SNMP_FLAGS_DONT_PROBE after initializing the session.

 2. How to handle the "send the original PDU as soon as you hit the main
    loop again" could be done properly with NetSNMP?

 3. I am  allocating some  "magic" structure to  keep the  original PDU,
    callback  and callback  argument.  Is  there something  simpler with

The bug report  from Robert Story tells that the bug  should be fixed by
queueing the  original PDU. Maybe there  is a simpler way  of doing this
than what I am currently doing?
Vincent Bernat ☯

  *   Should be panic but... (Why are BSD people panic obsessed ??)
        2.0.38 /usr/src/linux/net/ipv4/ip_fw.c

Keep Your Developer Skills Current with LearnDevNow!
The most comprehensive online learning library for Microsoft developers
is just $99.99! Visual Studio, SharePoint, SQL - plus HTML5, CSS3, MVC3,
Metro Style Apps, more. Free future releases when you subscribe now!
Net-snmp-coders mailing list

Reply via email to