summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_cache/drivers/rlm_cache_memcached/rlm_cache_memcached.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/rlm_cache/drivers/rlm_cache_memcached/rlm_cache_memcached.c')
-rw-r--r--src/modules/rlm_cache/drivers/rlm_cache_memcached/rlm_cache_memcached.c347
1 files changed, 347 insertions, 0 deletions
diff --git a/src/modules/rlm_cache/drivers/rlm_cache_memcached/rlm_cache_memcached.c b/src/modules/rlm_cache/drivers/rlm_cache_memcached/rlm_cache_memcached.c
new file mode 100644
index 0000000..a8161cb
--- /dev/null
+++ b/src/modules/rlm_cache/drivers/rlm_cache_memcached/rlm_cache_memcached.c
@@ -0,0 +1,347 @@
+/*
+ * This program is 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
+ */
+
+/**
+ * $Id$
+ * @file rlm_cache_memcached.c
+ * @brief memcached based cache.
+ *
+ * @copyright 2014 The FreeRADIUS server project
+ */
+#include <libmemcached/memcached.h>
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/rad_assert.h>
+
+#include "../../rlm_cache.h"
+#include "../../serialize.h"
+
+typedef struct rlm_cache_memcached_handle {
+ memcached_st *handle;
+} rlm_cache_memcached_handle_t;
+
+typedef struct rlm_cache_memcached {
+ char const *options; //!< Connection options
+ fr_connection_pool_t *pool;
+} rlm_cache_memcached_t;
+
+static const CONF_PARSER driver_config[] = {
+ { "options", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_cache_memcached_t, options), "--SERVER=localhost" },
+ CONF_PARSER_TERMINATOR
+};
+
+/** Free a connection handle
+ *
+ * @param mandle to free.
+ */
+static int _mod_conn_free(rlm_cache_memcached_handle_t *mandle)
+{
+ if (mandle->handle) memcached_free(mandle->handle);
+
+ return 0;
+}
+
+/** Create a new memcached handle
+ *
+ * @param ctx to allocate handle in.
+ * @param instance data.
+ */
+static void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
+{
+ rlm_cache_t *inst = instance;
+ rlm_cache_memcached_t *driver = inst->driver;
+ rlm_cache_memcached_handle_t *mandle;
+
+ memcached_st *sandle;
+ memcached_return_t ret;
+
+ sandle = memcached(driver->options, talloc_array_length(driver->options) -1);
+ if (!sandle) {
+ ERROR("rlm_cache_memcached: Failed creating memcached connection");
+
+ return NULL;
+ }
+
+ ret = memcached_version(sandle);
+ if (ret != MEMCACHED_SUCCESS) {
+ ERROR("rlm_cache_memcached: Failed getting server info: %s: %s", memcached_strerror(sandle, ret),
+ memcached_last_error_message(sandle));
+ memcached_free(sandle);
+ return NULL;
+ }
+
+ mandle = talloc_zero(ctx, rlm_cache_memcached_handle_t);
+ mandle->handle = sandle;
+ talloc_set_destructor(mandle, _mod_conn_free);
+
+ return mandle;
+}
+
+/** Cleanup a rlm_cache_memcached instance
+ *
+ * @param driver to free.
+ * @return 0
+ */
+static int _mod_detach(rlm_cache_memcached_t *driver)
+{
+ fr_connection_pool_free(driver->pool);
+ return 0;
+}
+
+/** Create a new rlm_cache_memcached instance
+ *
+ * @param conf memcached specific conf section.
+ * @param inst main rlm_cache instance.
+ * @return 0 on success, -1 on failure.
+ */
+static int mod_instantiate(CONF_SECTION *conf, rlm_cache_t *inst)
+{
+ rlm_cache_memcached_t *driver;
+ memcached_return_t ret;
+
+ char buffer[256];
+
+ static bool version_done;
+
+ buffer[0] = '\0';
+
+ /*
+ * Get version info from the libmemcached API.
+ */
+ if (!version_done) {
+ version_done = true;
+
+ INFO("rlm_cache_memcached: libmemcached version: %s", memcached_lib_version());
+ }
+
+ driver = talloc_zero(inst, rlm_cache_memcached_t);
+ talloc_set_destructor(driver, _mod_detach);
+
+ if (cf_section_parse(conf, driver, driver_config) < 0) return -1;
+
+ ret = libmemcached_check_configuration(driver->options, talloc_array_length(driver->options) -1,
+ buffer, sizeof(buffer));
+ if (ret != MEMCACHED_SUCCESS) {
+ ERROR("rlm_cache_memcached: Failed validating options string: %s", buffer);
+ return -1;
+ }
+
+ inst->driver = driver;
+
+ snprintf(buffer, sizeof(buffer), "rlm_cache (%s)", inst->name);
+
+ driver->pool = fr_connection_pool_module_init(conf, inst, mod_conn_create, NULL, buffer);
+ if (!driver->pool) return -1;
+
+ if (inst->max_entries > 0) WARN("rlm_cache_memcached: max_entries is not supported by this driver");
+
+ return 0;
+}
+
+static void cache_entry_free(rlm_cache_entry_t *c)
+{
+ talloc_free(c);
+}
+
+/** Locate a cache entry in memcached
+ *
+ * @param out Where to write the pointer to the cach entry.
+ * @param inst main rlm_cache instance.
+ * @param request The current request.
+ * @param handle Pointer to memcached handle.
+ * @param key to search for.
+ * @return CACHE_OK on success CACHE_MISS if no entry found, CACHE_ERROR on error.
+ */
+static cache_status_t cache_entry_find(rlm_cache_entry_t **out, UNUSED rlm_cache_t *inst, REQUEST *request,
+ rlm_cache_handle_t **handle, char const *key)
+{
+ rlm_cache_memcached_handle_t *mandle = *handle;
+
+ memcached_return_t mret;
+ size_t len;
+ int ret;
+ uint32_t flags;
+
+ char *from_store;
+
+ rlm_cache_entry_t *c;
+
+ from_store = memcached_get(mandle->handle, key, strlen(key), &len, &flags, &mret);
+ if (!from_store) {
+ if (mret == MEMCACHED_NOTFOUND) return CACHE_MISS;
+
+ RERROR("Failed retrieving entry for key \"%s\": %s: %s", key, memcached_strerror(mandle->handle, mret),
+ memcached_last_error_message(mandle->handle));
+
+ return CACHE_ERROR;
+ }
+ RDEBUG2("Retrieved %zu bytes from memcached", len);
+ RDEBUG2("%s", from_store);
+
+ c = talloc_zero(NULL, rlm_cache_entry_t);
+ ret = cache_deserialize(c, from_store, len);
+ free(from_store);
+ if (ret < 0) {
+ RERROR("%s", fr_strerror());
+ talloc_free(c);
+ return CACHE_ERROR;
+ }
+ c->key = talloc_strdup(c, key);
+ *out = c;
+
+ return CACHE_OK;
+}
+
+/** Insert a new entry into the data store
+ *
+ * @param inst main rlm_cache instance.
+ * @param request The current request.
+ * @param handle Pointer to memcached handle.
+ * @param c entry to insert.
+ * @return CACHE_OK on success else CACHE_ERROR on error.
+ */
+static cache_status_t cache_entry_insert(UNUSED rlm_cache_t *inst, REQUEST *request, rlm_cache_handle_t **handle,
+ rlm_cache_entry_t *c)
+{
+ rlm_cache_memcached_handle_t *mandle = *handle;
+
+ memcached_return_t ret;
+
+ TALLOC_CTX *pool;
+ char *to_store;
+
+ pool = talloc_pool(NULL, 1024);
+ if (!pool) return CACHE_ERROR;
+
+ if (cache_serialize(pool, &to_store, c) < 0) {
+ talloc_free(pool);
+
+ return CACHE_ERROR;
+ }
+
+ ret = memcached_set(mandle->handle, c->key, talloc_array_length(c->key) - 1,
+ to_store ? to_store : "",
+ to_store ? talloc_array_length(to_store) - 1 : 0, c->expires, 0);
+ talloc_free(pool);
+ if (ret != MEMCACHED_SUCCESS) {
+ RERROR("Failed storing entry with key \"%s\": %s: %s", c->key,
+ memcached_strerror(mandle->handle, ret),
+ memcached_last_error_message(mandle->handle));
+
+ return CACHE_ERROR;
+ }
+
+ return CACHE_OK;
+}
+
+/** Call delete the cache entry from memcached
+ *
+ * @param inst main rlm_cache instance.
+ * @param request The current request.
+ * @param handle Pointer to memcached handle.
+ * @param c entry to expire.
+ * @return CACHE_OK on success else CACHE_ERROR.
+ */
+static cache_status_t cache_entry_expire(UNUSED rlm_cache_t *inst, REQUEST *request, rlm_cache_handle_t **handle,
+ rlm_cache_entry_t *c)
+{
+ rlm_cache_memcached_handle_t *mandle = *handle;
+
+ memcached_return_t ret;
+
+ ret = memcached_delete(mandle->handle, c->key, talloc_array_length(c->key) - 1, 0);
+ if (ret != MEMCACHED_SUCCESS) {
+ RERROR("Failed deleting entry with key \"%s\": %s", c->key,
+ memcached_last_error_message(mandle->handle));
+
+ return CACHE_ERROR;
+ }
+
+ return CACHE_OK;
+}
+
+/** Get a memcached handle
+ *
+ * @param out Where to write the handle.
+ * @param inst rlm_cache instance.
+ * @param request The current request.
+ */
+static int mod_conn_get(rlm_cache_handle_t **out, rlm_cache_t *inst, UNUSED REQUEST *request)
+{
+ rlm_cache_memcached_t *driver = inst->driver;
+ rlm_cache_handle_t *mandle;
+
+ *out = NULL;
+
+ mandle = fr_connection_get(driver->pool);
+ if (!mandle) {
+ *out = NULL;
+ return -1;
+ }
+ *out = mandle;
+
+ return 0;
+}
+
+/** Release a socket
+ *
+ * @param inst main rlm_cache instance.
+ * @param request The current request.
+ * @param handle Pointer to the handle to release (will be set to NULL).
+ */
+static void mod_conn_release(rlm_cache_t *inst, UNUSED REQUEST *request, rlm_cache_handle_t **handle)
+{
+ rlm_cache_memcached_t *driver = inst->driver;
+
+ fr_connection_release(driver->pool, *handle);
+ *handle = NULL;
+}
+
+/** Reconnect a socket
+ *
+ * @param inst main rlm_cache instance.
+ * @param request The current request.
+ * @param handle Pointer to the handle to reconnect (will be set to NULL if reconnection fails).
+ */
+static int mod_conn_reconnect(rlm_cache_t *inst, UNUSED REQUEST *request, rlm_cache_handle_t **handle)
+{
+ rlm_cache_memcached_t *driver = inst->driver;
+ rlm_cache_handle_t *mandle;
+
+ mandle = fr_connection_reconnect(driver->pool, *handle);
+ if (!mandle) {
+ *handle = NULL;
+ return -1;
+ }
+ *handle = mandle;
+
+ return 0;
+}
+
+extern cache_module_t rlm_cache_memcached;
+cache_module_t rlm_cache_memcached = {
+ .name = "rlm_cache_memcached",
+ .instantiate = mod_instantiate,
+ .free = cache_entry_free,
+
+ .find = cache_entry_find,
+ .insert = cache_entry_insert,
+ .expire = cache_entry_expire,
+
+ .acquire = mod_conn_get,
+ .release = mod_conn_release,
+ .reconnect = mod_conn_reconnect
+};