diff options
Diffstat (limited to 'src/libhydra/plugins/attr_sql/sql_attribute.c')
-rw-r--r-- | src/libhydra/plugins/attr_sql/sql_attribute.c | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/src/libhydra/plugins/attr_sql/sql_attribute.c b/src/libhydra/plugins/attr_sql/sql_attribute.c new file mode 100644 index 000000000..a7cfde649 --- /dev/null +++ b/src/libhydra/plugins/attr_sql/sql_attribute.c @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2008 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * 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 <time.h> + +#include <debug.h> +#include <library.h> + +#include "sql_attribute.h" + +typedef struct private_sql_attribute_t private_sql_attribute_t; + +/** + * private data of sql_attribute + */ +struct private_sql_attribute_t { + + /** + * public functions + */ + sql_attribute_t public; + + /** + * database connection + */ + database_t *db; + + /** + * wheter to record lease history in lease table + */ + bool history; +}; + +/** + * lookup/insert an identity + */ +static u_int get_identity(private_sql_attribute_t *this, identification_t *id) +{ + enumerator_t *e; + u_int row; + + /* look for peer identity in the identities table */ + e = this->db->query(this->db, + "SELECT id FROM identities WHERE type = ? AND data = ?", + DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id), + DB_UINT); + + if (e && e->enumerate(e, &row)) + { + e->destroy(e); + return row; + } + DESTROY_IF(e); + /* not found, insert new one */ + if (this->db->execute(this->db, &row, + "INSERT INTO identities (type, data) VALUES (?, ?)", + DB_INT, id->get_type(id), DB_BLOB, id->get_encoding(id)) == 1) + { + return row; + } + return 0; +} + +/** + * Lookup pool by name + */ +static u_int get_pool(private_sql_attribute_t *this, char *name, u_int *timeout) +{ + enumerator_t *e; + u_int pool; + + e = this->db->query(this->db, "SELECT id, timeout FROM pools WHERE name = ?", + DB_TEXT, name, DB_UINT, DB_UINT); + if (e && e->enumerate(e, &pool, timeout)) + { + e->destroy(e); + return pool; + } + DESTROY_IF(e); + return 0; +} + +/** + * Look up an existing lease + */ +static host_t* check_lease(private_sql_attribute_t *this, char *name, + u_int pool, u_int identity) +{ + while (TRUE) + { + u_int id; + chunk_t address; + enumerator_t *e; + time_t now = time(NULL); + + e = this->db->query(this->db, + "SELECT id, address FROM addresses " + "WHERE pool = ? AND identity = ? AND released != 0 LIMIT 1", + DB_UINT, pool, DB_UINT, identity, DB_UINT, DB_BLOB); + if (!e || !e->enumerate(e, &id, &address)) + { + DESTROY_IF(e); + break; + } + address = chunk_clonea(address); + e->destroy(e); + + if (this->db->execute(this->db, NULL, + "UPDATE addresses SET acquired = ?, released = 0 " + "WHERE id = ? AND identity = ? AND released != 0", + DB_UINT, now, DB_UINT, id, DB_UINT, identity) > 0) + { + host_t *host; + + host = host_create_from_chunk(AF_UNSPEC, address, 0); + if (host) + { + DBG1(DBG_CFG, "acquired existing lease for address %H in" + " pool '%s'", host, name); + return host; + } + } + } + return NULL; +} + +/** + * We check for unallocated addresses or expired leases. First we select an + * address as a candidate, but double check later on if it is still available + * during the update operation. This allows us to work without locking. + */ +static host_t* get_lease(private_sql_attribute_t *this, char *name, + u_int pool, u_int timeout, u_int identity) +{ + while (TRUE) + { + u_int id; + chunk_t address; + enumerator_t *e; + time_t now = time(NULL); + int hits; + + if (timeout) + { + /* check for an expired lease */ + e = this->db->query(this->db, + "SELECT id, address FROM addresses " + "WHERE pool = ? AND released != 0 AND released < ? LIMIT 1", + DB_UINT, pool, DB_UINT, now - timeout, DB_UINT, DB_BLOB); + } + else + { + /* with static leases, check for an unallocated address */ + e = this->db->query(this->db, + "SELECT id, address FROM addresses " + "WHERE pool = ? AND identity = 0 LIMIT 1", + DB_UINT, pool, DB_UINT, DB_BLOB); + + } + + if (!e || !e->enumerate(e, &id, &address)) + { + DESTROY_IF(e); + break; + } + address = chunk_clonea(address); + e->destroy(e); + + if (timeout) + { + hits = this->db->execute(this->db, NULL, + "UPDATE addresses SET " + "acquired = ?, released = 0, identity = ? " + "WHERE id = ? AND released != 0 AND released < ?", + DB_UINT, now, DB_UINT, identity, + DB_UINT, id, DB_UINT, now - timeout); + } + else + { + hits = this->db->execute(this->db, NULL, + "UPDATE addresses SET " + "acquired = ?, released = 0, identity = ? " + "WHERE id = ? AND identity = 0", + DB_UINT, now, DB_UINT, identity, DB_UINT, id); + } + if (hits > 0) + { + host_t *host; + + host = host_create_from_chunk(AF_UNSPEC, address, 0); + if (host) + { + DBG1(DBG_CFG, "acquired new lease for address %H in pool '%s'", + host, name); + return host; + } + } + } + DBG1(DBG_CFG, "no available address found in pool '%s'", name); + return NULL; +} + +/** + * Implementation of attribute_provider_t.acquire_address + */ +static host_t* acquire_address(private_sql_attribute_t *this, + char *names, identification_t *id, + host_t *requested) +{ + host_t *address = NULL; + u_int identity, pool, timeout; + + identity = get_identity(this, id); + if (identity) + { + /* check for a single pool first (no concatenation and enumeration) */ + if (strchr(names, ',') == NULL) + { + pool = get_pool(this, names, &timeout); + if (pool) + { + /* check for an existing lease */ + address = check_lease(this, names, pool, identity); + if (address == NULL) + { + /* get an unallocated address or expired lease */ + address = get_lease(this, names, pool, timeout, identity); + } + } + } + else + { + enumerator_t *enumerator; + char *name; + + /* in a first step check for an existing lease over all pools */ + enumerator = enumerator_create_token(names, ",", " "); + while (enumerator->enumerate(enumerator, &name)) + { + pool = get_pool(this, name, &timeout); + if (pool) + { + address = check_lease(this, name, pool, identity); + if (address) + { + enumerator->destroy(enumerator); + return address; + } + } + } + enumerator->destroy(enumerator); + + /* in a second step get an unallocated address or expired lease */ + enumerator = enumerator_create_token(names, ",", " "); + while (enumerator->enumerate(enumerator, &name)) + { + pool = get_pool(this, name, &timeout); + if (pool) + { + address = get_lease(this, name, pool, timeout, identity); + if (address) + { + break; + } + } + } + enumerator->destroy(enumerator); + } + } + return address; +} + +/** + * Implementation of attribute_provider_t.release_address + */ +static bool release_address(private_sql_attribute_t *this, + char *name, host_t *address, identification_t *id) +{ + enumerator_t *enumerator; + bool found = FALSE; + time_t now = time(NULL); + + enumerator = enumerator_create_token(name, ",", " "); + while (enumerator->enumerate(enumerator, &name)) + { + u_int pool, timeout; + + pool = get_pool(this, name, &timeout); + if (pool) + { + if (this->history) + { + this->db->execute(this->db, NULL, + "INSERT INTO leases (address, identity, acquired, released)" + " SELECT id, identity, acquired, ? FROM addresses " + " WHERE pool = ? AND address = ?", + DB_UINT, now, DB_UINT, pool, + DB_BLOB, address->get_address(address)); + } + if (this->db->execute(this->db, NULL, + "UPDATE addresses SET released = ? WHERE " + "pool = ? AND address = ?", DB_UINT, time(NULL), + DB_UINT, pool, DB_BLOB, address->get_address(address)) > 0) + { + found = TRUE; + break; + } + } + } + enumerator->destroy(enumerator); + return found; +} + +/** + * Implementation of sql_attribute_t.create_attribute_enumerator + */ +static enumerator_t* create_attribute_enumerator(private_sql_attribute_t *this, + identification_t *id, host_t *vip) +{ + if (vip) + { + enumerator_t *enumerator; + + enumerator = this->db->query(this->db, + "SELECT type, value FROM attributes", DB_INT, DB_BLOB); + if (enumerator) + { + return enumerator; + } + } + return enumerator_create_empty(); +} + +/** + * Implementation of sql_attribute_t.destroy + */ +static void destroy(private_sql_attribute_t *this) +{ + free(this); +} + +/* + * see header file + */ +sql_attribute_t *sql_attribute_create(database_t *db) +{ + private_sql_attribute_t *this = malloc_thing(private_sql_attribute_t); + time_t now = time(NULL); + + this->public.provider.acquire_address = (host_t*(*)(attribute_provider_t *this, char*, identification_t *, host_t *))acquire_address; + this->public.provider.release_address = (bool(*)(attribute_provider_t *this, char*,host_t *, identification_t*))release_address; + this->public.provider.create_attribute_enumerator = (enumerator_t*(*)(attribute_provider_t*, identification_t *id, host_t *host))create_attribute_enumerator; + this->public.destroy = (void(*)(sql_attribute_t*))destroy; + + this->db = db; + this->history = lib->settings->get_bool(lib->settings, + "libhydra.plugins.attr-sql.lease_history", TRUE); + + /* close any "online" leases in the case we crashed */ + if (this->history) + { + this->db->execute(this->db, NULL, + "INSERT INTO leases (address, identity, acquired, released)" + " SELECT id, identity, acquired, ? FROM addresses " + " WHERE released = 0", DB_UINT, now); + } + this->db->execute(this->db, NULL, + "UPDATE addresses SET released = ? WHERE released = 0", + DB_UINT, now); + return &this->public; +} + |