OoO En cette matinée pluvieuse du samedi 03 mars 2012, vers 10:53, je disais:
> Hi! > I am hit by this annoying bug: > > https://sourceforge.net/tracker/?func=detail&aid=3446148&group_id=12694&atid=112694 > 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 triggered. 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) { free(magic); return 0; } /* Send it. */ session->flags |= SNMP_FLAGS_DONT_PROBE; /* prevent recursion */ if (!snmp_async_send(session, probe, probe_engine_step1_cb, magic)) { snmp_free_pdu(probe); free(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? */ if (operation == NETSNMP_CALLBACK_OP_RECEIVED_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; } probe_failed: sp->flags &= ~SNMP_FLAGS_DONT_PROBE; ret = magic->cb(NETSNMP_CALLBACK_OP_SEND_FAILED, sp, reqid, pdu, magic->arg); if (magic->next) event_free(magic->next); free(magic); 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 || callback(NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE, sp, 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) == SNMPERR_NOT_IN_TIME_WINDOW) { /* * 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); break; } } else { if (SNMPV3_IGNORE_UNAUTH_REPORTS) { break; } } /* * 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, pdu->securityEngineIDLen); sp->securityEngineIDLen = pdu->securityEngineIDLen; if (!sp->contextEngineIDLen) { sp->contextEngineID = (u_char *) malloc(pdu-> securityEngineIDLen); if (sp->contextEngineID == NULL) { /* * TODO FIX: recover after message callback *? * return -1; */ } memcpy(sp->contextEngineID, pdu->securityEngineID, pdu->securityEngineIDLen); sp->contextEngineIDLen = pdu->securityEngineIDLen; } } } 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; free(magic); return; probe_failed2: session->flags &= ~SNMP_FLAGS_DONT_PROBE; magic->cb(NETSNMP_CALLBACK_OP_SEND_FAILED, session, 0, magic->pdu, magic->arg); free(magic); } This works fine but to propose a patch for NetSNMP, I have three questions: 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 NetSNMP? 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 ☯ http://vincent.bernat.im /* * 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! http://p.sf.net/sfu/learndevnow-d2d _______________________________________________ Net-snmp-coders mailing list Net-snmp-coders@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/net-snmp-coders