diff options
author | Stef Walter <stef@memberwebs.com> | 2008-03-02 01:25:00 +0000 |
---|---|---|
committer | Stef Walter <stef@memberwebs.com> | 2008-03-02 01:25:00 +0000 |
commit | 9a78f86f773cbf34e29ec51fc06e3f04072c88d0 (patch) | |
tree | 00054e6e536769a35b4215567755494486cc36ec /daemon | |
parent | ec1a79b0f75cfd34085e046ecb30382a402ea318 (diff) |
- Support failover between multiple agents
- Support table queries
- Major refactoring of internals.
Diffstat (limited to 'daemon')
-rw-r--r-- | daemon/Makefile.am | 22 | ||||
-rw-r--r-- | daemon/config.c | 290 | ||||
-rw-r--r-- | daemon/poll-engine.c | 454 | ||||
-rw-r--r-- | daemon/rrd-update.c | 15 | ||||
-rw-r--r-- | daemon/rrdbotd.c | 56 | ||||
-rw-r--r-- | daemon/rrdbotd.h | 72 | ||||
-rw-r--r-- | daemon/snmp-engine.c | 727 |
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; - } -} |