diff options
Diffstat (limited to 'src/libcharon/plugins/vici/vici_attribute.c')
-rw-r--r-- | src/libcharon/plugins/vici/vici_attribute.c | 713 |
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; +} |