diff options
-rw-r--r-- | src/Makefile.am | 6 | ||||
-rw-r--r-- | src/common/server-mainloop.c | 424 | ||||
-rw-r--r-- | src/common/server-mainloop.h | 27 | ||||
-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.c | 14 | ||||
-rw-r--r-- | src/common/stringx.h | 3 | ||||
-rw-r--r-- | src/config.c | 83 | ||||
-rw-r--r-- | src/rrdbotd.c | 46 | ||||
-rw-r--r-- | src/rrdbotd.h | 31 | ||||
-rw-r--r-- | src/snmp-engine.c | 626 |
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(¤t, 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(¤t, &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), ¤t) <= 0) + memcpy(&(timcb->at), ¤t, 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, ¤t); + } + + /* 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; + } +} |