diff options
author | Stef Walter <stef@memberwebs.com> | 2006-08-05 20:48:58 +0000 |
---|---|---|
committer | Stef Walter <stef@memberwebs.com> | 2006-08-05 20:48:58 +0000 |
commit | 2d975d635f1903a5a5b84ff808b0311d431f9e25 (patch) | |
tree | 04dcb3842e05dadd22764e56fcadc17f0072c632 | |
parent | 2b77de36782f4906b20b45d695524bbe48c731fc (diff) |
Added asynchronous DNS resolver. See #47
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | acsite.m4 | 228 | ||||
-rw-r--r-- | common/async-resolver.c | 322 | ||||
-rw-r--r-- | common/async-resolver.h | 16 | ||||
-rw-r--r-- | common/server-mainloop.h | 2 | ||||
-rw-r--r-- | common/sock-any.c | 35 | ||||
-rw-r--r-- | common/sock-any.h | 17 | ||||
-rw-r--r-- | configure.in | 6 | ||||
-rw-r--r-- | daemon/Makefile.am | 1 | ||||
-rw-r--r-- | daemon/config.c | 53 | ||||
-rw-r--r-- | daemon/rrdbotd.c | 16 | ||||
-rw-r--r-- | daemon/rrdbotd.h | 7 | ||||
-rw-r--r-- | daemon/snmp-engine.c | 83 | ||||
-rw-r--r-- | tools/rrdbot-create.c | 6 |
15 files changed, 762 insertions, 35 deletions
@@ -1,6 +1,7 @@ 0.4 - Reduced memory usage of SNMP requests - + - Added asynchronous DNS resolver + 0.3 - Initial public release diff --git a/Makefile.am b/Makefile.am index 77d0c5b..183974b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,5 @@ -EXTRA_DIST = common mib +EXTRA_DIST = common mib acsite.m4 SUBDIRS = bsnmp daemon tools mibs # Clean up any EXTRA_DIST we're distributing diff --git a/acsite.m4 b/acsite.m4 new file mode 100644 index 0000000..6ade204 --- /dev/null +++ b/acsite.m4 @@ -0,0 +1,228 @@ +dnl Available from the GNU Autoconf Macro Archive at: +dnl http://www.gnu.org/software/ac-archive/htmldoc/acx_pthread.html +dnl +AC_DEFUN([ACX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_LANG_SAVE +AC_LANG_C +acx_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) + AC_TRY_LINK_FUNC(pthread_join, acx_pthread_ok=yes) + AC_MSG_RESULT($acx_pthread_ok) + if test x"$acx_pthread_ok" = xno; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case "${host_cpu}-${host_os}" in + *solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthread or + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + acx_pthread_flags="-pthread -pthreads pthread -mt $acx_pthread_flags" + ;; +esac + +if test x"$acx_pthread_ok" = xno; then +for flag in $acx_pthread_flags; do + + case $flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $flag]) + PTHREAD_CFLAGS="$flag" + ;; + + pthread-config) + AC_CHECK_PROG(acx_pthread_config, pthread-config, yes, no) + if test x"$acx_pthread_config" = xno; then continue; fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$flag]) + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + AC_TRY_LINK([#include <pthread.h>], + [pthread_t th; pthread_join(th, 0); + pthread_attr_init(0); pthread_cleanup_push(0, 0); + pthread_create(0,0,0,0); pthread_cleanup_pop(0); ], + [acx_pthread_ok=yes]) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + AC_MSG_RESULT($acx_pthread_ok) + if test "x$acx_pthread_ok" = xyes; then + break; + fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$acx_pthread_ok" = xyes; then + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Detect AIX lossage: threads are created detached by default + # and the JOINABLE attribute has a nonstandard name (UNDETACHED). + AC_MSG_CHECKING([for joinable pthread attribute]) + AC_TRY_LINK([#include <pthread.h>], + [int attr=PTHREAD_CREATE_JOINABLE;], + ok=PTHREAD_CREATE_JOINABLE, ok=unknown) + if test x"$ok" = xunknown; then + AC_TRY_LINK([#include <pthread.h>], + [int attr=PTHREAD_CREATE_UNDETACHED;], + ok=PTHREAD_CREATE_UNDETACHED, ok=unknown) + fi + if test x"$ok" != xPTHREAD_CREATE_JOINABLE; then + AC_DEFINE(PTHREAD_CREATE_JOINABLE, $ok, + [Define to the necessary symbol if this constant + uses a non-standard name on your system.]) + fi + AC_MSG_RESULT(${ok}) + if test x"$ok" = xunknown; then + AC_MSG_WARN([we do not know how to create joinable pthreads]) + fi + + AC_MSG_CHECKING([if more special flags are required for pthreads]) + flag=no + case "${host_cpu}-${host_os}" in + *-aix* | *-freebsd*) flag="-D_THREAD_SAFE";; + *solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";; + esac + AC_MSG_RESULT(${flag}) + if test "x$flag" != xno; then + PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" + fi + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + # More AIX lossage: must compile with cc_r + AC_CHECK_PROG(PTHREAD_CC, cc_r, cc_r, ${CC}) +else + PTHREAD_CC="$CC" +fi + +AC_SUBST(PTHREAD_LIBS) +AC_SUBST(PTHREAD_CFLAGS) +AC_SUBST(PTHREAD_CC) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test x"$acx_pthread_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) + : +else + acx_pthread_ok=no + $2 +fi +AC_LANG_RESTORE +])dnl ACX_PTHREAD + + +AC_DEFUN(AC_CHECK_GLOBAL, +[ +for ac_global in $1 +do + ac_tr_global=HAVE_`echo $ac_global | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + AC_MSG_CHECKING([for global variable ${ac_global}]) + AC_CACHE_VAL(ac_cv_global_$ac_global, + [ + AC_TRY_LINK(dnl + [/* no includes */], + [ extern long int $ac_global; exit((int)$ac_global)], + eval "ac_cv_global_${ac_global}=yes", + eval "ac_cv_global_${ac_global}=no" + ) + ] + ) + if eval "test \"`echo '$ac_cv_global_'$ac_global`\" = yes"; then + AC_MSG_RESULT(yes) + AC_DEFINE_UNQUOTED($ac_tr_global,1,Define if the global variable $ac_global is available) + else + AC_MSG_RESULT(no) + fi +done +]) + + + diff --git a/common/async-resolver.c b/common/async-resolver.c new file mode 100644 index 0000000..c734bff --- /dev/null +++ b/common/async-resolver.c @@ -0,0 +1,322 @@ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <netdb.h> +#include <unistd.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> +#include <errno.h> + +#include "async-resolver.h" +#include "server-mainloop.h" + +/* ----------------------------------------------------------------------------- + * THREAD COMMUNICATION + */ + +#define TSIGNAL_UNITITIALIZED { -1 -1 } + +static int +tsignal_init(int* sig) +{ + if(pipe(sig) == -1) + return -1; + fcntl(sig[0], F_SETFL, fcntl(sig[0], F_GETFL, 0) | O_NONBLOCK); + return 0; +} + +static int +tsignal_get_fd(int* sig) +{ + return sig[0]; +} + +static void +tsignal_wake(int* sig) +{ + write(sig[1], "1", 1); +} + +static void +tsignal_clear(int* sig) +{ + char buf[16]; + while(read(sig[0], buf, sizeof(buf)) > 0); +} + +static void +tsignal_wait(int* sig, struct timeval* tv) +{ + fd_set watch; + FD_ZERO(&watch); + FD_SET(sig[0], &watch); + select(sig[0], &watch, NULL, NULL, tv); +} + +static void +tsignal_uninit(int* sig) +{ + if(sig[1] != -1) + close(sig[1]); + sig[1] = -1; + if(sig[0] != -1) + close(sig[0]); + sig[0] = -1; +} + +/* ----------------------------------------------------------------------------- + * RESOLVER + */ + +typedef struct _resolve_request +{ + char hostname[256]; + char servname[256]; + async_resolve_callback cb; + void *arg; + + int gaierr; + int errn; + struct addrinfo *ai; + + struct _resolve_request *next; +} +resolve_request; + +/* The queues */ +static int res_quit = 0; +static resolve_request* res_requests = NULL; +static resolve_request* res_done = NULL; + +/* Thread communication */ +static pthread_t res_thread = 0; +static pthread_mutex_t res_mutex = PTHREAD_MUTEX_INITIALIZER; +static int res_request_signal[2] = TSIGNAL_UNITITIALIZED; +static int res_done_signal[2] = TSIGNAL_UNITITIALIZED; + +static void* +resolver_thread(void* arg) +{ + resolve_request* req; + resolve_request* r; + struct timeval tv; + + while(!res_quit) + { + pthread_mutex_lock(&res_mutex); + + /* Dig out any requests */ + req = res_requests; + if(req) + { + res_requests = req->next; + req->next = NULL; + } + + pthread_mutex_unlock(&res_mutex); + + /* No requests, wait for a request */ + if(!req) + { + tv.tv_sec = 0; + tv.tv_usec = 500000; + tsignal_wait(res_request_signal, &tv); + tsignal_clear(res_request_signal); + continue; + } + + /* The actual resolve */ + req->gaierr = getaddrinfo(req->hostname, req->servname[0] ? req->servname : NULL, + NULL, &req->ai); + req->errn = errno; + + /* A timeout */ + if(!req->gaierr && !req->ai) + { + req->gaierr = EAI_SYSTEM; + req->errn = ETIMEDOUT; + } + + /* Append the result to done */ + pthread_mutex_lock(&res_mutex); + + if(!res_done) + { + res_done = req; + } + else + { + r = res_done; + while(r->next) + r = r->next; + r->next = req; + } + + pthread_mutex_unlock(&res_mutex); + + /* Tell the main thread to check outbound */ + tsignal_wake(res_done_signal); + } + + return NULL; +} + +static void +resolver_done(int fd, int type, void* arg) +{ + resolve_request* req; + resolve_request* r; + + tsignal_clear(res_done_signal); + + pthread_mutex_lock(&res_mutex); + + req = res_done; + res_done = NULL; + + pthread_mutex_unlock(&res_mutex); + + while(req) + { + /* Send off the result */ + errno = req->errn; + (req->cb)(req->gaierr, req->ai, req->arg); + + /* And free it all */ + r = req->next; + if(req->ai) + freeaddrinfo(req->ai); + free(req); + + req = r; + } +} + +int +async_resolver_init() +{ + int r; + + /* The signal pipes */ + if(tsignal_init(res_request_signal) < 0) + return -1; + if(tsignal_init(res_done_signal) < 0) + return -1; + + if(server_watch(tsignal_get_fd(res_done_signal), SERVER_READ, resolver_done, NULL) == -1) + return -1; + + r = pthread_create(&res_thread, NULL, resolver_thread, NULL); + if(r != 0) + { + res_thread = 0; + return -1; + } + + return 0; +} + +void +async_resolver_queue(const char* hostname, const char* servname, + async_resolve_callback cb, void* arg) +{ + resolve_request* req; + resolve_request* r; + char* t; + + req = calloc(1, sizeof(resolve_request)); + if(!req) + { + /* All errors go to callback */ + (cb)(EAI_MEMORY, NULL, arg); + return; + } + + req->cb = cb; + req->arg = arg; + + strncpy(req->hostname, hostname, sizeof(req->hostname)); + req->hostname[sizeof(req->hostname) - 1] = 0; + + /* A colon and we try to split */ + t = strchr(req->hostname, ':'); + if(t) + { + *t = 0; + strncpy(req->servname, t + 1, sizeof(req->servname)); + } + + if(servname && !req->servname[0]) + strncpy(req->servname, servname, sizeof(req->servname)); + req->servname[sizeof(req->servname) - 1] = 0; + + /* Append the result to requests */ + pthread_mutex_lock(&res_mutex); + + if(!res_requests) + { + res_requests = req; + } + else + { + r = res_requests; + while(r->next) + r = r->next; + r->next = req; + } + + pthread_mutex_unlock(&res_mutex); + + tsignal_wake(res_request_signal); +} + +void +async_resolver_uninit() +{ + resolve_request* req; + + /* No more responses from this point on */ + if(tsignal_get_fd(res_done_signal) != -1) + server_unwatch(tsignal_get_fd(res_done_signal)); + + pthread_mutex_lock(&res_mutex); + + while(res_requests) + { + req = res_requests->next; + if(res_requests->ai) + freeaddrinfo(res_requests->ai); + free(res_requests); + res_requests = req; + } + + while(res_done) + { + req = res_done->next; + if(res_done->ai) + freeaddrinfo(res_done->ai); + free(res_done); + res_done = req; + } + + pthread_mutex_unlock(&res_mutex); + + /* Wake up the resolver thread */ + res_quit = 1; + tsignal_uninit(res_request_signal); + + /* Wait for it to finish */ + if(res_thread) + { + pthread_join(res_thread, NULL); + res_thread = 0; + } + + /* And close up the signals in the other direction */ + tsignal_uninit(res_done_signal); +} diff --git a/common/async-resolver.h b/common/async-resolver.h new file mode 100644 index 0000000..6b31ff4 --- /dev/null +++ b/common/async-resolver.h @@ -0,0 +1,16 @@ + +#ifndef __ASYNC_RESOLVER_H__ +#define __ASYNC_RESOLVER_H__ + +#include <netdb.h> + +typedef void (*async_resolve_callback)(int ecode, struct addrinfo* ai, void* arg); + +int async_resolver_init(); +void async_resolver_uninit(); + +void async_resolver_queue(const char* hostname, const char* servname, + async_resolve_callback cb, void* arg); + + +#endif /* __ASYNC_RESOLVER_H__ */ diff --git a/common/server-mainloop.h b/common/server-mainloop.h index 098ef44..788f3ae 100644 --- a/common/server-mainloop.h +++ b/common/server-mainloop.h @@ -4,8 +4,6 @@ #include <stdint.h> -/* TODO: Prefix functions with svr */ - #define SERVER_READ 0x01 #define SERVER_WRITE 0x02 diff --git a/common/sock-any.c b/common/sock-any.c index 1938d5d..bb207ea 100644 --- a/common/sock-any.c +++ b/common/sock-any.c @@ -246,6 +246,7 @@ int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts) { struct addrinfo* res; int port = 0; + int family = 0; t = NULL; l = strlen(addr); @@ -273,13 +274,33 @@ int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts) break; } - /* Try and resolve the domain name */ - if(getaddrinfo(buf, NULL, NULL, &res) != 0 || !res) - break; + if(!(opts & SANY_OPT_NORESOLV)) + { + /* Try and resolve the domain name */ + if(getaddrinfo(buf, NULL, NULL, &res) != 0 || !res) + break; - memcpy(&(any->s.a), res->ai_addr, sizeof(struct sockaddr)); - any->namelen = res->ai_addrlen; - freeaddrinfo(res); + memcpy(&(any->s.a), res->ai_addr, sizeof(struct sockaddr)); + any->namelen = res->ai_addrlen; + family = any->s.a.sa_family; + freeaddrinfo(res); + } + else + { + family = SANY_AF_DNS; +#ifdef HAVE_INET6 + if(opt & SANY_OPT_DEFINET6) + { + any->s.a.sa_family = AF_INET6; + any->namelen = sizeof(any->s.in6); + } + else +#endif + { + any->s.a.sa_family = AF_INET; + any->namelen = sizeof(any->s.in); + } + } port = htons((unsigned short)(port <= 0 ? defport : port)); @@ -295,7 +316,7 @@ int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts) #endif }; - return any->s.a.sa_family; + return family; } while(0); diff --git a/common/sock-any.h b/common/sock-any.h index 31cb13b..7df54e1 100644 --- a/common/sock-any.h +++ b/common/sock-any.h @@ -64,6 +64,9 @@ struct sockaddr_any #define SANY_LEN(any) ((any).namelen) #define SANY_TYPE(any) ((any).s.a.sa_family) +/* -------------------------------------------------------------------------- */ + +/* Returns AF_XXX family type or -1 */ int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts); /* The default port to fill in when no IP/IPv6 port specified */ @@ -76,15 +79,25 @@ int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts); #define SANY_OPT_DEFLOCAL 0x00100000 /* When only port specified default to IPv6 */ -#ifdef HAVE_INET6 #define SANY_OPT_DEFINET6 0x00200000 -#endif +/* Don't resolve host name */ +#define SANY_OPT_NORESOLV 0x01000000 + +/* The family type returned when resolving is needed */ +#define SANY_AF_DNS 0x01000000 + +/* -------------------------------------------------------------------------- */ + +/* Returns -1 when failed */ int sock_any_ntop(const struct sockaddr_any* any, char* addr, size_t addrlen, int opts); /* Don't print or compare the port */ #define SANY_OPT_NOPORT 0x01000000 +/* -------------------------------------------------------------------------- */ + +/* Returns 0 for equal */ int sock_any_cmp(const struct sockaddr_any* a1, const struct sockaddr_any* a2, int opts); #endif /* __SOCK_ANY_H__ */ diff --git a/configure.in b/configure.in index 3c3907d..ab0f81e 100644 --- a/configure.in +++ b/configure.in @@ -26,6 +26,12 @@ AC_PROG_CC AC_PROG_INSTALL AC_PROG_RANLIB +# TODO: Figure out why we need this wierd hack +ACX_PTHREAD( , [echo "ERROR: Pthread support not found."; exit 1] ) + +LIBS="$PTHREAD_LIBS $LIBS" +CFLAGS="$CFLAGS $PTHREAD_CFLAGS -D_POSIX_PTHREAD_SEMANTICS" + dnl Checks for libraries AC_CHECK_LIB(rrd, rrd_update, , [echo "ERROR: librrd not found."; exit 1]) diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 63f5169..28dceaf 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -8,6 +8,7 @@ rrdbotd_SOURCES = rrdbotd.c rrdbotd.h config.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 \ ../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)\" diff --git a/daemon/config.c b/daemon/config.c index 0f68c79..3e7c534 100644 --- a/daemon/config.c +++ b/daemon/config.c @@ -196,6 +196,7 @@ parse_item(const char* field, char* uri, config_ctx *ctx) { rb_item *ritem; rb_host *rhost; + int r; const char *msg; char* copy; @@ -235,17 +236,29 @@ parse_item(const char* field, char* uri, config_ctx *ctx) rhost->version = 1; rhost->name = host; rhost->community = user ? user : "public"; + rhost->is_resolved = 1; + rhost->resolve_interval = 0; + rhost->last_resolved = 0; - /* TODO: Eventually resolving should be in a separate thread, - and done regularly */ - if(sock_any_pton(host, &(rhost->address), - SANY_OPT_DEFPORT(161) | SANY_OPT_DEFLOCAL) == -1) + /* Try and resolve the DNS name */ + r = sock_any_pton(host, &(rhost->address), + SANY_OPT_DEFPORT(161) | SANY_OPT_DEFLOCAL | SANY_OPT_NORESOLV); + + if(r == -1) { - rb_message(LOG_WARNING, "couldn't resolve host address (ignoring): %s", host); + rb_message(LOG_WARNING, "couldn't parse host address (ignoring): %s", host); free(rhost); return NULL; } + /* + * 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; @@ -346,6 +359,7 @@ void rb_config_parse() { config_ctx ctx; + rb_poller* poll; /* Setup the hash tables properly */ g_state.poll_by_key = hsh_create(); @@ -358,6 +372,35 @@ 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; + } + } + } } /* ----------------------------------------------------------------------------- diff --git a/daemon/rrdbotd.c b/daemon/rrdbotd.c index a2976a5..9e81350 100644 --- a/daemon/rrdbotd.c +++ b/daemon/rrdbotd.c @@ -50,6 +50,7 @@ #include "rrdbotd.h" #include "server-mainloop.h" +#include "async-resolver.h" /* The default command line options */ #define DEFAULT_CONFIG CONF_PREFIX "/rrdbot" @@ -134,14 +135,14 @@ rb_vmessage(int level, int err, const char* msg, va_list ap) buf[MAX_MSGLEN - 1] = 0; /* Either to syslog or stderr */ - if (daemonized) - vsyslog (level, buf, ap); + if(daemonized) + vsyslog(level, buf, ap); else - vwarnx (buf, ap); + vwarnx(buf, ap); } void -rb_messagex (int level, const char* msg, ...) +rb_messagex(int level, const char* msg, ...) { va_list ap; va_start(ap, msg); @@ -150,7 +151,7 @@ rb_messagex (int level, const char* msg, ...) } void -rb_message (int level, const char* msg, ...) +rb_message(int level, const char* msg, ...) { va_list ap; va_start(ap, msg); @@ -301,6 +302,10 @@ main(int argc, char* argv[]) /* The mainloop server */ server_init(); + /* Setup the Async DNS resolver */ + if(async_resolver_init() < 0) + err(1, "couldn't initialize resolver"); + /* Parse config and setup SNMP system */ rb_config_parse(); @@ -345,6 +350,7 @@ main(int argc, char* argv[]) /* Cleanups */ rb_snmp_engine_uninit(); rb_config_free(); + async_resolver_uninit(); server_uninit(); return 0; diff --git a/daemon/rrdbotd.h b/daemon/rrdbotd.h index 061beb2..35b3912 100644 --- a/daemon/rrdbotd.h +++ b/daemon/rrdbotd.h @@ -100,8 +100,10 @@ typedef struct _rb_host /* Host resolving and book keeping */ struct sockaddr_any address; - mstime interval; + mstime resolve_interval; + mstime last_resolve_try; mstime last_resolved; + int is_resolved; /* Next in list of hosts */ struct _rb_host* next; @@ -155,6 +157,9 @@ 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); diff --git a/daemon/snmp-engine.c b/daemon/snmp-engine.c index 479bc85..c2afbe9 100644 --- a/daemon/snmp-engine.c +++ b/daemon/snmp-engine.c @@ -37,16 +37,20 @@ */ #include "usuals.h" +#include <sys/types.h> +#include <sys/socket.h> #include <errno.h> #include <unistd.h> #include <syslog.h> #include <err.h> +#include <arpa/inet.h> #include <bsnmp/asn1.h> #include <bsnmp/snmp.h> #include "rrdbotd.h" #include "server-mainloop.h" +#include "async-resolver.h" /* The socket to use */ static int snmp_socket = -1; @@ -61,7 +65,6 @@ static unsigned char snmp_buffer[0x1000]; * REQUESTS */ -/* rb_request waaaaayyyyy too big */ typedef struct _rb_request { /* The SNMP request identifier */ @@ -229,6 +232,23 @@ 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->name); + return; + } + b.asn_ptr = snmp_buffer; b.asn_len = sizeof(snmp_buffer); @@ -243,14 +263,6 @@ send_req(rb_request* req, mstime when) else rb_messagex(LOG_DEBUG, "sent request #%d to: %s", req->id, req->host->name); } - - /* And update our bookkeeping */ - req->sent++; - if(req->sent <= g_state.retries) - req->next_retry = when + req->interval; - else - req->next_retry = 0; - req->last_sent = when; } static void @@ -272,7 +284,6 @@ timeout_req(rb_request* req, mstime when) if(it->req == req) { rb_messagex(LOG_DEBUG, "value for field '%s' timed out", it->rrdfield); - it->vtype = VALUE_UNSET; it->req = NULL; } @@ -601,6 +612,54 @@ prep_timer(mstime when, void* arg) 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->name, + 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->name); +} + +static int +resolve_timer(mstime when, void* arg) +{ + rb_host* host; + + /* 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) + { + /* Automatically strips port number */ + rb_messagex(LOG_DEBUG, "resolving host: %s", host->name); + async_resolver_queue(host->name, "161", 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() @@ -636,6 +695,10 @@ rb_snmp_engine_init() /* 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 diff --git a/tools/rrdbot-create.c b/tools/rrdbot-create.c index 5cbbb39..c75b95f 100644 --- a/tools/rrdbot-create.c +++ b/tools/rrdbot-create.c @@ -469,6 +469,11 @@ add_rras(create_ctx* ctx, char* value) char* p; char* p2; + /* + * Looks like: + * 10/minute, 10/hour, 10/day, 10/week * 2, 1/month, 5/year + */ + while(value && *value) { per = num = 0; @@ -523,7 +528,6 @@ add_rras(create_ctx* ctx, char* value) return -1; } - /* Parse out how many */ if(p2) { |