summaryrefslogtreecommitdiff
path: root/src/libcharon/plugins/vici/vici_attribute.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcharon/plugins/vici/vici_attribute.c')
-rw-r--r--src/libcharon/plugins/vici/vici_attribute.c713
1 files changed, 713 insertions, 0 deletions
diff --git a/src/libcharon/plugins/vici/vici_attribute.c b/src/libcharon/plugins/vici/vici_attribute.c
new file mode 100644
index 000000000..2178116c9
--- /dev/null
+++ b/src/libcharon/plugins/vici/vici_attribute.c
@@ -0,0 +1,713 @@
+/*
+ * Copyright (C) 2014 Martin Willi
+ * Copyright (C) 2014 revosec AG
+ *
+ * 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. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.
+ */
+
+#include "vici_attribute.h"
+#include "vici_builder.h"
+
+#include <daemon.h>
+#include <collections/hashtable.h>
+#include <collections/array.h>
+#include <threading/rwlock.h>
+#include <attributes/mem_pool.h>
+
+typedef struct private_vici_attribute_t private_vici_attribute_t;
+
+/**
+ * private data of vici_attribute
+ */
+struct private_vici_attribute_t {
+
+ /**
+ * public functions
+ */
+ vici_attribute_t public;
+
+ /**
+ * vici connection dispatcher
+ */
+ vici_dispatcher_t *dispatcher;
+
+ /**
+ * Configured pools, as char* => pool_t
+ */
+ hashtable_t *pools;
+
+ /**
+ * rwlock to lock access to pools
+ */
+ rwlock_t *lock;
+};
+
+/**
+ * Single configuration attribute with type
+ */
+typedef struct {
+ /** type of attribute */
+ configuration_attribute_type_t type;
+ /** attribute value */
+ chunk_t value;
+} attribute_t;
+
+/**
+ * Clean up an attribute
+ */
+static void attribute_destroy(attribute_t *attr)
+{
+ free(attr->value.ptr);
+ free(attr);
+}
+
+/**
+ * Pool instances with associated attributes
+ */
+typedef struct {
+ /** in-memory virtual IP pool */
+ mem_pool_t *vips;
+ /** configuration attributes, as attribute_t */
+ array_t *attrs;
+} pool_t;
+
+/**
+ * Clean up a pool instance
+ */
+static void pool_destroy(pool_t *pool)
+{
+ DESTROY_IF(pool->vips);
+ array_destroy_function(pool->attrs, (void*)attribute_destroy, NULL);
+ free(pool);
+}
+
+/**
+ * Find an existing or not yet existing lease
+ */
+static host_t *find_addr(private_vici_attribute_t *this, linked_list_t *pools,
+ identification_t *id, host_t *requested, mem_pool_op_t op)
+{
+ enumerator_t *enumerator;
+ host_t *addr = NULL;
+ pool_t *pool;
+ char *name;
+
+ enumerator = pools->create_enumerator(pools);
+ while (enumerator->enumerate(enumerator, &name))
+ {
+ pool = this->pools->get(this->pools, name);
+ if (pool)
+ {
+ addr = pool->vips->acquire_address(pool->vips, id, requested, op);
+ if (addr)
+ {
+ break;
+ }
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ return addr;
+}
+
+METHOD(attribute_provider_t, acquire_address, host_t*,
+ private_vici_attribute_t *this, linked_list_t *pools, identification_t *id,
+ host_t *requested)
+{
+ host_t *addr;
+
+ this->lock->read_lock(this->lock);
+
+ addr = find_addr(this, pools, id, requested, MEM_POOL_EXISTING);
+ if (!addr)
+ {
+ addr = find_addr(this, pools, id, requested, MEM_POOL_NEW);
+ if (!addr)
+ {
+ addr = find_addr(this, pools, id, requested, MEM_POOL_REASSIGN);
+ }
+ }
+
+ this->lock->unlock(this->lock);
+
+ return addr;
+}
+
+METHOD(attribute_provider_t, release_address, bool,
+ private_vici_attribute_t *this, linked_list_t *pools, host_t *address,
+ identification_t *id)
+{
+ enumerator_t *enumerator;
+ bool found = FALSE;
+ pool_t *pool;
+ char *name;
+
+ this->lock->read_lock(this->lock);
+
+ enumerator = pools->create_enumerator(pools);
+ while (enumerator->enumerate(enumerator, &name))
+ {
+ pool = this->pools->get(this->pools, name);
+ if (pool)
+ {
+ found = pool->vips->release_address(pool->vips, address, id);
+ if (found)
+ {
+ break;
+ }
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ this->lock->unlock(this->lock);
+
+ return found;
+}
+
+/**
+ * Filter mapping attribute_t to enumerated type/value arguments
+ */
+static bool attr_filter(void *data, attribute_t **attr,
+ configuration_attribute_type_t *type,
+ void *in, chunk_t *value)
+{
+ *type = (*attr)->type;
+ *value = (*attr)->value;
+ return TRUE;
+}
+
+/**
+ * Create nested inner enumerator over pool attributes
+ */
+CALLBACK(create_nested, enumerator_t*,
+ pool_t *pool, void *this)
+{
+ return enumerator_create_filter(array_create_enumerator(pool->attrs),
+ (void*)attr_filter, NULL, NULL);
+}
+
+/**
+ * Data associated to nested enumerator cleanup
+ */
+typedef struct {
+ private_vici_attribute_t *this;
+ linked_list_t *list;
+} nested_data_t;
+
+/**
+ * Clean up nested enumerator data
+ */
+CALLBACK(nested_cleanup, void,
+ nested_data_t *data)
+{
+ data->this->lock->unlock(data->this->lock);
+ data->list->destroy(data->list);
+ free(data);
+}
+
+/**
+ * Check if any of vips is from pool
+ */
+static bool have_vips_from_pool(mem_pool_t *pool, linked_list_t *vips)
+{
+ enumerator_t *enumerator;
+ host_t *host;
+ chunk_t start, end, current;
+ u_int32_t size;
+ bool found = FALSE;
+
+ host = pool->get_base(pool);
+ start = host->get_address(host);
+
+ if (start.len >= sizeof(size))
+ {
+ end = chunk_clone(start);
+
+ /* mem_pool is currenty limited to 2^31 addresses, so 32-bit
+ * calculations should be sufficient. */
+ size = untoh32(start.ptr + start.len - sizeof(size));
+ htoun32(end.ptr + end.len - sizeof(size), size + pool->get_size(pool));
+
+ enumerator = vips->create_enumerator(vips);
+ while (enumerator->enumerate(enumerator, &host))
+ {
+ current = host->get_address(host);
+ if (chunk_compare(current, start) >= 0 &&
+ chunk_compare(current, end) < 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ free(end.ptr);
+ }
+ return found;
+}
+
+METHOD(attribute_provider_t, create_attribute_enumerator, enumerator_t*,
+ private_vici_attribute_t *this, linked_list_t *pools,
+ identification_t *id, linked_list_t *vips)
+{
+ enumerator_t *enumerator;
+ nested_data_t *data;
+ pool_t *pool;
+ char *name;
+
+ INIT(data,
+ .this = this,
+ .list = linked_list_create(),
+ );
+
+ this->lock->read_lock(this->lock);
+
+ enumerator = pools->create_enumerator(pools);
+ while (enumerator->enumerate(enumerator, &name))
+ {
+ pool = this->pools->get(this->pools, name);
+ if (pool && have_vips_from_pool(pool->vips, vips))
+ {
+ data->list->insert_last(data->list, pool);
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ return enumerator_create_nested(data->list->create_enumerator(data->list),
+ create_nested, data, nested_cleanup);
+}
+
+/**
+ * Merge a pool configuration with existing ones
+ */
+static bool merge_pool(private_vici_attribute_t *this, pool_t *new)
+{
+ mem_pool_t *tmp;
+ host_t *base;
+ pool_t *old;
+ const char *name;
+ u_int size;
+
+ name = new->vips->get_name(new->vips);
+ base = new->vips->get_base(new->vips);
+ size = new->vips->get_size(new->vips);
+
+ old = this->pools->remove(this->pools, name);
+ if (!old)
+ {
+ this->pools->put(this->pools, name, new);
+ DBG1(DBG_CFG, "added vici pool %s: %H, %u entries", name, base, size);
+ return TRUE;
+ }
+
+ if (base->ip_equals(base, old->vips->get_base(old->vips)) &&
+ size == old->vips->get_size(old->vips))
+ {
+ /* no changes in pool, so keep existing, but use new attributes */
+ DBG1(DBG_CFG, "updated vici pool %s: %H, %u entries", name, base, size);
+ tmp = new->vips;
+ new->vips = old->vips;
+ old->vips = tmp;
+ this->pools->put(this->pools, new->vips->get_name(new->vips), new);
+ pool_destroy(old);
+ return TRUE;
+ }
+ if (old->vips->get_online(old->vips) == 0)
+ {
+ /* can replace old pool, no online leases */
+ DBG1(DBG_CFG, "replaced vici pool %s: %H, %u entries", name, base, size);
+ this->pools->put(this->pools, name, new);
+ pool_destroy(old);
+ return TRUE;
+ }
+ /* have online leases, unable to replace, TODO: migrate leases? */
+ DBG1(DBG_CFG, "vici pool %s has %u online leases, unable to replace",
+ name, old->vips->get_online(old->vips));
+ this->pools->put(this->pools, old->vips->get_name(old->vips), old);
+ return FALSE;
+}
+
+/**
+ * Create a (error) reply message
+ */
+static vici_message_t* create_reply(char *fmt, ...)
+{
+ vici_builder_t *builder;
+ va_list args;
+
+ builder = vici_builder_create();
+ builder->add_kv(builder, "success", fmt ? "no" : "yes");
+ if (fmt)
+ {
+ va_start(args, fmt);
+ builder->vadd_kv(builder, "errmsg", fmt, args);
+ va_end(args);
+ }
+ return builder->finalize(builder);
+}
+
+/**
+ * Parse callback data, passed to each callback
+ */
+typedef struct {
+ private_vici_attribute_t *this;
+ vici_message_t *reply;
+} request_data_t;
+
+/**
+ * Data associated to a pool load
+ */
+typedef struct {
+ request_data_t *request;
+ char *name;
+ pool_t *pool;
+} load_data_t;
+
+CALLBACK(pool_li, bool,
+ load_data_t *data, vici_message_t *message, char *name, chunk_t value)
+{
+ struct {
+ char *name;
+ configuration_attribute_type_t v4;
+ configuration_attribute_type_t v6;
+ } keys[] = {
+ {"address", INTERNAL_IP4_ADDRESS, INTERNAL_IP6_ADDRESS },
+ {"dns", INTERNAL_IP4_DNS, INTERNAL_IP6_DNS },
+ {"nbns", INTERNAL_IP4_NBNS, INTERNAL_IP6_NBNS },
+ {"dhcp", INTERNAL_IP4_DHCP, INTERNAL_IP6_DHCP },
+ {"netmask", INTERNAL_IP4_NETMASK, INTERNAL_IP6_NETMASK },
+ {"server", INTERNAL_IP4_SERVER, INTERNAL_IP6_SERVER },
+ {"subnet", INTERNAL_IP4_SUBNET, INTERNAL_IP6_SUBNET },
+ {"split_include", UNITY_SPLIT_INCLUDE, UNITY_SPLIT_INCLUDE },
+ {"split_exclude", UNITY_LOCAL_LAN, UNITY_LOCAL_LAN },
+ };
+ char buf[256];
+ int i, index = -1, mask = -1, type = 0;
+ chunk_t encoding;
+ attribute_t *attr;
+ host_t *host = NULL;
+
+ for (i = 0; i < countof(keys); i++)
+ {
+ if (streq(name, keys[i].name))
+ {
+ index = i;
+ break;
+ }
+ }
+ if (index == -1)
+ {
+ type = atoi(name);
+ if (!type)
+ {
+ data->request->reply = create_reply("invalid attribute: %s", name);
+ return FALSE;
+ }
+ }
+
+ if (vici_stringify(value, buf, sizeof(buf)))
+ {
+ if (strchr(buf, '/'))
+ {
+ host = host_create_from_subnet(buf, &mask);
+ }
+ else
+ {
+ host = host_create_from_string(buf, 0);
+ }
+ }
+ if (host)
+ {
+ if (index != -1)
+ {
+ switch (host->get_family(host))
+ {
+ case AF_INET:
+ type = keys[index].v4;
+ break;
+ case AF_INET6:
+ default:
+ type = keys[index].v6;
+ break;
+ }
+ }
+ if (mask == -1)
+ {
+ encoding = chunk_clone(host->get_address(host));
+ }
+ else
+ {
+ if (host->get_family(host) == AF_INET)
+ { /* IPv4 attributes contain a subnet mask */
+ u_int32_t netmask = 0;
+
+ if (mask)
+ { /* shifting u_int32_t by 32 or more is undefined */
+ mask = 32 - mask;
+ netmask = htonl((0xFFFFFFFF >> mask) << mask);
+ }
+ encoding = chunk_cat("cc", host->get_address(host),
+ chunk_from_thing(netmask));
+ }
+ else
+ { /* IPv6 addresses the prefix only */
+ encoding = chunk_cat("cc", host->get_address(host),
+ chunk_from_chars(mask));
+ }
+ }
+ host->destroy(host);
+ }
+ else
+ {
+ if (index != -1)
+ {
+ data->request->reply = create_reply("invalid attribute value "
+ "for %s", name);
+ return FALSE;
+ }
+ /* use raw binary data for numbered attributes */
+ encoding = chunk_clone(value);
+ }
+ INIT(attr,
+ .type = type,
+ .value = encoding,
+ );
+ array_insert_create(&data->pool->attrs, ARRAY_TAIL, attr);
+ return TRUE;
+}
+
+CALLBACK(pool_kv, bool,
+ load_data_t *data, vici_message_t *message, char *name, chunk_t value)
+{
+ if (streq(name, "addrs"))
+ {
+ char buf[128];
+ host_t *base;
+ int bits;
+
+ if (data->pool->vips)
+ {
+ data->request->reply = create_reply("multiple addrs defined");
+ return FALSE;
+ }
+ if (!vici_stringify(value, buf, sizeof(buf)))
+ {
+ data->request->reply = create_reply("invalid addrs value");
+ return FALSE;
+ }
+ base = host_create_from_subnet(buf, &bits);
+ if (!base)
+ {
+ data->request->reply = create_reply("invalid addrs value: %s", buf);
+ return FALSE;
+ }
+ data->pool->vips = mem_pool_create(data->name, base, bits);
+ base->destroy(base);
+ return TRUE;
+ }
+ data->request->reply = create_reply("invalid attribute: %s", name);
+ return FALSE;
+}
+
+CALLBACK(pool_sn, bool,
+ request_data_t *request, vici_message_t *message,
+ vici_parse_context_t *ctx, char *name)
+{
+ load_data_t data = {
+ .request = request,
+ .name = name,
+ };
+ bool merged;
+
+ INIT(data.pool);
+
+ if (!message->parse(message, ctx, NULL, pool_kv, pool_li, &data))
+ {
+ pool_destroy(data.pool);
+ return FALSE;
+ }
+
+ if (!data.pool->vips)
+ {
+ request->reply = create_reply("missing addrs for pool '%s'", name);
+ pool_destroy(data.pool);
+ return FALSE;
+ }
+
+ request->this->lock->write_lock(request->this->lock);
+ merged = merge_pool(request->this, data.pool);
+ request->this->lock->unlock(request->this->lock);
+
+ if (!merged)
+ {
+ request->reply = create_reply("vici pool %s has online leases, "
+ "unable to replace", name);
+ pool_destroy(data.pool);
+ }
+ return merged;
+}
+
+CALLBACK(load_pool, vici_message_t*,
+ private_vici_attribute_t *this, char *name, u_int id,
+ vici_message_t *message)
+{
+ request_data_t request = {
+ .this = this,
+ };
+
+ if (!message->parse(message, NULL, pool_sn, NULL, NULL, &request))
+ {
+ if (request.reply)
+ {
+ return request.reply;
+ }
+ return create_reply("parsing request failed");
+ }
+ return create_reply(NULL);
+}
+
+CALLBACK(unload_pool, vici_message_t*,
+ private_vici_attribute_t *this, char *name, u_int id,
+ vici_message_t *message)
+{
+ vici_message_t *reply;
+ u_int online;
+ pool_t *pool;
+
+ name = message->get_str(message, NULL, "name");
+ if (!name)
+ {
+ return create_reply("missing pool name to unload");
+ }
+
+ this->lock->write_lock(this->lock);
+
+ pool = this->pools->remove(this->pools, name);
+ if (pool)
+ {
+ online = pool->vips->get_online(pool->vips);
+ if (online)
+ {
+ DBG1(DBG_CFG, "vici pool %s has %u online leases, unable to unload",
+ name, online);
+ reply = create_reply("%s has online leases, unable to unload", name);
+ this->pools->put(this->pools, pool->vips->get_name(pool->vips), pool);
+ }
+ else
+ {
+ DBG1(DBG_CFG, "unloaded vici pool %s", name);
+ reply = create_reply(NULL);
+ pool_destroy(pool);
+ }
+ }
+ else
+ {
+ reply = create_reply("%s not found", name);
+ }
+
+ this->lock->unlock(this->lock);
+
+ return reply;
+}
+
+CALLBACK(get_pools, vici_message_t*,
+ private_vici_attribute_t *this, char *name, u_int id,
+ vici_message_t *message)
+{
+ vici_builder_t *builder;
+ enumerator_t *enumerator;
+ mem_pool_t *vips;
+ pool_t *pool;
+
+ builder = vici_builder_create();
+
+ this->lock->read_lock(this->lock);
+ enumerator = this->pools->create_enumerator(this->pools);
+ while (enumerator->enumerate(enumerator, &name, &pool))
+ {
+ vips = pool->vips;
+
+ builder->begin_section(builder, name);
+
+ builder->add_kv(builder, "base", "%H", vips->get_base(vips));
+ builder->add_kv(builder, "size", "%u", vips->get_size(vips));
+ builder->add_kv(builder, "online", "%u", vips->get_online(vips));
+ builder->add_kv(builder, "offline", "%u", vips->get_offline(vips));
+
+ builder->end_section(builder);
+ }
+ enumerator->destroy(enumerator);
+ this->lock->unlock(this->lock);
+
+ return builder->finalize(builder);
+}
+
+static void manage_command(private_vici_attribute_t *this,
+ char *name, vici_command_cb_t cb, bool reg)
+{
+ this->dispatcher->manage_command(this->dispatcher, name,
+ reg ? cb : NULL, this);
+}
+
+/**
+ * (Un-)register dispatcher functions
+ */
+static void manage_commands(private_vici_attribute_t *this, bool reg)
+{
+ manage_command(this, "load-pool", load_pool, reg);
+ manage_command(this, "unload-pool", unload_pool, reg);
+ manage_command(this, "get-pools", get_pools, reg);
+}
+
+METHOD(vici_attribute_t, destroy, void,
+ private_vici_attribute_t *this)
+{
+ enumerator_t *enumerator;
+ pool_t *pool;
+
+ manage_commands(this, FALSE);
+
+ enumerator = this->pools->create_enumerator(this->pools);
+ while (enumerator->enumerate(enumerator, NULL, &pool))
+ {
+ pool_destroy(pool);
+ }
+ enumerator->destroy(enumerator);
+ this->pools->destroy(this->pools);
+ this->lock->destroy(this->lock);
+ free(this);
+}
+
+/*
+ * see header file
+ */
+vici_attribute_t *vici_attribute_create(vici_dispatcher_t *dispatcher)
+{
+ private_vici_attribute_t *this;
+
+ INIT(this,
+ .public = {
+ .provider = {
+ .acquire_address = _acquire_address,
+ .release_address = _release_address,
+ .create_attribute_enumerator = _create_attribute_enumerator,
+ },
+ .destroy = _destroy,
+ },
+ .dispatcher = dispatcher,
+ .pools = hashtable_create(hashtable_hash_str, hashtable_equals_str, 4),
+ .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
+ );
+
+ manage_commands(this, TRUE);
+
+ return &this->public;
+}