summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rw-r--r--Makefile.am4
-rw-r--r--bsnmp/snmp.c54
-rw-r--r--bsnmp/snmp.h5
-rw-r--r--common/Makefile.am16
-rw-r--r--common/compat.c11
-rw-r--r--common/config-parser.c85
-rw-r--r--common/config-parser.h8
-rw-r--r--common/hash.c19
-rw-r--r--common/hash.h5
-rw-r--r--common/log.c59
-rw-r--r--common/log.h58
-rw-r--r--common/server-mainloop.c2
-rw-r--r--common/snmp-engine.c1011
-rw-r--r--common/snmp-engine.h27
-rw-r--r--configure.in1
-rw-r--r--daemon/Makefile.am22
-rw-r--r--daemon/config.c290
-rw-r--r--daemon/poll-engine.c454
-rw-r--r--daemon/rrd-update.c15
-rw-r--r--daemon/rrdbotd.c56
-rw-r--r--daemon/rrdbotd.h72
-rw-r--r--daemon/snmp-engine.c727
-rw-r--r--doc/rrdbot-get.128
-rw-r--r--doc/rrdbot.conf.537
-rw-r--r--doc/rrdbotd.89
-rw-r--r--doc/traffic-example.conf6
-rw-r--r--tools/Makefile.am23
-rw-r--r--tools/rrdbot-get.c707
29 files changed, 2449 insertions, 1367 deletions
diff --git a/ChangeLog b/ChangeLog
index fa1e7dd..1aff33a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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;
}