/*
* Copyright (C) 2010 Martin Willi
* Copyright (C) 2010 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 "ha_attribute.h"
#include <collections/linked_list.h>
#include <threading/mutex.h>
typedef struct private_ha_attribute_t private_ha_attribute_t;
/**
* Private data of an ha_attribute_t object.
*/
struct private_ha_attribute_t {
/**
* Public ha_attribute_t interface.
*/
ha_attribute_t public;
/**
* List of pools, pool_t
*/
linked_list_t *pools;
/**
* Mutex to lock mask
*/
mutex_t *mutex;
/**
* Kernel helper
*/
ha_kernel_t *kernel;
/**
* Segment responsibility
*/
ha_segments_t *segments;
};
/**
* In-memory pool.
*/
typedef struct {
/** name of the pool */
char *name;
/** base address of pool */
host_t *base;
/** total number of addresses in this pool */
int size;
/** bitmask for address usage */
u_char *mask;
} pool_t;
/**
* Clean up a pool entry
*/
static void pool_destroy(pool_t *pool)
{
pool->base->destroy(pool->base);
free(pool->name);
free(pool->mask);
free(pool);
}
/**
* convert a pool offset to an address
*/
static host_t* offset2host(pool_t *pool, int offset)
{
chunk_t addr;
host_t *host;
uint32_t *pos;
if (offset > pool->size)
{
return NULL;
}
addr = chunk_clone(pool->base->get_address(pool->base));
if (pool->base->get_family(pool->base) == AF_INET6)
{
pos = (uint32_t*)(addr.ptr + 12);
}
else
{
pos = (uint32_t*)addr.ptr;
}
*pos = htonl(offset + ntohl(*pos));
host = host_create_from_chunk(pool->base->get_family(pool->base), addr, 0);
free(addr.ptr);
return host;
}
/**
* convert a host to a pool offset
*/
static int host2offset(pool_t *pool, host_t *addr)
{
chunk_t host, base;
uint32_t hosti, basei;
if (addr->get_family(addr) != pool->base->get_family(pool->base))
{
return -1;
}
host = addr->get_address(addr);
base = pool->base->get_address(pool->base);
if (addr->get_family(addr) == AF_INET6)
{
/* only look at last /32 block */
if (!memeq(host.ptr, base.ptr, 12))
{
return -1;
}
host = chunk_skip(host, 12);
base = chunk_skip(base, 12);
}
hosti = ntohl(*(uint32_t*)(host.ptr));
basei = ntohl(*(uint32_t*)(base.ptr));
if (hosti > basei + pool->size)
{
return -1;
}
return hosti - basei;
}
/**
* Find a pool by its name
*/
static pool_t* get_pool(private_ha_attribute_t *this, char *name)
{
enumerator_t *enumerator;
pool_t *pool, *found = NULL;
enumerator = this->pools->create_enumerator(this->pools);
while (enumerator->enumerate(enumerator, &pool))
{
if (streq(name, pool->name))
{
found = pool;
}
}
enumerator->destroy(enumerator);
return found;
}
/**
* Check if we are responsible for an offset
*/
static bool responsible_for(private_ha_attribute_t *this, int offset)
{
u_int segment;
segment = offset % this->segments->count(this->segments) + 1;
return this->segments->is_active(this->segments, segment);
}
METHOD(attribute_provider_t, acquire_address, host_t*,
private_ha_attribute_t *this, linked_list_t *pools, ike_sa_t *ike_sa,
host_t *requested)
{
enumerator_t *enumerator;
pool_t *pool = NULL;
int offset = -1, tmp_offset, byte, bit;
host_t *address;
char *name;
enumerator = pools->create_enumerator(pools);
this->mutex->lock(this->mutex);
while (enumerator->enumerate(enumerator, &name))
{
pool = get_pool(this, name);
if (!pool)
{
continue;
}
if (pool->base->get_family(pool->base) !=
requested->get_family(requested))
{
continue;
}
for (byte = 0; byte < pool->size / 8; byte++)
{
if (pool->mask[byte] != 0xFF)
{
for (bit = 0; bit < 8; bit++)
{
tmp_offset = byte * 8 + bit;
if (!(pool->mask[byte] & 1 << bit) &&
responsible_for(this, tmp_offset))
{
offset = tmp_offset;
pool->mask[byte] |= 1 << bit;
break;
}
}
}
if (offset != -1)
{
break;
}
}
if (offset == -1)
{
DBG1(DBG_CFG, "no address belonging to a responsible segment left "
"in HA pool '%s'", name);
}
else
{
break;
}
}
this->mutex->unlock(this->mutex);
enumerator->destroy(enumerator);
if (offset != -1)
{
address = offset2host(pool, offset);
DBG1(DBG_CFG, "acquired address %H from HA pool '%s'", address, name);
return address;
}
return NULL;
}
METHOD(attribute_provider_t, release_address, bool,
private_ha_attribute_t *this, linked_list_t *pools, host_t *address,
ike_sa_t *ike_sa)
{
enumerator_t *enumerator;
pool_t *pool;
int offset;
char *name;
bool found = FALSE;
enumerator = pools->create_enumerator(pools);
this->mutex->lock(this->mutex);
while (enumerator->enumerate(enumerator, &name))
{
pool = get_pool(this, name);
if (!pool)
{
continue;
}
if (pool->base->get_family(pool->base) != address->get_family(address))
{
continue;
}
offset = host2offset(pool, address);
if (offset > 0 && offset < pool->size)
{
pool->mask[offset / 8] &= ~(1 << (offset % 8));
DBG1(DBG_CFG, "released address %H to HA pool '%s'", address, name);
found = TRUE;
break;
}
}
this->mutex->unlock(this->mutex);
enumerator->destroy(enumerator);
return found;
}
METHOD(ha_attribute_t, reserve, void,
private_ha_attribute_t *this, char *name, host_t *address)
{
pool_t *pool;
int offset;
this->mutex->lock(this->mutex);
pool = get_pool(this, name);
if (pool)
{
offset = host2offset(pool, address);
if (offset > 0 && offset < pool->size)
{
pool->mask[offset / 8] |= 1 << (offset % 8);
DBG1(DBG_CFG, "reserved address %H in HA pool '%s'", address, name);
}
}
this->mutex->unlock(this->mutex);
}
METHOD(ha_attribute_t, destroy, void,
private_ha_attribute_t *this)
{
this->pools->destroy_function(this->pools, (void*)pool_destroy);
this->mutex->destroy(this->mutex);
free(this);
}
/**
* Load the configured pools.
*/
static void load_pools(private_ha_attribute_t *this)
{
enumerator_t *enumerator;
char *name, *net, *bits;
host_t *base;
int mask, maxbits;
pool_t *pool;
enumerator = lib->settings->create_key_value_enumerator(lib->settings,
"%s.plugins.ha.pools", lib->ns);
while (enumerator->enumerate(enumerator, &name, &net))
{
net = strdup(net);
bits = strchr(net, '/');
if (!bits)
{
DBG1(DBG_CFG, "invalid HA pool '%s' subnet, skipped", name);
free(net);
continue;
}
*bits++ = '\0';
base = host_create_from_string(net, 0);
mask = atoi(bits);
free(net);
if (!base || !mask)
{
DESTROY_IF(base);
DBG1(DBG_CFG, "invalid HA pool '%s', skipped", name);
continue;
}
maxbits = base->get_family(base) == AF_INET ? 32 : 128;
mask = maxbits - mask;
if (mask > 24)
{
mask = 24;
DBG1(DBG_CFG, "size of HA pool '%s' limited to /%d",
name, maxbits - mask);
}
if (mask < 3)
{
DBG1(DBG_CFG, "HA pool '%s' too small, skipped", name);
base->destroy(base);
continue;
}
INIT(pool,
.name = strdup(name),
.base = base,
.size = (1 << mask),
);
pool->mask = calloc(pool->size / 8, 1);
/* do not use first/last address of pool */
pool->mask[0] |= 0x01;
pool->mask[pool->size / 8 - 1] |= 0x80;
DBG1(DBG_CFG, "loaded HA pool '%s' %H/%d (%d addresses)",
pool->name, pool->base, maxbits - mask, pool->size - 2);
this->pools->insert_last(this->pools, pool);
}
enumerator->destroy(enumerator);
}
/**
* See header
*/
ha_attribute_t *ha_attribute_create(ha_kernel_t *kernel, ha_segments_t *segments)
{
private_ha_attribute_t *this;
INIT(this,
.public = {
.provider = {
.acquire_address = _acquire_address,
.release_address = _release_address,
.create_attribute_enumerator = enumerator_create_empty,
},
.reserve = _reserve,
.destroy = _destroy,
},
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
.pools = linked_list_create(),
.kernel = kernel,
.segments = segments,
);
load_pools(this);
return &this->public;
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>