/* * Copyright (C) 2014-2016 Tobias Brunner * HSR Hochschule fuer Technik Rapperswil * * 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 . * * 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 #include #include #include #include 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, host_t *peer) { 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, peer); 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, ike_sa_t *ike_sa, host_t *requested) { identification_t *id; host_t *addr, *peer; id = ike_sa->get_other_eap_id(ike_sa); peer = ike_sa->get_other_host(ike_sa); this->lock->read_lock(this->lock); addr = find_addr(this, pools, id, requested, MEM_POOL_EXISTING, peer); if (!addr) { addr = find_addr(this, pools, id, requested, MEM_POOL_NEW, peer); if (!addr) { addr = find_addr(this, pools, id, requested, MEM_POOL_REASSIGN, peer); } } 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, ike_sa_t *ike_sa) { enumerator_t *enumerator; identification_t *id; bool found = FALSE; pool_t *pool; char *name; id = ike_sa->get_other_eap_id(ike_sa); 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; } CALLBACK(attr_filter, bool, void *data, enumerator_t *orig, va_list args) { attribute_t *attr; configuration_attribute_type_t *type; chunk_t *value; VA_ARGS_VGET(args, type, value); if (orig->enumerate(orig, &attr)) { *type = attr->type; *value = attr->value; return TRUE; } return FALSE; } /** * 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), 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; uint32_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 currently 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, ike_sa_t *ike_sa, 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 a range definition of an address pool */ static mem_pool_t *create_pool_range(char *name, char *buf) { mem_pool_t *pool; host_t *from, *to; if (!host_create_from_range(buf, &from, &to)) { return NULL; } pool = mem_pool_create_range(name, from, to); from->destroy(from); to->destroy(to); return pool; } /** * 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 */ uint32_t netmask = 0; if (mask) { /* shifting uint32_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]; mem_pool_t *pool; host_t *base = NULL; 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; } pool = create_pool_range(data->name, buf); if (!pool) { base = host_create_from_subnet(buf, &bits); if (base) { pool = mem_pool_create(data->name, base, bits); base->destroy(base); } } if (!pool) { data->request->reply = create_reply("invalid addrs value: %s", buf); return FALSE; } data->pool->vips = pool; 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, *leases; mem_pool_t *vips; pool_t *pool; identification_t *uid; host_t *lease; bool list_leases, on; char buf[32], *filter; int i; list_leases = message->get_bool(message, FALSE, "leases"); filter = message->get_str(message, NULL, "name"); builder = vici_builder_create(); this->lock->read_lock(this->lock); enumerator = this->pools->create_enumerator(this->pools); while (enumerator->enumerate(enumerator, &name, &pool)) { if (filter && !streq(name, filter)) { continue; } 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)); if (list_leases) { i = 0; builder->begin_section(builder, "leases"); leases = vips->create_lease_enumerator(vips); while (leases->enumerate(leases, &uid, &lease, &on)) { snprintf(buf, sizeof(buf), "%d", i++); builder->begin_section(builder, buf); builder->add_kv(builder, "address", "%H", lease); builder->add_kv(builder, "identity", "%Y", uid); builder->add_kv(builder, "status", on ? "online" : "offline"); builder->end_section(builder); } leases->destroy(leases); builder->end_section(builder); } 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; }