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 withNetSNMP. 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 {