summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
authorStef Walter <stef@memberwebs.com>2008-03-02 01:25:00 +0000
committerStef Walter <stef@memberwebs.com>2008-03-02 01:25:00 +0000
commit9a78f86f773cbf34e29ec51fc06e3f04072c88d0 (patch)
tree00054e6e536769a35b4215567755494486cc36ec /common
parentec1a79b0f75cfd34085e046ecb30382a402ea318 (diff)
- Support failover between multiple agents
- Support table queries - Major refactoring of internals.
Diffstat (limited to 'common')
-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
11 files changed, 1292 insertions, 9 deletions
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_*/