summaryrefslogtreecommitdiff
path: root/src/snmp-engine.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/snmp-engine.c')
-rw-r--r--src/snmp-engine.c636
1 files changed, 0 insertions, 636 deletions
diff --git a/src/snmp-engine.c b/src/snmp-engine.c
deleted file mode 100644
index e1bff30..0000000
--- a/src/snmp-engine.c
+++ /dev/null
@@ -1,636 +0,0 @@
-/*
- * Copyright (c) 2005, Nate Nielsen
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above
- * copyright notice, this list of conditions and the
- * following disclaimer.
- * * Redistributions in binary form must reproduce the
- * above copyright notice, this list of conditions and
- * the following disclaimer in the documentation and/or
- * other materials provided with the distribution.
- * * The names of contributors to this software may not be
- * used to endorse or promote products derived from this
- * software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
- *
- *
- * CONTRIBUTORS
- * Nate Nielsen <nielsen@memberwebs.com>
- *
- */
-
-#include "usuals.h"
-#include <errno.h>
-#include <syslog.h>
-
-#include <bsnmp/asn1.h>
-#include <bsnmp/snmp.h>
-
-#include "stringx.h"
-#include "rrdbotd.h"
-#include "server-mainloop.h"
-
-/* The socket to use */
-int snmp_socket = -1;
-
-/* The last request: start at a strange number */
-uint32_t snmp_request = 0x0A0A0A0A;
-
-/* Since we only deal with one packet at a time, global buffer */
-unsigned char snmp_buffer[0x1000];
-
-/* -----------------------------------------------------------------------------
- * REQUESTS
- */
-
-/* rb_request waaaaayyyyy too big */
-typedef struct _rb_request
-{
- /* The SNMP request identifier */
- uint32_t id;
-
- mstime next_retry; /* Time of the next retry */
- mstime interval; /* How long between retries */
- mstime timeout; /* When this request times out */
- uint sent; /* How many times we've sent */
-
- /* The poller and host associated with this request */
- rb_poller* poll;
- const rb_host* host;
-
- /* The actual request data */
- struct snmp_pdu pdu;
-}
-rb_request;
-
-/* a scrolling window on a loop */
-static rb_request* requests = NULL;
-static int reqhigh = -1;
-static int reqlow = -1;
-static uint nrequests = 0;
-
-static rb_request*
-new_req()
-{
- rb_request* req = NULL;
- uint num;
- int i, first, overlap = 0;
-
- if(nrequests)
- {
- /* We allocate in a loop starting after the last allocation. */
- for(i = (reqhigh + 1) % nrequests; i != reqhigh;
- i = (i + 1) % nrequests)
- {
- /*
- * We can overlap past reqlow, but in that case no
- * updating reqhigh. This can happen after reallocating.
- */
- if(i == reqlow)
- overlap = 1;
-
- if(requests[i].id == 0)
- {
- req = &(requests[i]);
-
- if(!overlap)
- reqhigh = i;
- break;
- }
- }
- }
-
- if(!req)
- {
- /*
- * A note about the scrolling window and extending allocations...
- * The only reason this works is because whenever we reallocate
- * reqhigh and reqlow are the same.
- */
- ASSERT(reqlow == reqhigh);
-
- /* Reallocate the request block */
- /* TODO: Once we use less memory this can be higher */
- num = nrequests ? nrequests * 2 : 32;
- requests = (rb_request*)realloc(requests, sizeof(rb_request) * num);
- if(!requests)
- {
- /* Note we leave old requests allocated */
- errno = ENOMEM;
- return NULL;
- }
-
- /* Clear out the new ones */
- memset(requests + nrequests, 0, sizeof(rb_request) * (num - nrequests));
-
- /* We return the next one */
- req = requests + nrequests;
-
- nrequests = num;
-
- if(reqhigh == -1)
- reqhigh = 0;
- if(reqlow == -1)
- reqlow = nrequests - 1;
- }
-
- /* A incrementing counter for each request */
- req->id = snmp_request++;
- return req;
-}
-
-static rb_request*
-find_req(uint32_t id)
-{
- int i, first;
-
- if(!nrequests)
- return NULL;
-
- /*
- * Search backwards from the in the scrolling window. This gives
- * us as high performance for the high performing pollers and
- * less performance for the low ones.
- */
- for(i = reqhigh, first = 1; first || i != reqlow;
- i = (i ? i : nrequests) - 1)
- {
- if(id == requests[i].id)
- return &(requests[i]);
- first = 0;
- }
-
- return NULL;
-}
-
-static void
-free_req(rb_request* req)
-{
- int i;
-
- memset(req, 0, sizeof(*req));
-
- /* Update the bottom of the scrolling loop */
- for(i = reqlow; i != reqhigh; i = (i + 1) % nrequests)
- {
- /* If used then done */
- if(requests[i].id)
- break;
-
- /* reqlow is not inclusive */
- reqlow = i;
- }
-}
-
-/* -----------------------------------------------------------------------------
- * PACKET HANDLING
- */
-
-static void
-finish_poll(rb_poller* poll, mstime when)
-{
-#ifdef _DEBUG
- {
- rb_item* it;
- for(it = poll->items; it; it = it->next)
- ASSERT(!it->req);
- }
-#endif
-
- /* Update the book-keeping */
- poll->last_polled = when;
-
- rb_messagex(LOG_DEBUG, "collected poll values. sending them to rrd");
-
- /* And send off our collection of values */
- rb_rrd_update(poll);
-}
-
-static void
-send_req(rb_request* req, mstime when)
-{
- struct asn_buf b;
- ssize_t ret;
-
- b.asn_ptr = snmp_buffer;
- b.asn_len = sizeof(snmp_buffer);
-
- if(snmp_pdu_encode(&(req->pdu), &b))
- rb_message(LOG_CRIT, "couldn't encode snmp buffer");
- else
- {
- ret = sendto(snmp_socket, snmp_buffer, b.asn_ptr - snmp_buffer, 0,
- &SANY_ADDR(req->host->address), SANY_LEN(req->host->address));
- if(ret == -1)
- rb_message(LOG_ERR, "couldn't send snmp packet to: %s", req->host->name);
- else
- rb_messagex(LOG_DEBUG, "sent request '%d' to: %s", req->id, req->host->name);
- }
-
- /* And update our bookkeeping */
- req->sent++;
- if(req->sent <= g_state.retries)
- req->next_retry = when + req->interval;
-}
-
-static void
-timeout_req(rb_request* req, mstime when)
-{
- rb_poller* poll = req->poll;
- int incomplete = 0;
- rb_item* it;
-
- ASSERT(poll);
-
- /*
- * Marks of this requests items as unknown. Request is
- * over, free. See if poller is done
- */
-
- for(it = poll->items; it; it = it->next)
- {
- if(it->req == req)
- {
- rb_messagex(LOG_DEBUG, "value for field '%s' timed out", it->rrdfield);
-
- it->value = RB_UNKNOWN;
- it->req = NULL;
- }
-
- else if(it->req)
- incomplete = 1;
- }
-
- free_req(req);
-
- if(!incomplete)
- finish_poll(poll, when);
-}
-
-static void
-check_req(rb_request* req, mstime when)
-{
- ASSERT(req->id);
-
- /* See if it's timed out */
- if(when >= req->timeout)
- timeout_req(req, when);
-
- if(!req->next_retry)
- return;
-
- /* Resend if necessary */
- if(when >= req->next_retry)
- send_req(req, when);
-}
-
-static void
-respond_req(rb_request* req, struct snmp_pdu* pdu, mstime when)
-{
- struct snmp_value* value;
- rb_poller* poll = req->poll;
- rb_item* item;
- int incomplete = 0;
- int i;
-
- ASSERT(req->id == pdu->request_id);
-
- for(i = 0; i < pdu->nbindings; i++)
- {
- value = &(pdu->bindings[i]);
-
- for(item = poll->items; item; item = item->next)
- {
- if(asn_compare_oid(&(value->var), &(item->snmpfield.var)) == 0)
- {
- const char *msg = NULL;
- switch(value->syntax)
- {
- case SNMP_SYNTAX_NULL:
- item->value = RB_UNKNOWN;
- break;
- case SNMP_SYNTAX_INTEGER:
- item->value = value->v.integer;
- break;
- case SNMP_SYNTAX_COUNTER:
- case SNMP_SYNTAX_GAUGE:
- case SNMP_SYNTAX_TIMETICKS:
- item->value = value->v.uint32;
- break;
- case SNMP_SYNTAX_COUNTER64:
- item->value = value->v.counter64;
- break;
- case SNMP_SYNTAX_OCTETSTRING:
- case SNMP_SYNTAX_OID:
- case SNMP_SYNTAX_IPADDRESS:
- msg = "snmp server returned non numeric value for field: %s";
- break;
- case SNMP_SYNTAX_NOSUCHOBJECT:
- case SNMP_SYNTAX_NOSUCHINSTANCE:
- case SNMP_SYNTAX_ENDOFMIBVIEW:
- msg = "value not available on snmp server for field: %s";
- break;
- default:
- msg = "snmp server returned invalid or unsupported value for field: %s";
- break;
- }
-
- if(msg)
- rb_messagex(LOG_WARNING, msg, item->rrdfield);
- else
- rb_messagex(LOG_DEBUG, "got value for field '%s': %0.2f",
- item->rrdfield, item->value);
-
- /* Mark this value as done */
- item->req = NULL;
- break;
- }
- }
- }
-
- /* We're done with this request */
- free_req(req);
-
- /* Now see if the entire request is done */
- for(item = poll->items; item; item = item->next)
- {
- if(item->req)
- return;
- }
-
-
- /* And if so then hand off */
- finish_poll(poll, when);
-}
-
-static int
-poller_timer(mstime when, void* arg)
-{
- rb_poller* poll = (rb_poller*)arg;
- const rb_host* last_host = NULL;
- rb_request* req = NULL;
- rb_item* it;
-
- /*
- * If the previous poll has not completed, then we count it
- * as a timeout.
- */
- for(it = poll->items; it; it = it->next)
- {
- if(it->req)
- {
- ASSERT(it->req->poll == poll);
- timeout_req(it->req, when);
- }
-
- /* timeout_req above should have cleared this */
- ASSERT(!it->req);
- }
-
-
- for(it = poll->items; it; it = it->next)
- {
- /*
- * We assume that the polled items are sorted by host. Done
- * in config.c. This allows us to fire off the least amount
- * of requests. Generate new requests when:
- *
- * - first or new host
- * - too many items in the same request
- */
- if(!req || it->host != last_host ||
- req->pdu.nbindings >= SNMP_MAX_BINDINGS)
- {
- /* Send off last request ... */
- if(req)
- send_req(req, when);
-
- /* ... and make a new one */
- req = new_req();
- if(!req)
- {
- rb_message(LOG_CRIT, "couldn't allocate a new snmp request");
- return 1;
- }
-
- req->poll = poll;
- req->host = it->host;
-
- rb_messagex(LOG_DEBUG, "preparing request '%d' for: %s@%s",
- req->id, req->host->community, req->host->name);
-
- /* Setup the packet */
- strlcpy(req->pdu.community, req->host->community, sizeof(req->pdu.community));
- req->pdu.request_id = req->id;
- req->pdu.version = req->host->version;
- req->pdu.type = SNMP_PDU_GET;
- req->pdu.error_status = 0;
- req->pdu.error_index = 0;
- req->pdu.nbindings = 0;
-
- /* Send interval is 200 ms when poll interval is below 2 seconds */
- req->interval = (poll->interval <= 2000) ? 200L : 600L;
-
- /* Timeout is for the last packet sent, not first */
- req->timeout = when + (req->interval * ((mstime)g_state.retries)) + poll->timeout;
- req->sent = 0;
-
- last_host = it->host;
- }
-
- /* Add an item to this request */
- req->pdu.bindings[req->pdu.nbindings].var = it->snmpfield.var;
- req->pdu.bindings[req->pdu.nbindings].syntax = it->snmpfield.syntax;
- req->pdu.nbindings++;
-
- /* Mark item as active by this request */
- it->req = req;
- it->value = RB_UNKNOWN;
- }
-
- if(req)
- send_req(req, when);
-
- return 1;
-}
-
-
-static void
-receive_resp(int fd, int type, void* arg)
-{
- char hostname[MAXPATHLEN];
- struct sockaddr_any from;
- struct snmp_pdu pdu;
- struct asn_buf b;
- rb_request* req;
- int len, ret;
- int32_t ip;
-
- ASSERT(snmp_socket == fd);
-
- /* Read in the packet */
-
- SANY_LEN(from) = sizeof(from);
- len = recvfrom(snmp_socket, snmp_buffer, sizeof(snmp_buffer), 0,
- &SANY_ADDR(from), &SANY_LEN(from));
- if(len < 0)
- {
- if(errno != EAGAIN && errno != EWOULDBLOCK)
- rb_message(LOG_ERR, "error receiving snmp packet from network");
- return;
- }
-
-
- if(sock_any_ntop(&from, hostname, MAXPATHLEN, 0) == -1)
- strcpy(hostname, "[UNKNOWN]");
-
- rb_messagex(LOG_DEBUG, "received packet from: %s", hostname);
-
- /* Now parse the packet */
-
- b.asn_ptr = snmp_buffer;
- b.asn_len = len;
-
- ret = snmp_pdu_decode(&b, &pdu, &ip);
- if(ret != SNMP_CODE_OK)
- {
- rb_message(LOG_WARNING, "invalid snmp packet received from: %s", hostname);
- return;
- }
-
- /* Lookup and check the request properly */
-
- req = find_req(pdu.request_id);
- if(!req)
- return;
-
- if(pdu.error_status != SNMP_ERR_NOERROR)
- {
- /* TODO: Textual errors */
- rb_message(LOG_ERR, "snmp error '%d' from host: %s", pdu.error_status, hostname);
- return;
- }
-
- if(pdu.version != req->pdu.version)
- rb_message(LOG_WARNING, "wrong version snmp packet from: %s", hostname);
-
- rb_messagex(LOG_DEBUG, "response to request '%d' from: %s", req->id, hostname);
- respond_req(req, &pdu, server_get_time());
-}
-
-static int
-resend_timer(mstime when, void* arg)
-{
- int i;
-
- /* Search forwards through the scrolling window */
- for(i = (reqlow + 1) % nrequests; i <= reqhigh;
- i = (i + 1) % nrequests)
- {
- if(requests[i].id)
- check_req(&(requests[i]), when);
- }
-
- return 1;
-}
-
-static int
-prep_timer(mstime when, void* arg)
-{
- /*
- * We don't prepare all timers at exactly the same time
- * but we sort of randomly start the various timers. We're
- * going to be hitting these over and over again, so there's
- * lots of benefits to spreading them out randomly over a
- * few seconds.
- */
-
- rb_poller* poll;
- int next;
-
- /* All done? */
- if(!arg)
- return 0;
-
- poll = (rb_poller*)arg;
- if(server_timer(poll->interval, poller_timer, poll) == -1)
- rb_message(LOG_CRIT, "couldn't setup poller timer");
-
- /* Setup the next poller anywhere between 0 and 750 ms */
- next = rand() % 750;
- server_oneshot(next, prep_timer, poll->next);
- return 0;
-}
-
-
-void
-rb_snmp_engine_init()
-{
- struct sockaddr_in addr;
- rb_request* req;
-
- ASSERT(snmp_socket == -1);
- snmp_socket = socket(PF_INET, SOCK_DGRAM, 0);
- if(snmp_socket < 0)
- err(1, "couldn't open snmp socket");
-
- /* Get a random IPv4 UDP socket for client use */
- memset(&addr, 0, sizeof(addr));
- addr.sin_family = AF_INET;
-
- if(bind(snmp_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0)
- err(1, "couldn't listen on port");
-
- if (server_watch(snmp_socket, SERVER_READ, receive_resp, NULL) == -1)
- err(1, "couldn't listen on socket");
-
- /* Allocate some requests to make sure we have memory */
- req = new_req();
- if(!req)
- err(1, "out of memory");
- free_req(req);
-
- /* Start the preparation timers for setting up randomly */
- if(server_oneshot(100, prep_timer, g_state.polls) == -1)
- err(1, "couldn't setup timer");
-
- /* We fire off the resend timer every 1/5 second */
- if(server_timer(200, resend_timer, NULL) == -1)
- err(1, "couldn't setup timer");
-}
-
-void
-rb_snmp_engine_uninit()
-{
- if(snmp_socket != -1)
- {
- server_unwatch(snmp_socket);
- close(snmp_socket);
- snmp_socket = -1;
- }
-
- if(requests)
- {
- free(requests);
- nrequests = 0;
- reqhigh = 0;
- reqlow = 0;
- }
-}