Tobias Brunner 2b11764b70 mem-pool: Adjust the base address if it's the network ID
Instead of just adding the offset internally, this way the reported
base address is always the first assignable address (e.g. for
192.168.0.0/24 vs. 192.168.0.1/24).

Closes strongswan/strongswan#2264
2024-06-17 14:55:43 +02:00

752 lines
16 KiB
C

/*
* Copyright (C) 2010-2024 Tobias Brunner
* Copyright (C) 2008-2010 Martin Willi
*
* Copyright (C) secunet Security Networks 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 "mem_pool.h"
#include <library.h>
#include <utils/debug.h>
#include <collections/hashtable.h>
#include <collections/array.h>
#include <threading/mutex.h>
#define POOL_LIMIT (sizeof(u_int)*8 - 1)
typedef struct private_mem_pool_t private_mem_pool_t;
/**
* private data of mem_pool_t
*/
struct private_mem_pool_t {
/**
* public interface
*/
mem_pool_t public;
/**
* name of the pool
*/
char *name;
/**
* base address of the pool
*/
host_t *base;
/**
* size of the pool
*/
u_int size;
/**
* next unused address
*/
u_int unused;
/**
* lease hashtable [identity => entry]
*/
hashtable_t *leases;
/**
* lock to safely access the pool
*/
mutex_t *mutex;
};
/**
* A unique lease address offset, with a hash of the peer host address
*/
typedef struct {
/** lease, as offset */
u_int offset;
/** hash of remote address, to allow duplicates */
u_int hash;
} unique_lease_t;
/**
* Lease entry.
*/
typedef struct {
/* identity reference */
identification_t *id;
/* array of online leases, as unique_lease_t */
array_t *online;
/* array of offline leases, as u_int offset */
array_t *offline;
} entry_t;
/**
* Create a new entry
*/
static entry_t* entry_create(identification_t *id)
{
entry_t *entry;
INIT(entry,
.id = id->clone(id),
.online = array_create(sizeof(unique_lease_t), 0),
.offline = array_create(sizeof(u_int), 0),
);
return entry;
}
/**
* Destroy an entry
*/
static void entry_destroy(entry_t *this)
{
this->id->destroy(this->id);
array_destroy(this->online);
array_destroy(this->offline);
free(this);
}
/**
* hashtable hash function for identities
*/
static u_int id_hash(identification_t *id)
{
return chunk_hash(id->get_encoding(id));
}
/**
* hashtable equals function for identities
*/
static bool id_equals(identification_t *a, identification_t *b)
{
return a->equals(a, b);
}
/**
* apply the given offset to a base address
*/
static host_t *apply_offset(host_t *base, int offset)
{
chunk_t addr;
host_t *host;
uint32_t *pos;
addr = chunk_clone(base->get_address(base));
if (base->get_family(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(base->get_family(base), addr, 0);
free(addr.ptr);
return host;
}
/**
* convert a pool offset to an address
*/
static host_t* offset2host(private_mem_pool_t *pool, int offset)
{
offset--;
if (offset > pool->size)
{
return NULL;
}
return apply_offset(pool->base, offset);
}
/**
* convert a host to a pool offset
*/
static int host2offset(private_mem_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 + 1;
}
METHOD(mem_pool_t, get_name, const char*,
private_mem_pool_t *this)
{
return this->name;
}
METHOD(mem_pool_t, get_base, host_t*,
private_mem_pool_t *this)
{
return this->base;
}
METHOD(mem_pool_t, get_size, u_int,
private_mem_pool_t *this)
{
return this->size;
}
METHOD(mem_pool_t, get_online, u_int,
private_mem_pool_t *this)
{
enumerator_t *enumerator;
entry_t *entry;
u_int count = 0;
this->mutex->lock(this->mutex);
enumerator = this->leases->create_enumerator(this->leases);
while (enumerator->enumerate(enumerator, NULL, &entry))
{
count += array_count(entry->online);
}
enumerator->destroy(enumerator);
this->mutex->unlock(this->mutex);
return count;
}
METHOD(mem_pool_t, get_offline, u_int,
private_mem_pool_t *this)
{
enumerator_t *enumerator;
entry_t *entry;
u_int count = 0;
this->mutex->lock(this->mutex);
enumerator = this->leases->create_enumerator(this->leases);
while (enumerator->enumerate(enumerator, NULL, &entry))
{
count += array_count(entry->offline);
}
enumerator->destroy(enumerator);
this->mutex->unlock(this->mutex);
return count;
}
/**
* Create a unique hash for a remote address
*/
static u_int hash_addr(host_t *addr)
{
if (addr)
{
return chunk_hash_inc(addr->get_address(addr), addr->get_port(addr));
}
return 0;
}
/**
* Get an existing lease for id
*/
static int get_existing(private_mem_pool_t *this, identification_t *id,
host_t *requested, host_t *peer)
{
enumerator_t *enumerator;
unique_lease_t *lease, reassign;
u_int *current;
entry_t *entry;
int offset = 0;
entry = this->leases->get(this->leases, id);
if (!entry)
{
return 0;
}
/* check for a valid offline lease, refresh */
enumerator = array_create_enumerator(entry->offline);
if (enumerator->enumerate(enumerator, &current))
{
reassign.offset = offset = *current;
reassign.hash = hash_addr(peer);
array_insert(entry->online, ARRAY_TAIL, &reassign);
array_remove_at(entry->offline, enumerator);
}
enumerator->destroy(enumerator);
if (offset)
{
DBG1(DBG_CFG, "reassigning offline lease to '%Y'", id);
return offset;
}
if (!peer)
{
return 0;
}
/* check for a valid online lease to reassign */
enumerator = array_create_enumerator(entry->online);
while (enumerator->enumerate(enumerator, &lease))
{
if (lease->hash == hash_addr(peer) &&
(requested->is_anyaddr(requested) ||
lease->offset == host2offset(this, requested)))
{
offset = lease->offset;
/* add an additional "online" entry */
array_insert(entry->online, ARRAY_TAIL, lease);
break;
}
}
enumerator->destroy(enumerator);
if (offset)
{
DBG1(DBG_CFG, "reassigning online lease to '%Y'", id);
}
return offset;
}
/**
* Get a new lease for id
*/
static int get_new(private_mem_pool_t *this, identification_t *id, host_t *peer)
{
entry_t *entry;
unique_lease_t lease = {};
if (this->unused < this->size)
{
entry = this->leases->get(this->leases, id);
if (!entry)
{
entry = entry_create(id);
this->leases->put(this->leases, entry->id, entry);
}
lease.offset = ++this->unused;
lease.hash = hash_addr(peer);
array_insert(entry->online, ARRAY_TAIL, &lease);
DBG1(DBG_CFG, "assigning new lease to '%Y'", id);
}
return lease.offset;
}
/**
* Get a reassigned lease for id in case the pool is full
*/
static int get_reassigned(private_mem_pool_t *this, identification_t *id,
host_t *peer)
{
enumerator_t *enumerator;
entry_t *entry;
u_int current;
unique_lease_t lease = {};
enumerator = this->leases->create_enumerator(this->leases);
while (enumerator->enumerate(enumerator, NULL, &entry))
{
if (array_remove(entry->offline, ARRAY_HEAD, &current))
{
lease.offset = current;
DBG1(DBG_CFG, "reassigning existing offline lease by '%Y' "
"to '%Y'", entry->id, id);
}
if (!array_count(entry->online) && !array_count(entry->offline))
{
this->leases->remove_at(this->leases, enumerator);
entry_destroy(entry);
}
if (lease.offset)
{
break;
}
}
enumerator->destroy(enumerator);
if (lease.offset)
{
entry = this->leases->get(this->leases, id);
if (!entry)
{
entry = entry_create(id);
this->leases->put(this->leases, entry->id, entry);
}
lease.hash = hash_addr(peer);
array_insert(entry->online, ARRAY_TAIL, &lease);
}
return lease.offset;
}
METHOD(mem_pool_t, acquire_address, host_t*,
private_mem_pool_t *this, identification_t *id, host_t *requested,
mem_pool_op_t operation, host_t *peer)
{
int offset = 0;
/* if the pool is empty (e.g. in the %config case) we simply return the
* requested address */
if (this->size == 0)
{
return requested->clone(requested);
}
if (requested->get_family(requested) !=
this->base->get_family(this->base))
{
return NULL;
}
this->mutex->lock(this->mutex);
switch (operation)
{
case MEM_POOL_EXISTING:
offset = get_existing(this, id, requested, peer);
break;
case MEM_POOL_NEW:
offset = get_new(this, id, peer);
break;
case MEM_POOL_REASSIGN:
offset = get_reassigned(this, id, peer);
if (!offset)
{
DBG1(DBG_CFG, "pool '%s' is full, unable to assign address",
this->name);
}
break;
default:
break;
}
this->mutex->unlock(this->mutex);
if (offset)
{
return offset2host(this, offset);
}
return NULL;
}
METHOD(mem_pool_t, release_address, bool,
private_mem_pool_t *this, host_t *address, identification_t *id)
{
enumerator_t *enumerator;
bool found = FALSE, more = FALSE;
entry_t *entry;
u_int offset;
unique_lease_t *current;
if (this->size != 0)
{
this->mutex->lock(this->mutex);
entry = this->leases->get(this->leases, id);
if (entry)
{
offset = host2offset(this, address);
enumerator = array_create_enumerator(entry->online);
while (enumerator->enumerate(enumerator, &current))
{
if (current->offset == offset)
{
if (!found)
{ /* remove the first entry only */
array_remove_at(entry->online, enumerator);
found = TRUE;
}
else
{ /* but check for more entries */
more = TRUE;
break;
}
}
}
enumerator->destroy(enumerator);
if (found && !more)
{
/* no tunnels are online anymore for this lease, make offline */
array_insert(entry->offline, ARRAY_TAIL, &offset);
DBG1(DBG_CFG, "lease %H by '%Y' went offline", address, id);
}
}
this->mutex->unlock(this->mutex);
}
return found;
}
/**
* lease enumerator
*/
typedef struct {
/** implemented enumerator interface */
enumerator_t public;
/** hash-table enumerator */
enumerator_t *entries;
/** online enumerator */
enumerator_t *online;
/** offline enumerator */
enumerator_t *offline;
/** enumerated pool */
private_mem_pool_t *pool;
/** currently enumerated entry */
entry_t *entry;
/** currently enumerated lease address */
host_t *addr;
} lease_enumerator_t;
METHOD(enumerator_t, lease_enumerate, bool,
lease_enumerator_t *this, va_list args)
{
identification_t **id;
unique_lease_t *lease;
host_t **addr;
u_int *offset;
bool *online;
VA_ARGS_VGET(args, id, addr, online);
DESTROY_IF(this->addr);
this->addr = NULL;
while (TRUE)
{
if (this->entry)
{
if (this->online->enumerate(this->online, &lease))
{
*id = this->entry->id;
*addr = this->addr = offset2host(this->pool, lease->offset);
*online = TRUE;
return TRUE;
}
if (this->offline->enumerate(this->offline, &offset))
{
*id = this->entry->id;
*addr = this->addr = offset2host(this->pool, *offset);
*online = FALSE;
return TRUE;
}
this->online->destroy(this->online);
this->offline->destroy(this->offline);
this->online = this->offline = NULL;
}
if (!this->entries->enumerate(this->entries, NULL, &this->entry))
{
return FALSE;
}
this->online = array_create_enumerator(this->entry->online);
this->offline = array_create_enumerator(this->entry->offline);
}
}
METHOD(enumerator_t, lease_enumerator_destroy, void,
lease_enumerator_t *this)
{
DESTROY_IF(this->addr);
DESTROY_IF(this->online);
DESTROY_IF(this->offline);
this->entries->destroy(this->entries);
this->pool->mutex->unlock(this->pool->mutex);
free(this);
}
METHOD(mem_pool_t, create_lease_enumerator, enumerator_t*,
private_mem_pool_t *this)
{
lease_enumerator_t *enumerator;
this->mutex->lock(this->mutex);
INIT(enumerator,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _lease_enumerate,
.destroy = _lease_enumerator_destroy,
},
.pool = this,
.entries = this->leases->create_enumerator(this->leases),
);
return &enumerator->public;
}
METHOD(mem_pool_t, destroy, void,
private_mem_pool_t *this)
{
enumerator_t *enumerator;
entry_t *entry;
enumerator = this->leases->create_enumerator(this->leases);
while (enumerator->enumerate(enumerator, NULL, &entry))
{
entry_destroy(entry);
}
enumerator->destroy(enumerator);
this->leases->destroy(this->leases);
this->mutex->destroy(this->mutex);
DESTROY_IF(this->base);
free(this->name);
free(this);
}
/**
* Generic constructor
*/
static private_mem_pool_t *create_generic(char *name)
{
private_mem_pool_t *this;
INIT(this,
.public = {
.get_name = _get_name,
.get_base = _get_base,
.get_size = _get_size,
.get_online = _get_online,
.get_offline = _get_offline,
.acquire_address = _acquire_address,
.release_address = _release_address,
.create_lease_enumerator = _create_lease_enumerator,
.destroy = _destroy,
},
.name = strdup(name),
.leases = hashtable_create((hashtable_hash_t)id_hash,
(hashtable_equals_t)id_equals, 16),
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
);
return this;
}
/**
* Check if the given host is the network ID of a subnet, that is, if hostbits
* are zero. Since we limit pools to 2^31 addresses we only have to check the
* last 4 bytes.
*/
static u_int network_id_diff(host_t *host, int hostbits)
{
uint32_t last;
chunk_t addr;
if (!hostbits)
{
return 0;
}
addr = host->get_address(host);
last = untoh32(addr.ptr + addr.len - sizeof(last));
hostbits = sizeof(last) * 8 - hostbits;
return (last << hostbits) >> hostbits;
}
/**
* Described in header
*/
mem_pool_t *mem_pool_create(char *name, host_t *base, int bits)
{
private_mem_pool_t *this;
u_int diff;
int addr_bits;
this = create_generic(name);
if (base)
{
addr_bits = base->get_family(base) == AF_INET ? 32 : 128;
bits = max(0, min(bits, addr_bits));
/* net bits -> host bits */
bits = addr_bits - bits;
if (bits > POOL_LIMIT)
{
bits = POOL_LIMIT;
DBG1(DBG_CFG, "virtual IP pool too large, limiting to %H/%d",
base, addr_bits - bits);
}
this->size = 1 << bits;
this->base = base->clone(base);
if (this->size > 2)
{
/* if base is the network id we skip the first address,
* otherwise adjust the size to represent the actual number
* of assignable addresses */
diff = network_id_diff(base, bits);
if (!diff)
{
this->base->destroy(this->base);
this->base = apply_offset(base, 1);
this->size--;
}
else
{
this->size -= diff;
}
/* skip the last address (broadcast) of the subnet */
this->size--;
}
else if (network_id_diff(base, bits))
{ /* only serve the second address of the subnet */
this->size--;
}
if (!this->size)
{
DBG1(DBG_CFG, "virtual IP pool %H/%d is empty",
base, addr_bits - bits);
destroy(this);
return NULL;
}
}
return &this->public;
}
/**
* Described in header
*/
mem_pool_t *mem_pool_create_range(char *name, host_t *from, host_t *to)
{
private_mem_pool_t *this;
chunk_t fromaddr, toaddr;
uint32_t diff;
fromaddr = from->get_address(from);
toaddr = to->get_address(to);
if (from->get_family(from) != to->get_family(to) ||
fromaddr.len != toaddr.len || fromaddr.len < sizeof(diff) ||
memcmp(fromaddr.ptr, toaddr.ptr, toaddr.len) > 0)
{
DBG1(DBG_CFG, "invalid IP address range: %H-%H", from, to);
return NULL;
}
if (fromaddr.len > sizeof(diff) &&
!chunk_equals(chunk_create(fromaddr.ptr, fromaddr.len - sizeof(diff)),
chunk_create(toaddr.ptr, toaddr.len - sizeof(diff))))
{
DBG1(DBG_CFG, "IP address range too large: %H-%H", from, to);
return NULL;
}
this = create_generic(name);
this->base = from->clone(from);
diff = untoh32(toaddr.ptr + toaddr.len - sizeof(diff)) -
untoh32(fromaddr.ptr + fromaddr.len - sizeof(diff));
this->size = diff + 1;
return &this->public;
}