Module which connects to a redis server.
authorGabriel Blanchard <gabe@teksavvy.ca>
Thu, 13 Jan 2011 22:09:35 +0000 (23:09 +0100)
committerAlan T. DeKok <aland@freeradius.org>
Thu, 13 Jan 2011 23:12:57 +0000 (00:12 +0100)
src/modules/rlm_redis/Makefile.in [new file with mode: 0644]
src/modules/rlm_redis/configure.in [new file with mode: 0644]
src/modules/rlm_redis/rlm_redis.c [new file with mode: 0644]
src/modules/rlm_redis/rlm_redis.h [new file with mode: 0644]

diff --git a/src/modules/rlm_redis/Makefile.in b/src/modules/rlm_redis/Makefile.in
new file mode 100644 (file)
index 0000000..bf4b37c
--- /dev/null
@@ -0,0 +1,9 @@
+TARGET      = @targetname@
+SRCS        = rlm_redis.c
+HEADERS     = rlm_redis.h
+RLM_CFLAGS  = @redis_cflags@
+RLM_LIBS    = @redis_ldflags@
+
+include ../rules.mak
+
+$(LT_OBJS): $(HEADERS)
diff --git a/src/modules/rlm_redis/configure.in b/src/modules/rlm_redis/configure.in
new file mode 100644 (file)
index 0000000..0eea2b9
--- /dev/null
@@ -0,0 +1,114 @@
+AC_PREREQ([2.61])
+AC_INIT(rlm_redis.c)
+AC_REVISION($Revision$)
+AC_DEFUN(modname,[rlm_redis])
+
+fail=
+SMART_LIBS=
+SMART_CLFAGS=
+if test x$with_[]modname != xno; then
+
+    dnl ############################################################
+    dnl # Check for command line options
+    dnl ############################################################
+
+    dnl extra argument: --with-redis-include-dir=DIR
+    redis_include_dir=
+    AC_ARG_WITH(redis-include-dir,
+       [AS_HELP_STRING([--with-redis-include-dir=DIR],
+               [Directory where the redis includes may be found])],
+       [case "$withval" in
+           no)
+               AC_MSG_ERROR(Need redis-include-dir)
+               ;;
+           yes)
+               ;;
+           *)
+               redis_include_dir="$withval"
+               ;;
+       esac])
+
+    dnl extra argument: --with-redis-lib-dir=DIR
+    redis_lib_dir=
+    AC_ARG_WITH(redis-lib-dir,
+       [AS_HELP_STRING([--with-redis-lib-dir=DIR],
+               [Directory where the redis libraries may be found])],
+       [case "$withval" in
+           no)
+               AC_MSG_ERROR(Need redis-lib-dir)
+               ;;
+           yes)
+               ;;
+           *)
+               redis_lib_dir="$withval"
+               ;;
+       esac])
+
+    dnl extra argument: --with-redis-dir=DIR
+    AC_ARG_WITH(redis-dir,
+       [AS_HELP_STRING([--with-redis-dir=DIR],
+               [Base directory where redis is installed])],
+       [case "$withval" in
+           no)
+               AC_MSG_ERROR(Need redis-dir)
+               ;;
+           yes)
+               ;;
+           *)
+               redis_lib_dir="$withval/lib"
+               redis_include_dir="$withval/include"
+               ;;
+       esac])
+
+    dnl ############################################################
+    dnl # Check for programs
+    dnl ############################################################
+
+    AC_PROG_CC
+
+    dnl ############################################################
+    dnl # Check for libraries
+    dnl ############################################################
+
+    smart_try_dir="$redis_lib_dir"
+    FR_SMART_CHECK_LIB(hiredis, redisConnect)
+    if test "x$ac_cv_lib_hiredis_redisConnect" != "xyes"
+    then
+      AC_MSG_WARN([hiredis libraries not found. Use --with-redis-lib-dir=<path>.])
+      fail="$fail libhiredis"
+    fi
+
+    dnl ############################################################
+    dnl # Check for header files
+    dnl ############################################################
+
+    smart_try_dir="$redis_include_dir"
+    FR_SMART_CHECK_INCLUDE(hiredis.h)
+    if test "x$ac_cv_header_hiredis_h" != "xyes"; then
+      AC_MSG_WARN([hiredis headers not found. Use --with-redis-include-dir=<path>.])
+      fail="$fail hiredis.h"
+    fi
+
+    targetname=modname
+else
+    targetname=
+    echo \*\*\* module modname is disabled.
+fi
+
+dnl Don't change this section.
+if test "x$fail" != x; then
+       if test "x${enable_strict_dependencies}" = xyes; then
+               AC_MSG_ERROR([set --without-]modname[ to disable it explicitly.])
+       else
+               AC_MSG_WARN([silently not building ]modname[.])
+               AC_MSG_WARN([FAILURE: ]modname[ requires:$fail.]);
+               targetname=
+       fi
+fi
+
+redis_ldflags="$SMART_LIBS"
+redis_cflags="$SMART_CFLAGS"
+AC_SUBST(redis_ldflags)
+AC_SUBST(redis_cflags)
+AC_SUBST(targetname)
+AC_OUTPUT(Makefile)
diff --git a/src/modules/rlm_redis/rlm_redis.c b/src/modules/rlm_redis/rlm_redis.c
new file mode 100644 (file)
index 0000000..2726488
--- /dev/null
@@ -0,0 +1,584 @@
+/*
+ * rlm_redis.c
+ *
+ * Version:    $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2000,2006  The FreeRADIUS server project
+ * Copyright 2011  TekSavvy Solutions <gabe@teksavvy.com>
+ */
+
+#include <freeradius-devel/ident.h>
+
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+
+#include "rlm_redis.h"
+
+static const CONF_PARSER module_config[] = {
+       { "num_connections", PW_TYPE_INTEGER,
+         offsetof(REDIS_INST, numconnections), NULL, "20"},
+       { "hostname", PW_TYPE_STRING_PTR,
+         offsetof(REDIS_INST, hostname), NULL, "127.0.0.1"},
+       { "port", PW_TYPE_INTEGER,
+         offsetof(REDIS_INST, port), NULL, "6379"},
+       {"connect_failure_retry_delay", PW_TYPE_INTEGER,
+        offsetof(REDIS_INST, connect_failure_retry_delay), NULL, "60"},
+       {"lifetime", PW_TYPE_INTEGER,
+        offsetof(REDIS_INST, lifetime), NULL, "0"},
+       {"max_queries", PW_TYPE_INTEGER,
+        offsetof(REDIS_INST, max_queries), NULL, "0"},
+
+       { NULL, -1, 0, NULL, NULL} /* end the list */
+};
+
+static int redis_close_socket(REDIS_INST *inst, REDISSOCK *dissocket)
+{
+       radlog(L_INFO, "rlm_redis (%s): Closing socket %d",
+              inst->xlat_name, dissocket->id);
+
+       if (dissocket->state == sockconnected) {
+               redisFree(dissocket->conn);
+       }
+
+#ifdef HAVE_PTHREAD_H
+       pthread_mutex_destroy(&dissocket->mutex);
+#endif
+
+       free(dissocket);
+       return 1;
+}
+
+static int connect_single_socket(REDISSOCK *dissocket, REDIS_INST *inst)
+{
+       radlog(L_INFO, "rlm_redis (%s): Attempting to connect #%d",
+              inst->xlat_name, dissocket->id);
+
+       dissocket->conn = redisConnect((char *) inst->hostname, inst->port);
+
+       if (!dissocket->conn->err) {
+               radlog(L_INFO, "rlm_redis (%s): Connected new DB handle, #%d",
+                      inst->xlat_name, dissocket->id);
+
+               dissocket->state = sockconnected;
+
+               dissocket->queries = 0;
+               return (0);
+       }
+
+       /*
+        *  Error, or redis is DOWN.
+        */
+       radlog(L_CONS | L_ERR, "rlm_redis (%s): Failed to connect DB handle #%d",
+              inst->xlat_name, dissocket->id);
+       inst->connect_after = time(NULL) + inst->connect_failure_retry_delay;
+       dissocket->state = sockunconnected;
+       return (-1);
+}
+
+static void redis_poolfree(REDIS_INST * inst)
+{
+       REDISSOCK *cur;
+       REDISSOCK *next;
+
+       for (cur = inst->redispool; cur; cur = next) {
+               next = cur->next;
+               redis_close_socket(inst, cur);
+       }
+
+       inst->redispool = NULL;
+}
+
+static int redis_xlat(void *instance, REQUEST *request,
+                     char *fmt, char *out, size_t freespace,
+                     RADIUS_ESCAPE_STRING func)
+{
+       REDIS_INST *inst = instance;
+       REDISSOCK *dissocket;
+       size_t ret = 0;
+       char *buffer_ptr;
+       char buffer[21];
+       char querystr[MAX_QUERY_LEN];
+
+       if (!radius_xlat(querystr, sizeof(querystr), fmt, request, func)) {
+               radlog(L_ERR, "rlm_redis (%s): xlat failed.",
+                      inst->xlat_name);
+
+               return 0;
+       }
+
+       if ((dissocket = redis_get_socket(inst)) == NULL) {
+               radlog(L_ERR, "rlm_redis (%s): redis_get_socket() failed",
+                      inst->xlat_name);
+        
+               return 0;
+       }
+
+       /* Query failed for some reason, release socket and return */
+       if (rlm_redis_query(dissocket, inst, querystr) < 0) {
+               rlm_redis_finish_query(dissocket);
+               redis_release_socket(inst,dissocket);
+        
+               return 0;
+       }
+
+        switch (dissocket->reply->type) {
+       case REDIS_REPLY_INTEGER:
+                buffer_ptr = buffer;
+                snprintf(buffer_ptr, sizeof(buffer), "%lld",
+                        dissocket->reply->integer);
+
+                ret = strlen(buffer_ptr);
+                break;
+
+       case REDIS_REPLY_STATUS:
+       case REDIS_REPLY_STRING:
+                buffer_ptr = dissocket->reply->str;
+                ret = dissocket->reply->len;
+                break;
+
+       default:
+                buffer_ptr = NULL;
+                break;
+        }
+
+       if ((ret >= freespace) || (buffer_ptr == NULL)) {
+               RDEBUG("rlm_redis (%s): Can't write result, insufficient space or unsupported result\n",
+                      inst->xlat_name);
+               
+               rlm_redis_finish_query(dissocket);
+               redis_release_socket(inst,dissocket);
+               
+               return 0;
+       }
+       
+       strlcpy(out,buffer_ptr,freespace);
+       
+       rlm_redis_finish_query(dissocket);
+       redis_release_socket(inst,dissocket);
+       
+       return ret;
+}
+
+/*
+ *     Only free memory we allocated.  The strings allocated via
+ *     cf_section_parse() do not need to be freed.
+ */
+static int redis_detach(void *instance)
+{
+       REDIS_INST *inst = instance;
+
+       redis_poolfree(inst);
+
+       if (inst->xlat_name) {
+               xlat_unregister(inst->xlat_name, (RAD_XLAT_FUNC)redis_xlat);
+               free(inst->xlat_name);
+       }
+       free(inst->xlat_name);
+       free(inst);
+
+       return 0;
+}
+
+static int redis_init_socketpool(REDIS_INST *inst)
+{
+       int i, rcode;
+       int success = 0;
+       REDISSOCK *dissocket;
+
+       inst->connect_after = 0;
+       inst->redispool = NULL;
+
+       for (i = 0; i < inst->numconnections; i++) {
+               radlog(L_DBG, "rlm_redis (%s): starting %d",
+                      inst->xlat_name, i);
+
+               dissocket = rad_malloc(sizeof (*dissocket));
+               if (dissocket == NULL) {
+                       return -1;
+               }
+               memset(dissocket, 0, sizeof (*dissocket));
+               dissocket->conn = NULL;
+               dissocket->id = i;
+               dissocket->state = sockunconnected;
+
+#ifdef HAVE_PTHREAD_H
+               rcode = pthread_mutex_init(&dissocket->mutex, NULL);
+               if (rcode != 0) {
+                       free(dissocket);
+                       radlog(L_ERR, "rlm_redis: Failed to init lock: %s",
+                              strerror(errno));
+                       return 0;
+               }
+#endif
+
+               if (time(NULL) > inst->connect_after) {
+                       /*
+                        *      This sets the dissocket->state, and
+                        *      possibly also inst->connect_after
+                        */
+                       if (connect_single_socket(dissocket, inst) == 0) {
+                               success = 1;
+                       }
+               }
+
+               /* Add "dis" socket to the list of sockets
+                * pun intended
+                */
+               dissocket->next = inst->redispool;
+               inst->redispool = dissocket;
+       }
+       inst->last_used = NULL;
+
+       if (!success) {
+               radlog(L_DBG, "rlm_redis (%s): Failed to connect to any redis server.",
+                      inst->xlat_name);
+       }
+
+       return 1;
+}
+
+/*
+ *     TODO, Actually escape something...
+ */
+static size_t redis_escape_func(char *out, size_t outlen, const char *in)
+{
+       size_t len = 0;
+
+       while (in[0]) {
+
+               if (outlen <= 1) {
+                       break;
+               }
+
+               /*
+                *      Allowed character.
+                */
+               *out = *in;
+               out++;
+               in++;
+               outlen--;
+               len++;
+       }
+       *out = '\0';
+       return len;
+}
+
+/*
+ *     Free the redis database
+ */
+int rlm_redis_query(REDISSOCK *dissocket, REDIS_INST *inst, char *query)
+{
+       if (!query || !*query) {
+               return -1;
+       }
+
+       DEBUG2("executing query %s", query);
+       dissocket->reply = redisCommand(dissocket->conn, query);
+
+       if (dissocket->reply == NULL) {
+               radlog(L_ERR, "rlm_redis: (%s) REDIS error: %s",
+                      inst->xlat_name, dissocket->conn->errstr);
+
+               /* close the socket that failed */
+               if (dissocket->state == sockconnected) {
+                       redis_close_socket(inst, dissocket);
+               }
+
+               /* reconnect the socket */
+               if (connect_single_socket(dissocket, inst) < 0) {
+                       radlog(L_ERR, "rlm_redis (%s): reconnect failed, database down?",
+                              inst->xlat_name);
+                       return -1;
+               }
+
+               DEBUG2("executing query %s", query);
+               /* retry the query on the newly connected socket */
+               dissocket->reply = redisCommand(dissocket->conn, query);
+
+               if (dissocket->reply == NULL) {
+                       radlog(L_ERR, "rlm_redis (%s): failed after re-connect",
+                              inst->xlat_name);
+                       return -1;
+               }
+       }
+
+       if (dissocket->reply->type == REDIS_REPLY_ERROR) {
+               radlog(L_ERR, "rlm_redis (%s): query failed, %s",
+                      inst->xlat_name, query);
+
+               /* Free the reply just in case */
+               rlm_redis_finish_query(dissocket);
+
+               return -1;
+       }
+
+       return 0;
+}
+
+/*
+ * Clear the redis reply object if any
+ */
+int rlm_redis_finish_query(REDISSOCK *dissocket)
+{
+       if (dissocket == NULL) {
+               return -1;
+       }
+
+       if (dissocket->reply != NULL) {
+               freeReplyObject(dissocket->reply);
+       } else {
+               return -1;
+       }
+
+       return 0;
+}
+
+static time_t last_logged_failure = 0;
+
+/*************************************************************************
+ *
+ *     Function: redis_get_socket
+ *
+ *     Purpose: Return a REDIS socket from the connection pool
+ *
+ *************************************************************************/
+REDISSOCK *redis_get_socket(REDIS_INST *inst)
+{
+       REDISSOCK *cur, *start;
+       int tried_to_connect = 0;
+       int unconnected = 0;
+       time_t now = time(NULL);
+
+       /*
+        *      Start at the last place we left off.
+        */
+       start = inst->last_used;
+       if (!start) start = inst->redispool;
+
+       cur = start;
+
+       while (cur) {
+#ifdef HAVE_PTHREAD_H
+               /*
+                *      If this socket is in use by another thread,
+                *      skip it, and try another socket.
+                *
+                *      If it isn't used, then grab it ourselves.
+                */
+               if (pthread_mutex_trylock(&cur->mutex) != 0) {
+                       goto next;
+               } /* else we now have the lock */
+#endif
+
+               /*
+                *      If the socket has outlived its lifetime, and
+                *      is connected, close it, and mark it as open for
+                *      reconnections.
+                */
+               if (inst->lifetime && (cur->state == sockconnected) &&
+                   ((cur->connected + inst->lifetime) < now)) {
+                       DEBUG2("Closing socket %d as its lifetime has been exceeded", cur->id);
+                       redis_close_socket(inst, cur);
+                       cur->state = sockunconnected;
+                       goto reconnect;
+               }
+
+               /*
+                *      If we have performed too many queries over this
+                *      socket, then close it.
+                */
+               if (inst->max_queries && (cur->state == sockconnected) &&
+                   (cur->queries >= inst->max_queries)) {
+                       DEBUG2("Closing socket %d as its max_queries has been exceeded", cur->id);
+                       redis_close_socket(inst, cur);
+                       cur->state = sockunconnected;
+                       goto reconnect;
+               }
+
+               /*
+                *      If we happen upon an unconnected socket, and
+                *      this instance's grace period on
+                *      (re)connecting has expired, then try to
+                *      connect it.  This should be really rare.
+                */
+               if ((cur->state == sockunconnected) && (now > inst->connect_after)) {
+               reconnect:
+                       radlog(L_INFO, "rlm_redis (%s): Trying to (re)connect unconnected handle %d..", inst->xlat_name, cur->id);
+                       tried_to_connect++;
+                       connect_single_socket(cur, inst);
+               }
+
+               /* if we still aren't connected, ignore this handle */
+               if (cur->state == sockunconnected) {
+                       DEBUG("rlm_redis (%s): Ignoring unconnected handle %d..", inst->xlat_name, cur->id);
+                       unconnected++;
+#ifdef HAVE_PTHREAD_H
+                       pthread_mutex_unlock(&cur->mutex);
+#endif
+                       goto next;
+               }
+
+               /* should be connected, grab it */
+               DEBUG("rlm_redis (%s): Reserving redis socket id: %d",
+                     inst->xlat_name, cur->id);
+
+               if (unconnected != 0 || tried_to_connect != 0) {
+                       DEBUG("rlm_redis (%s): got socket %d after skipping %d unconnected handles, tried to reconnect %d though",
+                             inst->xlat_name, cur->id, unconnected, tried_to_connect);
+               }
+
+               /*
+                *      The socket is returned in the locked
+                *      state.
+                *
+                *      We also remember where we left off,
+                *      so that the next search can start from
+                *      here.
+                *
+                *      Note that multiple threads MAY over-write
+                *      the 'inst->last_used' variable.  This is OK,
+                *      as it's a pointer only used for reading.
+                */
+               inst->last_used = cur->next;
+               cur->queries++;
+               return cur;
+
+               /* move along the list */
+       next:
+               cur = cur->next;
+
+               /*
+                *      Because we didnt start at the start, once we
+                *      hit the end of the linklist, we should go
+                *      back to the beginning and work toward the
+                *      middle!
+                */
+               if (!cur) {
+                       cur = inst->redispool;
+               }
+
+               /*
+                *      If we're at the socket we started
+                */
+               if (cur == start) {
+                       break;
+               }
+       }
+
+       /*
+        *      Suppress most of the log messages.  We don't want to
+        *      flood the log with this message for EVERY packet.
+        *      Instead, write to the log only once a second or so.
+        *
+        *      This code has race conditions when threaded, but the
+        *      only result is that a few more messages are logged.
+        */
+       if (now <= last_logged_failure) return NULL;
+       last_logged_failure = now;
+
+       /* We get here if every DB handle is unconnected and unconnectABLE */
+       radlog(L_INFO, "rlm_redis (%s): There are no DB handles to use! skipped %d, tried to connect %d",
+              inst->xlat_name, unconnected, tried_to_connect);
+       return NULL;
+}
+
+/*************************************************************************
+ *
+ *     Function: redis_release_socket
+ *
+ *     Purpose: Frees a REDIS socket back to the connection pool
+ *
+ *************************************************************************/
+int redis_release_socket(REDIS_INST *inst, REDISSOCK *dissocket)
+{
+
+#ifdef HAVE_PTHREAD_H
+       pthread_mutex_unlock(&dissocket->mutex);
+#endif
+
+       radlog(L_DBG, "rlm_redis (%s): Released redis socket id: %d",
+              inst->xlat_name, dissocket->id);
+
+       return 0;
+}
+
+static int redis_instantiate(CONF_SECTION *conf, void **instance)
+{
+       REDIS_INST *inst;
+       const char *xlat_name;
+
+       /*
+        *      Set up a storage area for instance data
+        */
+       inst = rad_malloc(sizeof (REDIS_INST));
+       if (!inst) {
+               return -1;
+       }
+       memset(inst, 0, sizeof (*inst));
+
+       /*
+        *      If the configuration parameters can't be parsed, then
+        *      fail.
+        */
+       if (cf_section_parse(conf, inst, module_config) < 0) {
+               free(inst);
+               return -1;
+       }
+
+       xlat_name = cf_section_name2(conf);
+
+       if (!xlat_name)
+               xlat_name = cf_section_name1(conf);
+
+       inst->xlat_name = strdup(xlat_name);
+       xlat_register(inst->xlat_name, (RAD_XLAT_FUNC)redis_xlat, inst);
+
+       if (redis_init_socketpool(inst) < 0) {
+               redis_detach(inst);
+               return -1;
+       }
+
+       inst->redis_query = rlm_redis_query;
+       inst->redis_finish_query = rlm_redis_finish_query;
+       inst->redis_get_socket = redis_get_socket;
+       inst->redis_release_socket = redis_release_socket;
+       inst->redis_escape_func = redis_escape_func;
+
+       *instance = inst;
+
+       return 0;
+}
+
+module_t rlm_redis = {
+       RLM_MODULE_INIT,
+       "redis",
+       RLM_TYPE_THREAD_SAFE, /* type */
+       redis_instantiate, /* instantiation */
+       redis_detach, /* detach */
+       {
+               NULL, /* authentication */
+               NULL, /* authorization */
+               NULL, /* preaccounting */
+               NULL, /* accounting */
+               NULL, /* checksimul */
+               NULL, /* pre-proxy */
+               NULL, /* post-proxy */
+               NULL /* post-auth */
+       },
+};
diff --git a/src/modules/rlm_redis/rlm_redis.h b/src/modules/rlm_redis/rlm_redis.h
new file mode 100644 (file)
index 0000000..c647b6b
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * rlm_redis.h
+ *
+ * Version:    $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2000,2006  The FreeRADIUS server project
+ * Copyright 2011  TekSavvy Solutions <gabe@teksavvy.com>
+ */
+
+#ifndef RLM_REDIS_H
+#define        RLM_REDIS_H
+
+#include <freeradius-devel/ident.h>
+RCSIDH(rlm_redis_h, "$Id$")
+
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+
+#include <freeradius-devel/modpriv.h>
+#include <hiredis/hiredis.h>
+
+typedef struct redis_socket {
+       int     id;
+
+#ifdef HAVE_PTHREAD_H
+       pthread_mutex_t mutex;
+#endif
+       struct redis_socket *next;
+       enum { sockconnected, sockunconnected } state;
+
+       redisContext    *conn;
+        redisReply      *reply;
+
+       time_t  connected;
+       int     queries;
+} REDISSOCK;
+
+typedef struct rlm_redis_t REDIS_INST;
+
+typedef struct rlm_redis_t {
+       time_t          connect_after;
+       REDISSOCK       *redispool;
+       REDISSOCK       *last_used;
+
+        char            *xlat_name;
+
+        int             numconnections;
+        int             connect_failure_retry_delay;
+       int             lifetime;
+       int             max_queries;
+
+        char            *hostname;
+        int             port;
+
+       REDISSOCK *(*redis_get_socket)(REDIS_INST * inst);
+       int (*redis_release_socket)(REDIS_INST * inst, REDISSOCK *dissocket);
+        int (*redis_query)(REDISSOCK *dissocket, REDIS_INST *inst, char *query);
+        int (*redis_finish_query)(REDISSOCK *dissocket);
+        size_t (*redis_escape_func)(char *out, size_t outlen, const char *in);
+
+} rlm_redis_t;
+
+#define MAX_QUERY_LEN                  4096
+
+int rlm_redis_query(REDISSOCK *dissocket, REDIS_INST *inst, char *query);
+int rlm_redis_finish_query(REDISSOCK *dissocket);
+
+REDISSOCK * redis_get_socket(REDIS_INST * inst);
+int redis_release_socket(REDIS_INST * inst, REDISSOCK *dissocket);
+
+#endif /* RLM_REDIS_H */
+