Added separate hashtable for hashes of initial IKE messages.

This does not require us to do a lookup for an SA by SPI first.
This commit is contained in:
Tobias Brunner 2012-02-29 18:15:42 +01:00
parent 68611395dc
commit 71cf97871f
1 changed files with 144 additions and 69 deletions

View File

@ -156,17 +156,6 @@ static entry_t *entry_create()
return this;
}
/**
* Function that matches entry_t objects by initiator SPI and the hash of the
* IKE_SA_INIT message.
*/
static bool entry_match_by_hash(entry_t *entry, ike_sa_id_t *id, chunk_t *hash)
{
return id->get_responder_spi(id) == 0 &&
id->get_initiator_spi(id) == entry->ike_sa_id->get_initiator_spi(entry->ike_sa_id) &&
chunk_equals(*hash, entry->init_hash);
}
/**
* Function that matches entry_t objects by ike_sa_id_t.
*/
@ -357,6 +346,16 @@ struct private_ike_sa_manager_t {
*/
shareable_segment_t *connected_peers_segments;
/**
* Hash table with chunk_t objects.
*/
linked_list_t **init_hashes_table;
/**
* Segments of the "hashes" hash table.
*/
segment_t *init_hashes_segments;
/**
* RNG to get random SPIs for our side
*/
@ -648,17 +647,6 @@ static status_t get_entry_by_id(private_ike_sa_manager_t *this,
(linked_list_match_t)entry_match_by_id, ike_sa_id, NULL);
}
/**
* Find an entry by initiator SPI and IKE_SA_INIT hash.
* Note: On SUCCESS, the caller has to unlock the segment.
*/
static status_t get_entry_by_hash(private_ike_sa_manager_t *this,
ike_sa_id_t *ike_sa_id, chunk_t hash, entry_t **entry, u_int *segment)
{
return get_entry_by_match_function(this, ike_sa_id, entry, segment,
(linked_list_match_t)entry_match_by_hash, ike_sa_id, &hash);
}
/**
* Find an entry by IKE_SA pointer.
* Note: On SUCCESS, the caller has to unlock the segment.
@ -900,6 +888,87 @@ static void remove_connected_peers(private_ike_sa_manager_t *this, entry_t *entr
lock->unlock(lock);
}
/**
* Check if we already have created an IKE_SA based on the initial IKE message
* with the given hash. If not the hash is stored.
*
* @returns TRUE if the message with the given hash was seen before
*/
static bool check_and_put_init_hash(private_ike_sa_manager_t *this,
chunk_t init_hash)
{
chunk_t *clone;
linked_list_t *list;
u_int row, segment;
mutex_t *mutex;
row = chunk_hash(init_hash) & this->table_mask;
segment = row & this->segment_mask;
mutex = this->init_hashes_segments[segment].mutex;
mutex->lock(mutex);
list = this->init_hashes_table[row];
if (list)
{
chunk_t *current;
if (list->find_first(list, (linked_list_match_t)chunk_equals_ptr,
(void**)&current, &init_hash) == SUCCESS)
{
mutex->unlock(mutex);
return TRUE;
}
}
else
{
list = this->init_hashes_table[row] = linked_list_create();
}
INIT(clone,
.len = init_hash.len,
.ptr = malloc(init_hash.len),
);
memcpy(clone->ptr, init_hash.ptr, clone->len);
list->insert_last(list, clone);
mutex->unlock(mutex);
return FALSE;
}
/**
* Remove the hash of an initial IKE message from the cache.
*/
static void remove_init_hash(private_ike_sa_manager_t *this, chunk_t init_hash)
{
linked_list_t *list;
u_int row, segment;
mutex_t *mutex;
row = chunk_hash(init_hash) & this->table_mask;
segment = row & this->segment_mask;
mutex = this->init_hashes_segments[segment].mutex;
mutex->lock(mutex);
list = this->init_hashes_table[row];
if (list)
{
enumerator_t *enumerator;
chunk_t *current;
enumerator = list->create_enumerator(list);
while (enumerator->enumerate(enumerator, &current))
{
if (chunk_equals_ptr(current, &init_hash))
{
list->remove_at(list, enumerator);
chunk_free(current);
free(current);
break;
}
}
enumerator->destroy(enumerator);
}
mutex->unlock(mutex);
}
/**
* Get a random SPI for new IKE_SAs
*/
@ -977,6 +1046,7 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*,
bool is_init = FALSE;
id = message->get_ike_sa_id(message);
/* clone the IKE_SA ID so we can modify the initiator flag */
id = id->clone(id);
id->switch_initiator(id);
@ -1009,61 +1079,45 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*,
}
if (is_init && this->hasher)
{
/* First request. Check for an IKE_SA with such a message hash. */
{ /* initial request. checking for the hasher prevents crashes once
* flush() has been called */
chunk_t hash;
this->hasher->allocate_hash(this->hasher,
message->get_packet_data(message), &hash);
if (get_entry_by_hash(this, id, hash, &entry, &segment) == SUCCESS)
/* ensure this is not a retransmit of an already handled init message */
if (check_and_put_init_hash(this, hash))
{
if (message->get_exchange_type(message) == IKE_SA_INIT &&
entry->message_id == 0)
{
unlock_single_segment(this, segment);
chunk_free(&hash);
id->destroy(id);
DBG1(DBG_MGR, "ignoring IKE_SA_INIT, already processing");
return NULL;
}
else if (wait_for_entry(this, entry, segment))
{
entry->checked_out = TRUE;
entry->message_id = message->get_message_id(message);
ike_sa = entry->ike_sa;
DBG2(DBG_MGR, "IKE_SA %s[%u] checked out by hash",
ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa));
chunk_free(&hash);
}
chunk_free(&hash);
id->destroy(id);
DBG1(DBG_MGR, "ignoring %s, already processing",
ike_version == IKEV2 ? "IKE_SA_INIT" : "initial IKE message");
return NULL;
}
/* no IKE_SA yet, create a new one */
id->set_responder_spi(id, get_spi(this));
ike_sa = ike_sa_create(id, FALSE, ike_version);
if (ike_sa)
{
entry = entry_create();
entry->ike_sa = ike_sa;
entry->ike_sa_id = id->clone(id);
segment = put_entry(this, entry);
entry->checked_out = TRUE;
unlock_single_segment(this, segment);
entry->message_id = message->get_message_id(message);
entry->init_hash = hash;
DBG2(DBG_MGR, "created IKE_SA %s[%u]",
ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa));
}
if (ike_sa == NULL)
{
/* no IKE_SA found, create a new one */
id->set_responder_spi(id, get_spi(this));
ike_sa = ike_sa_create(id, FALSE, ike_version);
if (ike_sa)
{
entry = entry_create();
/* a new SA checked out by message is a responder SA */
entry->ike_sa = ike_sa;
entry->ike_sa_id = id->clone(id);
segment = put_entry(this, entry);
entry->checked_out = TRUE;
unlock_single_segment(this, segment);
entry->message_id = message->get_message_id(message);
entry->init_hash = hash;
DBG2(DBG_MGR, "created IKE_SA %s[%u]",
ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa));
}
}
if (ike_sa == NULL)
else
{
remove_init_hash(this, hash);
chunk_free(&hash);
DBG1(DBG_MGR, "ignoring message, no such IKE_SA");
}
@ -1449,6 +1503,10 @@ METHOD(ike_sa_manager_t, checkin_and_destroy, void,
{
remove_connected_peers(this, entry);
}
if (entry->init_hash.ptr)
{
remove_init_hash(this, entry->init_hash);
}
entry_destroy(entry);
@ -1744,6 +1802,10 @@ METHOD(ike_sa_manager_t, flush, void,
{
remove_connected_peers(this, entry);
}
if (entry->init_hash.ptr)
{
remove_init_hash(this, entry->init_hash);
}
remove_entry_at((private_enumerator_t*)enumerator);
entry_destroy(entry);
}
@ -1767,19 +1829,23 @@ METHOD(ike_sa_manager_t, destroy, void,
DESTROY_IF(this->ike_sa_table[i]);
DESTROY_IF(this->half_open_table[i]);
DESTROY_IF(this->connected_peers_table[i]);
DESTROY_IF(this->init_hashes_table[i]);
}
free(this->ike_sa_table);
free(this->half_open_table);
free(this->connected_peers_table);
free(this->init_hashes_table);
for (i = 0; i < this->segment_count; i++)
{
this->segments[i].mutex->destroy(this->segments[i].mutex);
this->half_open_segments[i].lock->destroy(this->half_open_segments[i].lock);
this->connected_peers_segments[i].lock->destroy(this->connected_peers_segments[i].lock);
this->init_hashes_segments[i].mutex->destroy(this->init_hashes_segments[i].mutex);
}
free(this->segments);
free(this->half_open_segments);
free(this->connected_peers_segments);
free(this->init_hashes_segments);
free(this);
}
@ -1856,8 +1922,8 @@ ike_sa_manager_t *ike_sa_manager_create()
"charon.ikesa_table_segments", DEFAULT_SEGMENT_COUNT));
this->segment_count = max(1, min(this->segment_count, this->table_size));
this->segment_mask = this->segment_count - 1;
this->ike_sa_table = calloc(this->table_size, sizeof(linked_list_t*));
this->ike_sa_table = calloc(this->table_size, sizeof(linked_list_t*));
this->segments = (segment_t*)calloc(this->segment_count, sizeof(segment_t));
for (i = 0; i < this->segment_count; i++)
{
@ -1883,6 +1949,15 @@ ike_sa_manager_t *ike_sa_manager_create()
this->connected_peers_segments[i].count = 0;
}
/* and again for the table of hashes of seen initial IKE messages */
this->init_hashes_table = calloc(this->table_size, sizeof(linked_list_t*));
this->init_hashes_segments = calloc(this->segment_count, sizeof(segment_t));
for (i = 0; i < this->segment_count; i++)
{
this->init_hashes_segments[i].mutex = mutex_create(MUTEX_TYPE_RECURSIVE);
this->init_hashes_segments[i].count = 0;
}
this->reuse_ikesa = lib->settings->get_bool(lib->settings,
"charon.reuse_ikesa", TRUE);
return &this->public;