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; -    } -} | 
