summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStef Walter <stef@memberwebs.com>2006-08-05 20:48:58 +0000
committerStef Walter <stef@memberwebs.com>2006-08-05 20:48:58 +0000
commit2d975d635f1903a5a5b84ff808b0311d431f9e25 (patch)
tree04dcb3842e05dadd22764e56fcadc17f0072c632
parent2b77de36782f4906b20b45d695524bbe48c731fc (diff)
Added asynchronous DNS resolver. See #47
-rw-r--r--ChangeLog3
-rw-r--r--Makefile.am2
-rw-r--r--acsite.m4228
-rw-r--r--common/async-resolver.c322
-rw-r--r--common/async-resolver.h16
-rw-r--r--common/server-mainloop.h2
-rw-r--r--common/sock-any.c35
-rw-r--r--common/sock-any.h17
-rw-r--r--configure.in6
-rw-r--r--daemon/Makefile.am1
-rw-r--r--daemon/config.c53
-rw-r--r--daemon/rrdbotd.c16
-rw-r--r--daemon/rrdbotd.h7
-rw-r--r--daemon/snmp-engine.c83
-rw-r--r--tools/rrdbot-create.c6
15 files changed, 762 insertions, 35 deletions
diff --git a/ChangeLog b/ChangeLog
index 7ef867f..0a0093d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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)
{