diff options
Diffstat (limited to 'common/snmp-engine.c')
-rw-r--r-- | common/snmp-engine.c | 191 |
1 files changed, 122 insertions, 69 deletions
diff --git a/common/snmp-engine.c b/common/snmp-engine.c index 35fae26..0bfb77d 100644 --- a/common/snmp-engine.c +++ b/common/snmp-engine.c @@ -305,10 +305,20 @@ host_cleanup (void) * ASYNC REQUEST PROCESSING */ +#define MAKE_REQUEST_ID(snmp, cb) \ + ((((snmp) & 0xFFFFFF) << 8) | (cb & 0xFF)) +#define REQUEST_ID_SNMP(id) \ + ((id) >> 8) +#define REQUEST_ID_CB(id) \ + ((id) & 0xFF) + struct request { /* The SNMP request identifier */ - uint id; + uint snmp_id; + + /* References, useful since we have callbacks */ + int refs; mstime next_send; /* Time of the next packet send */ mstime last_sent; /* Time last sent */ @@ -332,7 +342,7 @@ struct request static int snmp_retries = 3; /* The last request id */ -static uint snmp_request_id = 100000; +static uint snmp_request_id = 1; /* The SNMP socket we're communicating on */ static int snmp_socket = -1; @@ -353,8 +363,8 @@ static void request_release (struct request *req) { /* It should no longer be referred to any of these places */ - ASSERT (!hsh_get (snmp_preparing, &req->id, sizeof (req->id))); - ASSERT (!hsh_get (snmp_processing, &req->id, sizeof (req->id))); + ASSERT (!hsh_get (snmp_preparing, &req->snmp_id, sizeof (req->snmp_id))); + ASSERT (!hsh_get (snmp_processing, &req->snmp_id, sizeof (req->snmp_id))); snmp_pdu_clear (&req->pdu); free (req); @@ -394,7 +404,7 @@ request_send (struct request* req, mstime when) if (ret == -1) log_error ("couldn't send snmp packet to: %s", req->host->hostname); else - log_debug ("sent request #%d to: %s", req->id, req->host->hostname); + log_debug ("sent request #%d to: %s", req->snmp_id, req->host->hostname); } } @@ -406,18 +416,30 @@ request_failure (struct request *req, int code) ASSERT (req); ASSERT (code != 0); + ASSERT (hsh_get (snmp_processing, &req->snmp_id, sizeof (req->snmp_id)) == req); - log_debug ("failed request #%d to '%s' with code %d", req->id, req->host->hostname, code); + log_debug ("failed request #%d to '%s' with code %d", req->snmp_id, req->host->hostname, code); /* For each request SNMP value... */ for (j = 0; j < req->pdu.nbindings; ++j) { + + if (!req->callbacks[j].func) + continue; + /* ... let callback know */ - if (req->callbacks[j].func) - (req->callbacks[j].func) (req->id, code, NULL, req->callbacks[j].arg); + (req->callbacks[j].func) (MAKE_REQUEST_ID (req->snmp_id, j), + code, NULL, req->callbacks[j].arg); + + /* + * Request could have been freed by the callback, by calling the cancel + * function, check and bail if so. + */ + if (hsh_get (snmp_processing, &req->snmp_id, sizeof (req->snmp_id)) != req) + return; } /* Remove from the processing list */ - val = hsh_rem (snmp_processing, &req->id, sizeof (req->id)); + val = hsh_rem (snmp_processing, &req->snmp_id, sizeof (req->snmp_id)); ASSERT (val == req); /* And free the request */ @@ -429,24 +451,29 @@ request_get_dispatch (struct request* req, struct snmp_pdu* pdu) { struct snmp_value* pvalue; struct snmp_value* rvalue; - int i, j, last, processed; + int i, j, skipped, processed; void *val; ASSERT (req); ASSERT (pdu); - ASSERT (req->id == pdu->request_id); + ASSERT (req->snmp_id == pdu->request_id); ASSERT (pdu->error_status == SNMP_ERR_NOERROR); ASSERT (req->pdu.type == SNMP_PDU_GET); + ASSERT (hsh_get (snmp_processing, &req->snmp_id, sizeof (req->snmp_id)) == req); /* * For SNMP GET requests we check that the values that came back * were in fact for the same values we requested, and fix any * ordering issues etc. */ - for (j = 0; j < req->pdu.nbindings; ++j) { + skipped = 0; + for (j = 0; j < SNMP_MAX_BINDINGS; ++j) { + + if (!req->callbacks[j].func) + continue; - processed = 0; rvalue = &(req->pdu.bindings[j]); + processed = 0; /* ... dig out matching value from response */ for (i = 0; i < pdu->nbindings; ++i) { @@ -455,37 +482,32 @@ request_get_dispatch (struct request* req, struct snmp_pdu* pdu) if (asn_compare_oid (&(rvalue->var), &(pvalue->var)) != 0) continue; - if (req->callbacks[j].func) - (req->callbacks[j].func) (req->id, SNMP_ERR_NOERROR, - pvalue, req->callbacks[j].arg); + (req->callbacks[j].func) (MAKE_REQUEST_ID (req->snmp_id, j), + SNMP_ERR_NOERROR, pvalue, req->callbacks[j].arg); + /* + * Request could have been freed by the callback, by calling the cancel + * function, check and bail if so. + */ + if (hsh_get (snmp_processing, &req->snmp_id, sizeof (req->snmp_id)) != req) + return; + + req->callbacks[j].func = NULL; processed = 1; break; } - /* If this one was processed, remove from request */ - if (processed) { - last = --req->pdu.nbindings; - - ASSERT (last >= 0); - if (last) { - memcpy (&req->callbacks[j], &req->callbacks[last], sizeof (req->callbacks[j])); - memcpy (&req->pdu.bindings[j], &req->pdu.bindings[last], sizeof (req->pdu.bindings[j])); - } - memset (&req->callbacks[last], 0, sizeof (req->callbacks[last])); - memset (&req->pdu.bindings[last], 0, sizeof (req->pdu.bindings[last])); - - /* Process this index again, since we have a new request here */ - --j; - } + /* Make note that we didn't find a match for at least one binding */ + if (!processed && !skipped) + skipped = 1; } /* All done? then remove request */ - if (req->pdu.nbindings == 0) { + if (!skipped) { - log_debug ("request #%d is complete", req->id); + log_debug ("request #%d is complete", req->snmp_id); - val = hsh_rem (snmp_processing, &req->id, sizeof (req->id)); + val = hsh_rem (snmp_processing, &req->snmp_id, sizeof (req->snmp_id)); ASSERT (val == req); request_release (req); } @@ -498,7 +520,7 @@ request_other_dispatch (struct request* req, struct snmp_pdu* pdu) ASSERT (req); ASSERT (pdu); - ASSERT (req->id == pdu->request_id); + ASSERT (req->snmp_id == pdu->request_id); ASSERT (pdu->error_status == SNMP_ERR_NOERROR); ASSERT (req->pdu.type != SNMP_PDU_GET); @@ -520,12 +542,12 @@ request_other_dispatch (struct request* req, struct snmp_pdu* pdu) ASSERT (req->pdu.nbindings == 1); if (req->callbacks[0].func) - (req->callbacks[0].func) (req->id, SNMP_ERR_NOERROR, + (req->callbacks[0].func) (MAKE_REQUEST_ID (req->snmp_id, 0), SNMP_ERR_NOERROR, &(pdu->bindings[0]), req->callbacks[0].arg); - log_debug ("request #%d is complete", req->id); + log_debug ("request #%d is complete", req->snmp_id); - val = hsh_rem (snmp_processing, &req->id, sizeof (req->id)); + val = hsh_rem (snmp_processing, &req->snmp_id, sizeof (req->snmp_id)); ASSERT (val == req); request_release (req); } @@ -573,7 +595,7 @@ request_response (int fd, int type, void* arg) id = pdu.request_id; req = hsh_get (snmp_processing, &id, sizeof (id)); if(!req) { - log_debug ("received extra or delayed packet from: %s", hostname); + log_debug ("received extra, cancelled or delayed packet from: %s", hostname); return; } @@ -583,7 +605,7 @@ request_response (int fd, int type, void* arg) /* Log any errors */ if(pdu.error_status == SNMP_ERR_NOERROR) { - log_debug ("response to request #%d from: %s", req->id, hostname); + log_debug ("response to request #%d from: %s", req->snmp_id, hostname); if (req->pdu.type == SNMP_PDU_GET) request_get_dispatch (req, &pdu); @@ -593,9 +615,9 @@ request_response (int fd, int type, void* arg) } else { msg = snmp_get_errmsg (pdu.error_status); if(msg) - log_debug ("failure for request #%d from: %s: %s", req->id, hostname, msg); + log_debug ("failure for request #%d from: %s: %s", req->snmp_id, hostname, msg); else - log_debug ("failure for request #%d from: %s: %d", req->id, hostname, + log_debug ("failure for request #%d from: %s: %d", req->snmp_id, hostname, pdu.error_status); request_failure (req, pdu.error_status); } @@ -641,7 +663,7 @@ request_flush (struct request *req, mstime when) ASSERT (req->host->prepared == req); - val = hsh_rem (snmp_preparing, &req->id, sizeof (req->id)); + val = hsh_rem (snmp_preparing, &req->snmp_id, sizeof (req->snmp_id)); ASSERT (val == req); /* Don't let us add more onto this request via the host */ @@ -651,7 +673,7 @@ request_flush (struct request *req, mstime when) /* Mark this packet to be sent now */ req->next_send = when; - if (!hsh_set (snmp_processing, &req->id, sizeof (req->id), req)) { + if (!hsh_set (snmp_processing, &req->snmp_id, sizeof (req->snmp_id), req)) { log_errorx ("out of memory, discarding packets"); request_release (req); } @@ -698,7 +720,7 @@ request_prep_instance (struct host *host, mstime interval, mstime timeout, int r /* See if we have one we can piggy back onto */ req = host->prepared; if (req) { - ASSERT (hsh_get (snmp_preparing, &req->id, sizeof (req->id))); + ASSERT (hsh_get (snmp_preparing, &req->snmp_id, sizeof (req->snmp_id))); /* We have one we can piggy back another request onto */ if (req->pdu.nbindings < SNMP_MAX_BINDINGS && req->pdu.type == reqtype) @@ -719,10 +741,14 @@ request_prep_instance (struct host *host, mstime interval, mstime timeout, int r } /* Assign the unique id */ - req->id = snmp_request_id++; + req->snmp_id = snmp_request_id++; + + /* Roll around after a decent amount of ids */ + if (snmp_request_id >= 0xFFFFFF) + snmp_request_id = 1; /* Mark it down as something we want to prepare */ - if (!hsh_set (snmp_preparing, &req->id, sizeof (req->id), req)) { + if (!hsh_set (snmp_preparing, &req->snmp_id, sizeof (req->snmp_id), req)) { log_error ("out of memory"); free (req); return NULL; @@ -730,7 +756,7 @@ request_prep_instance (struct host *host, mstime interval, mstime timeout, int r /* Setup the packet */ strlcpy (req->pdu.community, host->community, sizeof (req->pdu.community)); - req->pdu.request_id = req->id; + req->pdu.request_id = req->snmp_id; req->pdu.version = host->version; req->pdu.type = reqtype; req->pdu.error_status = 0; @@ -749,7 +775,7 @@ request_prep_instance (struct host *host, mstime interval, mstime timeout, int r ASSERT (host->prepared == NULL); host->prepared = req; - log_debug ("preparing request #%d for: %s@%s", req->id, + log_debug ("preparing request #%d for: %s@%s", req->snmp_id, req->host->community, req->host->hostname); return req; @@ -762,6 +788,9 @@ snmp_engine_request (const char *hostname, const char *community, int version, { struct host *host; struct request *req; + int callback_id; + + ASSERT (func); /* Lookup host for request */ host = host_instance (hostname, community, version, interval); @@ -776,10 +805,11 @@ snmp_engine_request (const char *hostname, const char *community, int version, ASSERT (req->pdu.nbindings < SNMP_MAX_BINDINGS); /* Add the oid to that request */ - req->pdu.bindings[req->pdu.nbindings].var = *oid; - req->pdu.bindings[req->pdu.nbindings].syntax = SNMP_SYNTAX_NULL; - req->callbacks[req->pdu.nbindings].func = func; - req->callbacks[req->pdu.nbindings].arg = arg; + callback_id = req->pdu.nbindings; + req->pdu.bindings[callback_id].var = *oid; + req->pdu.bindings[callback_id].syntax = SNMP_SYNTAX_NULL; + req->callbacks[callback_id].func = func; + req->callbacks[callback_id].arg = arg; req->pdu.nbindings++; /* All other than GET, only get one binding */ @@ -788,39 +818,62 @@ snmp_engine_request (const char *hostname, const char *community, int version, request_flush (req, server_get_time ()); } - if (!snmp_flush_pending) { + /* Otherwise flush on the idle callback */ + else if (!snmp_flush_pending) { server_oneshot (0, request_flush_cb, NULL); snmp_flush_pending = 1; } - return req->id; + return MAKE_REQUEST_ID (req->snmp_id, callback_id); } void -snmp_engine_cancel (int reqid) +snmp_engine_cancel (int id) { struct request *req; + int snmp_id, callback_id, i; + const char *during; + + ASSERT (id); + + snmp_id = REQUEST_ID_SNMP (id); + callback_id = REQUEST_ID_CB (id); + + ASSERT (snmp_id > 0 && snmp_id < 0xFFFFFF); + ASSERT (callback_id >= 0 && callback_id < SNMP_MAX_BINDINGS); /* Is it being processed? */ - req = hsh_rem (snmp_processing, &reqid, sizeof (reqid)); + req = hsh_rem (snmp_processing, &snmp_id, sizeof (snmp_id)); if (req) { - log_debug ("cancelling request #%d during processing", reqid); - request_release (req); - return; - } + during = "processing"; /* Is it being prepared? */ - req = hsh_rem (snmp_preparing, &reqid, sizeof (reqid)); - if (req) { - - /* Remove it from the host in question */ - ASSERT (req->host->prepared == req); - req->host->prepared = NULL; + } else { + req = hsh_rem (snmp_preparing, &snmp_id, sizeof (snmp_id)); + if (req) { + during = "prep"; + ASSERT (req->host->prepared == req); + } + } - log_debug ("cancelling request #%d during prep", reqid); - request_release (req); + if (!req) return; + + /* Remove this callback from the request */ + req->callbacks[callback_id].func = NULL; + req->callbacks[callback_id].arg = NULL; + + /* See if any callbacks exist in the request */ + for (i = 0; i < SNMP_MAX_BINDINGS; ++i) { + if (req->callbacks[i].func) + return; } + + /* If not, free the request */ + log_debug ("cancelling request #%d during %s", snmp_id, during); + if (req->host->prepared == req) + req->host->prepared = NULL; + request_release (req); } void |