kernel-interface: Add reqid allocation and release functions

To reassign reqids where appropriate, we explicitly allocate or confirm them
centrally on the kernel-interface.

Currently the state is stored in the kernel-interface wrapper for all
backends, but we may add appropriate methods to each backend to implement
a custom reqid allocation logic, if required.
This commit is contained in:
Martin Willi
2015-02-20 13:34:49 +01:00
parent 6d4649573e
commit cc08ce83f0
2 changed files with 329 additions and 1 deletions
+292
View File
@@ -43,6 +43,8 @@
#include <utils/debug.h>
#include <threading/mutex.h>
#include <collections/linked_list.h>
#include <collections/hashtable.h>
#include <collections/array.h>
typedef struct private_kernel_interface_t private_kernel_interface_t;
@@ -114,6 +116,16 @@ struct private_kernel_interface_t {
*/
linked_list_t *listeners;
/**
* Reqid entries indexed by reqids
*/
hashtable_t *reqids;
/**
* Reqid entries indexed by traffic selectors
*/
hashtable_t *reqids_by_ts;
/**
* mutex for algorithm mappings
*/
@@ -175,6 +187,278 @@ METHOD(kernel_interface_t, get_cpi, status_t,
return this->ipsec->get_cpi(this->ipsec, src, dst, cpi);
}
/**
* Reqid mapping entry
*/
typedef struct {
/** allocated reqid */
u_int32_t reqid;
/** references to this entry */
u_int refs;
/** inbound mark used for SA */
mark_t mark_in;
/** outbound mark used for SA */
mark_t mark_out;
/** local traffic selectors */
array_t *local;
/** remote traffic selectors */
array_t *remote;
} reqid_entry_t;
/**
* Destroy a reqid mapping entry
*/
static void reqid_entry_destroy(reqid_entry_t *entry)
{
array_destroy_offset(entry->local, offsetof(traffic_selector_t, destroy));
array_destroy_offset(entry->remote, offsetof(traffic_selector_t, destroy));
free(entry);
}
/**
* Hashtable hash function for reqid entries using reqid as key
*/
static u_int hash_reqid(reqid_entry_t *entry)
{
return chunk_hash_inc(chunk_from_thing(entry->reqid),
chunk_hash_inc(chunk_from_thing(entry->mark_in),
chunk_hash(chunk_from_thing(entry->mark_out))));
}
/**
* Hashtable equals function for reqid entries using reqid as key
*/
static bool equals_reqid(reqid_entry_t *a, reqid_entry_t *b)
{
return a->reqid == b->reqid &&
a->mark_in.value == b->mark_in.value &&
a->mark_in.mask == b->mark_in.mask &&
a->mark_out.value == b->mark_out.value &&
a->mark_out.mask == b->mark_out.mask;
}
/**
* Hash an array of traffic selectors
*/
static u_int hash_ts_array(array_t *array, u_int hash)
{
enumerator_t *enumerator;
traffic_selector_t *ts;
enumerator = array_create_enumerator(array);
while (enumerator->enumerate(enumerator, &ts))
{
hash = ts->hash(ts, hash);
}
enumerator->destroy(enumerator);
return hash;
}
/**
* Hashtable hash function for reqid entries using traffic selectors as key
*/
static u_int hash_reqid_by_ts(reqid_entry_t *entry)
{
return hash_ts_array(entry->local, hash_ts_array(entry->remote, 0));
}
/**
* Compare two array with traffic selectors for equality
*/
static bool ts_array_equals(array_t *a, array_t *b)
{
traffic_selector_t *tsa, *tsb;
enumerator_t *ae, *be;
bool equal = TRUE;
if (array_count(a) != array_count(b))
{
return FALSE;
}
ae = array_create_enumerator(a);
be = array_create_enumerator(b);
while (equal && ae->enumerate(ae, &tsa) && be->enumerate(be, &tsb))
{
equal = tsa->equals(tsa, tsb);
}
ae->destroy(ae);
be->destroy(be);
return equal;
}
/**
* Check if mark b matches to a, optionally with reqid match
*/
static bool mark_matches(mark_t a, mark_t b, u_int32_t reqid)
{
if (a.value == b.value)
{
return TRUE;
}
if (a.value == MARK_REQID && b.value == reqid)
{
return TRUE;
}
return FALSE;
}
/**
* Hashtable equals function for reqid entries using traffic selectors as key
*/
static bool equals_reqid_by_ts(reqid_entry_t *a, reqid_entry_t *b)
{
if (ts_array_equals(a->local, b->local) &&
ts_array_equals(a->remote, b->remote) &&
a->mark_in.mask == b->mark_in.mask &&
a->mark_out.mask == b->mark_out.mask)
{
if (mark_matches(a->mark_in, b->mark_in, a->reqid) &&
mark_matches(a->mark_out, b->mark_out, a->reqid))
{
return TRUE;
}
if (mark_matches(b->mark_in, a->mark_in, b->reqid) &&
mark_matches(b->mark_out, a->mark_out, b->reqid))
{
return TRUE;
}
}
return FALSE;
}
/**
* Create an array from copied traffic selector list items
*/
static array_t *array_from_ts_list(linked_list_t *list)
{
enumerator_t *enumerator;
traffic_selector_t *ts;
array_t *array;
array = array_create(0, 0);
enumerator = list->create_enumerator(list);
while (enumerator->enumerate(enumerator, &ts))
{
array_insert(array, ARRAY_TAIL, ts->clone(ts));
}
enumerator->destroy(enumerator);
return array;
}
METHOD(kernel_interface_t, alloc_reqid, status_t,
private_kernel_interface_t *this,
linked_list_t *local_ts, linked_list_t *remote_ts,
mark_t *mark_in, mark_t *mark_out, u_int32_t *reqid)
{
static u_int32_t counter = 0;
reqid_entry_t *entry = NULL, *tmpl;
status_t status = SUCCESS;
INIT(tmpl,
.local = array_from_ts_list(local_ts),
.remote = array_from_ts_list(remote_ts),
.mark_in = *mark_in,
.mark_out = *mark_out,
.reqid = *reqid,
);
this->mutex->lock(this->mutex);
if (tmpl->reqid)
{
/* search by reqid if given */
if (tmpl->mark_in.value == MARK_REQID)
{
tmpl->mark_in.value = tmpl->reqid;
}
if (tmpl->mark_out.value == MARK_REQID)
{
tmpl->mark_out.value = tmpl->reqid;
}
entry = this->reqids->get(this->reqids, tmpl);
}
if (entry)
{
/* we don't require a traffic selector match for explicit reqids,
* as we wan't to reuse a reqid for trap-triggered policies that
* got narrowed during negotiation. */
reqid_entry_destroy(tmpl);
}
else
{
/* search by traffic selectors. We do the search with MARK_REQID
* wildcards (if any), and update the marks if we find any match */
entry = this->reqids_by_ts->get(this->reqids_by_ts, tmpl);
if (entry)
{
reqid_entry_destroy(tmpl);
}
else
{
/* none found, create a new entry, allocating a reqid */
entry = tmpl;
entry->reqid = ++counter;
if (entry->mark_in.value == MARK_REQID)
{
entry->mark_in.value = entry->reqid;
}
if (entry->mark_out.value == MARK_REQID)
{
entry->mark_out.value = entry->reqid;
}
this->reqids_by_ts->put(this->reqids_by_ts, entry, entry);
this->reqids->put(this->reqids, entry, entry);
}
*reqid = entry->reqid;
}
*mark_in = entry->mark_in;
*mark_out = entry->mark_out;
entry->refs++;
this->mutex->unlock(this->mutex);
return status;
}
METHOD(kernel_interface_t, release_reqid, status_t,
private_kernel_interface_t *this, u_int32_t reqid,
mark_t mark_in, mark_t mark_out)
{
reqid_entry_t *entry, tmpl = {
.reqid = reqid,
.mark_in = mark_in,
.mark_out = mark_out,
};
this->mutex->lock(this->mutex);
entry = this->reqids->remove(this->reqids, &tmpl);
if (entry)
{
if (--entry->refs == 0)
{
entry = this->reqids_by_ts->remove(this->reqids_by_ts, entry);
if (entry)
{
reqid_entry_destroy(entry);
}
}
else
{
this->reqids->put(this->reqids, entry, entry);
}
}
this->mutex->unlock(this->mutex);
if (entry)
{
return SUCCESS;
}
return NOT_FOUND;
}
METHOD(kernel_interface_t, add_sa, status_t,
private_kernel_interface_t *this, host_t *src, host_t *dst,
u_int32_t spi, u_int8_t protocol, u_int32_t reqid, mark_t mark,
@@ -733,6 +1017,8 @@ METHOD(kernel_interface_t, destroy, void,
DESTROY_IF(this->ipsec);
DESTROY_IF(this->net);
DESTROY_FUNCTION_IF(this->ifaces_filter, (void*)free);
this->reqids->destroy(this->reqids);
this->reqids_by_ts->destroy(this->reqids_by_ts);
this->listeners->destroy(this->listeners);
this->mutex->destroy(this->mutex);
free(this);
@@ -751,6 +1037,8 @@ kernel_interface_t *kernel_interface_create()
.get_features = _get_features,
.get_spi = _get_spi,
.get_cpi = _get_cpi,
.alloc_reqid = _alloc_reqid,
.release_reqid = _release_reqid,
.add_sa = _add_sa,
.update_sa = _update_sa,
.query_sa = _query_sa,
@@ -795,6 +1083,10 @@ kernel_interface_t *kernel_interface_create()
.listeners = linked_list_create(),
.mutex_algs = mutex_create(MUTEX_TYPE_DEFAULT),
.algorithms = linked_list_create(),
.reqids = hashtable_create((hashtable_hash_t)hash_reqid,
(hashtable_equals_t)equals_reqid, 8),
.reqids_by_ts = hashtable_create((hashtable_hash_t)hash_reqid_by_ts,
(hashtable_equals_t)equals_reqid_by_ts, 8),
);
ifaces = lib->settings->get_str(lib->settings,
+37 -1
View File
@@ -121,6 +121,42 @@ struct kernel_interface_t {
status_t (*get_cpi)(kernel_interface_t *this, host_t *src, host_t *dst,
u_int16_t *cpi);
/**
* Allocate or confirm a reqid to use for a given SA pair.
*
* Each returned reqid by a successful call to alloc_reqid() must be
* released using release_reqid().
*
* The reqid parameter is an in/out parameter. If it points to non-zero,
* the reqid is confirmed and registered for use. If it points to zero,
* a reqid is allocated for the given selectors, and returned to reqid.
*
* The passed mark values get updated to the reqid value if they are set
* to the magic value MARK_REQID.
*
* @param local_ts traffic selectors of local side for SA
* @param remote_ts traffic selectors of remote side for SA
* @param mark_in inbound mark on SA
* @param mark_out outbound mark on SA
* @param reqid allocated reqid
* @return SUCCESS if reqid allocated
*/
status_t (*alloc_reqid)(kernel_interface_t *this,
linked_list_t *local_ts, linked_list_t *remote_ts,
mark_t *mark_in, mark_t *mark_out,
u_int32_t *reqid);
/**
* Release a previously allocated reqid.
*
* @param reqid reqid to release
* @param mark_in inbound mark on SA
* @param mark_out outbound mark on SA
* @return SUCCESS if reqid released
*/
status_t (*release_reqid)(kernel_interface_t *this, u_int32_t reqid,
mark_t mark_in, mark_t mark_out);
/**
* Add an SA to the SAD.
*
@@ -131,7 +167,7 @@ struct kernel_interface_t {
* @param dst destination address for this SA
* @param spi SPI allocated by us or remote peer
* @param protocol protocol for this SA (ESP/AH)
* @param reqid unique ID for this SA
* @param reqid reqid for this SA
* @param mark optional mark for this SA
* @param tfc Traffic Flow Confidentiality padding for this SA
* @param lifetime lifetime_cfg_t for this SA