From 9a78f86f773cbf34e29ec51fc06e3f04072c88d0 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Sun, 2 Mar 2008 01:25:00 +0000 Subject: - Support failover between multiple agents - Support table queries - Major refactoring of internals. --- ChangeLog | 5 + Makefile.am | 4 +- bsnmp/snmp.c | 54 ++- bsnmp/snmp.h | 5 +- common/Makefile.am | 16 + common/compat.c | 11 +- common/config-parser.c | 85 +++- common/config-parser.h | 8 +- common/hash.c | 19 + common/hash.h | 5 + common/log.c | 59 +++ common/log.h | 58 +++ common/server-mainloop.c | 2 +- common/snmp-engine.c | 1011 ++++++++++++++++++++++++++++++++++++++++++++++ common/snmp-engine.h | 27 ++ configure.in | 1 + daemon/Makefile.am | 22 +- daemon/config.c | 290 +++++-------- daemon/poll-engine.c | 454 +++++++++++++++++++++ daemon/rrd-update.c | 15 +- daemon/rrdbotd.c | 56 +-- daemon/rrdbotd.h | 72 ++-- daemon/snmp-engine.c | 727 --------------------------------- doc/rrdbot-get.1 | 28 ++ doc/rrdbot.conf.5 | 37 +- doc/rrdbotd.8 | 9 +- doc/traffic-example.conf | 6 + tools/Makefile.am | 23 +- tools/rrdbot-get.c | 707 +++++++++++++++++--------------- 29 files changed, 2449 insertions(+), 1367 deletions(-) create mode 100644 common/Makefile.am create mode 100644 common/log.c create mode 100644 common/log.h create mode 100644 common/snmp-engine.c create mode 100644 common/snmp-engine.h create mode 100644 daemon/poll-engine.c delete mode 100644 daemon/snmp-engine.c diff --git a/ChangeLog b/ChangeLog index fa1e7dd..1aff33a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +0.9 ????? + - Support failover between multiple agents + - Support table queries + - Major refactoring of internals. + 0.8 [2007-07-16] - Fix assertion when reallocating SNMP request memory diff --git a/Makefile.am b/Makefile.am index c3ce3c4..969582d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ -EXTRA_DIST = common mib acsite.m4 -SUBDIRS = bsnmp daemon tools mibs doc +EXTRA_DIST = mib acsite.m4 +SUBDIRS = bsnmp common daemon tools mibs doc # Clean up any EXTRA_DIST we're distributing dist-hook: diff --git a/bsnmp/snmp.c b/bsnmp/snmp.c index 6b2cc01..1a7fe46 100644 --- a/bsnmp/snmp.c +++ b/bsnmp/snmp.c @@ -408,7 +408,7 @@ snmp_pdu_decode(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *ip) case ASN_ERR_FAILED: case ASN_ERR_EOBUF: - snmp_pdu_free(pdu); + snmp_pdu_clear(pdu); return (SNMP_CODE_FAILED); case ASN_ERR_BADLEN: @@ -805,7 +805,7 @@ snmp_pdu_dump(const struct snmp_pdu *pdu) } void -snmp_value_free(struct snmp_value *value) +snmp_value_clear(struct snmp_value *value) { if (value->syntax == SNMP_SYNTAX_OCTETSTRING) free(value->v.octetstring.octets); @@ -834,12 +834,58 @@ snmp_value_copy(struct snmp_value *to, const struct snmp_value *from) } void -snmp_pdu_free(struct snmp_pdu *pdu) +snmp_pdu_clear(struct snmp_pdu *pdu) { u_int i; for (i = 0; i < pdu->nbindings; i++) - snmp_value_free(&pdu->bindings[i]); + snmp_value_clear(&pdu->bindings[i]); +} + +int +snmp_value_equal(const struct snmp_value *a, const struct snmp_value *b) +{ + if (asn_compare_oid (&a->var, &b->var) == 0) + return 0; + if (a->syntax != b->syntax) + return 0; + + switch (a->syntax) { + case SNMP_SYNTAX_NULL: + case SNMP_SYNTAX_NOSUCHOBJECT: + case SNMP_SYNTAX_NOSUCHINSTANCE: + case SNMP_SYNTAX_ENDOFMIBVIEW: + return 1; + + case SNMP_SYNTAX_INTEGER: + return a->v.integer == b->v.integer; + + case SNMP_SYNTAX_OCTETSTRING: + if (a->v.octetstring.len != b->v.octetstring.len) + return 0; + return memcmp (a->v.octetstring.octets, b->v.octetstring.octets, + a->v.octetstring.len) == 0; + + case SNMP_SYNTAX_OID: + if (a->v.oid.len != b->v.oid.len) + return 0; + return memcmp (&a->v.oid.subs, &b->v.oid.subs, + (b->v.oid.len * sizeof (b->v.oid.subs[0]))) == 0; + + case SNMP_SYNTAX_IPADDRESS: + return memcmp (&a->v.ipaddress, &b->v.ipaddress, sizeof (a->v.ipaddress)); + + case SNMP_SYNTAX_COUNTER: + case SNMP_SYNTAX_GAUGE: + case SNMP_SYNTAX_TIMETICKS: + return a->v.uint32 == b->v.uint32; + + case SNMP_SYNTAX_COUNTER64: + return a->v.counter64 == b->v.counter64; + + default: + return 0; + }; } /* diff --git a/bsnmp/snmp.h b/bsnmp/snmp.h index b708b3a..21390e4 100644 --- a/bsnmp/snmp.h +++ b/bsnmp/snmp.h @@ -152,11 +152,12 @@ enum snmp_code { SNMP_CODE_OORANGE, }; -void snmp_value_free(struct snmp_value *); +void snmp_value_clear(struct snmp_value *); int snmp_value_parse(const char *, enum snmp_syntax, union snmp_values *); int snmp_value_copy(struct snmp_value *, const struct snmp_value *); +int snmp_value_equal(const struct snmp_value *, const struct snmp_value *); -void snmp_pdu_free(struct snmp_pdu *); +void snmp_pdu_clear(struct snmp_pdu *); enum snmp_code snmp_pdu_decode(struct asn_buf *b, struct snmp_pdu *pdu, int32_t *); enum snmp_code snmp_pdu_encode(struct snmp_pdu *pdu, struct asn_buf *resp_b); diff --git a/common/Makefile.am b/common/Makefile.am new file mode 100644 index 0000000..61ef9a4 --- /dev/null +++ b/common/Makefile.am @@ -0,0 +1,16 @@ + +noinst_LIBRARIES = libcommon.a + +libcommon_a_SOURCES = \ + async-resolver.h async-resolver.c \ + config-parser.h config-parser.c \ + compat.h compat.c \ + hash.h hash.c \ + log.h log.c \ + server-mainloop.c server-mainloop.h \ + sock-any.h sock-any.c \ + snmp-engine.h snmp-engine.c \ + usuals.h + +libcommon_a_CFLAGS = -I${top_srcdir}/common/ -I${top_srcdir}/bsnmp/ -I${top_srcdir} + diff --git a/common/compat.c b/common/compat.c index 6f9c58d..53de9bf 100644 --- a/common/compat.c +++ b/common/compat.c @@ -136,15 +136,18 @@ strtob(const char* str) size_t strlcpy(char *dst, const char *src, size_t len) { - size_t ret = strlen(dst); + size_t ret = strlen(src); + size_t copied; - while (len > 1) { + while (ret > 0 && len > 1) { *dst++ = *src++; - len--; + --len; + --ret; + ++copied; } if (len > 0) *dst = '\0'; - return (ret); + return copied; } #endif /* HAVE_STRLCPY */ diff --git a/common/config-parser.c b/common/config-parser.c index cf1599d..f95d44e 100644 --- a/common/config-parser.c +++ b/common/config-parser.c @@ -36,6 +36,10 @@ */ #include "usuals.h" + +#include "config-parser.h" + +#include #include #include #include @@ -46,8 +50,6 @@ #include #include -#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 @@ -142,6 +142,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 +#include + +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 + * + */ + +#ifndef LOG_H_ +#define LOG_H_ + +#include + +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 + * + */ + +#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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 +#include + +typedef void (*snmp_response) (int request, int code, struct snmp_value *value, void *data); + +void snmp_engine_init (int retries); + +int snmp_engine_request (const char* host, const char* community, int version, + uint64_t interval, uint64_t timeout, int reqtype, + struct asn_oid *oid, snmp_response func, void *data); + +void snmp_engine_cancel (int reqid); + +void snmp_engine_flush (void); + +int snmp_engine_sync (const char* host, const char* community, int version, + uint64_t interval, uint64_t timeout, int reqtype, + struct snmp_value *value); + +void snmp_engine_stop (void); + +int snmp_engine_match (const struct snmp_value *value, const char *text); + +#endif /*SNMPENGINE_H_*/ diff --git a/configure.in b/configure.in index d247789..ea8d458 100644 --- a/configure.in +++ b/configure.in @@ -54,6 +54,7 @@ AC_MSG_RESULT() AC_CONFIG_FILES([Makefile mibs/Makefile + common/Makefile daemon/Makefile bsnmp/Makefile tools/Makefile diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 28dceaf..80945f6 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -2,14 +2,16 @@ sbin_PROGRAMS = rrdbotd rrdbotd_SOURCES = rrdbotd.c rrdbotd.h config.c \ - snmp-engine.c rrd-update.c \ - ../common/server-mainloop.c ../common/server-mainloop.h \ - ../common/sock-any.h ../common/sock-any.c \ - ../common/compat.h ../common/compat.c \ - ../common/hash.h ../common/hash.c \ - ../common/config-parser.h ../common/config-parser.c \ - ../common/async-resolver.h ../common/async-resolver.c \ + poll-engine.c rrd-update.c \ ../mib/mib-parser.h ../mib/mib-parser.c -rrdbotd_CFLAGS = -I${top_srcdir}/common/ -I${top_srcdir}/bsnmp/ -I${top_srcdir} \ - -DCONF_PREFIX=\"$(sysconfdir)\" -DDATA_PREFIX=\"$(datadir)\" -rrdbotd_LDADD = $(top_builddir)/bsnmp/libbsnmp-custom.a + +rrdbotd_CFLAGS = \ + -I${top_srcdir}/common/ \ + -I${top_srcdir}/bsnmp/ \ + -I${top_srcdir} \ + -DCONF_PREFIX=\"$(sysconfdir)\" \ + -DDATA_PREFIX=\"$(datadir)\" + +rrdbotd_LDADD = \ + $(top_builddir)/common/libcommon.a \ + $(top_builddir)/bsnmp/libbsnmp-custom.a diff --git a/daemon/config.c b/daemon/config.c index 1bf0e4b..70075ee 100644 --- a/daemon/config.c +++ b/daemon/config.c @@ -46,6 +46,7 @@ #include +#include "log.h" #include "rrdbotd.h" #include "config-parser.h" @@ -86,48 +87,6 @@ config_ctx; * CONFIG LOADING */ -static rb_item* -sort_items_by_host(rb_item *item) -{ - rb_item *sort = NULL; - rb_item *cur; - rb_item *it; - - while(item) - { - cur = item; - item = item->next; - cur->next = NULL; - - /* First item */ - if(!sort) - { - sort = cur; - continue; - } - - /* Before first item */ - else if(cur->host <= sort->host) - { - cur->next = sort; - sort = cur; - continue; - } - - for(it = sort; it->next; it = it->next) - { - if(cur->host <= sort->next->host) - break; - } - - ASSERT(it); - cur->next = it->next; - it->next = cur; - } - - return sort; -} - static void config_done(config_ctx* ctx) { @@ -187,7 +146,7 @@ config_done(config_ctx* ctx) /* Add the items to this poller */ it->next = poll->items; - poll->items = sort_items_by_host(ctx->items); + poll->items = ctx->items; } /* @@ -203,108 +162,125 @@ config_done(config_ctx* ctx) ctx->timeout = 0; } -static rb_item* -parse_item(const char* field, char* uri, config_ctx *ctx) +static void +parse_hosts (rb_item *item, char *host, config_ctx *ctx) { - char key[128]; - rb_item *ritem; - rb_host *rhost; - int r; - - enum snmp_version version; - const char *msg; - char* copy; - char* scheme; - char* host; - char* user; - char* path; - - /* Parse the SNMP URI */ - copy = strdup(uri); - msg = cfg_parse_uri(uri, &scheme, &host, &user, &path); - if(msg) - errx(2, "%s: %s: %s", ctx->confname, msg, copy); - free(copy); - - ASSERT(host && path); - - /* Currently we only support SNMP pollers */ - msg = cfg_parse_scheme(scheme, &version); - if(msg) - errx(2, "%s: %s", msg, scheme); + char *x; - /* - * Build a lookup key. We can only combine requests for the same - * host when the version and community match. - */ - user = user ? user : "public"; - snprintf(key, sizeof(key), "%d:%s:%s", version, host, user); - key[sizeof(key) - 1] = 0; + for(;;) { + x = strchr (host, ','); + if (x) + *x = 0; - /* See if we can find an associated host */ - rhost = (rb_host*)hsh_get(g_state.host_by_key, key, -1); - if(!rhost) - { - /* Make a new one if necessary */ - rhost = (rb_host*)xcalloc(sizeof(*rhost)); + if (*host) { + if (item->n_hostnames >= MAX_HOSTNAMES) { + log_warnx ("%s: too many host names: %s", ctx->confname, host); + break; + } - rhost->version = version; - rhost->hostname = host; - rhost->community = user; - rhost->is_resolved = 1; - rhost->resolve_interval = 0; - rhost->last_resolved = 0; + item->hostnames[item->n_hostnames] = host; + item->n_hostnames++; + } - /* Try and resolve the DNS name */ - r = sock_any_pton(host, &(rhost->address), - SANY_OPT_DEFPORT(161) | SANY_OPT_DEFLOCAL | SANY_OPT_NORESOLV); + if (!x) + break; - if(r == -1) - { - rb_message(LOG_WARNING, "couldn't parse host address (ignoring): %s", host); - free(rhost); - return NULL; - } + host = x + 1; + } - /* - * If we got back SANY_AF_DNS, then it needs resolving. The actual - * interval and stuff are worked out in rb_config_parse() once all - * the hosts, polls etc... have been parsed. - */ - if(r == SANY_AF_DNS) - rhost->is_resolved = 0; - - /* And add it to the list */ - rhost->next = g_state.hosts; - g_state.hosts = rhost; - - /* And into the hash table */ - if(!hsh_set(g_state.host_by_key, rhost->key, -1, rhost)) - errx(1, "out of memory"); - } + /* Default to localhost for a host name */ + if (!item->n_hostnames) { + log_warnx ("no host found in URI, defaulting to localhost"); + item->n_hostnames = 1; + item->hostnames[0] = "localhost"; + } + + item->hostindex = 0; +} + +static void +parse_query (rb_item *item, char *query, config_ctx *ctx) +{ + char *name, *value; + const char *msg; + + /* Parse the query if it exists */ + if (!query) + return; + + msg = cfg_parse_query (query, &name, &value, &query); + if (msg) + errx (2, "%s: %s", ctx->confname, msg); + + if (query && *query) + log_warnx ("%s: only using first query argument in snmp URI", ctx->confname); - /* Make a new item */ - ritem = (rb_item*)xcalloc(sizeof(*ritem)); - ritem->rrdfield = field; - ritem->host = rhost; - ritem->poller = NULL; /* Set later in config_done */ - ritem->req = NULL; - ritem->vtype = VALUE_UNSET; + item->has_query = 1; - /* And parse the OID */ - ritem->snmpfield.syntax = SNMP_SYNTAX_NULL; - memset(&(ritem->snmpfield.v), 0, sizeof(ritem->snmpfield.v)); - if(mib_parse(path, &(ritem->snmpfield.var)) == -1) - errx(2, "%s: invalid MIB: %s", ctx->confname, path); + /* And parse the query OID */ + if (mib_parse (name, &(item->query_oid)) == -1) + errx (2, "%s: invalid MIB: %s", ctx->confname, name); + if (item->query_oid.len >= ASN_MAXOIDLEN) + errx (2, "request OID is too long"); - rb_messagex(LOG_DEBUG, "parsed MIB into oid: %s -> %s", path, - asn_oid2str(&(ritem->snmpfield.var))); + log_debug ("parsed MIB into oid: %s -> %s", name, + asn_oid2str (&item->query_oid)); - /* And add it to the list */ - ritem->next = ctx->items; - ctx->items = ritem; + item->query_match = value; + item->query_last = 0; + item->query_value = 0; +} - return ritem; +static rb_item* +parse_item (const char *field, char *uri, config_ctx *ctx) +{ + rb_item *item; + enum snmp_version version; + const char *msg; + char *copy; + char *scheme, *host, *user, *path, *query; + + /* Parse the SNMP URI */ + copy = strdup (uri); + msg = cfg_parse_uri (uri, &scheme, &host, &user, &path, &query); + if (msg) + errx(2, "%s: %s: %s", ctx->confname, msg, copy); + free (copy); + + ASSERT (host && path); + + /* Currently we only support SNMP pollers */ + msg = cfg_parse_scheme (scheme, &version); + if (msg) + errx (2, "%s: %s: %s", ctx->confname, msg, scheme); + + /* Make a new item */ + item = (rb_item*)xcalloc (sizeof (*item)); + item->field = field; + item->community = user ? user : "public"; + item->version = version; + + item->poller = NULL; /* Set later in config_done */ + item->vtype = VALUE_UNSET; + + /* Parse the hosts, query */ + parse_hosts (item, host, ctx); + parse_query (item, query, ctx); + + /* And parse the main field OID */ + if (mib_parse (path, &(item->field_oid)) == -1) + errx (2, "%s: invalid MIB: %s", ctx->confname, path); + if (item->field_oid.len >= ASN_MAXOIDLEN) + errx (2, "request OID is too long"); + + log_debug ("parsed MIB into oid: %s -> %s", path, + asn_oid2str (&item->field_oid)); + + /* And add it to the list */ + item->next = ctx->items; + ctx->items = item; + + return item; } static void @@ -387,11 +363,9 @@ void rb_config_parse() { config_ctx ctx; - rb_poller* poll; /* Setup the hash tables properly */ g_state.poll_by_key = hsh_create(); - g_state.host_by_key = hsh_create(); memset(&ctx, 0, sizeof(ctx)); @@ -400,35 +374,6 @@ rb_config_parse() if(!g_state.polls) errx(1, "no config files found in config directory: %s", g_state.confdir); - - /* Organize the async resolve intervals */ - for(poll = g_state.polls; poll; poll = poll->next) - { - rb_item *item; - mstime resint; - - /* When less than three minutes, resolve once per minute */ - if(poll->interval <= 180000) - resint = 60000; - - /* When between 3 and 10 minutes resolve once per cycle */ - else if(poll->interval <= 600000) - resint = poll->interval; - - /* Otherwise resolve thrice per cycle */ - else - resint = poll->interval / 3; - - for(item = poll->items; item; item = item->next) - { - /* The lowest interval (since hosts can be shared by pollers) wins */ - if(!item->host->is_resolved && item->host->resolve_interval < resint) - { - rb_host* host = (rb_host*)item->host; - host->resolve_interval = resint; - } - } - } } /* ----------------------------------------------------------------------------- @@ -459,8 +404,7 @@ cfg_value(const char* filename, const char* header, const char* name, ASSERT(ctx->confname); ASSERT(name && value && header); - rb_messagex(LOG_DEBUG, "config: %s: [%s] %s = %s", - ctx->confname, header, name, value); + log_debug("config: %s: [%s] %s = %s", ctx->confname, header, name, value); config_value(header, name, value, ctx); @@ -490,17 +434,6 @@ free_items(rb_item* item) } } -static void -free_hosts(rb_host* host) -{ - rb_host* next; - for(; host; host = next) - { - next = host->next; - free(host); - } -} - static void free_pollers(rb_poller* poll) { @@ -519,9 +452,6 @@ void rb_config_free() { hsh_free(g_state.poll_by_key); - hsh_free(g_state.host_by_key); - - free_hosts(g_state.hosts); /* Note that rb_item's are owned by pollers */ free_pollers(g_state.polls); diff --git a/daemon/poll-engine.c b/daemon/poll-engine.c new file mode 100644 index 0000000..6130a89 --- /dev/null +++ b/daemon/poll-engine.c @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2008, Stefan Walter + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Stef Walter + * + */ + +#include "usuals.h" + +#include +#include +#include +#include + +#include +#include + +#include "log.h" +#include "rrdbotd.h" +#include "server-mainloop.h" +#include "snmp-engine.h" + +/* ----------------------------------------------------------------------------- + * PACKET HANDLING + */ + +static void +complete_request (rb_item *item, int code) +{ + int host; + + ASSERT (item); + + if (item->request) + snmp_engine_cancel (item->request); + item->request = 0; + + /* If we have multiple host names then try the next host */ + if (code != SNMP_ERR_NOERROR) { + host = (item->hostindex + 1) % item->n_hostnames; + if (host != item->hostindex) { + log_debug ("request failed, trying new host: %s", item->hostnames[host]); + item->hostindex = host; + } + } +} + +static void +cancel_request (rb_item *item, const char *reason) +{ + ASSERT (item); + ASSERT (reason); + ASSERT (item->request); + + log_debug ("value for field '%s': %s", item->field, reason); + item->vtype = VALUE_UNSET; + + complete_request (item, -1); +} + +static void +force_poll (rb_poller *poll, mstime when, const char *reason) +{ + rb_item *item; + int forced = 0; + + ASSERT (poll); + ASSERT (reason); + + /* Now see if the entire request is done */ + for (item = poll->items; item; item = item->next) { + if (item->request) { + cancel_request (item, reason); + forced = 1; + } + ASSERT (!item->request); + } + + if (forced) { + + /* + * We note the failure has having taken place halfway between + * the request and the current time. + */ + poll->last_polled = poll->last_request + ((when - poll->last_request) / 2); + + /* And send off our collection of values */ + rb_rrd_update (poll); + } +} + +static void +finish_poll (rb_poller *poll, mstime when) +{ + rb_item *item; + + ASSERT (poll); + + /* Now see if the entire request is done */ + for (item = poll->items; item; item = item->next) { + if (item->request) + return; + } + + /* Update the book-keeping */ + poll->last_polled = when; + + /* And send off our collection of values */ + rb_rrd_update (poll); +} + +static void +field_response (int request, int code, struct snmp_value *value, void *arg) +{ + rb_item *item = arg; + const char *msg = NULL; + + ASSERT (item->request == request); + + /* Mark this item as done */ + item->request = 0; + + /* Errors result in us writing U */ + if (code != SNMP_ERR_NOERROR) { + item->vtype = VALUE_UNSET; + + /* Parse the value from server */ + } else { + switch(value->syntax) + { + case SNMP_SYNTAX_NULL: + item->vtype = VALUE_UNSET; + break; + case SNMP_SYNTAX_INTEGER: + item->v.i_value = value->v.integer; + item->vtype = VALUE_REAL; + break; + case SNMP_SYNTAX_COUNTER: + case SNMP_SYNTAX_GAUGE: + case SNMP_SYNTAX_TIMETICKS: + item->v.i_value = value->v.uint32; + item->vtype = VALUE_REAL; + break; + case SNMP_SYNTAX_COUNTER64: + item->v.i_value = value->v.counter64; + item->vtype = VALUE_REAL; + break; + case SNMP_SYNTAX_OCTETSTRING: + case SNMP_SYNTAX_OID: + case SNMP_SYNTAX_IPADDRESS: + msg = "snmp server returned non numeric value for field: %s"; + break; + case SNMP_SYNTAX_NOSUCHOBJECT: + case SNMP_SYNTAX_NOSUCHINSTANCE: + case SNMP_SYNTAX_ENDOFMIBVIEW: + msg = "field not available on snmp server: %s"; + break; + default: + msg = "snmp server returned invalid or unsupported value for field: %s"; + break; + }; + + if (msg) + log_warnx (msg, item->field); + else if (item->vtype == VALUE_REAL) + log_debug ("got value for field '%s': %lld", + item->field, item->v.i_value); + else if (item->vtype == VALUE_FLOAT) + log_debug ("got value for field '%s': %.4lf", + item->field, item->v.f_value); + else + log_debug ("got value for field '%s': U", + item->field); + } + + complete_request (item, code); + + /* If the entire poll is done, then complete it */ + finish_poll (item->poller, server_get_time ()); +} + +static void +field_request (rb_item *item) +{ + int req; + + ASSERT (item); + ASSERT (!item->request); + + item->vtype = VALUE_UNSET; + + req = snmp_engine_request (item->hostnames[item->hostindex], item->community, + item->version, item->poller->interval, item->poller->timeout, + SNMP_PDU_GET, &item->field_oid, field_response, item); + item->request = req; +} + +/* Forward declaration */ +static void query_request (rb_item *item, int first); + +static void +query_response (int request, int code, struct snmp_value *value, void *arg) +{ + rb_item *item = arg; + struct asn_oid oid; + int matched, req, found; + + ASSERT (request == item->request); + ASSERT (item->has_query); + + /* + * This was the number we last appended. + */ + ASSERT (item->query_value >= 0); + item->request = 0; + + /* Problems communicating with the server? */ + if (code != SNMP_ERR_NOERROR && code != SNMP_ERR_NOSUCHNAME) { + complete_request (item, code); + return; + } + + found = 0; + matched = 0; + + if (code == SNMP_ERR_NOERROR) { + ASSERT (value); + + /* These all signify 'not found' in our book */ + switch (value->syntax) { + case SNMP_SYNTAX_NOSUCHOBJECT: + case SNMP_SYNTAX_NOSUCHINSTANCE: + case SNMP_SYNTAX_ENDOFMIBVIEW: + found = 0; + matched = 0; + break; + + /* See if we have a match */ + default: + if (item->query_match) + matched = snmp_engine_match (value, item->query_match); + + /* When query match is null, anything matches */ + else + matched = 1; + + found = 1; + break; + }; + } + + /* + * When we had found this before, but then can no longer find it, we + * start search again from the base. + */ + if (!matched && item->query_last != 0) { + item->query_last = 0; + query_request (item, 1); + + /* + * When we find no value at zero, then we skip ahead and see if + * perhaps its a one based table + */ + } else if (!found && item->query_value == 0) { + item->query_last = 0; + query_request (item, 0); + + /* + * Any other time we don't find a value, its game over for us, + * we didn't find a match and are out of values. + */ + } else if (!found) { + item->query_last = 0; + log_warn ("couldn't find match for query value: %s", item->query_match); + complete_request (item, SNMP_ERR_NOSUCHNAME); + + + /* + * Found a value but didn't match, so try next one. + */ + } else if (!matched) { + item->query_last = 0; + query_request (item, 0); + + /* + * When we have a match send off a new request, built from the original + * oid and the last numeric part of the query oid. + */ + } else { + + /* Build up the OID */ + oid = item->field_oid; + ASSERT (oid.len < ASN_MAXOIDLEN); + oid.subs[oid.len] = item->query_value; + ++oid.len; + + item->query_last = item->query_value; + item->vtype = VALUE_UNSET; + + req = snmp_engine_request (item->hostnames[item->hostindex], item->community, + item->version, item->poller->interval, item->poller->timeout, + SNMP_PDU_GET, &oid, field_response, item); + + item->request = req; + } +} + +static void +query_request (rb_item *item, int first) +{ + struct asn_oid oid; + int req; + + ASSERT (item); + ASSERT (!item->request); + ASSERT (item->has_query); + + item->vtype = VALUE_UNSET; + + /* + * Build up an appropriate oid. + * + * We first try any oid that worked last time, and see if + * it still has the same value, to avoid doing the brute + * force search each time needlessly. + */ + + /* The first time the request has been called */ + if (first) + item->query_value = item->query_last; + + /* Try the next one in turn */ + else + item->query_value = item->query_value + 1; + + /* Build up the OID */ + oid = item->query_oid; + ASSERT (oid.len < ASN_MAXOIDLEN); + oid.subs[oid.len] = item->query_value; + ++oid.len; + + /* Make the request */ + req = snmp_engine_request (item->hostnames[item->hostindex], item->community, + item->version, item->poller->interval, item->poller->timeout, + SNMP_PDU_GET, &oid, query_response, item); + + /* Mark item as active by this request */ + item->request = req; +} + +static int +poller_timer (mstime when, void *arg) +{ + rb_poller *poll = (rb_poller*)arg; + rb_item *item; + + /* + * If the previous poll has not completed, then we count it + * as a timeout. + */ + force_poll (poll, when, "timed out"); + + /* Mark this poller as starting requests now */ + poll->last_request = when; + + /* + * Send off the next query. This needs to be done after + * all the timeouts above, as the above could write to RRD. + */ + for (item = poll->items; item; item = item->next) { + if (item->has_query) + query_request (item, 1); + else + field_request (item); + } + + snmp_engine_flush (); + + return 1; +} + +static int +prep_timer (mstime when, void* arg) +{ + /* + * We don't prepare all timers at exactly the same time + * but we sort of randomly start the various timers. We're + * going to be hitting these over and over again, so there's + * lots of benefits to spreading them out randomly over a + * few seconds. + */ + + rb_poller* poll; + int next; + + /* All done? */ + if(!arg) + return 0; + + poll = (rb_poller*)arg; + if (server_timer (poll->interval, poller_timer, poll) == -1) + log_error ("couldn't setup poller timer"); + + /* Setup the next poller anywhere between 0 and 750 ms */ + next = rand () % 750; + server_oneshot (next, prep_timer, poll->next); + return 0; +} + + +void +rb_poll_engine_init (void) +{ + /* Start the preparation timers for setting up randomly */ + if (server_oneshot (100, prep_timer, g_state.polls) == -1) + err(1, "couldn't setup timer"); +} + +void +rb_poll_engine_uninit (void) +{ + +} diff --git a/daemon/rrd-update.c b/daemon/rrd-update.c index 7b43430..74e471b 100644 --- a/daemon/rrd-update.c +++ b/daemon/rrd-update.c @@ -45,6 +45,7 @@ #include +#include "log.h" #include "rrdbotd.h" #define MAX_NUMLEN 40 @@ -66,7 +67,7 @@ void rb_rrd_update(rb_poller *poll) for(it = poll->items; it; it = it->next) { - tlen += strlen(it->rrdfield) + 1; + tlen += strlen(it->field) + 1; ilen += 40; } @@ -79,7 +80,7 @@ void rb_rrd_update(rb_poller *poll) free(items); if(template) free(template); - rb_messagex(LOG_CRIT, "out of memory"); + log_errorx ("out of memory"); return; } @@ -95,7 +96,7 @@ void rb_rrd_update(rb_poller *poll) strlcat(items, ":", ilen); } - strlcat(template, it->rrdfield, tlen); + strlcat(template, it->field, tlen); if(it->vtype == VALUE_UNSET) strlcat(items, "U", ilen); @@ -120,15 +121,15 @@ void rb_rrd_update(rb_poller *poll) argv[3] = template; argv[4] = items; - rb_messagex(LOG_DEBUG, "updating RRD file: %s", poll->rrdname); - rb_messagex(LOG_DEBUG, "> template: %s", template); - rb_messagex(LOG_DEBUG, "> values: %s", items); + log_debug ("updating RRD file: %s", poll->rrdname); + log_debug ("> template: %s", template); + log_debug ("> values: %s", items); rrd_clear_error(); r = rrd_update(5, (char**)argv); if(r != 0) - rb_messagex(LOG_ERR, "couldn't update rrd file: %s: %s", + log_errorx ("couldn't update rrd file: %s: %s", poll->rrdname, rrd_get_error()); free(template); diff --git a/daemon/rrdbotd.c b/daemon/rrdbotd.c index 5b447a3..bbbc84b 100644 --- a/daemon/rrdbotd.c +++ b/daemon/rrdbotd.c @@ -37,6 +37,13 @@ */ #include "usuals.h" + +#include "async-resolver.h" +#include "log.h" +#include "rrdbotd.h" +#include "server-mainloop.h" +#include "snmp-engine.h" + #include #include #include @@ -48,9 +55,6 @@ #include #include -#include "rrdbotd.h" -#include "server-mainloop.h" -#include "async-resolver.h" /* The default command line options */ #define DEFAULT_CONFIG CONF_PREFIX "/rrdbot" @@ -105,11 +109,10 @@ test(int argc, char* argv[]) */ void -rb_vmessage(int level, int err, const char* msg, va_list ap) +log_vmessage(int level, int erno, const char* msg, va_list ap) { #define MAX_MSGLEN 1024 char buf[MAX_MSGLEN]; - int e = errno; if(daemonized) { if (level >= LOG_DEBUG) @@ -125,10 +128,10 @@ rb_vmessage(int level, int err, const char* msg, va_list ap) strlcpy(buf, msg, MAX_MSGLEN); stretrim(buf); - if(err) + if(erno) { strlcat(buf, ": ", MAX_MSGLEN); - strncat(buf, strerror(e), MAX_MSGLEN); + strncat(buf, strerror(erno), MAX_MSGLEN); } /* As a precaution */ @@ -141,24 +144,6 @@ rb_vmessage(int level, int err, const char* msg, va_list ap) vwarnx(buf, ap); } -void -rb_messagex(int level, const char* msg, ...) -{ - va_list ap; - va_start(ap, msg); - rb_vmessage(level, 0, msg, ap); - va_end(ap); -} - -void -rb_message(int level, const char* msg, ...) -{ - va_list ap; - va_start(ap, msg); - rb_vmessage(level, 1, msg, ap); - va_end(ap); -} - /* ----------------------------------------------------------------------------- * STARTUP */ @@ -195,13 +180,14 @@ writepid(const char* pidfile) FILE* f = fopen(pidfile, "w"); if(f == NULL) { - rb_message(LOG_WARNING, "couldn't open pid file: %s", pidfile); + log_warn("couldn't open pid file: %s", pidfile); return; } fprintf(f, "%d\n", (int)getpid()); if(ferror(f)) - rb_message(LOG_WARNING, "couldn't write to pid file: %s", pidfile); + log_warn("couldn't write to pid file: %s", pidfile); + fclose(f); } @@ -211,7 +197,7 @@ removepid(const char* pidfile) if(unlink(pidfile) < 0) { if(errno != ENOENT) - rb_message(LOG_WARNING, "couldn't remove pid file: %s", pidfile); + log_warn("couldn't remove pid file: %s", pidfile); } } @@ -319,7 +305,8 @@ main(int argc, char* argv[]) mib_uninit(); /* Rev up the main engine */ - rb_snmp_engine_init(); + snmp_engine_init(3); + rb_poll_engine_init(); if(daemonize) { @@ -327,14 +314,14 @@ main(int argc, char* argv[]) if(daemon(0, 0) == -1) err(1, "couldn't fork as a daemon"); - rb_messagex(LOG_DEBUG, "running as a daemon"); + log_debug("running as a daemon"); daemonized = 1; } /* Setup the Async DNS resolver */ if(async_resolver_init() < 0) { - rb_message(LOG_ERR, "couldn't initialize resolver"); + log_error("couldn't initialize resolver"); /* Allow things to proceed without resolver */ } @@ -352,16 +339,17 @@ main(int argc, char* argv[]) if(pidfile != NULL) writepid(pidfile); - rb_messagex(LOG_INFO, "rrdbotd version " VERSION " started up"); + log_info("rrdbotd version " VERSION " started up"); /* Now let it go */ if(server_run() == -1) err(1, "critical failure running SNMP engine"); - rb_messagex(LOG_INFO, "rrdbotd stopping"); + log_info("rrdbotd stopping"); /* Cleanups */ - rb_snmp_engine_uninit(); + rb_poll_engine_uninit(); + snmp_engine_stop(); rb_config_free(); async_resolver_uninit(); server_uninit(); diff --git a/daemon/rrdbotd.h b/daemon/rrdbotd.h index cfc8cb2..f907de2 100644 --- a/daemon/rrdbotd.h +++ b/daemon/rrdbotd.h @@ -55,8 +55,6 @@ typedef uint64_t mstime; struct _rb_item; struct _rb_poller; -struct _rb_host; -struct _rb_request; /* * Note that all the members are either in the config memory @@ -65,9 +63,28 @@ struct _rb_request; typedef struct _rb_item { - /* Specific to this item */ - const char* rrdfield; - struct snmp_value snmpfield; + /* The field name, RRD and display */ + const char* field; + + /* Connection information */ + const char* community; + int version; + + /* The oid that we are querying */ + struct asn_oid field_oid; + + /* Host names, with alternate hosts */ + #define MAX_HOSTNAMES 16 + const char* hostnames[MAX_HOSTNAMES]; + int hostindex; + int n_hostnames; + + /* Query related stuff */ + int has_query; + struct asn_oid query_oid; + const char* query_match; + asn_subid_t query_last; + int query_value; /* The last value / current request */ union @@ -81,38 +98,17 @@ typedef struct _rb_item #define VALUE_FLOAT 2 int vtype; - struct _rb_request* req; + /* A request in progress */ + int request; /* Pointers to related */ - const struct _rb_poller* poller; - const struct _rb_host* host; + struct _rb_poller* poller; /* Next in list of items */ struct _rb_item* next; } rb_item; -typedef struct _rb_host -{ - /* The hash key is version:hostname:community */ - char key[128]; - - const char* hostname; - const char* community; - int version; - - /* Host resolving and book keeping */ - struct sockaddr_any address; - mstime resolve_interval; - mstime last_resolve_try; - mstime last_resolved; - int is_resolved; - - /* Next in list of hosts */ - struct _rb_host* next; -} -rb_host; - typedef struct _rb_poller { /* The hash key is interval-timeout:rrdname */ @@ -128,6 +124,7 @@ typedef struct _rb_poller rb_item* items; /* Book keeping */ + mstime last_request; mstime last_polled; /* Next in list of pollers */ @@ -145,28 +142,15 @@ typedef struct _rb_state /* All the pollers/hosts */ rb_poller* polls; - rb_host* hosts; /* Quick lookups for responses */ hsh_t* poll_by_key; - hsh_t* host_by_key; } rb_state; /* One global rb_state with all the main settings */ extern rb_state g_state; -/* ----------------------------------------------------------------------------- - * UTILITIES (rrdbotd.c) - */ - -typedef void (*resolve_callback)(void *context, int unused, const char *name, - const unsigned char *addr, size_t addrlen); - -void rb_messagex(int level, const char* msg, ...); -void rb_message(int level, const char* msg, ...); -void rb_vmessage(int level, int err, const char* msg, va_list ap); - /* ----------------------------------------------------------------------------- * CONFIG (config.c) */ @@ -178,8 +162,8 @@ void rb_config_free(); * SNMP ENGINE (snmp-engine.c) */ -void rb_snmp_engine_init(); -void rb_snmp_engine_uninit(); +void rb_poll_engine_init(); +void rb_poll_engine_uninit(); /* ----------------------------------------------------------------------------- * RRD UPDATE CODE (rrd-update.c) diff --git a/daemon/snmp-engine.c b/daemon/snmp-engine.c deleted file mode 100644 index e79c6c2..0000000 --- a/daemon/snmp-engine.c +++ /dev/null @@ -1,727 +0,0 @@ -/* - * Copyright (c) 2005, Stefan Walter - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above - * copyright notice, this list of conditions and the - * following disclaimer. - * * Redistributions in binary form must reproduce the - * above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * * The names of contributors to this software may not be - * used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH - * DAMAGE. - * - * - * CONTRIBUTORS - * Stef Walter - * - */ - -#include "usuals.h" -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "rrdbotd.h" -#include "server-mainloop.h" -#include "async-resolver.h" - -/* The socket to use */ -static int snmp_socket = -1; - -/* The last request id */ -static uint32_t snmp_request = 100000; - -/* Since we only deal with one packet at a time, global buffer */ -static unsigned char snmp_buffer[0x1000]; - -/* ----------------------------------------------------------------------------- - * REQUESTS - */ - -typedef struct _rb_request -{ - /* The SNMP request identifier */ - uint32_t id; - - mstime next_retry; /* Time of the next retry */ - mstime last_sent; /* Time last sent */ - mstime interval; /* How long between retries */ - mstime timeout; /* When this request times out */ - uint sent; /* How many times we've sent */ - - /* The poller and host associated with this request */ - rb_poller* poll; - const rb_host* host; - - /* The actual request data */ - struct snmp_pdu pdu; -} -rb_request; - -/* a scrolling window on a loop */ -static rb_request* requests = NULL; -static int reqhigh = -1; -static int reqlow = -1; -static uint nrequests = 0; - -static rb_request* -new_req() -{ - rb_request* arequests; - rb_request* req = NULL; - uint num; - int i, overlap = 0; - - if(nrequests) - { - /* We allocate in a loop starting after the last allocation. */ - for(i = reqhigh; !overlap || i != reqhigh; i = (i + 1) % nrequests) - { - /* - * We can overlap past reqlow, but in that case no - * updating reqhigh. This can happen after reallocating. - */ - if(i == reqlow) - overlap = 1; - - if(requests[i].id == 0) - { - req = &(requests[i]); - - if(!overlap) - reqhigh = i; - break; - } - } - } - - if(!req) - { - /* - * A note about the scrolling window and extending allocations... - * The only reason this works is because whenever we reallocate - * reqhigh and reqlow are the same. - */ - ASSERT(reqlow == reqhigh); - - /* Reallocate the request block */ - /* TODO: Once we use less memory this can be higher */ - num = nrequests ? nrequests * 2 : 32; - arequests = (rb_request*)realloc(requests, sizeof(rb_request) * num); - if(!arequests) - { - /* Note we leave old requests allocated */ - errno = ENOMEM; - return NULL; - } - - /* Clear out the new ones */ - requests = arequests; - memset(requests + nrequests, 0, sizeof(rb_request) * (num - nrequests)); - - /* We return the next one */ - req = requests + nrequests; - - nrequests = num; - - if(reqhigh == -1) - reqhigh = 0; - if(reqlow == -1) - reqlow = nrequests - 1; - } - - /* A incrementing counter for each request */ - req->id = snmp_request++; - return req; -} - -static rb_request* -find_req(uint32_t id) -{ - int i, first; - - if(!nrequests) - return NULL; - - /* - * Search backwards from the in the scrolling window. This gives - * us as high performance for the high performing pollers and - * less performance for the low ones. - */ - for(i = reqhigh, first = 1; first || i != reqlow; - i = (i ? i : nrequests) - 1) - { - if(id == requests[i].id) - return &(requests[i]); - first = 0; - } - - return NULL; -} - -static void -free_req(rb_request* req) -{ - int i; - - memset(req, 0, sizeof(*req)); - - /* Update the bottom of the scrolling loop */ - for(i = reqlow; i != reqhigh; i = (i + 1) % nrequests) - { - /* If used then done */ - if(requests[i].id) - break; - - /* reqlow is not inclusive */ - reqlow = i; - } -} - -/* ----------------------------------------------------------------------------- - * PACKET HANDLING - */ - -static void -finish_poll(rb_poller* poll, mstime when) -{ -#ifdef _DEBUG - { - rb_item* it; - for(it = poll->items; it; it = it->next) - ASSERT(!it->req); - } -#endif - - /* Update the book-keeping */ - poll->last_polled = when; - - /* And send off our collection of values */ - rb_rrd_update(poll); -} - -static void -send_req(rb_request* req, mstime when) -{ - struct asn_buf b; - ssize_t ret; - - /* Update our bookkeeping */ - req->sent++; - if(req->sent <= g_state.retries) - req->next_retry = when + req->interval; - else - req->next_retry = 0; - req->last_sent = when; - - /* No sending if no address */ - if(!req->host->is_resolved) - { - if(req->sent <= 1) - rb_messagex(LOG_DEBUG, "skipping snmp request: host not resolved: %s", - req->host->hostname); - return; - } - - b.asn_ptr = snmp_buffer; - b.asn_len = sizeof(snmp_buffer); - - if(snmp_pdu_encode(&(req->pdu), &b)) - rb_message(LOG_CRIT, "couldn't encode snmp buffer"); - else - { - ret = sendto(snmp_socket, snmp_buffer, b.asn_ptr - snmp_buffer, 0, - &SANY_ADDR(req->host->address), SANY_LEN(req->host->address)); - if(ret == -1) - rb_message(LOG_ERR, "couldn't send snmp packet to: %s", req->host->hostname); - else - rb_messagex(LOG_DEBUG, "sent request #%d to: %s", req->id, req->host->hostname); - } -} - -static void -timeout_req(rb_request* req, mstime when) -{ - rb_poller* poll = req->poll; - int incomplete = 0; - rb_item* it; - - ASSERT(poll); - - /* - * Marks of this requests items as unknown. Request is - * over, free. See if poller is done - */ - - for(it = poll->items; it; it = it->next) - { - if(it->req == req) - { - rb_messagex(LOG_DEBUG, "value for field '%s' timed out", it->rrdfield); - it->vtype = VALUE_UNSET; - it->req = NULL; - } - - else if(it->req) - incomplete = 1; - } - - /* For timeouts we use the time the last request was sent */ - when = req->last_sent; - - free_req(req); - - if(!incomplete) - finish_poll(poll, when); -} - -static void -check_req(rb_request* req, mstime when) -{ - ASSERT(req->id); - - /* See if it's timed out */ - if(when >= req->timeout) - timeout_req(req, when); - - if(!req->next_retry) - return; - - /* Resend if necessary */ - if(when >= req->next_retry) - send_req(req, when); -} - -static void -respond_req(rb_request* req, struct snmp_pdu* pdu, mstime when) -{ - struct snmp_value* value; - rb_poller* poll = req->poll; - rb_item* item; - int i; - - ASSERT(req->id == pdu->request_id); - - for(i = 0; i < pdu->nbindings; i++) - { - value = &(pdu->bindings[i]); - - for(item = poll->items; item; item = item->next) - { - if(asn_compare_oid(&(value->var), &(item->snmpfield.var)) == 0) - { - const char *msg = NULL; - switch(value->syntax) - { - case SNMP_SYNTAX_NULL: - item->vtype = VALUE_UNSET; - break; - case SNMP_SYNTAX_INTEGER: - item->v.i_value = value->v.integer; - item->vtype = VALUE_REAL; - break; - case SNMP_SYNTAX_COUNTER: - case SNMP_SYNTAX_GAUGE: - case SNMP_SYNTAX_TIMETICKS: - item->v.i_value = value->v.uint32; - item->vtype = VALUE_REAL; - break; - case SNMP_SYNTAX_COUNTER64: - item->v.i_value = value->v.counter64; - item->vtype = VALUE_REAL; - break; - case SNMP_SYNTAX_OCTETSTRING: - case SNMP_SYNTAX_OID: - case SNMP_SYNTAX_IPADDRESS: - msg = "snmp server returned non numeric value for field: %s"; - break; - case SNMP_SYNTAX_NOSUCHOBJECT: - case SNMP_SYNTAX_NOSUCHINSTANCE: - case SNMP_SYNTAX_ENDOFMIBVIEW: - msg = "field not available on snmp server: %s"; - break; - default: - msg = "snmp server returned invalid or unsupported value for field: %s"; - break; - } - - if(msg) - rb_messagex(LOG_WARNING, msg, item->rrdfield); - else if(item->vtype == VALUE_REAL) - rb_messagex(LOG_DEBUG, "got value for field '%s': %lld", - item->rrdfield, item->v.i_value); - else if(item->vtype == VALUE_FLOAT) - rb_messagex(LOG_DEBUG, "got value for field '%s': %.4lf", - item->rrdfield, item->v.f_value); - else - rb_messagex(LOG_DEBUG, "got value for field '%s': U", - item->rrdfield); - - /* Mark this value as done */ - item->req = NULL; - break; - } - } - } - - /* We're done with this request */ - free_req(req); - - /* Now see if the entire request is done */ - for(item = poll->items; item; item = item->next) - { - if(item->req) - return; - } - - - /* And if so then hand off */ - finish_poll(poll, when); -} - -static int -poller_timer(mstime when, void* arg) -{ - rb_poller* poll = (rb_poller*)arg; - const rb_host* last_host = NULL; - rb_request* req = NULL; - rb_item* it; - - /* - * If the previous poll has not completed, then we count it - * as a timeout. - */ - for(it = poll->items; it; it = it->next) - { - if(it->req) - { - ASSERT(it->req->poll == poll); - timeout_req(it->req, when); - } - - /* timeout_req above should have cleared this */ - ASSERT(!it->req); - } - - - for(it = poll->items; it; it = it->next) - { - /* - * We assume that the polled items are sorted by host. Done - * in config.c. This allows us to fire off the least amount - * of requests. Generate new requests when: - * - * - first or new host - * - too many items in the same request - */ - if(!req || it->host != last_host || - req->pdu.nbindings >= SNMP_MAX_BINDINGS) - { - /* Send off last request ... */ - if(req) - send_req(req, when); - - /* ... and make a new one */ - req = new_req(); - if(!req) - { - rb_message(LOG_CRIT, "couldn't allocate a new snmp request"); - return 1; - } - - req->poll = poll; - req->host = it->host; - - /* rb_messagex(LOG_DEBUG, "preparing request #%d for: %s@%s", - req->id, req->host->community, req->host->name); */ - - /* Setup the packet */ - strlcpy(req->pdu.community, req->host->community, sizeof(req->pdu.community)); - req->pdu.request_id = req->id; - req->pdu.version = req->host->version; - req->pdu.type = SNMP_PDU_GET; - req->pdu.error_status = 0; - req->pdu.error_index = 0; - req->pdu.nbindings = 0; - - /* Send interval is 200 ms when poll interval is below 2 seconds */ - req->interval = (poll->interval <= 2000) ? 200L : 600L; - - /* Timeout is for the last packet sent, not first */ - req->timeout = when + (req->interval * ((mstime)g_state.retries)) + poll->timeout; - req->sent = 0; - - last_host = it->host; - } - - /* Add an item to this request */ - req->pdu.bindings[req->pdu.nbindings].var = it->snmpfield.var; - req->pdu.bindings[req->pdu.nbindings].syntax = it->snmpfield.syntax; - req->pdu.nbindings++; - - /* Mark item as active by this request */ - it->req = req; - it->vtype = VALUE_UNSET; - } - - if(req) - send_req(req, when); - - return 1; -} - - -static void -receive_resp(int fd, int type, void* arg) -{ - char hostname[MAXPATHLEN]; - struct sockaddr_any from; - struct snmp_pdu pdu; - struct asn_buf b; - rb_request* req; - const char* msg; - int len, ret; - int32_t ip; - - ASSERT(snmp_socket == fd); - - /* Read in the packet */ - - SANY_LEN(from) = sizeof(from); - len = recvfrom(snmp_socket, snmp_buffer, sizeof(snmp_buffer), 0, - &SANY_ADDR(from), &SANY_LEN(from)); - if(len < 0) - { - if(errno != EAGAIN && errno != EWOULDBLOCK) - rb_message(LOG_ERR, "error receiving snmp packet from network"); - return; - } - - - if(sock_any_ntop(&from, hostname, MAXPATHLEN, 0) == -1) - strcpy(hostname, "[UNKNOWN]"); - - /* Now parse the packet */ - - b.asn_ptr = snmp_buffer; - b.asn_len = len; - - ret = snmp_pdu_decode(&b, &pdu, &ip); - if(ret != SNMP_CODE_OK) - { - rb_message(LOG_WARNING, "invalid snmp packet received from: %s", hostname); - return; - } - - /* It needs to match something we're waiting for */ - req = find_req(pdu.request_id); - if(!req) - { - rb_messagex(LOG_DEBUG, "received extra or delayed packet from: %s", hostname); - return; - } - - /* Check for errors */ - if(pdu.error_status != SNMP_ERR_NOERROR) - { - msg = snmp_get_errmsg (pdu.error_status); - if(msg) - rb_messagex(LOG_ERR, "snmp error from host '%s': %s", - hostname, msg); - else - rb_messagex(LOG_ERR, "unknown snmp error from host '%s': %d", - hostname, pdu.error_status); - return; - } - - if(pdu.version != req->pdu.version) - rb_message(LOG_WARNING, "wrong version snmp packet from: %s", hostname); - - /* Dispatch the packet */ - rb_messagex(LOG_DEBUG, "response to request #%d from: %s", req->id, hostname); - respond_req(req, &pdu, server_get_time()); -} - -static int -resend_timer(mstime when, void* arg) -{ - int i, first; - - /* Search backwards through the scrolling window */ - for(i = reqhigh, first = 1; first || i != reqlow; - i = (i ? i : nrequests) - 1, first = 0) - { - if(requests[i].id) - check_req(&(requests[i]), when); - } - - return 1; -} - -static int -prep_timer(mstime when, void* arg) -{ - /* - * We don't prepare all timers at exactly the same time - * but we sort of randomly start the various timers. We're - * going to be hitting these over and over again, so there's - * lots of benefits to spreading them out randomly over a - * few seconds. - */ - - rb_poller* poll; - int next; - - /* All done? */ - if(!arg) - return 0; - - poll = (rb_poller*)arg; - if(server_timer(poll->interval, poller_timer, poll) == -1) - rb_message(LOG_CRIT, "couldn't setup poller timer"); - - /* Setup the next poller anywhere between 0 and 750 ms */ - next = rand() % 750; - server_oneshot(next, prep_timer, poll->next); - return 0; -} - -static void -resolve_cb(int ecode, struct addrinfo* ai, void* arg) -{ - rb_host* host = (rb_host*)arg; - - if(ecode) - { - rb_messagex(LOG_WARNING, "couldn't resolve hostname: %s: %s", host->hostname, - gai_strerror(ecode)); - return; - } - - /* A successful resolve */ - memcpy(&SANY_ADDR(host->address), ai->ai_addr, ai->ai_addrlen); - SANY_LEN(host->address) = ai->ai_addrlen; - host->last_resolved = server_get_time(); - host->is_resolved = 1; - - rb_messagex(LOG_DEBUG, "resolved host: %s", host->hostname); -} - -static int -resolve_timer(mstime when, void* arg) -{ - rb_host* host; - struct addrinfo hints; - - /* Go through hosts and see which ones need resolving */ - for(host = g_state.hosts; host; host = host->next) - { - /* No need to resolve? */ - if(!host->resolve_interval) - continue; - - if(when - host->resolve_interval > host->last_resolve_try) - { - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - - /* Automatically strips port number */ - rb_messagex(LOG_DEBUG, "resolving host: %s", host->hostname); - async_resolver_queue(host->hostname, "161", &hints, resolve_cb, host); - host->last_resolve_try = when; - } - - /* When the last 3 resolves have failed, set to unresolved */ - if(when - (host->resolve_interval * 3) > host->last_resolved) - host->is_resolved = 0; - } - - return 1; -} - -void -rb_snmp_engine_init() -{ - struct sockaddr_in addr; - rb_request* req; - - ASSERT(snmp_socket == -1); - snmp_socket = socket(PF_INET, SOCK_DGRAM, 0); - if(snmp_socket < 0) - err(1, "couldn't open snmp socket"); - - /* Get a random IPv4 UDP socket for client use */ - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - - if(bind(snmp_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) - err(1, "couldn't listen on port"); - - if (server_watch(snmp_socket, SERVER_READ, receive_resp, NULL) == -1) - err(1, "couldn't listen on socket"); - - /* Allocate some requests to make sure we have memory */ - req = new_req(); - if(!req) - err(1, "out of memory"); - free_req(req); - - /* Start the preparation timers for setting up randomly */ - if(server_oneshot(100, prep_timer, g_state.polls) == -1) - err(1, "couldn't setup timer"); - - /* We fire off the resend timer every 1/5 second */ - if(server_timer(200, resend_timer, NULL) == -1) - err(1, "couldn't setup timer"); - - /* resolve timer goes once per second */ - if(server_timer(1000, resolve_timer, NULL) == -1) - err(1, "couldn't setup timer"); -} - -void -rb_snmp_engine_uninit() -{ - if(snmp_socket != -1) - { - server_unwatch(snmp_socket); - close(snmp_socket); - snmp_socket = -1; - } - - if(requests) - { - free(requests); - nrequests = 0; - reqhigh = 0; - reqlow = 0; - } -} diff --git a/doc/rrdbot-get.1 b/doc/rrdbot-get.1 index 48e4689..e9515d6 100644 --- a/doc/rrdbot-get.1 +++ b/doc/rrdbot-get.1 @@ -76,6 +76,34 @@ Prints the version of .Nm and the locations of the configuration files, RRD files etc. .El +.Sh MULTIPLE AGENTS +.Nm +supports failover between multiple agents. If an SNMP query fails on one agent +or a value is not found when querying an agent, then it will switch to another +configured agent. +.Pp +When combined with a query (see TABLE QUERIES) you can use this feature to +search for a given value in a table on one of multiple agents. +.Pp +To use failover, simply use multiple host names with commas (without a space) +separating them. For example: +.Bd -literal -offset indent +snmp://public@two.example.com,one.example.com/sysUptime.0 +.Ed +.Sh TABLE QUERIES +.Nm +can query a value that corresponds to a certain row in an SNMP table. On +many SNMP agents the indexes of rows in tables are not fixed, and this +allows you to retrieve a certain value no matter what row of the table +it is on. +.Pp +Add the OID and value you want to search for in the table to the end +of the SNMP URL. Only one query value is supported. +.Pp +For example to get the outbound packet count on the 'eth0' interface, you would use: +.Bd -literal -offset indent +snmp://public@example.com/ifInUcastPkts?ifDescr=eth0 +.Ed .Sh SEE ALSO .Xr rrdbotd 8 , .Xr rrdbot.conf 5 , diff --git a/doc/rrdbot.conf.5 b/doc/rrdbot.conf.5 index ad93496..0a31ce4 100644 --- a/doc/rrdbot.conf.5 +++ b/doc/rrdbot.conf.5 @@ -111,16 +111,21 @@ above should be replaced with the RRD field name. Multiple options can be specified if the RRD file has multiple fields. The syntax of the SNMP url is as follows: .Bd -literal -offset indent -snmp[version]://community@host[:port]/oid +snmp[version]://community@host[:port]/oid[?query=value] .Ed .Pp The following are valid SNMP urls: .Bd -literal -offset indent snmp://public@gateway.example.com/sysUptime.0 snmp2c://mycommunity@uplink.example.com/ifInOctets.2 +snmp2c://mycommunity@example.com/ifInOctets?idDescr=eth0 snmp://public@www.example.com:10161/1.3.6.1.2.1.1.3.0 +snmp://pub@two.example.com,one.example.com/sysUptime.0 .Ed .Pp +See TABLE QUERIES for more info on how to use the query part. See MULTIPLE HOSTS +support for info on how to use multiple hosts. +.Pp To test that your SNMP urls are correct you can use the .Xr rrdbot-get 1 utility. @@ -254,7 +259,35 @@ option in the configuration file. .Pp Once you have configuration files in place, you can use the .Xr rrdbot-create 8 -tool to create the needed RRD files in the appropriate places. +tool to create the needed RRD files in the appropriate places. +.Sh MULTIPLE AGENTS +.Xr rrdbotd 8 +supports failover between multiple agents. If an SNMP query fails on one agent +or a value is not found when querying an agent, then it will switch to another +configured agent. +.Pp +When combined with a query (see TABLE QUERIES) you can use this feature to +search for a given value in a table on one of multiple agents. +.Pp +To use failover, simply use multiple host names with commas (without a space) +separating them. For example: +.Bd -literal -offset indent +snmp://public@two.example.com,one.example.com/sysUptime.0 +.Ed +.Sh TABLE QUERIES +.Xr rrdbotd 8 +can query a value that corresponds to a certain row in an SNMP table. On +many SNMP agents the indexes of rows in tables are not fixed, and this +allows you to retrieve a certain value no matter what row of the table +it is on. +.Pp +Add the OID and value you want to search for in the table to the end +of the SNMP URL. Only one query value is supported. +.Pp +For example to get the outbound packet count on the 'eth0' interface, you would use: +.Bd -literal -offset indent +snmp://public@example.com/ifInUcastPkts?ifDescr=eth0 +.Ed .Sh SEE ALSO .Xr rrdbotd 8 , .Xr rrdbot-create 8 , diff --git a/doc/rrdbotd.8 b/doc/rrdbotd.8 index bc4d7fd..8217626 100644 --- a/doc/rrdbotd.8 +++ b/doc/rrdbotd.8 @@ -56,7 +56,14 @@ .Nm is an SNMP polling daemon which writes the polled values to an .Xr rrdtool 1 -RRD database. An can poll many different SNMP sources in an efficient manner. +RRD database. An can poll many different SNMP sources in an efficient manner. +.Pp +Table queries are supported, where the OID index of a certain value is not +known beforehand, or may change regularly. +.Pp +In addition multiple SNMP agents may be specified for a certain value. If +one SNMP agent cannot be contacted or errors for some reason, another one +will be tried. .Pp The configuration (eg: SNMP sources, polling intervals) are located in files in a directory, with one configuration file per RRD. The format of the diff --git a/doc/traffic-example.conf b/doc/traffic-example.conf index aea1b0d..e0d40d2 100644 --- a/doc/traffic-example.conf +++ b/doc/traffic-example.conf @@ -17,6 +17,12 @@ rrd: /var/db/rrds/traffic.rrd in.source: snmp://public@router.example.com/ifInOctets.2 out.source: snmp://public@router.example.com/ifOutOctets.2 +# You might also use table queries to acheive the above. +# If the interface's names is 'eth0', then this would work. +# +# in.source: snmp://public@router.example.com/ifInOctets?ifDescr=eth0 +# out.source: snmp://public@router.example.com/ifOutOctets?ifDescr=eth0 + # Poll every 10 seconds interval: 10 diff --git a/tools/Makefile.am b/tools/Makefile.am index a2185ab..70c2c61 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,19 +1,20 @@ sbin_PROGRAMS = rrdbot-create rrdbot-get -rrdbot_create_SOURCES = rrdbot-create.c ../common/usuals.h \ - ../common/config-parser.h ../common/config-parser.c \ - ../common/compat.h ../common/compat.c +rrdbot_create_SOURCES = rrdbot-create.c + rrdbot_create_CFLAGS = -I${top_srcdir}/common/ -I${top_srcdir} \ -DCONF_PREFIX=\"$(sysconfdir)\" -DDATA_PREFIX=\"$(datadir)\" -rrdbot_get_SOURCES = rrdbot-get.c ../common/usuals.h \ - ../common/compat.h ../common/compat.c \ - ../common/config-parser.h ../common/config-parser.c \ - ../common/server-mainloop.h ../common/server-mainloop.c \ - ../common/sock-any.h ../common/sock-any.c \ - ../mib/mib-parser.c ../mib/mib-parser.h \ - ../bsnmp/asn1.h ../bsnmp/asn1.c \ - ../bsnmp/snmp.h ../bsnmp/snmp.c +rrdbot_create_LDADD = \ + $(top_builddir)/common/libcommon.a + +rrdbot_get_SOURCES = rrdbot-get.c \ + ../mib/mib-parser.c ../mib/mib-parser.h + rrdbot_get_CFLAGS = -I${top_srcdir}/common/ -I${top_srcdir} \ -DCONF_PREFIX=\"$(sysconfdir)\" -DDATA_PREFIX=\"$(datadir)\" + +rrdbot_get_LDADD = \ + $(top_builddir)/common/libcommon.a \ + $(top_builddir)/bsnmp/libbsnmp-custom.a diff --git a/tools/rrdbot-get.c b/tools/rrdbot-get.c index c14f25b..a140735 100644 --- a/tools/rrdbot-get.c +++ b/tools/rrdbot-get.c @@ -46,37 +46,41 @@ #include #include -#include "sock-any.h" -#include "server-mainloop.h" #include "config-parser.h" +#include "log.h" +#include "server-mainloop.h" +#include "snmp-engine.h" +#include "sock-any.h" - -#define RESEND_TIMEOUT 200 /* Time between SNMP resends */ #define DEFAULT_TIMEOUT 5000 /* Default timeout for SNMP response */ #define MAX_RETRIES 3 /* Number of SNMP packets we retry */ struct context { - int socket; /* The socket to use */ - unsigned char packet[0x1000]; /* The raw packet data to send */ - struct snmp_pdu pdu; /* The actual request data */ - struct asn_oid oid_first; /* The first OID we've done */ + struct asn_oid oid_first; /* The first OID we've done */ + + /* Request data */ + char host[128]; /* The remote host, resolved to an address */ + char *community; /* The community to use */ + int version; /* protocol version */ - struct sockaddr_any hostaddr; /* The remote host */ - char* hostname; /* The remote host */ + struct asn_oid request_oid; /* The OID to request */ - uint64_t lastsend; /* Time of last send */ - int retries; /* Number of retries */ - uint64_t timeout; /* Receive timeout */ + int has_query; /* Whether we are a table query or not */ + struct asn_oid query_oid; /* OID to use in table query */ + char *query_match; /* Value to match in table query */ - int recursive; /* Whether we're going recursive or not */ - int numeric; /* Print raw data */ + uint64_t timeout; /* Receive timeout */ + + int recursive; /* Whether we're going recursive or not */ + int numeric; /* Print raw data */ + int verbose; /* Print verbose messages */ }; static struct context ctx; /* ----------------------------------------------------------------------------- - * DUMMY CONFIG FUNCTIONS + * REQUIRED CALLBACK FUNCTIONS */ int @@ -92,260 +96,292 @@ cfg_error(const char* filename, const char* errmsg, void* data) return 0; } +void +log_vmessage (int level, int erno, const char *msg, va_list va) +{ + if (level >= LOG_DEBUG && !ctx.verbose) + return; + + if (erno) { + errno = erno; + vwarn (msg, va); + } else { + vwarnx (msg, va); + } + + if (level <= LOG_ERR) + exit (1); +} + /* ----------------------------------------------------------------------------- * SNMP ENGINE */ static void -send_req() +parse_host (char *host) { - struct asn_buf b; - ssize_t ret; - - b.asn_ptr = ctx.packet; - b.asn_len = sizeof(ctx.packet); + struct sockaddr_any addr; + char *x; - if(snmp_pdu_encode(&ctx.pdu, &b)) - errx(1, "couldn't encode snmp buffer"); + /* Use the first of multiple hosts */ + x = strchr (host, ','); + if (x) { + *x = 0; + warnx ("only using the first host name: %s", host); + } - ret = sendto(ctx.socket, ctx.packet, b.asn_ptr - ctx.packet, 0, - &SANY_ADDR(ctx.hostaddr), SANY_LEN(ctx.hostaddr)); - if(ret == -1) - err(1, "couldn't send snmp packet to: %s", ctx.hostname); + if (sock_any_pton (host, &addr, SANY_OPT_DEFPORT(161) | SANY_OPT_DEFLOCAL) == -1) + err (1, "couldn't resolve host address: %s", host); - /* Some bookkeeping */ - ctx.retries++; - ctx.lastsend = server_get_time(); + if (sock_any_ntop (&addr, ctx.host, sizeof (ctx.host), 0) == -1) + err (1, "couldn't convert host address: %s", host); } static void -setup_req(char* uri) +parse_argument (char *uri) { - enum snmp_version version; - const char* msg; - char* scheme; - char* copy; - char* user; - char* path; - - /* Parse the SNMP URI */ - copy = strdup(uri); - msg = cfg_parse_uri(uri, &scheme, &ctx.hostname, &user, &path); - if(msg) - errx(2, "%s: %s", msg, copy); - free(copy); - - ASSERT(ctx.hostname && path); - - /* Currently we only support SNMP pollers */ - msg = cfg_parse_scheme(scheme, &version); - if(msg) - errx(2, "%s: %s", msg, scheme); - - if(sock_any_pton(ctx.hostname, &ctx.hostaddr, - SANY_OPT_DEFPORT(161) | SANY_OPT_DEFLOCAL) == -1) - err(1, "couldn't resolve host address (ignoring): %s", ctx.hostname); - - memset(&ctx.pdu, 0, sizeof(ctx.pdu)); - ctx.pdu.version = version; - ctx.pdu.request_id = 0; - ctx.pdu.type = ctx.recursive ? SNMP_PDU_GETNEXT : SNMP_PDU_GET; - ctx.pdu.error_status = 0; - ctx.pdu.error_index = 0; - strlcpy(ctx.pdu.community, user ? user : "public", - sizeof(ctx.pdu.community)); - - - /* And parse the OID */ - ctx.pdu.bindings[0].syntax = 0; - memset(&(ctx.pdu.bindings[0].v), 0, sizeof(ctx.pdu.bindings[0].v)); - if(mib_parse(path, &(ctx.pdu.bindings[0].var)) == -1) - errx(2, "invalid MIB: %s", path); - - /* Add an item to this request */ - ctx.pdu.nbindings = 1; - - /* Keep track of top for recursiveness */ - memcpy(&ctx.oid_first, &(ctx.pdu.bindings[0].var), sizeof(ctx.oid_first)); - - /* Reset bookkeeping */ - ctx.retries = 0; - ctx.lastsend = 0; + enum snmp_version version; + const char* msg; + char* copy; + char *user, *host, *scheme, *path, *query; + char *value, *name; + + /* Parse the SNMP URI */ + copy = strdup (uri); + msg = cfg_parse_uri (uri, &scheme, &host, &user, &path, &query); + if (msg) + errx (2, "%s: %s", msg, copy); + free (copy); + + ASSERT (host && path); + + /* Host, community */ + parse_host (host); + ctx.community = user ? user : "public"; + + /* Currently we only support SNMP pollers */ + msg = cfg_parse_scheme (scheme, &version); + if (msg) + errx (2, "%s: %s", msg, scheme); + ctx.version = version; + + /* Parse the OID */ + if (mib_parse (path, &ctx.request_oid) == -1) + errx (2, "invalid MIB: %s", path); + if (ctx.request_oid.len >= ASN_MAXOIDLEN) + errx (2, "request OID is too long"); + + /* Parse any query */ + if (query) { + msg = cfg_parse_query (query, &name, &value, &query); + if (msg) + errx (2, "%s", msg); + if (query && *query) + warnx ("only using first query argument in snmp URI"); + + ctx.has_query = 1; + ctx.query_match = value; + + /* And parse the query OID */ + if (mib_parse (name, &(ctx.query_oid)) == -1) + errx (2, "invalid MIB: %s", name); + + if (ctx.query_oid.len >= ASN_MAXOIDLEN) + errx (2, "query OID is too long"); + } } static void -setup_next(struct snmp_value* value) +print_result (struct snmp_value* value) { - ctx.pdu.request_id++; - ctx.pdu.type = SNMP_PDU_GETNEXT; + char *t; + + if (ctx.numeric) + printf ("%s", asn_oid2str (&value->var)); + else + mib_format (&value->var, stdout); + + printf(": "); + + switch (value->syntax) { + case SNMP_SYNTAX_NULL: + printf ("[null]\n"); + break; + case SNMP_SYNTAX_INTEGER: + printf ("%d\n", value->v.integer); + break; + case SNMP_SYNTAX_COUNTER: + case SNMP_SYNTAX_GAUGE: + case SNMP_SYNTAX_TIMETICKS: + printf ("%d\n", value->v.uint32); + break; + case SNMP_SYNTAX_COUNTER64: + printf ("%lld\n", value->v.counter64); + break; + case SNMP_SYNTAX_OCTETSTRING: + t = xcalloc (value->v.octetstring.len + 1); + memcpy (t, value->v.octetstring.octets, value->v.octetstring.len); + printf ("%s\n", t); + free (t); + break; + case SNMP_SYNTAX_OID: + printf ("%s\n", asn_oid2str(&(value->v.oid))); + break; + case SNMP_SYNTAX_IPADDRESS: + printf ("%c.%c.%c.%c\n", value->v.ipaddress[0], + value->v.ipaddress[1], value->v.ipaddress[2], + value->v.ipaddress[3]); + break; + case SNMP_SYNTAX_NOSUCHOBJECT: + printf ("[field not available on snmp server]\n"); + break; + case SNMP_SYNTAX_NOSUCHINSTANCE: + printf ("[no such instance on snmp server]\n"); + break; + case SNMP_SYNTAX_ENDOFMIBVIEW: + printf ("[end of mib view on snmp server]\n"); + break; + default: + printf ("[unknown]\n"); + break; + } +} - /* And parse the OID */ - memcpy(&(ctx.pdu.bindings[0]), value, sizeof(struct snmp_value)); - ctx.pdu.bindings[0].syntax = 0; - ctx.pdu.nbindings = 1; +static void +had_failure (int code) +{ + ASSERT (code != 0); - /* Reset bookkeeping */ - ctx.retries = 0; - ctx.lastsend = 0; + if (code < 1) + errx (1, "couldn't successfully communicate with server"); + else + errx (1, "server returned error: %s", snmp_get_errmsg (code)); } -static int -print_resp(struct snmp_pdu* pdu, uint64_t when) +static void +process_recursive (void) { - struct snmp_value* value; - char *t; - int i; - - ASSERT(ctx.pdu.request_id == pdu->request_id); - - for(i = 0; i < pdu->nbindings; i++) - { - value = &(pdu->bindings[i]); - - if(ctx.numeric) - printf("%s", asn_oid2str(&(value->var))); - else - mib_format(&(value->var), stdout); - - printf(": "); - - switch(value->syntax) - { - case SNMP_SYNTAX_NULL: - printf("[null]\n"); - break; - case SNMP_SYNTAX_INTEGER: - printf("%d\n", value->v.integer); - break; - case SNMP_SYNTAX_COUNTER: - case SNMP_SYNTAX_GAUGE: - case SNMP_SYNTAX_TIMETICKS: - printf("%d\n", value->v.uint32); - break; - case SNMP_SYNTAX_COUNTER64: - printf("%lld\n", value->v.counter64); - break; - case SNMP_SYNTAX_OCTETSTRING: - t = xcalloc(value->v.octetstring.len + 1); - memcpy(t, value->v.octetstring.octets, value->v.octetstring.len); - printf("%s\n", t); - free(t); - break; - case SNMP_SYNTAX_OID: - printf("%s\n", asn_oid2str(&(value->v.oid))); - break; - case SNMP_SYNTAX_IPADDRESS: - printf("%c.%c.%c.%c\n", value->v.ipaddress[0], value->v.ipaddress[1], - value->v.ipaddress[2], value->v.ipaddress[3]); - break; - case SNMP_SYNTAX_NOSUCHOBJECT: - printf("[field not available on snmp server]\n"); - break; - case SNMP_SYNTAX_NOSUCHINSTANCE: - printf("[no such instance on snmp server]\n"); - break; - case SNMP_SYNTAX_ENDOFMIBVIEW: - return 0; - default: - printf("[unknown]\n"); - break; - } - } - - return 1; + struct snmp_value value; + struct asn_oid last; + int i, ret, done; + + memcpy (&last, &ctx.request_oid, sizeof (last)); + memset (&value, 0, sizeof (value)); + + for (i = 0; ; ++i) { + + memcpy (&value.var, &last, sizeof (value.var)); + + ret = snmp_engine_sync (ctx.host, ctx.community, ctx.version, + 0, ctx.timeout, SNMP_PDU_GETNEXT, &value); + + /* Reached the end */ + if (i == 0 && ret == SNMP_ERR_NOSUCHNAME) + return; + + if (ret != SNMP_ERR_NOERROR) { + had_failure (ret); + return; + } + + /* Check that its not past the end */ + done = asn_compare_oid (&ctx.request_oid, &value.var) != 0 && + !asn_is_suboid (&ctx.request_oid, &value.var); + + if (!done) { + print_result (&value); + memcpy (&last, &value.var, sizeof (last)); + } + + snmp_value_clear (&value); + + if (done) + return; + } } static void -receive_resp(int fd, int type, void* arg) +process_query (void) { - char hostname[MAXPATHLEN]; - struct sockaddr_any from; - struct snmp_pdu pdu; - struct snmp_value *val; - struct asn_buf b; - const char* msg; - int len, ret, subid; - int32_t ip; - - ASSERT(ctx.socket == fd); - - /* Read in the packet */ - - SANY_LEN(from) = sizeof(from); - len = recvfrom(ctx.socket, ctx.packet, sizeof(ctx.packet), 0, - &SANY_ADDR(from), &SANY_LEN(from)); - if(len < 0) - { - if(errno != EAGAIN && errno != EWOULDBLOCK) - err(1, "error receiving snmp packet from network"); - } - - if(sock_any_ntop(&from, hostname, MAXPATHLEN, 0) == -1) - strcpy(hostname, "[UNKNOWN]"); - - /* Now parse the packet */ - - b.asn_ptr = ctx.packet; - b.asn_len = len; - - ret = snmp_pdu_decode(&b, &pdu, &ip); - if(ret != SNMP_CODE_OK) - errx(1, "invalid snmp packet received from: %s", hostname); - - /* It needs to match something we're waiting for */ - if(pdu.request_id != ctx.pdu.request_id) - return; - - /* Check for errors */ - if(pdu.error_status != SNMP_ERR_NOERROR) - { - msg = snmp_get_errmsg (pdu.error_status); - if(msg) - errx(1, "snmp error from host '%s': %s", hostname, msg); - else - errx(1, "unknown snmp error from host '%s': %d", hostname, pdu.error_status); - return; - } - - subid = ret = 1; - - if(pdu.nbindings > 0) - { - val = &(pdu.bindings[pdu.nbindings - 1]); - subid = asn_compare_oid(&ctx.oid_first, &(val->var)) == 0 || - asn_is_suboid(&ctx.oid_first, &(val->var)); - } - - /* Print the packet values */ - if(!ctx.recursive || subid) - ret = print_resp(&pdu, server_get_time()); - - if(ret && ctx.recursive && subid) - { - /* If recursive, move onto next one */ - setup_next(&(pdu.bindings[pdu.nbindings - 1])); - send_req(); - return; - } - - server_stop (); + struct snmp_value value; + struct snmp_value match; + asn_subid_t sub; + int matched, ret; + + ASSERT (ctx.has_query); + memset (&value, 0, sizeof (value)); + memset (&match, 0, sizeof (match)); + + /* Loop looking for the value */ + for (sub = 0; ; ++sub) { + + /* Build up the query OID we're going for */ + memcpy (&value.var, &ctx.query_oid, sizeof (value.var)); + ASSERT (value.var.len < ASN_MAXOIDLEN); + value.var.subs[value.var.len] = sub; + value.var.len++; + + /* Do the request */ + ret = snmp_engine_sync (ctx.host, ctx.community, ctx.version, + 0, ctx.timeout, SNMP_PDU_GET, &value); + + /* Try and see if its not zero based */ + if (ret == SNMP_ERR_NOSUCHNAME && sub == 0) + continue; + + if (ret != SNMP_ERR_NOERROR) { + had_failure (ret); + return; + } + + matched = 0; + + /* Match the results */ + if (ctx.query_match) + matched = snmp_engine_match (&value, ctx.query_match); + + /* When query match is null, anything matches */ + else + matched = 1; + + snmp_value_clear (&value); + + if (matched) + break; + } + + memcpy (&value.var, &ctx.request_oid, sizeof (value.var)); + ASSERT (value.var.len < ASN_MAXOIDLEN); + value.var.subs[value.var.len] = sub; + value.var.len++; + + ret = snmp_engine_sync (ctx.host, ctx.community, ctx.version, + 0, ctx.timeout, SNMP_PDU_GET, &value); + + if (ret != SNMP_ERR_NOERROR) + had_failure (ret); + else + print_result (&value); } -static int -send_timer(uint64_t when, void* arg) +static void +process_simple (void) { - if(ctx.lastsend == 0) - return 1; + struct snmp_value value; + int ret; + + memset (&value, 0, sizeof (value)); + memcpy (&value.var, &ctx.request_oid, sizeof (value.var)); - /* Check for timeouts */ - if(ctx.lastsend + ctx.timeout < when) - errx(1, "timed out waiting for response from server"); + ret = snmp_engine_sync (ctx.host, ctx.community, ctx.version, + 0, ctx.timeout, SNMP_PDU_GET, &value); - /* Resend packets when no response */ - if(ctx.retries < MAX_RETRIES && ctx.lastsend + RESEND_TIMEOUT < when) - send_req(); + if (ret != SNMP_ERR_NOERROR) + had_failure (ret); + else + print_result (&value); - return 1; + snmp_value_clear (&value); } /* ----------------------------------------------------------------------------- @@ -355,7 +391,7 @@ send_timer(uint64_t when, void* arg) static void usage() { - fprintf(stderr, "usage: rrdbot-get [-Mnr] [-t timeout] [-m mibdir] snmp://community@host/oid\n"); + fprintf(stderr, "usage: rrdbot-get [-Mnrv] [-t timeout] [-m mibdir] snmp://community@host/oid\n"); fprintf(stderr, " rrdbot-get -V\n"); exit(2); } @@ -368,100 +404,103 @@ version() } int -main(int argc, char* argv[]) +main (int argc, char* argv[]) { - struct sockaddr_in addr; - char ch; - char* t; - - /* Defaults */ - memset(&ctx, 0, sizeof(ctx)); - ctx.socket = -1; - ctx.timeout = DEFAULT_TIMEOUT; - - /* Parse the arguments nicely */ - while((ch = getopt(argc, argv, "m:Mnrt:V")) != -1) - { - switch(ch) - { - - /* mib directory */ - case 'm': - mib_directory = optarg; - break; - - /* MIB load warnings */ - case 'M': - mib_warnings = 1; - break; - - /* Numeric output */ - case 'n': - ctx.numeric = 1; - break; - - /* SNMP walk (recursive)*/ - case 'r': - ctx.recursive = 1; - break; - - /* The timeout */ - case 't': - ctx.timeout = strtoul(optarg, &t, 10); - if(*t) - errx(2, "invalid timeout: %s", optarg); - ctx.timeout *= 1000; - break; - - /* Print version number */ - case 'V': - version(); - break; - - /* Usage information */ - case '?': - default: - usage(); - break; - } - } - - argc -= optind; - argv += optind; - - if(argc != 1) - usage(); - - server_init(); - - setup_req(argv[0]); - - /* Setup the SNMP socket */ - ctx.socket = socket(PF_INET, SOCK_DGRAM, 0); - if(ctx.socket < 0) - err(1, "couldn't open snmp socket"); - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - if(bind(ctx.socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) - err(1, "couldn't listen on port"); - if(server_watch(ctx.socket, SERVER_READ, receive_resp, NULL) == -1) - err(1, "couldn't listen on socket"); - - /* Send off first request */ - send_req(); - - /* We fire off the resend timer every 1/5 second */ - if(server_timer(RESEND_TIMEOUT, send_timer, NULL) == -1) - err(1, "couldn't setup timer"); - - /* Wait for responses */ - server_run(); - - /* Done */ - server_unwatch(ctx.socket); - close(ctx.socket); - - server_uninit(); - - return 0; + char ch; + char* t; + + /* Defaults */ + memset (&ctx, 0, sizeof (ctx)); + ctx.timeout = DEFAULT_TIMEOUT; + + /* Parse the arguments nicely */ + while ((ch = getopt (argc, argv, "m:Mnrt:vV")) != -1) { + switch (ch) + { + + /* mib directory */ + case 'm': + mib_directory = optarg; + break; + + /* MIB load warnings */ + case 'M': + mib_warnings = 1; + break; + + /* Numeric output */ + case 'n': + ctx.numeric = 1; + break; + + /* SNMP walk (recursive)*/ + case 'r': + ctx.recursive = 1; + break; + + /* The timeout */ + case 't': + ctx.timeout = strtoul (optarg, &t, 10); + if (*t) + errx (2, "invalid timeout: %s", optarg); + ctx.timeout *= 1000; + break; + + /* Verbose */ + case 'v': + ctx.verbose = 1; + break; + + /* Print version number */ + case 'V': + version (); + break; + + /* Usage information */ + case '?': + default: + usage (); + break; + } + } + + argc -= optind; + argv += optind; + + if(argc != 1) + usage (); + + server_init (); + snmp_engine_init (MAX_RETRIES); + + parse_argument (argv[0]); + + /* + * Recursive query walks everything at or lower than the + * specified OID in the tree. + */ + if (ctx.recursive) { + if (ctx.has_query) + errx (2, "cannot do a recursive table query"); + process_recursive (); + + /* + * Does a table query, lookup the appropriate row, and + * the value of the OID for that row. + */ + } else if (ctx.has_query) { + ASSERT (!ctx.recursive); + process_query (); + + /* + * A simple value lookup. + */ + } else { + process_simple (); + } + + snmp_engine_stop (); + server_uninit (); + + return 0; } -- cgit v1.2.3