diff options
| -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)          { | 
