diff options
| -rw-r--r-- | ChangeLog | 5 | ||||
| -rw-r--r-- | Makefile.am | 4 | ||||
| -rw-r--r-- | bsnmp/snmp.c | 54 | ||||
| -rw-r--r-- | bsnmp/snmp.h | 5 | ||||
| -rw-r--r-- | common/Makefile.am | 16 | ||||
| -rw-r--r-- | common/compat.c | 11 | ||||
| -rw-r--r-- | common/config-parser.c | 85 | ||||
| -rw-r--r-- | common/config-parser.h | 8 | ||||
| -rw-r--r-- | common/hash.c | 19 | ||||
| -rw-r--r-- | common/hash.h | 5 | ||||
| -rw-r--r-- | common/log.c | 59 | ||||
| -rw-r--r-- | common/log.h | 58 | ||||
| -rw-r--r-- | common/server-mainloop.c | 2 | ||||
| -rw-r--r-- | common/snmp-engine.c | 1011 | ||||
| -rw-r--r-- | common/snmp-engine.h | 27 | ||||
| -rw-r--r-- | configure.in | 1 | ||||
| -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 | ||||
| -rw-r--r-- | doc/rrdbot-get.1 | 28 | ||||
| -rw-r--r-- | doc/rrdbot.conf.5 | 37 | ||||
| -rw-r--r-- | doc/rrdbotd.8 | 9 | ||||
| -rw-r--r-- | doc/traffic-example.conf | 6 | ||||
| -rw-r--r-- | tools/Makefile.am | 23 | ||||
| -rw-r--r-- | tools/rrdbot-get.c | 707 | 
29 files changed, 2449 insertions, 1367 deletions
| @@ -1,3 +1,8 @@ +0.9 ????? +    - Support failover between multiple agents +    - Support table queries +    - Major refactoring of internals.  +      0.8  [2007-07-16]      - Fix assertion when reallocating SNMP request memory diff --git a/Makefile.am b/Makefile.am index c3ce3c4..969582d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ -EXTRA_DIST = common mib acsite.m4 -SUBDIRS = bsnmp daemon tools mibs doc +EXTRA_DIST = mib acsite.m4 +SUBDIRS = bsnmp common daemon tools mibs doc  # Clean up any EXTRA_DIST we're distributing  dist-hook: diff --git a/bsnmp/snmp.c b/bsnmp/snmp.c index 6b2cc01..1a7fe46 100644 --- a/bsnmp/snmp.c +++ b/bsnmp/snmp.c @@ -408,7 +408,7 @@ snmp_pdu_decode(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *ip)  	  case ASN_ERR_FAILED:  	  case ASN_ERR_EOBUF: -		snmp_pdu_free(pdu); +		snmp_pdu_clear(pdu);  		return (SNMP_CODE_FAILED);  	  case ASN_ERR_BADLEN: @@ -805,7 +805,7 @@ snmp_pdu_dump(const struct snmp_pdu *pdu)  }  void -snmp_value_free(struct snmp_value *value) +snmp_value_clear(struct snmp_value *value)  {  	if (value->syntax == SNMP_SYNTAX_OCTETSTRING)  		free(value->v.octetstring.octets); @@ -834,12 +834,58 @@ snmp_value_copy(struct snmp_value *to, const struct snmp_value *from)  }  void -snmp_pdu_free(struct snmp_pdu *pdu) +snmp_pdu_clear(struct snmp_pdu *pdu)  {  	u_int i;  	for (i = 0; i < pdu->nbindings; i++) -		snmp_value_free(&pdu->bindings[i]); +		snmp_value_clear(&pdu->bindings[i]); +} + +int +snmp_value_equal(const struct snmp_value *a, const struct snmp_value *b) +{ +	if (asn_compare_oid (&a->var, &b->var) == 0) +		return 0; +	if (a->syntax != b->syntax) +		return 0; + +	switch (a->syntax) { +	case SNMP_SYNTAX_NULL: +	case SNMP_SYNTAX_NOSUCHOBJECT: +	case SNMP_SYNTAX_NOSUCHINSTANCE: +	case SNMP_SYNTAX_ENDOFMIBVIEW: +		return 1; + +	case SNMP_SYNTAX_INTEGER: +		return a->v.integer == b->v.integer; + +	case SNMP_SYNTAX_OCTETSTRING: +		if (a->v.octetstring.len != b->v.octetstring.len) +			return 0; +		return memcmp (a->v.octetstring.octets, b->v.octetstring.octets, +		               a->v.octetstring.len) == 0; + +	case SNMP_SYNTAX_OID: +		if (a->v.oid.len != b->v.oid.len) +			return 0; +		return memcmp (&a->v.oid.subs, &b->v.oid.subs, +		               (b->v.oid.len * sizeof (b->v.oid.subs[0]))) == 0; + +	case SNMP_SYNTAX_IPADDRESS: +		return memcmp (&a->v.ipaddress, &b->v.ipaddress, sizeof (a->v.ipaddress)); + +	case SNMP_SYNTAX_COUNTER: +	case SNMP_SYNTAX_GAUGE: +	case SNMP_SYNTAX_TIMETICKS: +		return a->v.uint32 == b->v.uint32; + +	case SNMP_SYNTAX_COUNTER64: +		return a->v.counter64 == b->v.counter64; + +	default: +		return 0; +	};  }  /* diff --git a/bsnmp/snmp.h b/bsnmp/snmp.h index b708b3a..21390e4 100644 --- a/bsnmp/snmp.h +++ b/bsnmp/snmp.h @@ -152,11 +152,12 @@ enum snmp_code {  	SNMP_CODE_OORANGE,  }; -void snmp_value_free(struct snmp_value *); +void snmp_value_clear(struct snmp_value *);  int snmp_value_parse(const char *, enum snmp_syntax, union snmp_values *);  int snmp_value_copy(struct snmp_value *, const struct snmp_value *); +int snmp_value_equal(const struct snmp_value *, const struct snmp_value *); -void snmp_pdu_free(struct snmp_pdu *); +void snmp_pdu_clear(struct snmp_pdu *);  enum snmp_code snmp_pdu_decode(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *);  enum snmp_code snmp_pdu_encode(struct snmp_pdu *pdu, struct asn_buf *resp_b); diff --git a/common/Makefile.am b/common/Makefile.am new file mode 100644 index 0000000..61ef9a4 --- /dev/null +++ b/common/Makefile.am @@ -0,0 +1,16 @@ + +noinst_LIBRARIES = libcommon.a + +libcommon_a_SOURCES = \ +	async-resolver.h async-resolver.c \ +	config-parser.h config-parser.c \ +	compat.h compat.c \ +	hash.h hash.c \ +	log.h log.c \ +	server-mainloop.c server-mainloop.h \ +	sock-any.h sock-any.c \ +	snmp-engine.h snmp-engine.c \ +	usuals.h + +libcommon_a_CFLAGS = -I${top_srcdir}/common/ -I${top_srcdir}/bsnmp/ -I${top_srcdir} + diff --git a/common/compat.c b/common/compat.c index 6f9c58d..53de9bf 100644 --- a/common/compat.c +++ b/common/compat.c @@ -136,15 +136,18 @@ strtob(const char* str)  size_t  strlcpy(char *dst, const char *src, size_t len)  { -        size_t ret = strlen(dst); +        size_t ret = strlen(src); +        size_t copied; -        while (len > 1) { +        while (ret > 0 && len > 1) {                  *dst++ = *src++; -                len--; +                --len; +                --ret; +                ++copied;          }          if (len > 0)                  *dst = '\0'; -        return (ret); +        return copied;  }  #endif /* HAVE_STRLCPY */ diff --git a/common/config-parser.c b/common/config-parser.c index cf1599d..f95d44e 100644 --- a/common/config-parser.c +++ b/common/config-parser.c @@ -36,6 +36,10 @@   */  #include "usuals.h" + +#include "config-parser.h" + +#include <ctype.h>  #include <errno.h>  #include <unistd.h>  #include <syslog.h> @@ -46,8 +50,6 @@  #include <bsnmp/asn1.h>  #include <bsnmp/snmp.h> -#include "config-parser.h" -  static void  errmsg(const char* filename, void* data, const char* msg, ...)  { @@ -346,7 +348,8 @@ cfg_parse_dir(const char* dirname, void* data)  }  const char* -cfg_parse_uri (char *uri, char** scheme, char** host, char** user, char** path) +cfg_parse_uri (char *uri, char** scheme, char** host, char** user, +               char** path, char** query)  {      char* t; @@ -354,6 +357,7 @@ cfg_parse_uri (char *uri, char** scheme, char** host, char** user, char** path)      *host = NULL;      *user = NULL;      *path = NULL; +    *query = NULL;      *scheme = strsep(&uri, ":");      if(uri == NULL || (uri[0] != '/' && uri[1] != '/')) @@ -384,6 +388,13 @@ cfg_parse_uri (char *uri, char** scheme, char** host, char** user, char** path)      while((*path)[0] == '/')          (*path)++; +    *query = strchr(*path, '?'); +    if(*query) +    { +        *(*query) = 0; +        (*query)++; +    } +      return NULL;  } @@ -407,3 +418,71 @@ cfg_parse_scheme(const char *str, enum snmp_version *scheme)      return NULL;  } +const char* +cfg_parse_query (char *query, char **name, char **value, char **remainder) +{ +	const char *msg; +	char *x; + +	*remainder = NULL; + +	if (*query == '&' || *query == '?') +		query++; + +	/* Only use the first argument */ +	x = strchr (query, '&'); +	if (x) { +		*x = 0; +		*remainder = x + 1; +	} + +	/* Parse into argument and value */ +	*value = strchr (query, '='); +	if (*value) { +		*(*value) = 0; +		(*value)++; +		msg = cfg_parse_url_decode (*value); +		if (msg) +			return msg; +	} + +	*name = query; +	return NULL; +} + +const static char HEX_CHARS[] = "0123456789abcdef"; + +const char* +cfg_parse_url_decode (char *value) +{ +	char *p, *a, *b; + +	/* Change all plusses to spaces */ +	for (p = strchr (value, '+'); p; p = strchr (p, '+')) +		*p = ' '; + +	/* Now loop through looking for escapes */ +	p = value; +	while (*value) { +		/* +		 * A percent sign followed by two hex digits means +		 * that the digits represent an escaped character. +		 */ +		if (*value == '%') { +			value++; +			a = strchr (HEX_CHARS, tolower(value[0])); +			b = strchr (HEX_CHARS, tolower(value[1])); +			if (a && b) { +				*p = (a - HEX_CHARS) << 4; +				*(p++) |= (b - HEX_CHARS); +				value += 2; +				continue; +			} +		} + +		*(p++) = *(value++); +	} + +	*p = 0; +	return NULL; +} diff --git a/common/config-parser.h b/common/config-parser.h index 19a300a..c804874 100644 --- a/common/config-parser.h +++ b/common/config-parser.h @@ -51,9 +51,15 @@ int cfg_parse_dir(const char* dirname, void* data);  int cfg_parse_file(const char* filename, void* data, char** memory);  /* A helper for parsing URIs */ -const char* cfg_parse_uri (char *uri, char** scheme, char** host, char** user, char** path); +const char* cfg_parse_uri (char *uri, char** scheme, char** host, char** user, char** path, char** query);  /* Parsing snmp, snmp2 snmp2c etc... */  const char* cfg_parse_scheme (const char *input, enum snmp_version *scheme); +/* Parse query=xxxx arguments */ +const char* cfg_parse_query (char *query, char **name, char **value, char **remainder); + +/* A helper for URL decoding URI values */ +const char* cfg_parse_url_decode (char *value); +  #endif /* __CONFIG_PARSER_H__ */ diff --git a/common/hash.c b/common/hash.c index 9acd2df..512a914 100644 --- a/common/hash.c +++ b/common/hash.c @@ -374,6 +374,25 @@ void* hsh_rem(hsh_t* ht, const void* key, size_t klen)      return val;  } +void hsh_clear(hsh_t* ht) +{ +	hsh_entry_t *he, *next; +	int i; + +	/* Free all entries in the array */ +	for (i = 0; i < ht->max; ++i) { +		he = ht->array[i]; +		while (he) { +			next = he->next; +			free (he); +			he = next; +		} +	} + +	memset (ht->array, 0, ht->max * sizeof (hsh_entry_t*)); +	ht->count = 0; +} +  unsigned int hsh_count(hsh_t* ht)  {      return ht->count; diff --git a/common/hash.h b/common/hash.h index c046b38..30153f6 100644 --- a/common/hash.h +++ b/common/hash.h @@ -143,6 +143,11 @@ hsh_index_t* hsh_next(hsh_index_t* hi);  void* hsh_this(hsh_index_t* hi, const void** key, size_t* klen);  /* + * hsh_clear: Clear all values from has htable. + */ +void hsh_clear(hsh_t* ht); + +/*   * This can be passed as 'klen' in any of the above functions to indicate   * a string-valued key, and have hash compute the length automatically.   */ diff --git a/common/log.c b/common/log.c new file mode 100644 index 0000000..f6a4338 --- /dev/null +++ b/common/log.c @@ -0,0 +1,59 @@ + +#include "log.h" + +#include <errno.h> +#include <syslog.h> + +void +log_error (const char *msg, ...) +{ +	va_list va; +	va_start (va, msg); +	log_vmessage (LOG_ERR, errno, msg, va); +	va_end (va); +} + +void +log_errorx (const char *msg, ...) +{ +	va_list va; +	va_start (va, msg); +	log_vmessage (LOG_ERR, 0, msg, va); +	va_end (va); +} + +void +log_warn (const char *msg, ...) +{ +	va_list va; +	va_start (va, msg); +	log_vmessage (LOG_WARNING, errno, msg, va); +	va_end (va); +} + +void +log_warnx (const char *msg, ...) +{ +	va_list va; +	va_start (va, msg); +	log_vmessage (LOG_WARNING, 0, msg, va); +	va_end (va); +} + +void +log_debug (const char *msg, ...) +{ +	va_list va; +	va_start (va, msg); +	log_vmessage (LOG_DEBUG, 0, msg, va); +	va_end (va); +} + +void +log_info (const char *msg, ...) +{ +	va_list va; +	va_start (va, msg); +	log_vmessage (LOG_INFO, 0, msg, va); +	va_end (va); +} diff --git a/common/log.h b/common/log.h new file mode 100644 index 0000000..f399328 --- /dev/null +++ b/common/log.h @@ -0,0 +1,58 @@ +/* + * 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> + * + */ + +#ifndef LOG_H_ +#define LOG_H_ + +#include <stdarg.h> + +void log_error  (const char *msg, ...); + +void log_errorx (const char *msg, ...); + +void log_warn   (const char *msg, ...); + +void log_warnx  (const char *msg, ...); + +void log_debug  (const char *msg, ...); + +void log_info   (const char *msg, ...); + +void log_vmessage (int level, int erno, const char *msg, va_list va); + +#endif /*LOG_H_*/ diff --git a/common/server-mainloop.c b/common/server-mainloop.c index d569d61..c6d2018 100644 --- a/common/server-mainloop.c +++ b/common/server-mainloop.c @@ -138,7 +138,7 @@ add_timer(int ms, int oneshot, server_timer_callback callback, void* arg)      struct timeval interval;      timer_callback* cb; -    ASSERT(ms > 0); +    ASSERT (ms || oneshot);      ASSERT(callback != NULL);      interval.tv_sec = ms / 1000; diff --git a/common/snmp-engine.c b/common/snmp-engine.c new file mode 100644 index 0000000..35fae26 --- /dev/null +++ b/common/snmp-engine.c @@ -0,0 +1,1011 @@ +/* + * 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 "async-resolver.h" +#include "hash.h" +#include "log.h" +#include "server-mainloop.h" +#include "snmp-engine.h" +#include "sock-any.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 <mib/mib-parser.h> + +struct host; +struct request; + +typedef uint64_t mstime; + +/* ------------------------------------------------------------------------------ + * HOSTS + */ + +struct host { +	/* The hash key is hostname:options:community */ +	char key[128]; + +	char *hostname; +	char *community; +	int version; + +	mstime interval; + +	/* Host resolving and book keeping */ +	struct sockaddr_any address; +	mstime resolve_interval; +	mstime last_resolve_try; +	mstime last_resolved; +	int is_resolved; +	int is_resolving; +	int must_resolve; + +	/* Requests that are queued of this host */ +	struct request *prepared; + +	/* Next in list of hosts */ +	struct host *next; +}; + +/* All hosts we've allocated */ +static struct host *host_list = NULL; + +/* Hosts hashed by the host:version:community string */ +static hsh_t *host_by_key = NULL; + +static void +resolve_cb (int ecode, struct addrinfo* ai, void* arg) +{ +	struct host *host = (struct host*)arg; +	host->is_resolving = 0; + +	if (ecode) { +		log_warnx ("couldn't resolve host name: %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; + +	log_debug ("resolved host: %s", host->hostname); +} + +static void +host_resolve (struct host *host, mstime when) +{ +	struct addrinfo hints; + +	if (host->is_resolving) +		return; + +	memset (&hints, 0, sizeof (hints)); +	hints.ai_family = PF_UNSPEC; +	hints.ai_socktype = SOCK_DGRAM; + +	/* Automatically strips port number */ +	log_debug ("resolving host: %s", host->hostname); +	host->last_resolve_try = when; +	host->is_resolving = 0; +	async_resolver_queue (host->hostname, "161", &hints, resolve_cb, host); +} + +static int +host_resolve_timer (mstime when, void* arg) +{ +	struct host* h; + +	/* Go through hosts and see which ones need resolving */ +	for (h = host_list; h; h = h->next) { + +		/* No need to resolve? */ +		if (!h->must_resolve) +			continue; + +		ASSERT (h->resolve_interval); + +		if (when - h->resolve_interval > h->last_resolve_try) +			host_resolve (h, when); + +		/* When the last 3 resolves have failed, set to unresolved */ +		if (h->is_resolved && when - (h->resolve_interval * 3) > h->last_resolved) { +			log_debug ("host address expired, and was not resolved: %s", h->hostname); +			h->is_resolved = 0; +		} +	} + +	return 1; +} + +static void +host_update_interval (struct host *host, mstime interval) +{ +	mstime resint; + +	if (!host->must_resolve) +		return; + +	/* When less than three minutes, resolve once per minute */ +	if (interval <= 180000) +		resint = 60000; + +	/* When between 3 and 10 minutes resolve once per cycle */ +	else if(interval <= 600000) +		resint = interval; + +	/* Otherwise resolve thrice per cycle */ +	else +		resint = interval / 3; + +        /* The lowest interval (since hosts can be shared by pollers) wins */ +	if (!host->resolve_interval || host->resolve_interval > resint) { +		host->resolve_interval = resint; +		log_debug ("will resolve host '%s' every %d seconds", host->hostname, resint / 1000); +	} +} + +static struct host* +host_instance (const char *hostname, const char *community, int version, mstime interval) +{ +	struct host *host; +	char key[128]; +	int r, initialize; + +	ASSERT (hostname); +	initialize = 0; + +	/* +	 * Build a lookup key. We can only combine requests for the same +	 * host when the version and community match. +	 */ +	community = community ? community : "public"; +	snprintf (key, sizeof(key), "%s:%d:%s", hostname, version, community); +	key[sizeof(key) - 1] = 0; + +	/* See if we can find an associated host */ +	host = hsh_get (host_by_key, key, -1); +	if (!host) { + +		host = calloc (1, sizeof (struct host)); +		if (!host) { +			log_errorx ("out of memory"); +			return NULL; +		} + +		/* Try and resolve the DNS name */ +		r = sock_any_pton (hostname, &host->address, SANY_OPT_DEFPORT(161) | SANY_OPT_DEFLOCAL | +		                   SANY_OPT_NORESOLV); +		if (r == -1) { +			log_warn ("couldn't parse host address (ignoring): %s", hostname); +			free (host); +			return NULL; +		} + +		/* And into the hash table */ +		memcpy (&host->key, key, sizeof (host->key)); +		if (!hsh_set (host_by_key, host->key, -1, host)) { +			log_errorx ("out of memory"); +			free (host); +			return NULL; +		} + +		/* And add it to the list */ +		host->next = host_list; +		host_list = host; + +		/* +		 * If we got back SANY_AF_DNS, then it needs resolving. The actual +		 * interval and stuff are worked out in once all the hosts, polls etc... +		 * have been parsed. +		 */ +		host->must_resolve = (r == SANY_AF_DNS); +		host->is_resolved = (r != SANY_AF_DNS); + +		host->version = version; +		host->hostname = strdup (hostname); +		host->community = strdup (community); +		host->resolve_interval = 0; +		host->last_resolved = 0; +		host->last_resolve_try = 0; + +		/* Start the resolving process */ +		if (!host->is_resolved) +			host_resolve (host, server_get_time ()); +	} + +	/* Update the host's resolve interval based on the poll interval requested */ +	host_update_interval (host, interval); + +	return host; +} + +static void +host_initialize (void) +{ +	/* Initialize stuff if necessary */ +	host_by_key = hsh_create (); +	if (!host_by_key) +		err (1, "out of memory"); + +	/* resolve timer goes once per second */ +	if (server_timer (1000, host_resolve_timer, NULL) == -1) +		err (1, "couldn't setup resolve timer"); +} + +static void +host_cleanup (void) +{ +	struct host *next, *host; + +	if (host_by_key) +		hsh_free (host_by_key); +	host_by_key = NULL; + +	for (host = host_list; host; host = next) { +		next = host->next; +	        if (host->hostname) +			free (host->hostname); +		if (host->community) +			free (host->community); +		free (host); +	} + +	host_list = NULL; +} + +/* ------------------------------------------------------------------------------ + * ASYNC REQUEST PROCESSING + */ + +struct request +{ +	/* The SNMP request identifier */ +	uint id; + +	mstime next_send;         /* Time of the next packet send */ +	mstime last_sent;         /* Time last sent */ +	mstime retry_interval;    /* How long between retries */ +	mstime when_timeout;      /* When this request times out */ +	uint num_sent;            /* How many times we've sent */ + +	struct host *host;        /* Host associated with this request */ + +	/* One callback entry for each binding */ +	struct { +		snmp_response func; +		void *arg; +	} callbacks[SNMP_MAX_BINDINGS]; + +	/* The actual request data */ +	struct snmp_pdu pdu; +}; + +/* The number of SNMP packet retries */ +static int snmp_retries = 3; + +/* The last request id */ +static uint snmp_request_id = 100000; + +/* The SNMP socket we're communicating on */ +static int snmp_socket = -1; + +/* Since we only deal with one packet at a time, global buffer */ +static unsigned char snmp_buffer[0x1000]; + +/* Hash table of all requests being processed */ +static hsh_t *snmp_processing = NULL; + +/* Hash table of all requests being prepared */ +static hsh_t *snmp_preparing = NULL; + +/* A flush of prepared packets is pending */ +static int snmp_flush_pending = 0; + +static void +request_release (struct request *req) +{ +	/* It should no longer be referred to any of these places */ +	ASSERT (!hsh_get (snmp_preparing, &req->id, sizeof (req->id))); +	ASSERT (!hsh_get (snmp_processing, &req->id, sizeof (req->id))); + +	snmp_pdu_clear (&req->pdu); +	free (req); +} + +static void +request_send (struct request* req, mstime when) +{ +	struct asn_buf b; +	ssize_t ret; + +	ASSERT (snmp_socket != -1); + +	/* Update our bookkeeping */ +	req->num_sent++; +	if (req->num_sent <= snmp_retries) +		req->next_send = when + req->retry_interval; +	else +		req->next_send = 0; +	req->last_sent = when; + +	if (!req->host->is_resolved) { +		if (req->num_sent <= 1) +			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)) { +		log_error("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) +			log_error ("couldn't send snmp packet to: %s", req->host->hostname); +		else +			log_debug ("sent request #%d to: %s", req->id, req->host->hostname); +	} +} + +static void +request_failure (struct request *req, int code) +{ +	void *val; +	int j; + +	ASSERT (req); +	ASSERT (code != 0); + +	log_debug ("failed request #%d to '%s' with code %d", req->id, req->host->hostname, code); + +	/* For each request SNMP value... */ +	for (j = 0; j < req->pdu.nbindings; ++j) { +		/* ... let callback know */ +		if (req->callbacks[j].func) +			(req->callbacks[j].func) (req->id, code, NULL, req->callbacks[j].arg); +	} + +	/* Remove from the processing list */ +	val = hsh_rem (snmp_processing, &req->id, sizeof (req->id)); +	ASSERT (val == req); + +	/* And free the request */ +	request_release (req); +} + +static void +request_get_dispatch (struct request* req, struct snmp_pdu* pdu) +{ +	struct snmp_value* pvalue; +	struct snmp_value* rvalue; +	int i, j, last, processed; +	void *val; + +	ASSERT (req); +	ASSERT (pdu); +	ASSERT (req->id == pdu->request_id); +	ASSERT (pdu->error_status == SNMP_ERR_NOERROR); +	ASSERT (req->pdu.type == SNMP_PDU_GET); + +	/* +	 * For SNMP GET requests we check that the values that came back +	 * were in fact for the same values we requested, and fix any +	 * ordering issues etc. +	 */ +	for (j = 0; j < req->pdu.nbindings; ++j) { + +		processed = 0; +		rvalue = &(req->pdu.bindings[j]); + +		/* ... dig out matching value from response */ +		for (i = 0; i < pdu->nbindings; ++i) { +			pvalue = &(pdu->bindings[i]); + +			if (asn_compare_oid (&(rvalue->var), &(pvalue->var)) != 0) +				continue; + +			if (req->callbacks[j].func) +				(req->callbacks[j].func) (req->id, SNMP_ERR_NOERROR, +				                          pvalue, req->callbacks[j].arg); + +			processed = 1; +			break; +		} + +		/* If this one was processed, remove from request */ +		if (processed) { +			last = --req->pdu.nbindings; + +			ASSERT (last >= 0); +			if (last) { +				memcpy (&req->callbacks[j], &req->callbacks[last], sizeof (req->callbacks[j])); +				memcpy (&req->pdu.bindings[j], &req->pdu.bindings[last], sizeof (req->pdu.bindings[j])); +			} +			memset (&req->callbacks[last], 0, sizeof (req->callbacks[last])); +			memset (&req->pdu.bindings[last], 0, sizeof (req->pdu.bindings[last])); + +			/* Process this index again, since we have a new request here */ +			--j; +		} +	} + +	/* All done? then remove request */ +	if (req->pdu.nbindings == 0) { + +		log_debug ("request #%d is complete", req->id); + +		val = hsh_rem (snmp_processing, &req->id, sizeof (req->id)); +		ASSERT (val == req); +		request_release (req); +	} +} + +static void +request_other_dispatch (struct request* req, struct snmp_pdu* pdu) +{ +	void *val; + +	ASSERT (req); +	ASSERT (pdu); +	ASSERT (req->id == pdu->request_id); +	ASSERT (pdu->error_status == SNMP_ERR_NOERROR); +	ASSERT (req->pdu.type != SNMP_PDU_GET); + +	/* +	 * For requests other than GET we just use the first value +	 * that was sent. See below where we limit to one binding +	 * per SNMP request when other than GET. +	 */ + +	if (pdu->nbindings == 0) { +		log_warn ("received response from the server without any values"); +		return; +	} + +	if (pdu->nbindings > 1) +		log_warn ("received response from the server with extra values"); + +	/* Shouldn't have sent more than one binding */ +	ASSERT (req->pdu.nbindings == 1); + +	if (req->callbacks[0].func) +		(req->callbacks[0].func) (req->id, SNMP_ERR_NOERROR, +				          &(pdu->bindings[0]), req->callbacks[0].arg); + +	log_debug ("request #%d is complete", req->id); + +	val = hsh_rem (snmp_processing, &req->id, sizeof (req->id)); +	ASSERT (val == req); +	request_release (req); +} + +static void +request_response (int fd, int type, void* arg) +{ +	char hostname[MAXPATHLEN]; +	struct sockaddr_any from; +	struct snmp_pdu pdu; +	struct asn_buf b; +	struct request* req; +	const char* msg; +	int len, ret; +	int ip, id; + +	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) +			log_error ("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) { +		log_warnx ("invalid snmp packet received from: %s", hostname); +		return; +	} + +	/* It needs to match something we're waiting for */ +	id = pdu.request_id; +	req = hsh_get (snmp_processing, &id, sizeof (id)); +	if(!req) { +		log_debug ("received extra or delayed packet from: %s", hostname); +		return; +	} + +	if(pdu.version != req->pdu.version) +		log_warnx ("wrong version snmp packet from: %s", hostname); + + +	/* Log any errors */ +	if(pdu.error_status == SNMP_ERR_NOERROR) { +		log_debug ("response to request #%d from: %s", req->id, hostname); + +		if (req->pdu.type == SNMP_PDU_GET) +			request_get_dispatch (req, &pdu); +		else +			request_other_dispatch (req, &pdu); + +	} else { +		msg = snmp_get_errmsg (pdu.error_status); +		if(msg) +			log_debug ("failure for request #%d from: %s: %s", req->id, hostname, msg); +		else +			log_debug ("failure for request #%d from: %s: %d", req->id, hostname, +			           pdu.error_status); +		request_failure (req, pdu.error_status); +	} + +} + +static void +request_process_all (mstime when) +{ +	struct request *req; +	hsh_index_t *i; + +	/* Go through all processing packets */ +	for (i = hsh_first (snmp_processing); i; ) { + +		req = hsh_this (i, NULL, NULL); +		ASSERT (req); + +		/* Move to the next, as we may delete below */ +		i = hsh_next (i); + +		if (when >= req->when_timeout) { +			request_failure (req, -1); +			continue; +		} + +		if (req->next_send && when >= req->next_send) +			request_send (req, when); +	} +} + +static int +request_resend_timer (mstime when, void* arg) +{ +	request_process_all (when); +	return 1; +} + +static void +request_flush (struct request *req, mstime when) +{ +	void *val; + +	ASSERT (req->host->prepared == req); + +	val = hsh_rem (snmp_preparing, &req->id, sizeof (req->id)); +	ASSERT (val == req); + +	/* Don't let us add more onto this request via the host */ +	ASSERT (req->host->prepared == req); +	req->host->prepared = NULL; + +	/* Mark this packet to be sent now */ +	req->next_send = when; + +	if (!hsh_set (snmp_processing, &req->id, sizeof (req->id), req)) { +		log_errorx ("out of memory, discarding packets"); +		request_release (req); +	} +} + +static void +request_flush_all (mstime when) +{ +	struct request *req; +	hsh_index_t *i; + +	/* Transfer everything to the processing table */ +	for (i = hsh_first (snmp_preparing); i; ) { +		req = hsh_this (i, NULL, NULL); + +		/* Do this here, because below removes from table */ +		i = hsh_next (i); + +		request_flush (req, when); +	} + +	/* Clear the preparing table */ +	hsh_clear (snmp_preparing); + +	/* Process all packets in processing */ +	request_process_all (when); +} + + + +static int +request_flush_cb (mstime when, void *arg) +{ +	snmp_flush_pending = 0; +	request_flush_all (when); +	return 0; +} + +static struct request* +request_prep_instance (struct host *host, mstime interval, mstime timeout, int reqtype) +{ +	struct request *req; + +	/* See if we have one we can piggy back onto */ +	req = host->prepared; +	if (req) { +		ASSERT (hsh_get (snmp_preparing, &req->id, sizeof (req->id))); + +		/* We have one we can piggy back another request onto */ +		if (req->pdu.nbindings < SNMP_MAX_BINDINGS && req->pdu.type == reqtype) +			return req; + +		/* It's too full, so send it off */ +		request_flush (req, server_get_time ()); +		req = NULL; +	} + +	ASSERT (host->prepared == NULL); + +	/* Create a new request */ +	req = calloc (1, sizeof (struct request)); +	if (!req) { +		log_error ("out of memory"); +		return NULL; +	} + +	/* Assign the unique id */ +	req->id = snmp_request_id++; + +	/* Mark it down as something we want to prepare */ +	if (!hsh_set (snmp_preparing, &req->id, sizeof (req->id), req)) { +		log_error ("out of memory"); +		free (req); +		return NULL; +	} + +        /* Setup the packet */ +        strlcpy (req->pdu.community, host->community, sizeof (req->pdu.community)); +        req->pdu.request_id = req->id; +        req->pdu.version = host->version; +        req->pdu.type = reqtype; +        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->retry_interval = (interval <= 2000) ? 200L : 600L; + +        /* Timeout is for the last packet sent, not first */ +        req->when_timeout = server_get_time () + (req->retry_interval * ((mstime)snmp_retries)) + timeout; +        req->num_sent = 0; + +        /* Add it to the host */ +	req->host = host; +	ASSERT (host->prepared == NULL); +        host->prepared = req; + +        log_debug ("preparing request #%d for: %s@%s", req->id, +                   req->host->community, req->host->hostname); + +	return req; +} + +int +snmp_engine_request (const char *hostname, const char *community, int version, +                     mstime interval, mstime timeout, int reqtype, +                     struct asn_oid *oid, snmp_response func, void *arg) +{ +	struct host *host; +	struct request *req; + +	/* Lookup host for request */ +	host = host_instance (hostname, community, version, interval); +	if (!host) +		return 0; + +	/* Get a request with space or a new request for that host */ +	req = request_prep_instance (host, interval, timeout, reqtype); +	if (!req) +		return 0; + +	ASSERT (req->pdu.nbindings < SNMP_MAX_BINDINGS); + +	/* Add the oid to that request */ +        req->pdu.bindings[req->pdu.nbindings].var = *oid; +        req->pdu.bindings[req->pdu.nbindings].syntax = SNMP_SYNTAX_NULL; +        req->callbacks[req->pdu.nbindings].func = func; +        req->callbacks[req->pdu.nbindings].arg = arg; +        req->pdu.nbindings++; + +        /* All other than GET, only get one binding */ +        if (reqtype != SNMP_PDU_GET) { +        	ASSERT (req->pdu.nbindings == 1); +        	request_flush (req, server_get_time ()); +        } + +        if (!snmp_flush_pending) { +        	server_oneshot (0, request_flush_cb, NULL); +        	snmp_flush_pending = 1; +        } + +        return req->id; +} + +void +snmp_engine_cancel (int reqid) +{ +	struct request *req; + +	/* Is it being processed? */ +	req = hsh_rem (snmp_processing, &reqid, sizeof (reqid)); +	if (req) { +		log_debug ("cancelling request #%d during processing", reqid); +		request_release (req); +		return; +	} + +	/* Is it being prepared? */ +	req = hsh_rem (snmp_preparing, &reqid, sizeof (reqid)); +	if (req) { + +		/* Remove it from the host in question */ +		ASSERT (req->host->prepared == req); +		req->host->prepared = NULL; + +		log_debug ("cancelling request #%d during prep", reqid); +		request_release (req); +		return; +	} +} + +void +snmp_engine_flush (void) +{ +	request_flush_all (server_get_time ()); +} + +/* ------------------------------------------------------------------------------- + * SYNC REQUESTS + */ + +struct sync_data { +	int valid; +	int code; +	int id; +	struct snmp_value *dest; +}; + +static void +sync_response (int req, int code, struct snmp_value *value, void *data) +{ +	struct sync_data *sync = data; + +	ASSERT (req == sync->id); + +	sync->valid = 1; +	sync->code = code; +	if (value) +		snmp_value_copy (sync->dest, value); + +	server_stop (); +} + +int +snmp_engine_sync (const char* host, const char* community, int version, +                  uint64_t interval, uint64_t timeout, int reqtype, +                  struct snmp_value *value) +{ +	struct sync_data sync; + +	/* Can't run a sync request with the server running */ +	ASSERT (server_stopped()); + +	sync.valid = 0; +	sync.code = 0; +	sync.dest = value; + +	sync.id = snmp_engine_request (host, community, version, interval, timeout, +	                               reqtype, &value->var, sync_response, &sync); + +	if (!sync.id) +		return -1; + +	snmp_engine_flush (); +	server_run (); + +	ASSERT (sync.valid); +	return sync.code; +} + +/* ----------------------------------------------------------------------------- + * INIT + */ + +void +snmp_engine_init (int retries) +{ +	struct sockaddr_in addr; + +	snmp_retries = retries; + +	snmp_processing = hsh_create (); +	if (!snmp_processing) +		err (1, "out of memory"); + +	snmp_preparing = hsh_create (); +	if (!snmp_preparing) +		err (1, "out of memory"); + +	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, request_response, NULL) == -1) +		err (1, "couldn't listen on socket"); + +	/* We fire off the resend timer every 1/5 second */ +	if (server_timer (200, request_resend_timer, NULL) == -1) +	    err(1, "couldn't setup timer"); + +	host_initialize (); +} + +void +snmp_engine_stop (void) +{ +	if (snmp_socket != -1) { +		server_unwatch (snmp_socket); +		close (snmp_socket); +		snmp_socket = -1; +	} + +	host_cleanup (); +} + +int +snmp_engine_match (const struct snmp_value *value, const char *text) +{ +	char *end; + +	ASSERT (value); +	ASSERT (text); + +	switch (value->syntax) { + +	/* Empty string */ +	case SNMP_SYNTAX_NULL: +	case SNMP_SYNTAX_NOSUCHOBJECT: +	case SNMP_SYNTAX_NOSUCHINSTANCE: +	case SNMP_SYNTAX_ENDOFMIBVIEW: +		return *text == '\0'; + +	/* Integer value */ +	case SNMP_SYNTAX_INTEGER: +		{ +			int num = strtoll (text, &end, 0); +			if (*end != '\0') +				return 0; +			return num == value->v.integer; +		} + +	/* String of bytes */ +	case SNMP_SYNTAX_OCTETSTRING: +		{ +			int len = strlen (text); +			if (value->v.octetstring.len != len) +				return 0; +			return memcmp (value->v.octetstring.octets, text, len) == 0; +		} + + +	case SNMP_SYNTAX_OID: +		{ +			struct asn_oid oid; +			if (mib_parse (text, &oid) < 0) +				return 0; +			return asn_compare_oid (&oid, &value->v.oid) == 0; +		} + +	case SNMP_SYNTAX_IPADDRESS: +		{ +		    struct in_addr addr; +		    if (!inet_aton (text, &addr)) +			    return 0; +		    return memcmp (&addr, value->v.ipaddress, 4) == 0; +		} + +	case SNMP_SYNTAX_COUNTER: +	case SNMP_SYNTAX_GAUGE: +	case SNMP_SYNTAX_TIMETICKS: +		{ +			uint64_t sub = strtoull (text, &end, 0); +			if (*end != '\0' || sub > 0xffffffff) +				return 0; +			return sub == value->v.uint32; +		} + +	case SNMP_SYNTAX_COUNTER64: +		{ +			uint64_t sub = strtoull (text, &end, 0); +			if (*end != '\0' || sub > 0xffffffff) +				return 0; +			return sub == value->v.counter64; +		} + +	default: +		return 0; +	}; +} diff --git a/common/snmp-engine.h b/common/snmp-engine.h new file mode 100644 index 0000000..36b2a3d --- /dev/null +++ b/common/snmp-engine.h @@ -0,0 +1,27 @@ +#ifndef SNMPENGINE_H_ +#define SNMPENGINE_H_ + +#include <bsnmp/asn1.h> +#include <bsnmp/snmp.h> + +typedef void (*snmp_response) (int request, int code, struct snmp_value *value, void *data); + +void snmp_engine_init (int retries); + +int  snmp_engine_request (const char* host, const char* community, int version, +                          uint64_t interval, uint64_t timeout, int reqtype, +                          struct asn_oid *oid, snmp_response func, void *data); + +void snmp_engine_cancel (int reqid); + +void snmp_engine_flush (void); + +int  snmp_engine_sync (const char* host, const char* community, int version, +                       uint64_t interval, uint64_t timeout, int reqtype, +                       struct snmp_value *value); + +void snmp_engine_stop (void); + +int  snmp_engine_match (const struct snmp_value *value, const char *text); + +#endif /*SNMPENGINE_H_*/ diff --git a/configure.in b/configure.in index d247789..ea8d458 100644 --- a/configure.in +++ b/configure.in @@ -54,6 +54,7 @@ AC_MSG_RESULT()  AC_CONFIG_FILES([Makefile      mibs/Makefile +    common/Makefile      daemon/Makefile      bsnmp/Makefile      tools/Makefile 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; -    } -} diff --git a/doc/rrdbot-get.1 b/doc/rrdbot-get.1 index 48e4689..e9515d6 100644 --- a/doc/rrdbot-get.1 +++ b/doc/rrdbot-get.1 @@ -76,6 +76,34 @@ Prints the version of  .Nm  and the locations of the configuration files, RRD files etc.  .El +.Sh MULTIPLE AGENTS +.Nm +supports failover between multiple agents. If an SNMP query fails on one agent +or a value is not found when querying an agent, then it will switch to another +configured agent.  +.Pp +When combined with a query (see TABLE QUERIES) you can use this feature to  +search for a given value in a table on one of multiple agents. +.Pp +To use failover, simply use multiple host names with commas (without a space) +separating them. For example: +.Bd -literal -offset indent +snmp://public@two.example.com,one.example.com/sysUptime.0 +.Ed +.Sh TABLE QUERIES +.Nm  +can query a value that corresponds to a certain row in an SNMP table. On  +many SNMP agents the indexes of rows in tables are not fixed, and this  +allows you to retrieve a certain value no matter what row of the table  +it is on. +.Pp +Add the OID and value you want to search for in the table to the end  +of the SNMP URL. Only one query value is supported.  +.Pp +For example to get the outbound packet count on the 'eth0' interface, you would use: +.Bd -literal -offset indent +snmp://public@example.com/ifInUcastPkts?ifDescr=eth0 +.Ed  .Sh SEE ALSO  .Xr rrdbotd 8 ,  .Xr rrdbot.conf 5 , diff --git a/doc/rrdbot.conf.5 b/doc/rrdbot.conf.5 index ad93496..0a31ce4 100644 --- a/doc/rrdbot.conf.5 +++ b/doc/rrdbot.conf.5 @@ -111,16 +111,21 @@ above should be replaced with the RRD field name. Multiple  options can be specified if the RRD file has multiple fields. The syntax of the   SNMP url is as follows:  .Bd -literal -offset indent -snmp[version]://community@host[:port]/oid +snmp[version]://community@host[:port]/oid[?query=value]  .Ed  .Pp  The following are valid SNMP urls:  .Bd -literal -offset indent  snmp://public@gateway.example.com/sysUptime.0  snmp2c://mycommunity@uplink.example.com/ifInOctets.2 +snmp2c://mycommunity@example.com/ifInOctets?idDescr=eth0  snmp://public@www.example.com:10161/1.3.6.1.2.1.1.3.0 +snmp://pub@two.example.com,one.example.com/sysUptime.0  .Ed  .Pp +See TABLE QUERIES for more info on how to use the query part. See MULTIPLE HOSTS +support for info on how to use multiple hosts. +.Pp  To test that your SNMP urls are correct you can use the   .Xr rrdbot-get 1  utility. @@ -254,7 +259,35 @@ option in the configuration file.  .Pp  Once you have configuration files in place, you can use the   .Xr rrdbot-create 8 -tool to create the needed RRD files in the appropriate places.  +tool to create the needed RRD files in the appropriate places. +.Sh MULTIPLE AGENTS +.Xr rrdbotd 8  +supports failover between multiple agents. If an SNMP query fails on one agent +or a value is not found when querying an agent, then it will switch to another +configured agent.  +.Pp +When combined with a query (see TABLE QUERIES) you can use this feature to  +search for a given value in a table on one of multiple agents. +.Pp +To use failover, simply use multiple host names with commas (without a space) +separating them. For example: +.Bd -literal -offset indent +snmp://public@two.example.com,one.example.com/sysUptime.0 +.Ed +.Sh TABLE QUERIES +.Xr rrdbotd 8  +can query a value that corresponds to a certain row in an SNMP table. On  +many SNMP agents the indexes of rows in tables are not fixed, and this  +allows you to retrieve a certain value no matter what row of the table  +it is on. +.Pp +Add the OID and value you want to search for in the table to the end  +of the SNMP URL. Only one query value is supported.  +.Pp +For example to get the outbound packet count on the 'eth0' interface, you would use: +.Bd -literal -offset indent +snmp://public@example.com/ifInUcastPkts?ifDescr=eth0 +.Ed  .Sh SEE ALSO  .Xr rrdbotd 8 ,  .Xr rrdbot-create 8 , diff --git a/doc/rrdbotd.8 b/doc/rrdbotd.8 index bc4d7fd..8217626 100644 --- a/doc/rrdbotd.8 +++ b/doc/rrdbotd.8 @@ -56,7 +56,14 @@  .Nm  is an SNMP polling daemon which writes the polled values to an   .Xr rrdtool 1 -RRD database. An can poll many different SNMP sources in an efficient manner.  +RRD database. An can poll many different SNMP sources in an efficient manner.   +.Pp +Table queries are supported, where the OID index of a certain value is not  +known beforehand, or may change regularly.  +.Pp +In addition multiple SNMP agents may be specified for a certain value. If  +one SNMP agent cannot be contacted or errors for some reason, another one  +will be tried.  .Pp  The configuration (eg: SNMP sources, polling intervals) are located in files   in a directory, with one configuration file per RRD. The format of the  diff --git a/doc/traffic-example.conf b/doc/traffic-example.conf index aea1b0d..e0d40d2 100644 --- a/doc/traffic-example.conf +++ b/doc/traffic-example.conf @@ -17,6 +17,12 @@ rrd: /var/db/rrds/traffic.rrd  in.source: snmp://public@router.example.com/ifInOctets.2  out.source: snmp://public@router.example.com/ifOutOctets.2 +# You might also use table queries to acheive the above. +# If the interface's names is 'eth0', then this would work. +# +# in.source: snmp://public@router.example.com/ifInOctets?ifDescr=eth0 +# out.source: snmp://public@router.example.com/ifOutOctets?ifDescr=eth0 +  # Poll every 10 seconds  interval: 10 diff --git a/tools/Makefile.am b/tools/Makefile.am index a2185ab..70c2c61 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,19 +1,20 @@  sbin_PROGRAMS = rrdbot-create rrdbot-get -rrdbot_create_SOURCES = rrdbot-create.c ../common/usuals.h \ -                ../common/config-parser.h ../common/config-parser.c \ -                ../common/compat.h ../common/compat.c +rrdbot_create_SOURCES = rrdbot-create.c +  rrdbot_create_CFLAGS = -I${top_srcdir}/common/ -I${top_srcdir} \                  -DCONF_PREFIX=\"$(sysconfdir)\" -DDATA_PREFIX=\"$(datadir)\" -rrdbot_get_SOURCES = rrdbot-get.c ../common/usuals.h \ -                ../common/compat.h ../common/compat.c \ -                ../common/config-parser.h ../common/config-parser.c \ -                ../common/server-mainloop.h ../common/server-mainloop.c \ -                ../common/sock-any.h ../common/sock-any.c \ -                ../mib/mib-parser.c ../mib/mib-parser.h \ -                ../bsnmp/asn1.h ../bsnmp/asn1.c \ -                ../bsnmp/snmp.h ../bsnmp/snmp.c +rrdbot_create_LDADD = \ +	$(top_builddir)/common/libcommon.a + +rrdbot_get_SOURCES = rrdbot-get.c \ +                ../mib/mib-parser.c ../mib/mib-parser.h +  rrdbot_get_CFLAGS = -I${top_srcdir}/common/ -I${top_srcdir} \                  -DCONF_PREFIX=\"$(sysconfdir)\" -DDATA_PREFIX=\"$(datadir)\" + +rrdbot_get_LDADD = \ +	$(top_builddir)/common/libcommon.a \ +	$(top_builddir)/bsnmp/libbsnmp-custom.a diff --git a/tools/rrdbot-get.c b/tools/rrdbot-get.c index c14f25b..a140735 100644 --- a/tools/rrdbot-get.c +++ b/tools/rrdbot-get.c @@ -46,37 +46,41 @@  #include <bsnmp/snmp.h>  #include <mib/mib-parser.h> -#include "sock-any.h" -#include "server-mainloop.h"  #include "config-parser.h" +#include "log.h" +#include "server-mainloop.h" +#include "snmp-engine.h" +#include "sock-any.h" - -#define RESEND_TIMEOUT      200         /* Time between SNMP resends */  #define DEFAULT_TIMEOUT     5000        /* Default timeout for SNMP response */  #define MAX_RETRIES         3           /* Number of SNMP packets we retry */  struct context  { -    int socket;                         /* The socket to use */ -    unsigned char packet[0x1000];       /* The raw packet data to send */ -    struct snmp_pdu pdu;                /* The actual request data */ -    struct asn_oid oid_first;           /* The first OID we've done */ +	struct asn_oid oid_first;           	/* The first OID we've done */ + +	/* Request data */ +	char host[128];                    	/* The remote host, resolved to an address */ +	char *community;			/* The community to use */ +	int version;				/* protocol version */ -    struct sockaddr_any hostaddr;       /* The remote host */ -    char* hostname;                     /* The remote host */ +	struct asn_oid request_oid;		/* The OID to request */ -    uint64_t lastsend;                  /* Time of last send */ -    int retries;                        /* Number of retries */ -    uint64_t timeout;                   /* Receive timeout */ +	int has_query;				/* Whether we are a table query or not */ +	struct asn_oid query_oid;		/* OID to use in table query */ +	char *query_match;			/* Value to match in table query */ -    int recursive;                      /* Whether we're going recursive or not */ -    int numeric;                        /* Print raw data */ +	uint64_t timeout;                   /* Receive timeout */ + +	int recursive;                      /* Whether we're going recursive or not */ +	int numeric;                        /* Print raw data */ +	int verbose;				/* Print verbose messages */  };  static struct context ctx;  /* ----------------------------------------------------------------------------- - * DUMMY CONFIG FUNCTIONS + * REQUIRED CALLBACK FUNCTIONS   */  int @@ -92,260 +96,292 @@ cfg_error(const char* filename, const char* errmsg, void* data)      return 0;  } +void +log_vmessage (int level, int erno, const char *msg, va_list va) +{ +	if (level >= LOG_DEBUG && !ctx.verbose) +		return; + +	if (erno) { +		errno = erno; +		vwarn (msg, va); +	} else { +		vwarnx (msg, va); +	} + +	if (level <= LOG_ERR) +		exit (1); +} +  /* -----------------------------------------------------------------------------   * SNMP ENGINE   */  static void -send_req() +parse_host (char *host)  { -    struct asn_buf b; -    ssize_t ret; - -    b.asn_ptr = ctx.packet; -    b.asn_len = sizeof(ctx.packet); +	struct sockaddr_any addr; +	char *x; -    if(snmp_pdu_encode(&ctx.pdu, &b)) -        errx(1, "couldn't encode snmp buffer"); +	/* Use the first of multiple hosts */ +	x = strchr (host, ','); +	if (x) { +		*x = 0; +		warnx ("only using the first host name: %s", host); +	} -    ret = sendto(ctx.socket, ctx.packet, b.asn_ptr - ctx.packet, 0, -                 &SANY_ADDR(ctx.hostaddr), SANY_LEN(ctx.hostaddr)); -    if(ret == -1) -        err(1, "couldn't send snmp packet to: %s", ctx.hostname); +	if (sock_any_pton (host, &addr, SANY_OPT_DEFPORT(161) | SANY_OPT_DEFLOCAL) == -1) +		err (1, "couldn't resolve host address: %s", host); -    /* Some bookkeeping */ -    ctx.retries++; -    ctx.lastsend = server_get_time(); +	if (sock_any_ntop (&addr, ctx.host, sizeof (ctx.host), 0) == -1) +		err (1, "couldn't convert host address: %s", host);  }  static void -setup_req(char* uri) +parse_argument (char *uri)  { -    enum snmp_version version; -    const char* msg; -    char* scheme; -    char* copy; -    char* user; -    char* path; - -    /* Parse the SNMP URI */ -    copy = strdup(uri); -    msg = cfg_parse_uri(uri, &scheme, &ctx.hostname, &user, &path); -    if(msg) -        errx(2, "%s: %s", msg, copy); -    free(copy); - -    ASSERT(ctx.hostname && path); - -    /* Currently we only support SNMP pollers */ -    msg = cfg_parse_scheme(scheme, &version); -    if(msg) -        errx(2, "%s: %s", msg, scheme); - -    if(sock_any_pton(ctx.hostname, &ctx.hostaddr, -                     SANY_OPT_DEFPORT(161) | SANY_OPT_DEFLOCAL) == -1) -        err(1, "couldn't resolve host address (ignoring): %s", ctx.hostname); - -    memset(&ctx.pdu, 0, sizeof(ctx.pdu)); -    ctx.pdu.version = version; -    ctx.pdu.request_id = 0; -    ctx.pdu.type = ctx.recursive ? SNMP_PDU_GETNEXT : SNMP_PDU_GET; -    ctx.pdu.error_status = 0; -    ctx.pdu.error_index = 0; -    strlcpy(ctx.pdu.community, user ? user : "public", -            sizeof(ctx.pdu.community)); - - -    /* And parse the OID */ -    ctx.pdu.bindings[0].syntax = 0; -    memset(&(ctx.pdu.bindings[0].v), 0, sizeof(ctx.pdu.bindings[0].v)); -    if(mib_parse(path, &(ctx.pdu.bindings[0].var)) == -1) -        errx(2, "invalid MIB: %s", path); - -    /* Add an item to this request */ -    ctx.pdu.nbindings = 1; - -    /* Keep track of top for recursiveness */ -    memcpy(&ctx.oid_first, &(ctx.pdu.bindings[0].var), sizeof(ctx.oid_first)); - -    /* Reset bookkeeping */ -    ctx.retries = 0; -    ctx.lastsend = 0; +	enum snmp_version version; +	const char* msg; +	char* copy; +	char *user, *host, *scheme, *path, *query; +	char *value, *name; + +	/* Parse the SNMP URI */ +	copy = strdup (uri); +	msg = cfg_parse_uri (uri, &scheme, &host, &user, &path, &query); +	if (msg) +		errx (2, "%s: %s", msg, copy); +	free (copy); + +	ASSERT (host && path); + +	/* Host, community */ +	parse_host (host); +	ctx.community = user ? user : "public"; + +	/* Currently we only support SNMP pollers */ +	msg = cfg_parse_scheme (scheme, &version); +	if (msg) +		errx (2, "%s: %s", msg, scheme); +	ctx.version = version; + +	/* Parse the OID */ +	if (mib_parse (path, &ctx.request_oid) == -1) +		errx (2, "invalid MIB: %s", path); +	if (ctx.request_oid.len >= ASN_MAXOIDLEN) +		errx (2, "request OID is too long"); + +	/* Parse any query */ +	if (query) { +		msg = cfg_parse_query (query, &name, &value, &query); +		if (msg) +			errx (2, "%s", msg); +		if (query && *query) +			warnx ("only using first query argument in snmp URI"); + +		ctx.has_query = 1; +		ctx.query_match = value; + +		/* And parse the query OID */ +		if (mib_parse (name, &(ctx.query_oid)) == -1) +			errx (2, "invalid MIB: %s", name); + +		if (ctx.query_oid.len >= ASN_MAXOIDLEN) +			errx (2, "query OID is too long"); +	}  }  static void -setup_next(struct snmp_value* value) +print_result (struct snmp_value* value)  { -    ctx.pdu.request_id++; -    ctx.pdu.type = SNMP_PDU_GETNEXT; +	char *t; + +	if (ctx.numeric) +		printf ("%s", asn_oid2str (&value->var)); +	else +		mib_format (&value->var, stdout); + +	printf(": "); + +        switch (value->syntax) { +	case SNMP_SYNTAX_NULL: +		printf ("[null]\n"); +		break; +	case SNMP_SYNTAX_INTEGER: +		printf ("%d\n", value->v.integer); +		break; +	case SNMP_SYNTAX_COUNTER: +	case SNMP_SYNTAX_GAUGE: +	case SNMP_SYNTAX_TIMETICKS: +		printf ("%d\n", value->v.uint32); +		break; +	case SNMP_SYNTAX_COUNTER64: +		printf ("%lld\n", value->v.counter64); +		break; +	case SNMP_SYNTAX_OCTETSTRING: +		t = xcalloc (value->v.octetstring.len + 1); +		memcpy (t, value->v.octetstring.octets, value->v.octetstring.len); +		printf ("%s\n", t); +		free (t); +		break; +	case SNMP_SYNTAX_OID: +		printf ("%s\n", asn_oid2str(&(value->v.oid))); +		break; +	case SNMP_SYNTAX_IPADDRESS: +		printf ("%c.%c.%c.%c\n", value->v.ipaddress[0], +		        value->v.ipaddress[1], value->v.ipaddress[2], +		        value->v.ipaddress[3]); +		break; +	case SNMP_SYNTAX_NOSUCHOBJECT: +		printf ("[field not available on snmp server]\n"); +		break; +	case SNMP_SYNTAX_NOSUCHINSTANCE: +		printf ("[no such instance on snmp server]\n"); +		break; +	case SNMP_SYNTAX_ENDOFMIBVIEW: +		printf ("[end of mib view on snmp server]\n"); +		break; +	default: +		printf ("[unknown]\n"); +		break; +	} +} -    /* And parse the OID */ -    memcpy(&(ctx.pdu.bindings[0]), value, sizeof(struct snmp_value)); -    ctx.pdu.bindings[0].syntax = 0; -    ctx.pdu.nbindings = 1; +static void +had_failure (int code) +{ +	ASSERT (code != 0); -    /* Reset bookkeeping */ -    ctx.retries = 0; -    ctx.lastsend = 0; +	if (code < 1) +		errx (1, "couldn't successfully communicate with server"); +	else +		errx (1, "server returned error: %s", snmp_get_errmsg (code));  } -static int -print_resp(struct snmp_pdu* pdu, uint64_t when) +static void +process_recursive (void)  { -    struct snmp_value* value; -    char *t; -    int i; - -    ASSERT(ctx.pdu.request_id == pdu->request_id); - -    for(i = 0; i < pdu->nbindings; i++) -    { -        value = &(pdu->bindings[i]); - -        if(ctx.numeric) -            printf("%s", asn_oid2str(&(value->var))); -        else -            mib_format(&(value->var), stdout); - -        printf(": "); - -        switch(value->syntax) -        { -        case SNMP_SYNTAX_NULL: -            printf("[null]\n"); -            break; -        case SNMP_SYNTAX_INTEGER: -            printf("%d\n", value->v.integer); -            break; -        case SNMP_SYNTAX_COUNTER: -        case SNMP_SYNTAX_GAUGE: -        case SNMP_SYNTAX_TIMETICKS: -            printf("%d\n", value->v.uint32); -            break; -        case SNMP_SYNTAX_COUNTER64: -            printf("%lld\n", value->v.counter64); -            break; -        case SNMP_SYNTAX_OCTETSTRING: -            t = xcalloc(value->v.octetstring.len + 1); -            memcpy(t, value->v.octetstring.octets, value->v.octetstring.len); -            printf("%s\n", t); -            free(t); -            break; -        case SNMP_SYNTAX_OID: -            printf("%s\n", asn_oid2str(&(value->v.oid))); -            break; -        case SNMP_SYNTAX_IPADDRESS: -            printf("%c.%c.%c.%c\n", value->v.ipaddress[0], value->v.ipaddress[1], -                   value->v.ipaddress[2], value->v.ipaddress[3]); -            break; -        case SNMP_SYNTAX_NOSUCHOBJECT: -            printf("[field not available on snmp server]\n"); -            break; -        case SNMP_SYNTAX_NOSUCHINSTANCE: -            printf("[no such instance on snmp server]\n"); -            break; -        case SNMP_SYNTAX_ENDOFMIBVIEW: -            return 0; -        default: -            printf("[unknown]\n"); -            break; -        } -    } - -    return 1; +	struct snmp_value value; +	struct asn_oid last; +	int i, ret, done; + +	memcpy (&last, &ctx.request_oid, sizeof (last)); +	memset (&value, 0, sizeof (value)); + +	for (i = 0; ; ++i) { + +		memcpy (&value.var, &last, sizeof (value.var)); + +		ret = snmp_engine_sync (ctx.host, ctx.community, ctx.version, +		                        0, ctx.timeout, SNMP_PDU_GETNEXT, &value); + +		/* Reached the end */ +		if (i == 0 && ret == SNMP_ERR_NOSUCHNAME) +			return; + +		if (ret != SNMP_ERR_NOERROR) { +			had_failure (ret); +			return; +		} + +		/* Check that its not past the end */ +		done = asn_compare_oid (&ctx.request_oid, &value.var) != 0 && +		        !asn_is_suboid (&ctx.request_oid, &value.var); + +		if (!done) { +			print_result (&value); +			memcpy (&last, &value.var, sizeof (last)); +		} + +		snmp_value_clear (&value); + +		if (done) +			return; +	}  }  static void -receive_resp(int fd, int type, void* arg) +process_query (void)  { -    char hostname[MAXPATHLEN]; -    struct sockaddr_any from; -    struct snmp_pdu pdu; -    struct snmp_value *val; -    struct asn_buf b; -    const char* msg; -    int len, ret, subid; -    int32_t ip; - -    ASSERT(ctx.socket == fd); - -    /* Read in the packet */ - -    SANY_LEN(from) = sizeof(from); -    len = recvfrom(ctx.socket, ctx.packet, sizeof(ctx.packet), 0, -                   &SANY_ADDR(from), &SANY_LEN(from)); -    if(len < 0) -    { -        if(errno != EAGAIN && errno != EWOULDBLOCK) -            err(1, "error receiving snmp packet from network"); -    } - -    if(sock_any_ntop(&from, hostname, MAXPATHLEN, 0) == -1) -        strcpy(hostname, "[UNKNOWN]"); - -    /* Now parse the packet */ - -    b.asn_ptr = ctx.packet; -    b.asn_len = len; - -    ret = snmp_pdu_decode(&b, &pdu, &ip); -    if(ret != SNMP_CODE_OK) -        errx(1, "invalid snmp packet received from: %s", hostname); - -    /* It needs to match something we're waiting for */ -    if(pdu.request_id != ctx.pdu.request_id) -        return; - -    /* Check for errors */ -    if(pdu.error_status != SNMP_ERR_NOERROR) -    { -        msg = snmp_get_errmsg (pdu.error_status); -        if(msg) -            errx(1, "snmp error from host '%s': %s", hostname, msg); -        else -            errx(1, "unknown snmp error from host '%s': %d", hostname, pdu.error_status); -        return; -    } - -    subid = ret = 1; - -    if(pdu.nbindings > 0) -    { -        val = &(pdu.bindings[pdu.nbindings - 1]); -        subid = asn_compare_oid(&ctx.oid_first, &(val->var)) == 0 || -                asn_is_suboid(&ctx.oid_first, &(val->var)); -    } - -    /* Print the packet values */ -    if(!ctx.recursive || subid) -        ret = print_resp(&pdu, server_get_time()); - -    if(ret && ctx.recursive && subid) -    { -        /* If recursive, move onto next one */ -        setup_next(&(pdu.bindings[pdu.nbindings - 1])); -        send_req(); -        return; -    } - -    server_stop (); +	struct snmp_value value; +	struct snmp_value match; +	asn_subid_t sub; +	int matched, ret; + +	ASSERT (ctx.has_query); +	memset (&value, 0, sizeof (value)); +	memset (&match, 0, sizeof (match)); + +	/* Loop looking for the value */ +	for (sub = 0; ; ++sub) { + +		/* Build up the query OID we're going for */ +		memcpy (&value.var, &ctx.query_oid, sizeof (value.var)); +		ASSERT (value.var.len < ASN_MAXOIDLEN); +		value.var.subs[value.var.len] = sub; +		value.var.len++; + +		/* Do the request */ +		ret = snmp_engine_sync (ctx.host, ctx.community, ctx.version, +		                        0, ctx.timeout, SNMP_PDU_GET, &value); + +		/* Try and see if its not zero based */ +		if (ret == SNMP_ERR_NOSUCHNAME && sub == 0) +			continue; + +		if (ret != SNMP_ERR_NOERROR) { +			had_failure (ret); +			return; +		} + +		matched = 0; + +		/* Match the results */ +		if (ctx.query_match) +			matched = snmp_engine_match (&value, ctx.query_match); + +		/* When query match is null, anything matches */ +		else +			matched = 1; + +		snmp_value_clear (&value); + +		if (matched) +			break; +	} + +	memcpy (&value.var, &ctx.request_oid, sizeof (value.var)); +	ASSERT (value.var.len < ASN_MAXOIDLEN); +	value.var.subs[value.var.len] = sub; +	value.var.len++; + +	ret = snmp_engine_sync (ctx.host, ctx.community, ctx.version, +	                        0, ctx.timeout, SNMP_PDU_GET, &value); + +	if (ret != SNMP_ERR_NOERROR) +		had_failure (ret); +	else +		print_result (&value);  } -static int -send_timer(uint64_t when, void* arg) +static void +process_simple (void)  { -    if(ctx.lastsend == 0) -        return 1; +	struct snmp_value value; +	int ret; + +	memset (&value, 0, sizeof (value)); +	memcpy (&value.var, &ctx.request_oid, sizeof (value.var)); -    /* Check for timeouts */ -    if(ctx.lastsend + ctx.timeout < when) -        errx(1, "timed out waiting for response from server"); +	ret = snmp_engine_sync (ctx.host, ctx.community, ctx.version, +	                        0, ctx.timeout, SNMP_PDU_GET, &value); -    /* Resend packets when no response */ -    if(ctx.retries < MAX_RETRIES && ctx.lastsend + RESEND_TIMEOUT < when) -        send_req(); +	if (ret != SNMP_ERR_NOERROR) +		had_failure (ret); +	else +		print_result (&value); -    return 1; +	snmp_value_clear (&value);  }  /* ----------------------------------------------------------------------------- @@ -355,7 +391,7 @@ send_timer(uint64_t when, void* arg)  static void  usage()  { -    fprintf(stderr, "usage: rrdbot-get [-Mnr] [-t timeout] [-m mibdir] snmp://community@host/oid\n"); +    fprintf(stderr, "usage: rrdbot-get [-Mnrv] [-t timeout] [-m mibdir] snmp://community@host/oid\n");      fprintf(stderr, "       rrdbot-get -V\n");      exit(2);  } @@ -368,100 +404,103 @@ version()  }  int -main(int argc, char* argv[]) +main (int argc, char* argv[])  { -    struct sockaddr_in addr; -    char ch; -    char* t; - -    /* Defaults */ -    memset(&ctx, 0, sizeof(ctx)); -    ctx.socket = -1; -    ctx.timeout = DEFAULT_TIMEOUT; - -    /* Parse the arguments nicely */ -    while((ch = getopt(argc, argv, "m:Mnrt:V")) != -1) -    { -        switch(ch) -        { - -        /* mib directory */ -        case 'm': -            mib_directory = optarg; -            break; - -        /* MIB load warnings */ -        case 'M': -            mib_warnings = 1; -            break; - -        /* Numeric output */ -        case 'n': -            ctx.numeric = 1; -            break; - -        /* SNMP walk (recursive)*/ -        case 'r': -            ctx.recursive = 1; -            break; - -        /* The timeout */ -        case 't': -            ctx.timeout = strtoul(optarg, &t, 10); -            if(*t) -                errx(2, "invalid timeout: %s", optarg); -            ctx.timeout *= 1000; -            break; - -        /* Print version number */ -        case 'V': -            version(); -            break; - -        /* Usage information */ -        case '?': -        default: -            usage(); -            break; -        } -    } - -    argc -= optind; -    argv += optind; - -    if(argc != 1) -        usage(); - -    server_init(); - -    setup_req(argv[0]); - -    /* Setup the SNMP socket */ -    ctx.socket = socket(PF_INET, SOCK_DGRAM, 0); -    if(ctx.socket < 0) -        err(1, "couldn't open snmp socket"); -    memset(&addr, 0, sizeof(addr)); -    addr.sin_family = AF_INET; -    if(bind(ctx.socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) -        err(1, "couldn't listen on port"); -    if(server_watch(ctx.socket, SERVER_READ, receive_resp, NULL) == -1) -        err(1, "couldn't listen on socket"); - -    /* Send off first request */ -    send_req(); - -    /* We fire off the resend timer every 1/5 second */ -    if(server_timer(RESEND_TIMEOUT, send_timer, NULL) == -1) -        err(1, "couldn't setup timer"); - -    /* Wait for responses */ -    server_run(); - -    /* Done */ -    server_unwatch(ctx.socket); -    close(ctx.socket); - -    server_uninit(); - -    return 0; +	char ch; +	char* t; + +	/* Defaults */ +	memset (&ctx, 0, sizeof (ctx)); +	ctx.timeout = DEFAULT_TIMEOUT; + +	/* Parse the arguments nicely */ +	while ((ch = getopt (argc, argv, "m:Mnrt:vV")) != -1) { +		switch (ch) +		{ + +		/* mib directory */ +		case 'm': +			mib_directory = optarg; +			break; + +		/* MIB load warnings */ +		case 'M': +			mib_warnings = 1; +			break; + +		/* Numeric output */ +		case 'n': +			ctx.numeric = 1; +			break; + +		/* SNMP walk (recursive)*/ +		case 'r': +			ctx.recursive = 1; +			break; + +		/* The timeout */ +		case 't': +			ctx.timeout = strtoul (optarg, &t, 10); +			if (*t) +				errx (2, "invalid timeout: %s", optarg); +			ctx.timeout *= 1000; +			break; + +		/* Verbose */ +		case 'v': +			ctx.verbose = 1; +			break; + +		/* Print version number */ +		case 'V': +			version (); +			break; + +		/* Usage information */ +		case '?': +		default: +			usage (); +			break; +		} +	} + +	argc -= optind; +	argv += optind; + +	if(argc != 1) +		usage (); + +	server_init (); +    	snmp_engine_init (MAX_RETRIES); + +    	parse_argument (argv[0]); + +    	/* +    	 * Recursive query walks everything at or lower than the +    	 * specified OID in the tree. +    	 */ +    	if (ctx.recursive) { +    		if (ctx.has_query) +    			errx (2, "cannot do a recursive table query"); +    		process_recursive (); + +    	/* +    	 * Does a table query, lookup the appropriate row, and +    	 * the value of the OID for that row. +    	 */ +    	} else if (ctx.has_query) { +    		ASSERT (!ctx.recursive); +    		process_query (); + +    	/* +    	 * A simple value lookup. +    	 */ +    	} else { +    		process_simple (); +    	} + +    	snmp_engine_stop (); +    	server_uninit (); + +    	return 0;  } | 
