summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.am6
-rw-r--r--src/common/server-mainloop.c424
-rw-r--r--src/common/server-mainloop.h27
-rw-r--r--src/common/sock-any.c (renamed from src/common/sock_any.c)2
-rw-r--r--src/common/sock-any.h (renamed from src/common/sock_any.h)0
-rw-r--r--src/common/stringx.c14
-rw-r--r--src/common/stringx.h3
-rw-r--r--src/config.c83
-rw-r--r--src/rrdbotd.c46
-rw-r--r--src/rrdbotd.h31
-rw-r--r--src/snmp-engine.c626
11 files changed, 1216 insertions, 46 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 9f460fb..2c345c8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2,8 +2,10 @@ SUBDIRS = bsnmp
sbin_PROGRAMS = rrdbotd
-rrdbotd_SOURCES = rrdbotd.c rrdbotd.h config.c snmp-help.c \
- snmpclient.c snmpclient.h \
+rrdbotd_SOURCES = rrdbotd.c rrdbotd.h config.c \
+ snmp-help.c snmp-engine.c \
+ common/server-mainloop.c common/server-mainloop.h \
+ common/sock-any.h common/sock-any.c \
usuals.h common/stringx.h common/stringx.c common/hash.h common/hash.c
rrdbotd_CFLAGS = -I${top_srcdir}/src/common/ -I${top_srcdir} -Ibsnmp
rrdbotd_LDADD = $(top_builddir)/src/bsnmp/libbsnmp-custom.a
diff --git a/src/common/server-mainloop.c b/src/common/server-mainloop.c
new file mode 100644
index 0000000..07f02b2
--- /dev/null
+++ b/src/common/server-mainloop.c
@@ -0,0 +1,424 @@
+
+#include "usuals.h"
+#include <errno.h>
+#include <sys/time.h>
+
+#include "server-mainloop.h"
+
+typedef struct _socket_callback
+{
+ int fd;
+ server_socket_callback callback;
+ void* arg;
+
+ struct _socket_callback* next;
+}
+socket_callback;
+
+typedef struct _timer_callback
+{
+ struct timeval at;
+ struct timeval interval;
+ server_timer_callback callback;
+ void* arg;
+
+ struct _timer_callback* next;
+}
+timer_callback;
+
+typedef struct _server_context
+{
+ int stopped;
+ fd_set read_fds;
+ fd_set write_fds;
+ int max_fd;
+ socket_callback* callbacks;
+ timer_callback* timers;
+}
+server_context;
+
+/* Global context */
+static server_context ctx;
+
+static void
+timeval_add(struct timeval* t1, struct timeval* t2)
+{
+ ASSERT(t1->tv_usec < 1000000);
+ ASSERT(t2->tv_usec < 1000000);
+
+ t1->tv_sec += t2->tv_sec;
+ t1->tv_usec += t2->tv_usec;
+ if(t1->tv_usec >= 1000000)
+ {
+ t1->tv_usec -= 1000000;
+ t1->tv_sec += 1;
+ }
+}
+
+static void
+timeval_subtract(struct timeval* t1, struct timeval* t2)
+{
+ ASSERT(t1->tv_usec < 1000000);
+ ASSERT(t2->tv_usec < 1000000);
+
+ t1->tv_sec -= t2->tv_sec;
+ if(t1->tv_usec < t2->tv_usec)
+ {
+ t1->tv_usec += 1000000;
+ t1->tv_sec -= 1;
+ }
+ t1->tv_usec -= t2->tv_usec;
+}
+
+static int
+timeval_compare(struct timeval* t1, struct timeval* t2)
+{
+ ASSERT(t1->tv_usec < 1000000);
+ ASSERT(t2->tv_usec < 1000000);
+
+ if(t1->tv_sec > t2->tv_sec)
+ return 1;
+ else if(t1->tv_sec < t2->tv_sec)
+ return -1;
+ else
+ {
+ if(t1->tv_usec > t2->tv_usec)
+ return 1;
+ else if(t1->tv_usec < t2->tv_usec)
+ return -1;
+ else
+ return 0;
+ }
+}
+
+#define timeval_empty(tv) \
+ ((tv)->tv_sec == 0 && (tv)->tv_usec == 0)
+
+#define timeval_to_ms(tv) \
+ ((((uint64_t)(tv).tv_sec) * 1000L) + (((uint64_t)(tv).tv_usec) / 1000L))
+
+static int
+timeval_dump(struct timeval* tv)
+{
+ fprintf(stderr, "{ %d:%d }", tv->tv_sec, tv->tv_usec / 1000);
+}
+
+static int
+add_timer(int ms, int oneshot, server_timer_callback callback, void* arg)
+{
+ struct timeval interval;
+ timer_callback* cb;
+ int i;
+
+ ASSERT(ms > 0);
+ ASSERT(callback != NULL);
+
+ interval.tv_sec = ms / 1000;
+ interval.tv_usec = (ms % 1000) * 1000; /* into micro seconds */
+
+ cb = (timer_callback*)calloc(1, sizeof(*cb));
+ if(!cb)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if(gettimeofday(&(cb->at), NULL) == -1)
+ {
+ free(cb);
+ return -1;
+ }
+
+ timeval_add(&(cb->at), &interval);
+
+ if (oneshot)
+ memset(&(cb->interval), 0, sizeof(cb->interval));
+ else
+ memcpy(&(cb->interval), &interval, sizeof(cb->interval));
+
+ cb->callback = callback;
+ cb->arg = arg;
+
+ cb->next = ctx.timers;
+ ctx.timers = cb;
+
+ return 0;
+}
+
+static timer_callback*
+remove_timer(timer_callback* timcb)
+{
+ timer_callback* cb;
+ timer_callback* next;
+ int i;
+
+ if(!ctx.timers)
+ return;
+
+ /* First in list */;
+ if(ctx.timers == timcb)
+ {
+ cb = ctx.timers;
+ ctx.timers = ctx.timers->next;
+ free(cb);
+ return ctx.timers;
+ }
+
+ /* One ahead processing of rest */
+ for(cb = ctx.timers; cb->next; cb = cb->next)
+ {
+ if(cb->next == timcb)
+ {
+ next = cb->next->next;
+ free(cb->next);
+ cb->next = next;
+ return cb->next;
+ }
+ }
+}
+
+void
+server_init()
+{
+ memset(&ctx, 0, sizeof (ctx));
+ FD_ZERO(&ctx.read_fds);
+ FD_ZERO(&ctx.write_fds);
+
+ ctx.max_fd = -1;
+ ctx.stopped = 1;
+ ctx.callbacks = NULL;
+ ctx.timers = NULL;
+}
+
+void
+server_uninit()
+{
+ timer_callback* timcb;
+ timer_callback* timn;
+ socket_callback* sockcb;
+ socket_callback* sockn;
+
+ for(timcb = ctx.timers; timcb; timcb = timn)
+ {
+ timn = timcb->next;
+ free(timcb);
+ }
+
+ ctx.timers = NULL;
+
+ for(sockcb = ctx.callbacks; sockcb; sockcb = sockn)
+ {
+ sockn = sockcb->next;
+ free(sockcb);
+ }
+
+ ctx.timers = NULL;
+}
+
+uint64_t
+server_get_time()
+{
+ struct timeval tv;
+ if(gettimeofday(&tv, NULL) == -1)
+ return 0L;
+ return timeval_to_ms(tv);
+}
+
+int
+server_run()
+{
+ struct timeval* timeout;
+ struct timeval tv, current;
+ timer_callback* timcb;
+ socket_callback* sockcb;
+ fd_set rfds, wfds;
+ int r, i;
+
+ /* No watches have been set */
+ ASSERT(ctx.max_fd > -1);
+
+ ctx.stopped = 0;
+
+ while(!ctx.stopped)
+ {
+ /* Watch for the various fds */
+ memcpy(&rfds, &ctx.read_fds, sizeof(rfds));
+ memcpy(&wfds, &ctx.write_fds, sizeof(wfds));
+
+ /* Prepare for timers */
+ timeout = NULL;
+ if(gettimeofday(&current, NULL) == -1)
+ return -1;
+
+ /* Cycle through timers */
+ for(timcb = ctx.timers; timcb; )
+ {
+ ASSERT(timcb->callback);
+
+ /* Call any timers that have already passed */
+ if(timeval_compare(&current, &timcb->at) >= 0)
+ {
+ /* Convert to milliseconds, and make the call */
+ r = (timcb->callback)(timeval_to_ms(current), timcb->arg);
+
+ /* Reset timer if so desired */
+ if (r == 1 && !timeval_empty(&timcb->interval))
+ {
+ timeval_add(&timcb->at, &timcb->interval);
+
+ /* If the time has already passed, just use current time */
+ if(timeval_compare(&(timcb->at), &current) <= 0)
+ memcpy(&(timcb->at), &current, sizeof(timcb->at));
+ }
+
+ /* Otherwise remove it. Either one shot, or returned 0 */
+ else
+ {
+ timcb = remove_timer(timcb);
+ continue;
+ }
+ }
+
+ /* Get soonest timer */
+ if (!timeout || timeval_compare(timeout, &timcb->at) < 0)
+ timeout = &timcb->at;
+
+ timcb = timcb->next;
+ }
+
+ /* Convert to an offset */
+ if(timeout)
+ {
+ memcpy(&tv, timeout, sizeof(tv));
+ timeout = &tv;
+ timeval_subtract(timeout, &current);
+ }
+
+ /* fprintf(stderr, "selecting with timeout: ");
+ timeval_dump(timeout);
+ fprintf(stderr, "\n"); */
+
+ r = select(ctx.max_fd, &rfds, &wfds, NULL, timeout);
+ if (r < 0)
+ {
+ /* Interrupted so try again, and possibly exit */
+ if (errno == EINTR)
+ continue;
+
+ /* Programmer errors */
+ ASSERT (errno != EBADF);
+ ASSERT (errno != EINVAL);
+ return r;
+ }
+
+ /* Timeout, just jump to timeout processing */
+ if(r == 0)
+ continue;
+
+ for(sockcb = ctx.callbacks; sockcb; sockcb = sockcb->next)
+ {
+ ASSERT(sockcb->fd != -1);
+
+ /* Call any that are set */
+ if (FD_ISSET(sockcb->fd, &rfds))
+ (sockcb->callback)(sockcb->fd, SERVER_READ, sockcb->arg);
+ if (FD_ISSET(sockcb->fd, &wfds))
+ (sockcb->callback)(sockcb->fd, SERVER_WRITE, sockcb->arg);
+ }
+ }
+
+ return 0;
+}
+
+void
+server_stop()
+{
+ ctx.stopped = 1;
+}
+
+int
+server_stopped()
+{
+ return ctx.stopped;
+}
+
+int
+server_watch(int fd, int type, server_socket_callback callback, void* arg)
+{
+ socket_callback* cb;
+ int i;
+ ASSERT(type != 0);
+ ASSERT(fd != -1);
+ ASSERT(callback != NULL);
+
+ cb = (socket_callback*)calloc(sizeof(*cb), 1);
+ if(!cb)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ cb->fd = fd;
+ cb->callback = callback;
+ cb->arg = arg;
+
+ cb->next = ctx.callbacks;
+ ctx.callbacks = cb;
+
+ if (type & SERVER_READ)
+ FD_SET(fd, &ctx.read_fds);
+ if (type & SERVER_WRITE)
+ FD_SET(fd, &ctx.write_fds);
+
+ if(fd >= ctx.max_fd)
+ ctx.max_fd = fd + 1;
+
+ return 0;
+}
+
+void
+server_unwatch(int fd)
+{
+ socket_callback* cb;
+ int i;
+
+ ASSERT(fd != -1);
+
+ FD_CLR(fd, &ctx.read_fds);
+ FD_CLR(fd, &ctx.write_fds);
+
+ if(!ctx.callbacks)
+ return;
+
+ /* First in list */;
+ if(ctx.callbacks->fd == fd)
+ {
+ cb = ctx.callbacks;
+ ctx.callbacks = cb->next;
+ free(cb);
+ return;
+ }
+
+ /* One ahead processing of rest */
+ for(cb = ctx.callbacks; cb->next; cb = cb->next)
+ {
+ if(cb->next->fd == fd)
+ {
+ cb->next = cb->next->next;
+ free(cb->next);
+ return;
+ }
+ }
+}
+
+int
+server_timer(int ms, server_timer_callback callback, void* arg)
+{
+ return add_timer(ms, 0, callback, arg);
+}
+
+int
+server_oneshot(int ms, server_timer_callback callback, void* arg)
+{
+ return add_timer(ms, 1, callback, arg);
+}
diff --git a/src/common/server-mainloop.h b/src/common/server-mainloop.h
new file mode 100644
index 0000000..ceff28d
--- /dev/null
+++ b/src/common/server-mainloop.h
@@ -0,0 +1,27 @@
+
+#ifndef __SERVER_MAINLOOP_H__
+#define __SERVER_MAINLOOP_H__
+
+#include <stdint.h>
+
+/* TODO: Prefix functions with svr */
+
+#define SERVER_READ 0x01
+#define SERVER_WRITE 0x02
+
+typedef void (*server_socket_callback)(int fd, int type, void* arg);
+/* TODO: We should declare our own time type: 'mstime' */
+typedef int (*server_timer_callback)(uint64_t when, void* arg);
+
+void server_init();
+void server_uninit();
+int server_run();
+void server_stop();
+int server_stopped();
+int server_watch(int fd, int type, server_socket_callback callback, void* arg);
+void server_unwatch(int fd);
+int server_timer(int length, server_timer_callback callback, void* arg);
+int server_oneshot(int length, server_timer_callback callback, void* arg);
+uint64_t server_get_time();
+
+#endif /* __SERVER_MAINLOOP_H__ */
diff --git a/src/common/sock_any.c b/src/common/sock-any.c
index fc38768..1938d5d 100644
--- a/src/common/sock_any.c
+++ b/src/common/sock-any.c
@@ -46,7 +46,7 @@
#include <string.h>
#include <stdio.h>
-#include "sock_any.h"
+#include "sock-any.h"
#include <arpa/inet.h>
diff --git a/src/common/sock_any.h b/src/common/sock-any.h
index 31cb13b..31cb13b 100644
--- a/src/common/sock_any.h
+++ b/src/common/sock-any.h
diff --git a/src/common/stringx.c b/src/common/stringx.c
index 06e4879..89deb9b 100644
--- a/src/common/stringx.c
+++ b/src/common/stringx.c
@@ -108,3 +108,17 @@ int strtob(const char* str)
return -1;
}
+
+size_t
+strlcpy(char *dst, const char *src, size_t len)
+{
+ size_t ret = strlen(dst);
+
+ while (len > 1) {
+ *dst++ = *src++;
+ len--;
+ }
+ if (len > 0)
+ *dst = '\0';
+ return (ret);
+}
diff --git a/src/common/stringx.h b/src/common/stringx.h
index 2fff195..042cf2d 100644
--- a/src/common/stringx.h
+++ b/src/common/stringx.h
@@ -47,4 +47,7 @@ char* trim_space(char* data);
int strtob(const char* str);
+size_t
+strlcpy(char *dst, const char *src, size_t len);
+
#endif /* __STRINGX_H__ */
diff --git a/src/config.c b/src/config.c
index 066203f..ca64514 100644
--- a/src/config.c
+++ b/src/config.c
@@ -57,7 +57,8 @@ typedef struct _config_ctx
{
const char* confname;
char* configmem;
- uint32_t interval;
+ uint interval;
+ uint timeout;
rb_item* items;
}
config_ctx;
@@ -77,6 +78,48 @@ 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)
{
@@ -93,8 +136,12 @@ config_done(config_ctx* ctx)
if(ctx->interval == 0)
errx(2, "no interval specified in config file: %s", ctx->confname);
+ if(ctx->timeout == 0)
+ ctx->timeout = g_state.timeout;
+
/* And a nice key for lookups */
- snprintf(key, sizeof(key), "%d:%s/%s.rrd", ctx->interval, g_state.rrddir, ctx->confname);
+ snprintf(key, sizeof(key), "%d-%d:%s/%s.rrd", ctx->timeout,
+ ctx->interval, g_state.rrddir, ctx->confname);
key[sizeof(key) - 1] = 0;
/* See if we have one of these pollers already */
@@ -111,7 +158,8 @@ config_done(config_ctx* ctx)
ASSERT(t);
poll->rrdname = t + 1;
- poll->interval = ctx->interval;
+ poll->interval = ctx->interval * 1000;
+ poll->timeout = ctx->timeout * 1000;
/* Add it to the main lists */
poll->next = g_state.polls;
@@ -121,11 +169,13 @@ config_done(config_ctx* ctx)
/* Get the last item and add to the list */
for(it = ctx->items; it->next; it = it->next)
it->poller = poll;
- it->next->poller = poll;
+
+ ASSERT(it);
+ it->poller = poll;
/* Add the items to this poller */
it->next = poll->items;
- poll->items = ctx->items;
+ poll->items = sort_items_by_host(ctx->items);
/*
* This remains allocated for the life of the program as
@@ -203,10 +253,15 @@ parse_item(const char* field, char* uri, config_ctx *ctx)
parse_uri(uri, &scheme, &host, &user, &path, ctx);
ASSERT(scheme && host && path);
+ /* TODO: SNMP version support */
+
/* Currently we only support SNMP pollers */
if(strcmp(scheme, CONFIG_SNMP) != 0)
errx(2, "invalid poll scheme: %s", scheme);
+ /* TODO: THis code assumes all hosts have the same community
+ the lookups below won't work wehn host/community is different */
+
/* See if we can find an associated host */
rhost = (rb_host*)hsh_get(g_state.host_by_name, host, -1);
if(!rhost)
@@ -217,7 +272,20 @@ parse_item(const char* field, char* uri, config_ctx *ctx)
if(!rhost || !hsh_set(g_state.host_by_name, host, -1, rhost))
errx(1, "out of memory");
+ /* TODO: Version support */
+ rhost->version = 1;
rhost->name = host;
+ rhost->community = user ? user : "public";
+
+ /* TODO: Eventually resolving should be in a separate thread,
+ and done regularly */
+ if(sock_any_pton(host, &(rhost->address),
+ SANY_OPT_DEFPORT(161) | SANY_OPT_DEFLOCAL) == -1)
+ {
+ rb_message(LOG_WARNING, "couldn't resolve host address (ignoring): %s", host);
+ free(rhost);
+ return;
+ }
/* And add it to the list */
rhost->next = g_state.hosts;
@@ -230,12 +298,13 @@ parse_item(const char* field, char* uri, config_ctx *ctx)
errx(1, "out of memory");
ritem->rrdfield = field;
- ritem->community = user ? user : "public";
ritem->host = rhost;
ritem->poller = NULL; /* Set later in config_done */
+ ritem->req = NULL;
+ ritem->value = RB_UNKNOWN;
/* And parse the OID */
- if(rb_parse_mib(path, &(ritem->value)) == -1)
+ if(rb_parse_mib(path, &(ritem->snmpfield)) == -1)
errx(2, "invalid OID: %s", path + 1);
/* And add it to the list */
diff --git a/src/rrdbotd.c b/src/rrdbotd.c
index bee836e..a28f0b0 100644
--- a/src/rrdbotd.c
+++ b/src/rrdbotd.c
@@ -64,36 +64,6 @@ static int daemonized = 0;
static int debug_level = 7;
/* -----------------------------------------------------------------------------
- * TESTS
- */
-
-#define OIDX_ifInOctets { 11, { 1, 3, 6, 1, 2, 1, 2, 2, 1, 10, 2, } }
-struct asn_oid ifInOctens = OIDX_ifInOctets;
-
-static void
-test_bsnmpd()
-{
- struct snmp_pdu pdu, resp;
- int n;
-
- snmp_client_init(&snmp_client);
- snmp_client.trans = SNMP_TRANS_UDP;
- snmp_open("northstar-link.ws.local", NULL, "wsnettle", "public");
-
- snmp_pdu_create(&pdu, SNMP_PDU_GET);
-
- n = snmp_add_binding(&pdu, &ifInOctens, SNMP_SYNTAX_COUNTER, NULL);
- if (snmp_dialog(&pdu, &resp))
- errx(1, "No response from '%s': %s", snmp_client.chost, snmp_client.error);
-
- if (snmp_pdu_check(&pdu, &resp) <= 0)
- errx(1, "Error reading from server: %s", snmp_client.error);
-
- snmp_pdu_dump(&resp);
- printf("done\n");
-}
-
-/* -----------------------------------------------------------------------------
* CLEANUP
*/
@@ -208,6 +178,8 @@ usage()
exit(2);
}
+#include <values.h>
+
int
main(int argc, char* argv[])
{
@@ -220,6 +192,8 @@ main(int argc, char* argv[])
/* TODO: These should come from configure, and from arguments */
g_state.rrddir = "/data/projects/rrdui/work";
g_state.confdir = "/data/projects/rrdui/conf";
+ g_state.retries = 3;
+ g_state.timeout = 5000;
/* Parse the arguments nicely */
while((ch = getopt(argc, argv, "v")) != -1)
@@ -244,11 +218,21 @@ main(int argc, char* argv[])
argc -= optind;
argv += optind;
+ /* The mainloop server */
+ server_init();
+
+ /* Parse config and setup SNMP system */
rb_config_parse();
+ rb_snmp_engine_init();
- printf("a test\n");
+ /* Now let it go */
+ if(server_run() == -1)
+ err(1, "critical failure running SNMP engine");
+ /* Cleanups */
+ rb_snmp_engine_uninit();
rb_config_free();
+ server_uninit();
return 0;
}
diff --git a/src/rrdbotd.h b/src/rrdbotd.h
index 9c4f2b4..974c8f4 100644
--- a/src/rrdbotd.h
+++ b/src/rrdbotd.h
@@ -39,18 +39,23 @@
#ifndef __RRDBOTD_H__
#define __RRDBOTD_H__
+#include <values.h>
+
#include "asn1.h"
#include "snmp.h"
-#include "sock_any.h"
+#include "sock-any.h"
#include "hash.h"
/* -----------------------------------------------------------------------------
* DATA
*/
+#define RB_UNKNOWN -DBL_MAX
+
struct _rb_item;
struct _rb_poller;
struct _rb_host;
+struct _rb_request;
/*
* Note that all the members are either in the config memory
@@ -59,10 +64,14 @@ struct _rb_host;
typedef struct _rb_item
{
+ struct _rb_request* req;
+
/* Specific to this item */
const char* rrdfield;
- struct snmp_value value;
- const char* community;
+ struct snmp_value snmpfield;
+
+ /* The last value / current request */
+ double value;
/* Pointers to related */
const struct _rb_poller* poller;
@@ -76,6 +85,8 @@ rb_item;
typedef struct _rb_host
{
const char* name;
+ const char* community;
+ int version;
/* Host resolving and book keeping */
struct sockaddr_any address;
@@ -89,13 +100,14 @@ rb_host;
typedef struct _rb_poller
{
- /* The hash key is interval-rrdname */
+ /* The hash key is interval-timeout:rrdname */
char key[MAXPATHLEN];
/* This points into the memory above */
const char* rrdname;
uint32_t interval;
+ uint32_t timeout;
/* The things to poll. rb_poller owns this list */
rb_item* items;
@@ -113,6 +125,8 @@ typedef struct _rb_state
/* Settings from command line */
const char* confdir;
const char* rrddir;
+ uint retries;
+ uint timeout;
/* All the pollers/hosts */
rb_poller* polls;
@@ -148,6 +162,13 @@ void rb_config_free();
* SNMP HELPERS (snmp-help.c)
*/
-int rb_parse_mib(const char* oid, struct snmp_value* value);
+int rb_snmp_parse_mib(const char* oid, struct snmp_value* value);
+
+/* -----------------------------------------------------------------------------
+ * SNMP ENGINE (snmp-engine.c)
+ */
+
+void rb_snmp_engine_init();
+void rb_snmp_engine_uninit();
#endif /* __RRDBOTD_H__ */
diff --git a/src/snmp-engine.c b/src/snmp-engine.c
new file mode 100644
index 0000000..fef7ad4
--- /dev/null
+++ b/src/snmp-engine.c
@@ -0,0 +1,626 @@
+/*
+ * Copyright (c) 2005, Nate Nielsen
+ * 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
+ * Nate Nielsen <nielsen@memberwebs.com>
+ *
+ */
+
+#include "usuals.h"
+#include <errno.h>
+#include <syslog.h>
+
+#include <bsnmp/asn1.h>
+#include <bsnmp/snmp.h>
+
+#include "stringx.h"
+#include "rrdbotd.h"
+#include "server-mainloop.h"
+
+/* The socket to use */
+int snmp_socket = -1;
+
+/* The last request: start at a strange number */
+uint32_t snmp_request = 0x0A0A0A0A;
+
+/* Since we only deal with one packet at a time, global buffer */
+unsigned char snmp_buffer[0x1000];
+
+/* -----------------------------------------------------------------------------
+ * REQUESTS
+ */
+
+/* rb_request waaaaayyyyy too big */
+typedef struct _rb_request
+{
+ /* The SNMP request identifier */
+ uint32_t id;
+
+ uint64_t next_retry; /* Time of the next retry */
+ uint32_t interval; /* How long between retries */
+ uint64_t timeout; /* When this request times out */
+ uint32_t 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* req = NULL;
+ uint num;
+ int i, first, overlap = 0;
+
+ if(nrequests)
+ {
+ /* We allocate in a loop starting after the last allocation. */
+ for(i = (reqhigh + 1) % nrequests; 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;
+ fprintf(stderr, "REALLOCATING: %d!!!\n", nrequests);
+ requests = (rb_request*)realloc(requests, sizeof(rb_request) * num);
+ if(!requests)
+ {
+ /* Note we leave old requests allocated */
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ /* Clear out the new ones */
+ fprintf(stderr, "clearing: %d %d\n", sizeof(rb_request), num - nrequests);
+ memset(requests + nrequests, 0, sizeof(rb_request) * (num - nrequests));
+
+ /* We return the next one */
+ req = requests + nrequests;
+ nrequests = num;
+ }
+
+ /* 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, uint64_t 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;
+
+ rb_messagex(LOG_DEBUG, "collected poll values. sending them to rrd");
+
+ /* And send off our collection of values */
+ // rb_rrd_update(poll);
+}
+
+static void
+send_req(rb_request* req, uint64_t when)
+{
+ struct asn_buf b;
+ ssize_t ret;
+
+ 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->name);
+ else
+ rb_messagex(LOG_DEBUG, "sent request '%d' to: %s", req->id, req->host->name);
+ }
+
+ /* And update our bookkeeping */
+ req->sent++;
+ if(req->sent <= g_state.retries)
+ req->next_retry = when + req->interval;
+}
+
+static void
+timeout_req(rb_request* req, uint64_t 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->value = RB_UNKNOWN;
+ it->req = NULL;
+ }
+
+ else if(it->req)
+ incomplete = 1;
+ }
+
+ if(!incomplete)
+ finish_poll(poll, when);
+}
+
+static void
+check_req(rb_request* req, uint64_t 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, uint64_t when)
+{
+ struct snmp_value* value;
+ rb_poller* poll = req->poll;
+ rb_item* item;
+ int incomplete = 0;
+ 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->value = RB_UNKNOWN;
+ break;
+ case SNMP_SYNTAX_INTEGER:
+ item->value = value->v.integer;
+ break;
+ case SNMP_SYNTAX_COUNTER:
+ case SNMP_SYNTAX_GAUGE:
+ case SNMP_SYNTAX_TIMETICKS:
+ item->value = value->v.uint32;
+ break;
+ case SNMP_SYNTAX_COUNTER64:
+ item->value = value->v.counter64;
+ 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 = "value not available on snmp server for field: %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
+ rb_messagex(LOG_DEBUG, "got value for field '%s': %0.2f",
+ item->rrdfield, item->value);
+
+ /* Mark this value as done */
+ item->req = NULL;
+ break;
+ }
+ }
+ }
+
+ /* 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(uint64_t 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 < 3) ? 200 : 600;
+
+ /* Timeout is for the last packet sent, not first */
+ req->timeout = when + (req->interval * 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->value = RB_UNKNOWN;
+ }
+
+ 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;
+ 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]");
+
+ rb_messagex(LOG_DEBUG, "received packet from: %s", hostname);
+
+ /* 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;
+ }
+
+ /* Lookup and check the request properly */
+
+ req = find_req(pdu.request_id);
+ if(!req)
+ return;
+
+ if(pdu.error_status != SNMP_ERR_NOERROR)
+ {
+ /* TODO: Textual errors */
+ rb_message(LOG_ERR, "snmp error '%d' from host: %s", pdu.error_status, hostname);
+ return;
+ }
+
+ if(pdu.version != req->pdu.version)
+ rb_message(LOG_WARNING, "wrong version snmp packet from: %s", hostname);
+
+ rb_messagex(LOG_DEBUG, "response to request '%d' from: %s", req->id, hostname);
+ respond_req(req, &pdu, server_get_time());
+}
+
+static int
+resend_timer(uint64_t when, void* arg)
+{
+ int i;
+
+ /* Search forwards through the scrolling window */
+ for(i = (reqlow + 1) % nrequests; i <= reqhigh;
+ i = (i + 1) % nrequests)
+ {
+ if(requests[i].id)
+ check_req(&(requests[i]), when);
+ }
+
+ return 1;
+}
+
+static int
+prep_timer(uint64_t 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;
+}
+
+
+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");
+}
+
+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;
+ }
+}