summaryrefslogtreecommitdiff
path: root/daemon
diff options
context:
space:
mode:
Diffstat (limited to 'daemon')
-rw-r--r--daemon/Makefile.am22
-rw-r--r--daemon/config.c290
-rw-r--r--daemon/poll-engine.c454
-rw-r--r--daemon/rrd-update.c15
-rw-r--r--daemon/rrdbotd.c56
-rw-r--r--daemon/rrdbotd.h72
-rw-r--r--daemon/snmp-engine.c727
7 files changed, 634 insertions, 1002 deletions
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 28dceaf..80945f6 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -2,14 +2,16 @@
sbin_PROGRAMS = rrdbotd
rrdbotd_SOURCES = rrdbotd.c rrdbotd.h config.c \
- snmp-engine.c rrd-update.c \
- ../common/server-mainloop.c ../common/server-mainloop.h \
- ../common/sock-any.h ../common/sock-any.c \
- ../common/compat.h ../common/compat.c \
- ../common/hash.h ../common/hash.c \
- ../common/config-parser.h ../common/config-parser.c \
- ../common/async-resolver.h ../common/async-resolver.c \
+ poll-engine.c rrd-update.c \
../mib/mib-parser.h ../mib/mib-parser.c
-rrdbotd_CFLAGS = -I${top_srcdir}/common/ -I${top_srcdir}/bsnmp/ -I${top_srcdir} \
- -DCONF_PREFIX=\"$(sysconfdir)\" -DDATA_PREFIX=\"$(datadir)\"
-rrdbotd_LDADD = $(top_builddir)/bsnmp/libbsnmp-custom.a
+
+rrdbotd_CFLAGS = \
+ -I${top_srcdir}/common/ \
+ -I${top_srcdir}/bsnmp/ \
+ -I${top_srcdir} \
+ -DCONF_PREFIX=\"$(sysconfdir)\" \
+ -DDATA_PREFIX=\"$(datadir)\"
+
+rrdbotd_LDADD = \
+ $(top_builddir)/common/libcommon.a \
+ $(top_builddir)/bsnmp/libbsnmp-custom.a
diff --git a/daemon/config.c b/daemon/config.c
index 1bf0e4b..70075ee 100644
--- a/daemon/config.c
+++ b/daemon/config.c
@@ -46,6 +46,7 @@
#include <mib/mib-parser.h>
+#include "log.h"
#include "rrdbotd.h"
#include "config-parser.h"
@@ -86,48 +87,6 @@ config_ctx;
* CONFIG LOADING
*/
-static rb_item*
-sort_items_by_host(rb_item *item)
-{
- rb_item *sort = NULL;
- rb_item *cur;
- rb_item *it;
-
- while(item)
- {
- cur = item;
- item = item->next;
- cur->next = NULL;
-
- /* First item */
- if(!sort)
- {
- sort = cur;
- continue;
- }
-
- /* Before first item */
- else if(cur->host <= sort->host)
- {
- cur->next = sort;
- sort = cur;
- continue;
- }
-
- for(it = sort; it->next; it = it->next)
- {
- if(cur->host <= sort->next->host)
- break;
- }
-
- ASSERT(it);
- cur->next = it->next;
- it->next = cur;
- }
-
- return sort;
-}
-
static void
config_done(config_ctx* ctx)
{
@@ -187,7 +146,7 @@ config_done(config_ctx* ctx)
/* Add the items to this poller */
it->next = poll->items;
- poll->items = sort_items_by_host(ctx->items);
+ poll->items = ctx->items;
}
/*
@@ -203,108 +162,125 @@ config_done(config_ctx* ctx)
ctx->timeout = 0;
}
-static rb_item*
-parse_item(const char* field, char* uri, config_ctx *ctx)
+static void
+parse_hosts (rb_item *item, char *host, config_ctx *ctx)
{
- char key[128];
- rb_item *ritem;
- rb_host *rhost;
- int r;
-
- enum snmp_version version;
- const char *msg;
- char* copy;
- char* scheme;
- char* host;
- char* user;
- char* path;
-
- /* Parse the SNMP URI */
- copy = strdup(uri);
- msg = cfg_parse_uri(uri, &scheme, &host, &user, &path);
- if(msg)
- errx(2, "%s: %s: %s", ctx->confname, msg, copy);
- free(copy);
-
- ASSERT(host && path);
-
- /* Currently we only support SNMP pollers */
- msg = cfg_parse_scheme(scheme, &version);
- if(msg)
- errx(2, "%s: %s", msg, scheme);
+ char *x;
- /*
- * Build a lookup key. We can only combine requests for the same
- * host when the version and community match.
- */
- user = user ? user : "public";
- snprintf(key, sizeof(key), "%d:%s:%s", version, host, user);
- key[sizeof(key) - 1] = 0;
+ for(;;) {
+ x = strchr (host, ',');
+ if (x)
+ *x = 0;
- /* See if we can find an associated host */
- rhost = (rb_host*)hsh_get(g_state.host_by_key, key, -1);
- if(!rhost)
- {
- /* Make a new one if necessary */
- rhost = (rb_host*)xcalloc(sizeof(*rhost));
+ if (*host) {
+ if (item->n_hostnames >= MAX_HOSTNAMES) {
+ log_warnx ("%s: too many host names: %s", ctx->confname, host);
+ break;
+ }
- rhost->version = version;
- rhost->hostname = host;
- rhost->community = user;
- rhost->is_resolved = 1;
- rhost->resolve_interval = 0;
- rhost->last_resolved = 0;
+ item->hostnames[item->n_hostnames] = host;
+ item->n_hostnames++;
+ }
- /* Try and resolve the DNS name */
- r = sock_any_pton(host, &(rhost->address),
- SANY_OPT_DEFPORT(161) | SANY_OPT_DEFLOCAL | SANY_OPT_NORESOLV);
+ if (!x)
+ break;
- if(r == -1)
- {
- rb_message(LOG_WARNING, "couldn't parse host address (ignoring): %s", host);
- free(rhost);
- return NULL;
- }
+ host = x + 1;
+ }
- /*
- * If we got back SANY_AF_DNS, then it needs resolving. The actual
- * interval and stuff are worked out in rb_config_parse() once all
- * the hosts, polls etc... have been parsed.
- */
- if(r == SANY_AF_DNS)
- rhost->is_resolved = 0;
-
- /* And add it to the list */
- rhost->next = g_state.hosts;
- g_state.hosts = rhost;
-
- /* And into the hash table */
- if(!hsh_set(g_state.host_by_key, rhost->key, -1, rhost))
- errx(1, "out of memory");
- }
+ /* Default to localhost for a host name */
+ if (!item->n_hostnames) {
+ log_warnx ("no host found in URI, defaulting to localhost");
+ item->n_hostnames = 1;
+ item->hostnames[0] = "localhost";
+ }
+
+ item->hostindex = 0;
+}
+
+static void
+parse_query (rb_item *item, char *query, config_ctx *ctx)
+{
+ char *name, *value;
+ const char *msg;
+
+ /* Parse the query if it exists */
+ if (!query)
+ return;
+
+ msg = cfg_parse_query (query, &name, &value, &query);
+ if (msg)
+ errx (2, "%s: %s", ctx->confname, msg);
+
+ if (query && *query)
+ log_warnx ("%s: only using first query argument in snmp URI", ctx->confname);
- /* Make a new item */
- ritem = (rb_item*)xcalloc(sizeof(*ritem));
- ritem->rrdfield = field;
- ritem->host = rhost;
- ritem->poller = NULL; /* Set later in config_done */
- ritem->req = NULL;
- ritem->vtype = VALUE_UNSET;
+ item->has_query = 1;
- /* And parse the OID */
- ritem->snmpfield.syntax = SNMP_SYNTAX_NULL;
- memset(&(ritem->snmpfield.v), 0, sizeof(ritem->snmpfield.v));
- if(mib_parse(path, &(ritem->snmpfield.var)) == -1)
- errx(2, "%s: invalid MIB: %s", ctx->confname, path);
+ /* And parse the query OID */
+ if (mib_parse (name, &(item->query_oid)) == -1)
+ errx (2, "%s: invalid MIB: %s", ctx->confname, name);
+ if (item->query_oid.len >= ASN_MAXOIDLEN)
+ errx (2, "request OID is too long");
- rb_messagex(LOG_DEBUG, "parsed MIB into oid: %s -> %s", path,
- asn_oid2str(&(ritem->snmpfield.var)));
+ log_debug ("parsed MIB into oid: %s -> %s", name,
+ asn_oid2str (&item->query_oid));
- /* And add it to the list */
- ritem->next = ctx->items;
- ctx->items = ritem;
+ item->query_match = value;
+ item->query_last = 0;
+ item->query_value = 0;
+}
- return ritem;
+static rb_item*
+parse_item (const char *field, char *uri, config_ctx *ctx)
+{
+ rb_item *item;
+ enum snmp_version version;
+ const char *msg;
+ char *copy;
+ char *scheme, *host, *user, *path, *query;
+
+ /* Parse the SNMP URI */
+ copy = strdup (uri);
+ msg = cfg_parse_uri (uri, &scheme, &host, &user, &path, &query);
+ if (msg)
+ errx(2, "%s: %s: %s", ctx->confname, msg, copy);
+ free (copy);
+
+ ASSERT (host && path);
+
+ /* Currently we only support SNMP pollers */
+ msg = cfg_parse_scheme (scheme, &version);
+ if (msg)
+ errx (2, "%s: %s: %s", ctx->confname, msg, scheme);
+
+ /* Make a new item */
+ item = (rb_item*)xcalloc (sizeof (*item));
+ item->field = field;
+ item->community = user ? user : "public";
+ item->version = version;
+
+ item->poller = NULL; /* Set later in config_done */
+ item->vtype = VALUE_UNSET;
+
+ /* Parse the hosts, query */
+ parse_hosts (item, host, ctx);
+ parse_query (item, query, ctx);
+
+ /* And parse the main field OID */
+ if (mib_parse (path, &(item->field_oid)) == -1)
+ errx (2, "%s: invalid MIB: %s", ctx->confname, path);
+ if (item->field_oid.len >= ASN_MAXOIDLEN)
+ errx (2, "request OID is too long");
+
+ log_debug ("parsed MIB into oid: %s -> %s", path,
+ asn_oid2str (&item->field_oid));
+
+ /* And add it to the list */
+ item->next = ctx->items;
+ ctx->items = item;
+
+ return item;
}
static void
@@ -387,11 +363,9 @@ void
rb_config_parse()
{
config_ctx ctx;
- rb_poller* poll;
/* Setup the hash tables properly */
g_state.poll_by_key = hsh_create();
- g_state.host_by_key = hsh_create();
memset(&ctx, 0, sizeof(ctx));
@@ -400,35 +374,6 @@ rb_config_parse()
if(!g_state.polls)
errx(1, "no config files found in config directory: %s", g_state.confdir);
-
- /* Organize the async resolve intervals */
- for(poll = g_state.polls; poll; poll = poll->next)
- {
- rb_item *item;
- mstime resint;
-
- /* When less than three minutes, resolve once per minute */
- if(poll->interval <= 180000)
- resint = 60000;
-
- /* When between 3 and 10 minutes resolve once per cycle */
- else if(poll->interval <= 600000)
- resint = poll->interval;
-
- /* Otherwise resolve thrice per cycle */
- else
- resint = poll->interval / 3;
-
- for(item = poll->items; item; item = item->next)
- {
- /* The lowest interval (since hosts can be shared by pollers) wins */
- if(!item->host->is_resolved && item->host->resolve_interval < resint)
- {
- rb_host* host = (rb_host*)item->host;
- host->resolve_interval = resint;
- }
- }
- }
}
/* -----------------------------------------------------------------------------
@@ -459,8 +404,7 @@ cfg_value(const char* filename, const char* header, const char* name,
ASSERT(ctx->confname);
ASSERT(name && value && header);
- rb_messagex(LOG_DEBUG, "config: %s: [%s] %s = %s",
- ctx->confname, header, name, value);
+ log_debug("config: %s: [%s] %s = %s", ctx->confname, header, name, value);
config_value(header, name, value, ctx);
@@ -491,17 +435,6 @@ free_items(rb_item* item)
}
static void
-free_hosts(rb_host* host)
-{
- rb_host* next;
- for(; host; host = next)
- {
- next = host->next;
- free(host);
- }
-}
-
-static void
free_pollers(rb_poller* poll)
{
rb_poller* next;
@@ -519,9 +452,6 @@ void
rb_config_free()
{
hsh_free(g_state.poll_by_key);
- hsh_free(g_state.host_by_key);
-
- free_hosts(g_state.hosts);
/* Note that rb_item's are owned by pollers */
free_pollers(g_state.polls);
diff --git a/daemon/poll-engine.c b/daemon/poll-engine.c
new file mode 100644
index 0000000..6130a89
--- /dev/null
+++ b/daemon/poll-engine.c
@@ -0,0 +1,454 @@
+/*
+ * Copyright (c) 2008, Stefan Walter
+ * 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
+ * Stef Walter <stef@memberwebs.com>
+ *
+ */
+
+#include "usuals.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <err.h>
+
+#include <bsnmp/asn1.h>
+#include <bsnmp/snmp.h>
+
+#include "log.h"
+#include "rrdbotd.h"
+#include "server-mainloop.h"
+#include "snmp-engine.h"
+
+/* -----------------------------------------------------------------------------
+ * PACKET HANDLING
+ */
+
+static void
+complete_request (rb_item *item, int code)
+{
+ int host;
+
+ ASSERT (item);
+
+ if (item->request)
+ snmp_engine_cancel (item->request);
+ item->request = 0;
+
+ /* If we have multiple host names then try the next host */
+ if (code != SNMP_ERR_NOERROR) {
+ host = (item->hostindex + 1) % item->n_hostnames;
+ if (host != item->hostindex) {
+ log_debug ("request failed, trying new host: %s", item->hostnames[host]);
+ item->hostindex = host;
+ }
+ }
+}
+
+static void
+cancel_request (rb_item *item, const char *reason)
+{
+ ASSERT (item);
+ ASSERT (reason);
+ ASSERT (item->request);
+
+ log_debug ("value for field '%s': %s", item->field, reason);
+ item->vtype = VALUE_UNSET;
+
+ complete_request (item, -1);
+}
+
+static void
+force_poll (rb_poller *poll, mstime when, const char *reason)
+{
+ rb_item *item;
+ int forced = 0;
+
+ ASSERT (poll);
+ ASSERT (reason);
+
+ /* Now see if the entire request is done */
+ for (item = poll->items; item; item = item->next) {
+ if (item->request) {
+ cancel_request (item, reason);
+ forced = 1;
+ }
+ ASSERT (!item->request);
+ }
+
+ if (forced) {
+
+ /*
+ * We note the failure has having taken place halfway between
+ * the request and the current time.
+ */
+ poll->last_polled = poll->last_request + ((when - poll->last_request) / 2);
+
+ /* And send off our collection of values */
+ rb_rrd_update (poll);
+ }
+}
+
+static void
+finish_poll (rb_poller *poll, mstime when)
+{
+ rb_item *item;
+
+ ASSERT (poll);
+
+ /* Now see if the entire request is done */
+ for (item = poll->items; item; item = item->next) {
+ if (item->request)
+ return;
+ }
+
+ /* Update the book-keeping */
+ poll->last_polled = when;
+
+ /* And send off our collection of values */
+ rb_rrd_update (poll);
+}
+
+static void
+field_response (int request, int code, struct snmp_value *value, void *arg)
+{
+ rb_item *item = arg;
+ const char *msg = NULL;
+
+ ASSERT (item->request == request);
+
+ /* Mark this item as done */
+ item->request = 0;
+
+ /* Errors result in us writing U */
+ if (code != SNMP_ERR_NOERROR) {
+ item->vtype = VALUE_UNSET;
+
+ /* Parse the value from server */
+ } else {
+ switch(value->syntax)
+ {
+ case SNMP_SYNTAX_NULL:
+ item->vtype = VALUE_UNSET;
+ break;
+ case SNMP_SYNTAX_INTEGER:
+ item->v.i_value = value->v.integer;
+ item->vtype = VALUE_REAL;
+ break;
+ case SNMP_SYNTAX_COUNTER:
+ case SNMP_SYNTAX_GAUGE:
+ case SNMP_SYNTAX_TIMETICKS:
+ item->v.i_value = value->v.uint32;
+ item->vtype = VALUE_REAL;
+ break;
+ case SNMP_SYNTAX_COUNTER64:
+ item->v.i_value = value->v.counter64;
+ item->vtype = VALUE_REAL;
+ 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 = "field not available on snmp server: %s";
+ break;
+ default:
+ msg = "snmp server returned invalid or unsupported value for field: %s";
+ break;
+ };
+
+ if (msg)
+ log_warnx (msg, item->field);
+ else if (item->vtype == VALUE_REAL)
+ log_debug ("got value for field '%s': %lld",
+ item->field, item->v.i_value);
+ else if (item->vtype == VALUE_FLOAT)
+ log_debug ("got value for field '%s': %.4lf",
+ item->field, item->v.f_value);
+ else
+ log_debug ("got value for field '%s': U",
+ item->field);
+ }
+
+ complete_request (item, code);
+
+ /* If the entire poll is done, then complete it */
+ finish_poll (item->poller, server_get_time ());
+}
+
+static void
+field_request (rb_item *item)
+{
+ int req;
+
+ ASSERT (item);
+ ASSERT (!item->request);
+
+ item->vtype = VALUE_UNSET;
+
+ req = snmp_engine_request (item->hostnames[item->hostindex], item->community,
+ item->version, item->poller->interval, item->poller->timeout,
+ SNMP_PDU_GET, &item->field_oid, field_response, item);
+ item->request = req;
+}
+
+/* Forward declaration */
+static void query_request (rb_item *item, int first);
+
+static void
+query_response (int request, int code, struct snmp_value *value, void *arg)
+{
+ rb_item *item = arg;
+ struct asn_oid oid;
+ int matched, req, found;
+
+ ASSERT (request == item->request);
+ ASSERT (item->has_query);
+
+ /*
+ * This was the number we last appended.
+ */
+ ASSERT (item->query_value >= 0);
+ item->request = 0;
+
+ /* Problems communicating with the server? */
+ if (code != SNMP_ERR_NOERROR && code != SNMP_ERR_NOSUCHNAME) {
+ complete_request (item, code);
+ return;
+ }
+
+ found = 0;
+ matched = 0;
+
+ if (code == SNMP_ERR_NOERROR) {
+ ASSERT (value);
+
+ /* These all signify 'not found' in our book */
+ switch (value->syntax) {
+ case SNMP_SYNTAX_NOSUCHOBJECT:
+ case SNMP_SYNTAX_NOSUCHINSTANCE:
+ case SNMP_SYNTAX_ENDOFMIBVIEW:
+ found = 0;
+ matched = 0;
+ break;
+
+ /* See if we have a match */
+ default:
+ if (item->query_match)
+ matched = snmp_engine_match (value, item->query_match);
+
+ /* When query match is null, anything matches */
+ else
+ matched = 1;
+
+ found = 1;
+ break;
+ };
+ }
+
+ /*
+ * When we had found this before, but then can no longer find it, we
+ * start search again from the base.
+ */
+ if (!matched && item->query_last != 0) {
+ item->query_last = 0;
+ query_request (item, 1);
+
+ /*
+ * When we find no value at zero, then we skip ahead and see if
+ * perhaps its a one based table
+ */
+ } else if (!found && item->query_value == 0) {
+ item->query_last = 0;
+ query_request (item, 0);
+
+ /*
+ * Any other time we don't find a value, its game over for us,
+ * we didn't find a match and are out of values.
+ */
+ } else if (!found) {
+ item->query_last = 0;
+ log_warn ("couldn't find match for query value: %s", item->query_match);
+ complete_request (item, SNMP_ERR_NOSUCHNAME);
+
+
+ /*
+ * Found a value but didn't match, so try next one.
+ */
+ } else if (!matched) {
+ item->query_last = 0;
+ query_request (item, 0);
+
+ /*
+ * When we have a match send off a new request, built from the original
+ * oid and the last numeric part of the query oid.
+ */
+ } else {
+
+ /* Build up the OID */
+ oid = item->field_oid;
+ ASSERT (oid.len < ASN_MAXOIDLEN);
+ oid.subs[oid.len] = item->query_value;
+ ++oid.len;
+
+ item->query_last = item->query_value;
+ item->vtype = VALUE_UNSET;
+
+ req = snmp_engine_request (item->hostnames[item->hostindex], item->community,
+ item->version, item->poller->interval, item->poller->timeout,
+ SNMP_PDU_GET, &oid, field_response, item);
+
+ item->request = req;
+ }
+}
+
+static void
+query_request (rb_item *item, int first)
+{
+ struct asn_oid oid;
+ int req;
+
+ ASSERT (item);
+ ASSERT (!item->request);
+ ASSERT (item->has_query);
+
+ item->vtype = VALUE_UNSET;
+
+ /*
+ * Build up an appropriate oid.
+ *
+ * We first try any oid that worked last time, and see if
+ * it still has the same value, to avoid doing the brute
+ * force search each time needlessly.
+ */
+
+ /* The first time the request has been called */
+ if (first)
+ item->query_value = item->query_last;
+
+ /* Try the next one in turn */
+ else
+ item->query_value = item->query_value + 1;
+
+ /* Build up the OID */
+ oid = item->query_oid;
+ ASSERT (oid.len < ASN_MAXOIDLEN);
+ oid.subs[oid.len] = item->query_value;
+ ++oid.len;
+
+ /* Make the request */
+ req = snmp_engine_request (item->hostnames[item->hostindex], item->community,
+ item->version, item->poller->interval, item->poller->timeout,
+ SNMP_PDU_GET, &oid, query_response, item);
+
+ /* Mark item as active by this request */
+ item->request = req;
+}
+
+static int
+poller_timer (mstime when, void *arg)
+{
+ rb_poller *poll = (rb_poller*)arg;
+ rb_item *item;
+
+ /*
+ * If the previous poll has not completed, then we count it
+ * as a timeout.
+ */
+ force_poll (poll, when, "timed out");
+
+ /* Mark this poller as starting requests now */
+ poll->last_request = when;
+
+ /*
+ * Send off the next query. This needs to be done after
+ * all the timeouts above, as the above could write to RRD.
+ */
+ for (item = poll->items; item; item = item->next) {
+ if (item->has_query)
+ query_request (item, 1);
+ else
+ field_request (item);
+ }
+
+ snmp_engine_flush ();
+
+ 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)
+ log_error ("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_poll_engine_init (void)
+{
+ /* Start the preparation timers for setting up randomly */
+ if (server_oneshot (100, prep_timer, g_state.polls) == -1)
+ err(1, "couldn't setup timer");
+}
+
+void
+rb_poll_engine_uninit (void)
+{
+
+}
diff --git a/daemon/rrd-update.c b/daemon/rrd-update.c
index 7b43430..74e471b 100644
--- a/daemon/rrd-update.c
+++ b/daemon/rrd-update.c
@@ -45,6 +45,7 @@
#include <rrd.h>
+#include "log.h"
#include "rrdbotd.h"
#define MAX_NUMLEN 40
@@ -66,7 +67,7 @@ void rb_rrd_update(rb_poller *poll)
for(it = poll->items; it; it = it->next)
{
- tlen += strlen(it->rrdfield) + 1;
+ tlen += strlen(it->field) + 1;
ilen += 40;
}
@@ -79,7 +80,7 @@ void rb_rrd_update(rb_poller *poll)
free(items);
if(template)
free(template);
- rb_messagex(LOG_CRIT, "out of memory");
+ log_errorx ("out of memory");
return;
}
@@ -95,7 +96,7 @@ void rb_rrd_update(rb_poller *poll)
strlcat(items, ":", ilen);
}
- strlcat(template, it->rrdfield, tlen);
+ strlcat(template, it->field, tlen);
if(it->vtype == VALUE_UNSET)
strlcat(items, "U", ilen);
@@ -120,15 +121,15 @@ void rb_rrd_update(rb_poller *poll)
argv[3] = template;
argv[4] = items;
- rb_messagex(LOG_DEBUG, "updating RRD file: %s", poll->rrdname);
- rb_messagex(LOG_DEBUG, "> template: %s", template);
- rb_messagex(LOG_DEBUG, "> values: %s", items);
+ log_debug ("updating RRD file: %s", poll->rrdname);
+ log_debug ("> template: %s", template);
+ log_debug ("> values: %s", items);
rrd_clear_error();
r = rrd_update(5, (char**)argv);
if(r != 0)
- rb_messagex(LOG_ERR, "couldn't update rrd file: %s: %s",
+ log_errorx ("couldn't update rrd file: %s: %s",
poll->rrdname, rrd_get_error());
free(template);
diff --git a/daemon/rrdbotd.c b/daemon/rrdbotd.c
index 5b447a3..bbbc84b 100644
--- a/daemon/rrdbotd.c
+++ b/daemon/rrdbotd.c
@@ -37,6 +37,13 @@
*/
#include "usuals.h"
+
+#include "async-resolver.h"
+#include "log.h"
+#include "rrdbotd.h"
+#include "server-mainloop.h"
+#include "snmp-engine.h"
+
#include <errno.h>
#include <unistd.h>
#include <stdarg.h>
@@ -48,9 +55,6 @@
#include <bsnmp/snmp.h>
#include <mib/mib-parser.h>
-#include "rrdbotd.h"
-#include "server-mainloop.h"
-#include "async-resolver.h"
/* The default command line options */
#define DEFAULT_CONFIG CONF_PREFIX "/rrdbot"
@@ -105,11 +109,10 @@ test(int argc, char* argv[])
*/
void
-rb_vmessage(int level, int err, const char* msg, va_list ap)
+log_vmessage(int level, int erno, const char* msg, va_list ap)
{
#define MAX_MSGLEN 1024
char buf[MAX_MSGLEN];
- int e = errno;
if(daemonized) {
if (level >= LOG_DEBUG)
@@ -125,10 +128,10 @@ rb_vmessage(int level, int err, const char* msg, va_list ap)
strlcpy(buf, msg, MAX_MSGLEN);
stretrim(buf);
- if(err)
+ if(erno)
{
strlcat(buf, ": ", MAX_MSGLEN);
- strncat(buf, strerror(e), MAX_MSGLEN);
+ strncat(buf, strerror(erno), MAX_MSGLEN);
}
/* As a precaution */
@@ -141,24 +144,6 @@ rb_vmessage(int level, int err, const char* msg, va_list ap)
vwarnx(buf, ap);
}
-void
-rb_messagex(int level, const char* msg, ...)
-{
- va_list ap;
- va_start(ap, msg);
- rb_vmessage(level, 0, msg, ap);
- va_end(ap);
-}
-
-void
-rb_message(int level, const char* msg, ...)
-{
- va_list ap;
- va_start(ap, msg);
- rb_vmessage(level, 1, msg, ap);
- va_end(ap);
-}
-
/* -----------------------------------------------------------------------------
* STARTUP
*/
@@ -195,13 +180,14 @@ writepid(const char* pidfile)
FILE* f = fopen(pidfile, "w");
if(f == NULL)
{
- rb_message(LOG_WARNING, "couldn't open pid file: %s", pidfile);
+ log_warn("couldn't open pid file: %s", pidfile);
return;
}
fprintf(f, "%d\n", (int)getpid());
if(ferror(f))
- rb_message(LOG_WARNING, "couldn't write to pid file: %s", pidfile);
+ log_warn("couldn't write to pid file: %s", pidfile);
+
fclose(f);
}
@@ -211,7 +197,7 @@ removepid(const char* pidfile)
if(unlink(pidfile) < 0)
{
if(errno != ENOENT)
- rb_message(LOG_WARNING, "couldn't remove pid file: %s", pidfile);
+ log_warn("couldn't remove pid file: %s", pidfile);
}
}
@@ -319,7 +305,8 @@ main(int argc, char* argv[])
mib_uninit();
/* Rev up the main engine */
- rb_snmp_engine_init();
+ snmp_engine_init(3);
+ rb_poll_engine_init();
if(daemonize)
{
@@ -327,14 +314,14 @@ main(int argc, char* argv[])
if(daemon(0, 0) == -1)
err(1, "couldn't fork as a daemon");
- rb_messagex(LOG_DEBUG, "running as a daemon");
+ log_debug("running as a daemon");
daemonized = 1;
}
/* Setup the Async DNS resolver */
if(async_resolver_init() < 0)
{
- rb_message(LOG_ERR, "couldn't initialize resolver");
+ log_error("couldn't initialize resolver");
/* Allow things to proceed without resolver */
}
@@ -352,16 +339,17 @@ main(int argc, char* argv[])
if(pidfile != NULL)
writepid(pidfile);
- rb_messagex(LOG_INFO, "rrdbotd version " VERSION " started up");
+ log_info("rrdbotd version " VERSION " started up");
/* Now let it go */
if(server_run() == -1)
err(1, "critical failure running SNMP engine");
- rb_messagex(LOG_INFO, "rrdbotd stopping");
+ log_info("rrdbotd stopping");
/* Cleanups */
- rb_snmp_engine_uninit();
+ rb_poll_engine_uninit();
+ snmp_engine_stop();
rb_config_free();
async_resolver_uninit();
server_uninit();
diff --git a/daemon/rrdbotd.h b/daemon/rrdbotd.h
index cfc8cb2..f907de2 100644
--- a/daemon/rrdbotd.h
+++ b/daemon/rrdbotd.h
@@ -55,8 +55,6 @@ typedef uint64_t mstime;
struct _rb_item;
struct _rb_poller;
-struct _rb_host;
-struct _rb_request;
/*
* Note that all the members are either in the config memory
@@ -65,9 +63,28 @@ struct _rb_request;
typedef struct _rb_item
{
- /* Specific to this item */
- const char* rrdfield;
- struct snmp_value snmpfield;
+ /* The field name, RRD and display */
+ const char* field;
+
+ /* Connection information */
+ const char* community;
+ int version;
+
+ /* The oid that we are querying */
+ struct asn_oid field_oid;
+
+ /* Host names, with alternate hosts */
+ #define MAX_HOSTNAMES 16
+ const char* hostnames[MAX_HOSTNAMES];
+ int hostindex;
+ int n_hostnames;
+
+ /* Query related stuff */
+ int has_query;
+ struct asn_oid query_oid;
+ const char* query_match;
+ asn_subid_t query_last;
+ int query_value;
/* The last value / current request */
union
@@ -81,38 +98,17 @@ typedef struct _rb_item
#define VALUE_FLOAT 2
int vtype;
- struct _rb_request* req;
+ /* A request in progress */
+ int request;
/* Pointers to related */
- const struct _rb_poller* poller;
- const struct _rb_host* host;
+ struct _rb_poller* poller;
/* Next in list of items */
struct _rb_item* next;
}
rb_item;
-typedef struct _rb_host
-{
- /* The hash key is version:hostname:community */
- char key[128];
-
- const char* hostname;
- const char* community;
- int version;
-
- /* Host resolving and book keeping */
- struct sockaddr_any address;
- mstime resolve_interval;
- mstime last_resolve_try;
- mstime last_resolved;
- int is_resolved;
-
- /* Next in list of hosts */
- struct _rb_host* next;
-}
-rb_host;
-
typedef struct _rb_poller
{
/* The hash key is interval-timeout:rrdname */
@@ -128,6 +124,7 @@ typedef struct _rb_poller
rb_item* items;
/* Book keeping */
+ mstime last_request;
mstime last_polled;
/* Next in list of pollers */
@@ -145,11 +142,9 @@ typedef struct _rb_state
/* All the pollers/hosts */
rb_poller* polls;
- rb_host* hosts;
/* Quick lookups for responses */
hsh_t* poll_by_key;
- hsh_t* host_by_key;
}
rb_state;
@@ -157,17 +152,6 @@ rb_state;
extern rb_state g_state;
/* -----------------------------------------------------------------------------
- * UTILITIES (rrdbotd.c)
- */
-
-typedef void (*resolve_callback)(void *context, int unused, const char *name,
- const unsigned char *addr, size_t addrlen);
-
-void rb_messagex(int level, const char* msg, ...);
-void rb_message(int level, const char* msg, ...);
-void rb_vmessage(int level, int err, const char* msg, va_list ap);
-
-/* -----------------------------------------------------------------------------
* CONFIG (config.c)
*/
@@ -178,8 +162,8 @@ void rb_config_free();
* SNMP ENGINE (snmp-engine.c)
*/
-void rb_snmp_engine_init();
-void rb_snmp_engine_uninit();
+void rb_poll_engine_init();
+void rb_poll_engine_uninit();
/* -----------------------------------------------------------------------------
* RRD UPDATE CODE (rrd-update.c)
diff --git a/daemon/snmp-engine.c b/daemon/snmp-engine.c
deleted file mode 100644
index e79c6c2..0000000
--- a/daemon/snmp-engine.c
+++ /dev/null
@@ -1,727 +0,0 @@
-/*
- * Copyright (c) 2005, Stefan Walter
- * 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
- * Stef Walter <stef@memberwebs.com>
- *
- */
-
-#include "usuals.h"
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <errno.h>
-#include <unistd.h>
-#include <syslog.h>
-#include <err.h>
-#include <arpa/inet.h>
-
-#include <bsnmp/asn1.h>
-#include <bsnmp/snmp.h>
-
-#include "rrdbotd.h"
-#include "server-mainloop.h"
-#include "async-resolver.h"
-
-/* The socket to use */
-static int snmp_socket = -1;
-
-/* The last request id */
-static uint32_t snmp_request = 100000;
-
-/* Since we only deal with one packet at a time, global buffer */
-static unsigned char snmp_buffer[0x1000];
-
-/* -----------------------------------------------------------------------------
- * REQUESTS
- */
-
-typedef struct _rb_request
-{
- /* The SNMP request identifier */
- uint32_t id;
-
- mstime next_retry; /* Time of the next retry */
- mstime last_sent; /* Time last sent */
- 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* arequests;
- rb_request* req = NULL;
- uint num;
- int i, overlap = 0;
-
- if(nrequests)
- {
- /* We allocate in a loop starting after the last allocation. */
- for(i = reqhigh; !overlap || 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;
- arequests = (rb_request*)realloc(requests, sizeof(rb_request) * num);
- if(!arequests)
- {
- /* Note we leave old requests allocated */
- errno = ENOMEM;
- return NULL;
- }
-
- /* Clear out the new ones */
- requests = arequests;
- 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;
-
- /* 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;
-
- /* Update our bookkeeping */
- req->sent++;
- if(req->sent <= g_state.retries)
- req->next_retry = when + req->interval;
- else
- req->next_retry = 0;
- req->last_sent = when;
-
- /* No sending if no address */
- if(!req->host->is_resolved)
- {
- if(req->sent <= 1)
- rb_messagex(LOG_DEBUG, "skipping snmp request: host not resolved: %s",
- req->host->hostname);
- return;
- }
-
- 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->hostname);
- else
- rb_messagex(LOG_DEBUG, "sent request #%d to: %s", req->id, req->host->hostname);
- }
-}
-
-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->vtype = VALUE_UNSET;
- it->req = NULL;
- }
-
- else if(it->req)
- incomplete = 1;
- }
-
- /* For timeouts we use the time the last request was sent */
- when = req->last_sent;
-
- 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 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->vtype = VALUE_UNSET;
- break;
- case SNMP_SYNTAX_INTEGER:
- item->v.i_value = value->v.integer;
- item->vtype = VALUE_REAL;
- break;
- case SNMP_SYNTAX_COUNTER:
- case SNMP_SYNTAX_GAUGE:
- case SNMP_SYNTAX_TIMETICKS:
- item->v.i_value = value->v.uint32;
- item->vtype = VALUE_REAL;
- break;
- case SNMP_SYNTAX_COUNTER64:
- item->v.i_value = value->v.counter64;
- item->vtype = VALUE_REAL;
- 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 = "field not available on snmp server: %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 if(item->vtype == VALUE_REAL)
- rb_messagex(LOG_DEBUG, "got value for field '%s': %lld",
- item->rrdfield, item->v.i_value);
- else if(item->vtype == VALUE_FLOAT)
- rb_messagex(LOG_DEBUG, "got value for field '%s': %.4lf",
- item->rrdfield, item->v.f_value);
- else
- rb_messagex(LOG_DEBUG, "got value for field '%s': U",
- item->rrdfield);
-
- /* 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->vtype = VALUE_UNSET;
- }
-
- 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;
- const char* msg;
- 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]");
-
- /* 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;
- }
-
- /* It needs to match something we're waiting for */
- req = find_req(pdu.request_id);
- if(!req)
- {
- rb_messagex(LOG_DEBUG, "received extra or delayed packet from: %s", hostname);
- return;
- }
-
- /* Check for errors */
- if(pdu.error_status != SNMP_ERR_NOERROR)
- {
- msg = snmp_get_errmsg (pdu.error_status);
- if(msg)
- rb_messagex(LOG_ERR, "snmp error from host '%s': %s",
- hostname, msg);
- else
- rb_messagex(LOG_ERR, "unknown snmp error from host '%s': %d",
- hostname, pdu.error_status);
- return;
- }
-
- if(pdu.version != req->pdu.version)
- rb_message(LOG_WARNING, "wrong version snmp packet from: %s", hostname);
-
- /* Dispatch the packet */
- 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, first;
-
- /* Search backwards through the scrolling window */
- for(i = reqhigh, first = 1; first || i != reqlow;
- i = (i ? i : nrequests) - 1, first = 0)
- {
- 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;
-}
-
-static void
-resolve_cb(int ecode, struct addrinfo* ai, void* arg)
-{
- rb_host* host = (rb_host*)arg;
-
- if(ecode)
- {
- rb_messagex(LOG_WARNING, "couldn't resolve hostname: %s: %s", host->hostname,
- gai_strerror(ecode));
- return;
- }
-
- /* A successful resolve */
- memcpy(&SANY_ADDR(host->address), ai->ai_addr, ai->ai_addrlen);
- SANY_LEN(host->address) = ai->ai_addrlen;
- host->last_resolved = server_get_time();
- host->is_resolved = 1;
-
- rb_messagex(LOG_DEBUG, "resolved host: %s", host->hostname);
-}
-
-static int
-resolve_timer(mstime when, void* arg)
-{
- rb_host* host;
- struct addrinfo hints;
-
- /* Go through hosts and see which ones need resolving */
- for(host = g_state.hosts; host; host = host->next)
- {
- /* No need to resolve? */
- if(!host->resolve_interval)
- continue;
-
- if(when - host->resolve_interval > host->last_resolve_try)
- {
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = PF_UNSPEC;
- hints.ai_socktype = SOCK_DGRAM;
-
- /* Automatically strips port number */
- rb_messagex(LOG_DEBUG, "resolving host: %s", host->hostname);
- async_resolver_queue(host->hostname, "161", &hints, resolve_cb, host);
- host->last_resolve_try = when;
- }
-
- /* When the last 3 resolves have failed, set to unresolved */
- if(when - (host->resolve_interval * 3) > host->last_resolved)
- host->is_resolved = 0;
- }
-
- return 1;
-}
-
-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");
-
- /* resolve timer goes once per second */
- if(server_timer(1000, resolve_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;
- }
-}