summaryrefslogtreecommitdiff
path: root/src/charon/sa/ike_sa_manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/charon/sa/ike_sa_manager.c')
-rw-r--r--src/charon/sa/ike_sa_manager.c1365
1 files changed, 1036 insertions, 329 deletions
diff --git a/src/charon/sa/ike_sa_manager.c b/src/charon/sa/ike_sa_manager.c
index a760409c0..447fa2dd5 100644
--- a/src/charon/sa/ike_sa_manager.c
+++ b/src/charon/sa/ike_sa_manager.c
@@ -1,5 +1,6 @@
/*
- * Copyright (C) 2005-2006 Martin Willi
+ * Copyright (C) 2008 Tobias Brunner
+ * Copyright (C) 2005-2008 Martin Willi
* Copyright (C) 2005 Jan Hutter
* Hochschule fuer Technik Rapperswil
*
@@ -13,7 +14,7 @@
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
- * $Id: ike_sa_manager.c 4579 2008-11-05 11:29:56Z martin $
+ * $Id: ike_sa_manager.c 4811 2008-12-17 09:00:22Z martin $
*/
#include <string.h>
@@ -27,6 +28,15 @@
#include <utils/linked_list.h>
#include <crypto/hashers/hasher.h>
+/* the default size of the hash table (MUST be a power of 2) */
+#define DEFAULT_HASHTABLE_SIZE 1
+
+/* the maximum size of the hash table (MUST be a power of 2) */
+#define MAX_HASHTABLE_SIZE (1 << 30)
+
+/* the default number of segments (MUST be a power of 2) */
+#define DEFAULT_SEGMENT_COUNT 1
+
typedef struct entry_t entry_t;
/**
@@ -60,7 +70,7 @@ struct entry_t {
bool driveout_waiting_threads;
/**
- * Identifiaction of an IKE_SA (SPIs).
+ * Identification of an IKE_SA (SPIs).
*/
ike_sa_id_t *ike_sa_id;
@@ -80,6 +90,11 @@ struct entry_t {
host_t *other;
/**
+ * As responder: Is this SA half-open?
+ */
+ bool half_open;
+
+ /**
* own identity, required for duplicate checking
*/
identification_t *my_id;
@@ -115,7 +130,7 @@ static status_t entry_destroy(entry_t *this)
/**
* Creates a new entry for the ike_sa_t list.
*/
-static entry_t *entry_create(ike_sa_id_t *ike_sa_id)
+static entry_t *entry_create()
{
entry_t *this = malloc_thing(entry_t);
@@ -129,18 +144,151 @@ static entry_t *entry_create(ike_sa_id_t *ike_sa_id)
this->message_id = -1;
this->init_hash = chunk_empty;
this->other = NULL;
+ this->half_open = FALSE;
this->my_id = NULL;
this->other_id = NULL;
+ this->ike_sa_id = NULL;
+ this->ike_sa = NULL;
- /* ike_sa_id is always cloned */
- this->ike_sa_id = ike_sa_id->clone(ike_sa_id);
+ return this;
+}
- /* create new ike_sa */
- this->ike_sa = ike_sa_create(ike_sa_id);
+/**
+ * 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->is_initiator(id) == entry->ike_sa_id->is_initiator(entry->ike_sa_id) &&
+ id->get_initiator_spi(id) == entry->ike_sa_id->get_initiator_spi(entry->ike_sa_id) &&
+ chunk_equals(*hash, entry->init_hash);
+}
- return this;
+/**
+ * Function that matches entry_t objects by ike_sa_id_t.
+ */
+static bool entry_match_by_id(entry_t *entry, ike_sa_id_t *id)
+{
+ if (id->equals(id, entry->ike_sa_id))
+ {
+ return TRUE;
+ }
+ if ((id->get_responder_spi(id) == 0 ||
+ entry->ike_sa_id->get_responder_spi(entry->ike_sa_id) == 0) &&
+ id->is_initiator(id) == entry->ike_sa_id->is_initiator(entry->ike_sa_id) &&
+ id->get_initiator_spi(id) == entry->ike_sa_id->get_initiator_spi(entry->ike_sa_id))
+ {
+ /* this is TRUE for IKE_SAs that we initiated but have not yet received a response */
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Function that matches entry_t objects by ike_sa_t pointers.
+ */
+static bool entry_match_by_sa(entry_t *entry, ike_sa_t *ike_sa)
+{
+ return entry->ike_sa == ike_sa;
+}
+
+/**
+ * Hash function for ike_sa_id_t objects.
+ */
+static u_int ike_sa_id_hash(ike_sa_id_t *ike_sa_id)
+{
+ /* we always use initiator spi as key */
+ return ike_sa_id->get_initiator_spi(ike_sa_id);
}
+typedef struct half_open_t half_open_t;
+
+/**
+ * Struct to manage half-open IKE_SAs per peer.
+ */
+struct half_open_t {
+ /** chunk of remote host address */
+ chunk_t other;
+
+ /** the number of half-open IKE_SAs with that host */
+ u_int count;
+};
+
+/**
+ * Destroys a half_open_t object.
+ */
+static void half_open_destroy(half_open_t *this)
+{
+ chunk_free(&this->other);
+ free(this);
+}
+
+/**
+ * Function that matches half_open_t objects by the given IP address chunk.
+ */
+static bool half_open_match(half_open_t *half_open, chunk_t *addr)
+{
+ return chunk_equals(*addr, half_open->other);
+}
+
+typedef struct connected_peers_t connected_peers_t;
+
+struct connected_peers_t {
+ /** own identity */
+ identification_t *my_id;
+
+ /** remote identity */
+ identification_t *other_id;
+
+ /** list of ike_sa_id_t objects of IKE_SAs between the two identities */
+ linked_list_t *sas;
+};
+
+static void connected_peers_destroy(connected_peers_t *this)
+{
+ this->my_id->destroy(this->my_id);
+ this->other_id->destroy(this->other_id);
+ this->sas->destroy(this->sas);
+ free(this);
+}
+
+/**
+ * Function that matches connected_peers_t objects by the given ids.
+ */
+static bool connected_peers_match(connected_peers_t *connected_peers,
+ identification_t *my_id, identification_t *other_id)
+{
+ return my_id->equals(my_id, connected_peers->my_id) &&
+ other_id->equals(other_id, connected_peers->other_id);
+}
+
+typedef struct segment_t segment_t;
+
+/**
+ * Struct to manage segments of the hash table.
+ */
+struct segment_t {
+ /** mutex to access a segment exclusively */
+ mutex_t *mutex;
+
+ /** the number of entries in this segment */
+ u_int count;
+};
+
+typedef struct shareable_segment_t shareable_segment_t;
+
+/**
+ * Struct to manage segments of the "half-open" and "connected peers" hash tables.
+ */
+struct shareable_segment_t {
+ /** rwlock to access a segment non-/exclusively */
+ rwlock_t *lock;
+
+ /** the number of entries in this segment - in case of the "half-open table"
+ * it's the sum of all half_open_t.count in a segment. */
+ u_int count;
+};
typedef struct private_ike_sa_manager_t private_ike_sa_manager_t;
@@ -154,14 +302,54 @@ struct private_ike_sa_manager_t {
ike_sa_manager_t public;
/**
- * Lock for exclusivly accessing the manager.
+ * Hash table with entries for the ike_sa_t objects.
*/
- mutex_t *mutex;
-
+ linked_list_t **ike_sa_table;
+
+ /**
+ * The size of the hash table.
+ */
+ u_int table_size;
+
+ /**
+ * Mask to map the hashes to table rows.
+ */
+ u_int table_mask;
+
+ /**
+ * Segments of the hash table.
+ */
+ segment_t *segments;
+
+ /**
+ * The number of segments.
+ */
+ u_int segment_count;
+
+ /**
+ * Mask to map a table row to a segment.
+ */
+ u_int segment_mask;
+
+ /**
+ * Hash table with half_open_t objects.
+ */
+ linked_list_t **half_open_table;
+
/**
- * Linked list with entries for the ike_sa_t objects.
+ * Segments of the "half-open" hash table.
*/
- linked_list_t *ike_sa_list;
+ shareable_segment_t *half_open_segments;
+
+ /**
+ * Hash table with connected_peers_t objects.
+ */
+ linked_list_t **connected_peers_table;
+
+ /**
+ * Segments of the "connected peers" hash table.
+ */
+ shareable_segment_t *connected_peers_segments;
/**
* RNG to get random SPIs for our side
@@ -180,126 +368,304 @@ struct private_ike_sa_manager_t {
};
/**
- * Implementation of private_ike_sa_manager_t.get_entry_by_id.
+ * Acquire a lock to access the segment of the table row with the given index.
+ * It also works with the segment index directly.
*/
-static status_t get_entry_by_id(private_ike_sa_manager_t *this,
- ike_sa_id_t *ike_sa_id, entry_t **entry)
+static void lock_single_segment(private_ike_sa_manager_t *this, u_int index)
{
- enumerator_t *enumerator;
- entry_t *current;
- status_t status;
+ mutex_t *lock = this->segments[index & this->segment_mask].mutex;
- /* create enumerator over list of ike_sa's */
- enumerator = this->ike_sa_list->create_enumerator(this->ike_sa_list);
+ lock->lock(lock);
+}
- /* default status */
- status = NOT_FOUND;
+/**
+ * Release the lock required to access the segment of the table row with the given index.
+ * It also works with the segment index directly.
+ */
+static void unlock_single_segment(private_ike_sa_manager_t *this, u_int index)
+{
+ mutex_t *lock = this->segments[index & this->segment_mask].mutex;
- while (enumerator->enumerate(enumerator, &current))
+ lock->unlock(lock);
+}
+
+/**
+ * Lock all segments
+ */
+static void lock_all_segments(private_ike_sa_manager_t *this)
+{
+ u_int i;
+
+ for (i = 0; i < this->segment_count; ++i)
{
- if (current->ike_sa_id->equals(current->ike_sa_id, ike_sa_id))
- {
- DBG2(DBG_MGR, "found entry by both SPIs");
- *entry = current;
- status = SUCCESS;
- break;
- }
- if (ike_sa_id->get_responder_spi(ike_sa_id) == 0 ||
- current->ike_sa_id->get_responder_spi(current->ike_sa_id) == 0)
- {
- /* seems to be a half ready ike_sa */
- if ((current->ike_sa_id->get_initiator_spi(current->ike_sa_id) ==
- ike_sa_id->get_initiator_spi(ike_sa_id)) &&
- (current->ike_sa_id->is_initiator(ike_sa_id) ==
- ike_sa_id->is_initiator(current->ike_sa_id)))
- {
- DBG2(DBG_MGR, "found entry by initiator SPI");
- *entry = current;
- status = SUCCESS;
- break;
- }
- }
+ this->segments[i].mutex->lock(this->segments[i].mutex);
}
-
- enumerator->destroy(enumerator);
- return status;
}
/**
- * Implementation of private_ike_sa_manager_t.get_entry_by_sa.
+ * Unlock all segments
*/
-static status_t get_entry_by_sa(private_ike_sa_manager_t *this,
- ike_sa_t *ike_sa, entry_t **entry)
+static void unlock_all_segments(private_ike_sa_manager_t *this)
{
- enumerator_t *enumerator;
- entry_t *current;
- status_t status;
+ u_int i;
+
+ for (i = 0; i < this->segment_count; ++i)
+ {
+ this->segments[i].mutex->unlock(this->segments[i].mutex);
+ }
+}
+
+typedef struct private_enumerator_t private_enumerator_t;
+
+/**
+ * hash table enumerator implementation
+ */
+struct private_enumerator_t {
+
+ /**
+ * implements enumerator interface
+ */
+ enumerator_t enumerator;
+
+ /**
+ * associated ike_sa_manager_t
+ */
+ private_ike_sa_manager_t *manager;
+
+ /**
+ * current segment index
+ */
+ u_int segment;
- enumerator = this->ike_sa_list->create_enumerator(this->ike_sa_list);
+ /**
+ * currently enumerating entry
+ */
+ entry_t *entry;
- /* default status */
- status = NOT_FOUND;
+ /**
+ * current table row index
+ */
+ u_int row;
- while (enumerator->enumerate(enumerator, &current))
+ /**
+ * enumerator for the current table row
+ */
+ enumerator_t *current;
+};
+
+/**
+ * Implementation of private_enumerator_t.enumerator.enumerate.
+ */
+static bool enumerate(private_enumerator_t *this, entry_t **entry, u_int *segment)
+{
+ if (this->entry)
+ {
+ this->entry->condvar->signal(this->entry->condvar);
+ this->entry = NULL;
+ }
+ while (this->segment < this->manager->segment_count)
{
- /* only pointers are compared */
- if (current->ike_sa == ike_sa)
+ while (this->row < this->manager->table_size)
{
- DBG2(DBG_MGR, "found entry by pointer");
- *entry = current;
- status = SUCCESS;
- break;
+ if (this->current)
+ {
+ entry_t *item;
+
+ if (this->current->enumerate(this->current, &item))
+ {
+ *entry = this->entry = item;
+ *segment = this->segment;
+ return TRUE;
+ }
+ this->current->destroy(this->current);
+ this->current = NULL;
+ unlock_single_segment(this->manager, this->segment);
+ }
+ else
+ {
+ linked_list_t *list;
+
+ lock_single_segment(this->manager, this->segment);
+ if ((list = this->manager->ike_sa_table[this->row]) != NULL &&
+ list->get_count(list))
+ {
+ this->current = list->create_enumerator(list);
+ continue;
+ }
+ unlock_single_segment(this->manager, this->segment);
+ }
+ this->row += this->manager->segment_count;
}
+ this->segment++;
+ this->row = this->segment;
}
- enumerator->destroy(enumerator);
+ return FALSE;
+}
+
+/**
+ * Implementation of private_enumerator_t.enumerator.destroy.
+ */
+static void enumerator_destroy(private_enumerator_t *this)
+{
+ if (this->entry)
+ {
+ this->entry->condvar->signal(this->entry->condvar);
+ }
+ if (this->current)
+ {
+ this->current->destroy(this->current);
+ unlock_single_segment(this->manager, this->segment);
+ }
+ free(this);
+}
+
+/**
+ * Creates an enumerator to enumerate the entries in the hash table.
+ */
+static enumerator_t* create_table_enumerator(private_ike_sa_manager_t *this)
+{
+ private_enumerator_t *enumerator = malloc_thing(private_enumerator_t);
+
+ enumerator->enumerator.enumerate = (void*)enumerate;
+ enumerator->enumerator.destroy = (void*)enumerator_destroy;
+ enumerator->manager = this;
+ enumerator->segment = 0;
+ enumerator->entry = NULL;
+ enumerator->row = 0;
+ enumerator->current = NULL;
- return status;
+ return &enumerator->enumerator;
}
/**
- * Implementation of private_ike_sa_manager_s.delete_entry.
+ * Put an entry into the hash table.
+ * Note: The caller has to unlock the returned segment.
*/
-static status_t delete_entry(private_ike_sa_manager_t *this, entry_t *entry)
+static u_int put_entry(private_ike_sa_manager_t *this, entry_t *entry)
{
- enumerator_t *enumerator;
- entry_t *current;
- status_t status;
+ linked_list_t *list;
+ u_int row = ike_sa_id_hash(entry->ike_sa_id) & this->table_mask;
+ u_int segment = row & this->segment_mask;
- enumerator = this->ike_sa_list->create_enumerator(this->ike_sa_list);
+ lock_single_segment(this, segment);
+ if ((list = this->ike_sa_table[row]) == NULL)
+ {
+ list = this->ike_sa_table[row] = linked_list_create();
+ }
+ list->insert_last(list, entry);
+ this->segments[segment].count++;
+ return segment;
+}
- status = NOT_FOUND;
+/**
+ * Remove an entry from the hash table.
+ * Note: The caller MUST have a lock on the segment of this entry.
+ */
+static void remove_entry(private_ike_sa_manager_t *this, entry_t *entry)
+{
+ linked_list_t *list;
+ u_int row = ike_sa_id_hash(entry->ike_sa_id) & this->table_mask;
+ u_int segment = row & this->segment_mask;
- while (enumerator->enumerate(enumerator, &current))
+ if ((list = this->ike_sa_table[row]) != NULL)
{
- if (current == entry)
+ entry_t *current;
+
+ enumerator_t *enumerator = list->create_enumerator(list);
+ while (enumerator->enumerate(enumerator, &current))
{
- /* mark it, so now new threads can get this entry */
- entry->driveout_new_threads = TRUE;
- /* wait until all workers have done their work */
- while (entry->waiting_threads)
+ if (current == entry)
{
- /* wake up all */
- entry->condvar->broadcast(entry->condvar);
- /* they will wake us again when their work is done */
- entry->condvar->wait(entry->condvar, this->mutex);
+ list->remove_at(list, enumerator);
+ this->segments[segment].count--;
+ break;
}
-
- DBG2(DBG_MGR, "found entry by pointer, deleting it");
- this->ike_sa_list->remove_at(this->ike_sa_list, enumerator);
- entry_destroy(entry);
- status = SUCCESS;
- break;
}
+ enumerator->destroy(enumerator);
}
- enumerator->destroy(enumerator);
- return status;
+}
+
+/**
+ * Remove the entry at the current enumerator position.
+ */
+static void remove_entry_at(private_enumerator_t *this)
+{
+ this->entry = NULL;
+ if (this->current)
+ {
+ linked_list_t *list = this->manager->ike_sa_table[this->row];
+ list->remove_at(list, this->current);
+ this->manager->segments[this->segment].count--;
+ }
+}
+
+/**
+ * Find an entry using the provided match function to compare the entries for
+ * equality.
+ */
+static status_t get_entry_by_match_function(private_ike_sa_manager_t *this,
+ ike_sa_id_t *ike_sa_id, entry_t **entry, u_int *segment,
+ linked_list_match_t match, void *p1, void *p2)
+{
+ entry_t *current;
+ linked_list_t *list;
+ u_int row = ike_sa_id_hash(ike_sa_id) & this->table_mask;
+ u_int seg = row & this->segment_mask;
+
+ lock_single_segment(this, seg);
+ if ((list = this->ike_sa_table[row]) != NULL)
+ {
+ if (list->find_first(list, match, (void**)&current, p1, p2) == SUCCESS)
+ {
+ *entry = current;
+ *segment = seg;
+ /* the locked segment has to be unlocked by the caller */
+ return SUCCESS;
+ }
+ }
+ unlock_single_segment(this, seg);
+ return NOT_FOUND;
+}
+
+/**
+ * Find an entry by ike_sa_id_t.
+ * Note: On SUCCESS, the caller has to unlock the segment.
+ */
+static status_t get_entry_by_id(private_ike_sa_manager_t *this,
+ ike_sa_id_t *ike_sa_id, 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_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.
+ */
+static status_t get_entry_by_sa(private_ike_sa_manager_t *this,
+ ike_sa_id_t *ike_sa_id, ike_sa_t *ike_sa, 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_sa, ike_sa, NULL);
}
/**
* Wait until no other thread is using an IKE_SA, return FALSE if entry not
- * acquireable
+ * acquirable.
*/
-static bool wait_for_entry(private_ike_sa_manager_t *this, entry_t *entry)
+static bool wait_for_entry(private_ike_sa_manager_t *this, entry_t *entry,
+ u_int segment)
{
if (entry->driveout_new_threads)
{
@@ -311,7 +677,7 @@ static bool wait_for_entry(private_ike_sa_manager_t *this, entry_t *entry)
/* so wait until we can get it for us.
* we register us as waiting. */
entry->waiting_threads++;
- entry->condvar->wait(entry->condvar, this->mutex);
+ entry->condvar->wait(entry->condvar, this->segments[segment].mutex);
entry->waiting_threads--;
}
/* hm, a deletion request forbids us to get this SA, get next one */
@@ -325,6 +691,176 @@ static bool wait_for_entry(private_ike_sa_manager_t *this, entry_t *entry)
}
/**
+ * Put a half-open SA into the hash table.
+ */
+static void put_half_open(private_ike_sa_manager_t *this, entry_t *entry)
+{
+ half_open_t *half_open = NULL;
+ linked_list_t *list;
+ chunk_t addr = entry->other->get_address(entry->other);
+ u_int row = chunk_hash(addr) & this->table_mask;
+ u_int segment = row & this->segment_mask;
+
+ rwlock_t *lock = this->half_open_segments[segment].lock;
+ lock->write_lock(lock);
+ if ((list = this->half_open_table[row]) == NULL)
+ {
+ list = this->half_open_table[row] = linked_list_create();
+ }
+ else
+ {
+ half_open_t *current;
+ if (list->find_first(list, (linked_list_match_t)half_open_match,
+ (void**)&current, &addr) == SUCCESS)
+ {
+ half_open = current;
+ half_open->count++;
+ this->half_open_segments[segment].count++;
+ }
+ }
+
+ if (!half_open)
+ {
+ half_open = malloc_thing(half_open_t);
+ half_open->other = chunk_clone(addr);
+ half_open->count = 1;
+ list->insert_last(list, half_open);
+ this->half_open_segments[segment].count++;
+ }
+ lock->unlock(lock);
+}
+
+/**
+ * Remove a half-open SA from the hash table.
+ */
+static void remove_half_open(private_ike_sa_manager_t *this, entry_t *entry)
+{
+ linked_list_t *list;
+ chunk_t addr = entry->other->get_address(entry->other);
+ u_int row = chunk_hash(addr) & this->table_mask;
+ u_int segment = row & this->segment_mask;
+
+ rwlock_t *lock = this->half_open_segments[segment].lock;
+ lock->write_lock(lock);
+ if ((list = this->half_open_table[row]) != NULL)
+ {
+ half_open_t *current;
+ enumerator_t *enumerator = list->create_enumerator(list);
+ while (enumerator->enumerate(enumerator, &current))
+ {
+ if (half_open_match(current, &addr))
+ {
+ if (--current->count == 0)
+ {
+ list->remove_at(list, enumerator);
+ half_open_destroy(current);
+ }
+ this->half_open_segments[segment].count--;
+ break;
+ }
+ }
+ enumerator->destroy(enumerator);
+ }
+ lock->unlock(lock);
+}
+
+/**
+ * Put an SA between two peers into the hash table.
+ */
+static void put_connected_peers(private_ike_sa_manager_t *this, entry_t *entry)
+{
+ linked_list_t *list;
+ connected_peers_t *connected_peers = NULL;
+ chunk_t my_id = entry->my_id->get_encoding(entry->my_id),
+ other_id = entry->other_id->get_encoding(entry->other_id);
+ u_int row = chunk_hash_inc(other_id, chunk_hash(my_id)) & this->table_mask;
+ u_int segment = row & this->segment_mask;
+
+ rwlock_t *lock = this->connected_peers_segments[segment].lock;
+ lock->write_lock(lock);
+ if ((list = this->connected_peers_table[row]) == NULL)
+ {
+ list = this->connected_peers_table[row] = linked_list_create();
+ }
+ else
+ {
+ connected_peers_t *current;
+ if (list->find_first(list, (linked_list_match_t)connected_peers_match,
+ (void**)&current, entry->my_id, entry->other_id) == SUCCESS)
+ {
+ connected_peers = current;
+ if (connected_peers->sas->find_first(connected_peers->sas,
+ (linked_list_match_t)entry->ike_sa_id->equals,
+ NULL, entry->ike_sa_id) == SUCCESS)
+ {
+ lock->unlock(lock);
+ return;
+ }
+ }
+ }
+
+ if (!connected_peers)
+ {
+ connected_peers = malloc_thing(connected_peers_t);
+ connected_peers->my_id = entry->my_id->clone(entry->my_id);
+ connected_peers->other_id = entry->other_id->clone(entry->other_id);
+ connected_peers->sas = linked_list_create();
+ list->insert_last(list, connected_peers);
+ }
+ connected_peers->sas->insert_last(connected_peers->sas,
+ entry->ike_sa_id->clone(entry->ike_sa_id));
+ this->connected_peers_segments[segment].count++;
+ lock->unlock(lock);
+}
+
+/**
+ * Remove an SA between two peers from the hash table.
+ */
+static void remove_connected_peers(private_ike_sa_manager_t *this, entry_t *entry)
+{
+ linked_list_t *list;
+ chunk_t my_id = entry->my_id->get_encoding(entry->my_id),
+ other_id = entry->other_id->get_encoding(entry->other_id);
+ u_int row = chunk_hash_inc(other_id, chunk_hash(my_id)) & this->table_mask;
+ u_int segment = row & this->segment_mask;
+
+ rwlock_t *lock = this->connected_peers_segments[segment].lock;
+ lock->write_lock(lock);
+ if ((list = this->connected_peers_table[row]) != NULL)
+ {
+ connected_peers_t *current;
+ enumerator_t *enumerator = list->create_enumerator(list);
+ while (enumerator->enumerate(enumerator, &current))
+ {
+ if (connected_peers_match(current, entry->my_id, entry->other_id))
+ {
+ ike_sa_id_t *ike_sa_id;
+ enumerator_t *inner = current->sas->create_enumerator(current->sas);
+ while (inner->enumerate(inner, &ike_sa_id))
+ {
+ if (ike_sa_id->equals(ike_sa_id, entry->ike_sa_id))
+ {
+ current->sas->remove_at(current->sas, inner);
+ ike_sa_id->destroy(ike_sa_id);
+ this->connected_peers_segments[segment].count--;
+ break;
+ }
+ }
+ inner->destroy(inner);
+ if (current->sas->get_count(current->sas) == 0)
+ {
+ list->remove_at(list, enumerator);
+ connected_peers_destroy(current);
+ }
+ break;
+ }
+ }
+ enumerator->destroy(enumerator);
+ }
+ lock->unlock(lock);
+}
+
+/**
* Implementation of private_ike_sa_manager_t.get_next_spi.
*/
static u_int64_t get_next_spi(private_ike_sa_manager_t *this)
@@ -342,21 +878,20 @@ static ike_sa_t* checkout(private_ike_sa_manager_t *this, ike_sa_id_t *ike_sa_id
{
ike_sa_t *ike_sa = NULL;
entry_t *entry;
+ u_int segment;
- DBG2(DBG_MGR, "checkout IKE_SA, %d IKE_SAs in manager",
- this->ike_sa_list->get_count(this->ike_sa_list));
+ DBG2(DBG_MGR, "checkout IKE_SA");
- this->mutex->lock(this->mutex);
- if (get_entry_by_id(this, ike_sa_id, &entry) == SUCCESS)
+ if (get_entry_by_id(this, ike_sa_id, &entry, &segment) == SUCCESS)
{
- if (wait_for_entry(this, entry))
+ if (wait_for_entry(this, entry, segment))
{
DBG2(DBG_MGR, "IKE_SA successfully checked out");
entry->checked_out = TRUE;
ike_sa = entry->ike_sa;
}
+ unlock_single_segment(this, segment);
}
- this->mutex->unlock(this->mutex);
charon->bus->set_sa(charon->bus, ike_sa);
return ike_sa;
}
@@ -367,24 +902,24 @@ static ike_sa_t* checkout(private_ike_sa_manager_t *this, ike_sa_id_t *ike_sa_id
static ike_sa_t *checkout_new(private_ike_sa_manager_t* this, bool initiator)
{
entry_t *entry;
- ike_sa_id_t *id;
+ u_int segment;
+ entry = entry_create();
if (initiator)
{
- id = ike_sa_id_create(get_next_spi(this), 0, TRUE);
+ entry->ike_sa_id = ike_sa_id_create(get_next_spi(this), 0, TRUE);
}
else
{
- id = ike_sa_id_create(0, get_next_spi(this), FALSE);
+ entry->ike_sa_id = ike_sa_id_create(0, get_next_spi(this), FALSE);
}
- entry = entry_create(id);
- id->destroy(id);
- this->mutex->lock(this->mutex);
- this->ike_sa_list->insert_last(this->ike_sa_list, entry);
+ entry->ike_sa = ike_sa_create(entry->ike_sa_id);
+
+ segment = put_entry(this, entry);
entry->checked_out = TRUE;
- this->mutex->unlock(this->mutex);
- DBG2(DBG_MGR, "created IKE_SA, %d IKE_SAs in manager",
- this->ike_sa_list->get_count(this->ike_sa_list));
+ unlock_single_segment(this, segment);
+
+ DBG2(DBG_MGR, "created IKE_SA");
return entry->ike_sa;
}
@@ -394,53 +929,45 @@ static ike_sa_t *checkout_new(private_ike_sa_manager_t* this, bool initiator)
static ike_sa_t* checkout_by_message(private_ike_sa_manager_t* this,
message_t *message)
{
+ u_int segment;
entry_t *entry;
ike_sa_t *ike_sa = NULL;
ike_sa_id_t *id = message->get_ike_sa_id(message);
+
id = id->clone(id);
id->switch_initiator(id);
- DBG2(DBG_MGR, "checkout IKE_SA by message, %d IKE_SAs in manager",
- this->ike_sa_list->get_count(this->ike_sa_list));
+ DBG2(DBG_MGR, "checkout IKE_SA by message");
if (message->get_request(message) &&
message->get_exchange_type(message) == IKE_SA_INIT)
{
/* IKE_SA_INIT request. Check for an IKE_SA with such a message hash. */
- enumerator_t *enumerator;
chunk_t data, hash;
-
+
data = message->get_packet_data(message);
this->hasher->allocate_hash(this->hasher, data, &hash);
chunk_free(&data);
- this->mutex->lock(this->mutex);
- enumerator = this->ike_sa_list->create_enumerator(this->ike_sa_list);
- while (enumerator->enumerate(enumerator, &entry))
+ if (get_entry_by_hash(this, id, hash, &entry, &segment) == SUCCESS)
{
- if (chunk_equals(hash, entry->init_hash))
+ if (entry->message_id == 0)
{
- if (entry->message_id == 0)
- {
- enumerator->destroy(enumerator);
- this->mutex->unlock(this->mutex);
- chunk_free(&hash);
- id->destroy(id);
- DBG1(DBG_MGR, "ignoring IKE_SA_INIT, already processing");
- return NULL;
- }
- else if (wait_for_entry(this, entry))
- {
- DBG2(DBG_MGR, "IKE_SA checked out by hash");
- entry->checked_out = TRUE;
- entry->message_id = message->get_message_id(message);
- ike_sa = entry->ike_sa;
- }
- break;
+ 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))
+ {
+ DBG2(DBG_MGR, "IKE_SA checked out by hash");
+ entry->checked_out = TRUE;
+ entry->message_id = message->get_message_id(message);
+ ike_sa = entry->ike_sa;
}
+ unlock_single_segment(this, segment);
}
- enumerator->destroy(enumerator);
- this->mutex->unlock(this->mutex);
if (ike_sa == NULL)
{
@@ -449,15 +976,19 @@ static ike_sa_t* checkout_by_message(private_ike_sa_manager_t* this,
{
/* no IKE_SA found, create a new one */
id->set_responder_spi(id, get_next_spi(this));
- entry = entry_create(id);
+ entry = entry_create();
+ entry->ike_sa = ike_sa_create(id);
+ entry->ike_sa_id = id->clone(id);
- this->mutex->lock(this->mutex);
- this->ike_sa_list->insert_last(this->ike_sa_list, entry);
+ segment = put_entry(this, entry);
entry->checked_out = TRUE;
- entry->message_id = message->get_message_id(message);
- this->mutex->unlock(this->mutex);
+ unlock_single_segment(this, segment);
+
+ entry->message_id = message->get_message_id(message);
entry->init_hash = hash;
ike_sa = entry->ike_sa;
+
+ DBG2(DBG_MGR, "created IKE_SA");
}
else
{
@@ -474,8 +1005,7 @@ static ike_sa_t* checkout_by_message(private_ike_sa_manager_t* this,
return ike_sa;
}
- this->mutex->lock(this->mutex);
- if (get_entry_by_id(this, id, &entry) == SUCCESS)
+ if (get_entry_by_id(this, id, &entry, &segment) == SUCCESS)
{
/* only check out if we are not processing this request */
if (message->get_request(message) &&
@@ -484,7 +1014,7 @@ static ike_sa_t* checkout_by_message(private_ike_sa_manager_t* this,
DBG1(DBG_MGR, "ignoring request with ID %d, already processing",
entry->message_id);
}
- else if (wait_for_entry(this, entry))
+ else if (wait_for_entry(this, entry, segment))
{
ike_sa_id_t *ike_id = entry->ike_sa->get_id(entry->ike_sa);
DBG2(DBG_MGR, "IKE_SA successfully checked out");
@@ -496,8 +1026,8 @@ static ike_sa_t* checkout_by_message(private_ike_sa_manager_t* this,
}
ike_sa = entry->ike_sa;
}
+ unlock_single_segment(this, segment);
}
- this->mutex->unlock(this->mutex);
id->destroy(id);
charon->bus->set_sa(charon->bus, ike_sa);
return ike_sa;
@@ -515,6 +1045,14 @@ static ike_sa_t* checkout_by_config(private_ike_sa_manager_t *this,
identification_t *my_id, *other_id;
host_t *my_host, *other_host;
ike_cfg_t *ike_cfg;
+ u_int segment;
+
+ if (!this->reuse_ikesa)
+ { /* IKE_SA reuse disable by config */
+ ike_sa = checkout_new(this, TRUE);
+ charon->bus->set_sa(charon->bus, ike_sa);
+ return ike_sa;
+ }
ike_cfg = peer_cfg->get_ike_cfg(peer_cfg);
my_id = peer_cfg->get_my_id(peer_cfg);
@@ -522,24 +1060,22 @@ static ike_sa_t* checkout_by_config(private_ike_sa_manager_t *this,
my_host = host_create_from_dns(ike_cfg->get_my_addr(ike_cfg), 0, 0);
other_host = host_create_from_dns(ike_cfg->get_other_addr(ike_cfg), 0, 0);
- this->mutex->lock(this->mutex);
-
- if (my_host && other_host && this->reuse_ikesa)
+ if (my_host && other_host)
{
- enumerator = this->ike_sa_list->create_enumerator(this->ike_sa_list);
- while (enumerator->enumerate(enumerator, &entry))
+ enumerator = create_table_enumerator(this);
+ while (enumerator->enumerate(enumerator, &entry, &segment))
{
identification_t *found_my_id, *found_other_id;
host_t *found_my_host, *found_other_host;
- if (!wait_for_entry(this, entry))
+ if (!wait_for_entry(this, entry, segment))
{
continue;
}
if (entry->ike_sa->get_state(entry->ike_sa) == IKE_DELETING)
{
- /* skip IKE_SA which are not useable */
+ /* skip IKE_SAs which are not usable */
continue;
}
@@ -584,28 +1120,9 @@ static ike_sa_t* checkout_by_config(private_ike_sa_manager_t *this,
DESTROY_IF(other_host);
if (!ike_sa)
- {
- u_int64_t initiator_spi;
- entry_t *new_entry;
- ike_sa_id_t *new_ike_sa_id;
-
- initiator_spi = get_next_spi(this);
- new_ike_sa_id = ike_sa_id_create(0, 0, TRUE);
- new_ike_sa_id->set_initiator_spi(new_ike_sa_id, initiator_spi);
-
- /* create entry */
- new_entry = entry_create(new_ike_sa_id);
- DBG2(DBG_MGR, "created IKE_SA");
- new_ike_sa_id->destroy(new_ike_sa_id);
-
- this->ike_sa_list->insert_last(this->ike_sa_list, new_entry);
-
- /* check ike_sa out */
- DBG2(DBG_MGR, "new IKE_SA created for IDs [%D]...[%D]", my_id, other_id);
- new_entry->checked_out = TRUE;
- ike_sa = new_entry->ike_sa;
+ { /* no IKE_SA using such a config, hand out a new */
+ ike_sa = checkout_new(this, TRUE);
}
- this->mutex->unlock(this->mutex);
charon->bus->set_sa(charon->bus, ike_sa);
return ike_sa;
}
@@ -621,13 +1138,12 @@ static ike_sa_t* checkout_by_id(private_ike_sa_manager_t *this, u_int32_t id,
entry_t *entry;
ike_sa_t *ike_sa = NULL;
child_sa_t *child_sa;
+ u_int segment;
- this->mutex->lock(this->mutex);
-
- enumerator = this->ike_sa_list->create_enumerator(this->ike_sa_list);
- while (enumerator->enumerate(enumerator, &entry))
+ enumerator = create_table_enumerator(this);
+ while (enumerator->enumerate(enumerator, &entry, &segment))
{
- if (wait_for_entry(this, entry))
+ if (wait_for_entry(this, entry, segment))
{
/* look for a child with such a reqid ... */
if (child)
@@ -659,7 +1175,6 @@ static ike_sa_t* checkout_by_id(private_ike_sa_manager_t *this, u_int32_t id,
}
}
enumerator->destroy(enumerator);
- this->mutex->unlock(this->mutex);
charon->bus->set_sa(charon->bus, ike_sa);
return ike_sa;
@@ -676,13 +1191,12 @@ static ike_sa_t* checkout_by_name(private_ike_sa_manager_t *this, char *name,
entry_t *entry;
ike_sa_t *ike_sa = NULL;
child_sa_t *child_sa;
+ u_int segment;
- this->mutex->lock(this->mutex);
-
- enumerator = this->ike_sa_list->create_enumerator(this->ike_sa_list);
- while (enumerator->enumerate(enumerator, &entry))
+ enumerator = create_table_enumerator(this);
+ while (enumerator->enumerate(enumerator, &entry, &segment))
{
- if (wait_for_entry(this, entry))
+ if (wait_for_entry(this, entry, segment))
{
/* look for a child with such a policy name ... */
if (child)
@@ -714,68 +1228,18 @@ static ike_sa_t* checkout_by_name(private_ike_sa_manager_t *this, char *name,
}
}
enumerator->destroy(enumerator);
- this->mutex->unlock(this->mutex);
charon->bus->set_sa(charon->bus, ike_sa);
return ike_sa;
}
-
-/**
- * Implementation of ike_sa_manager_t.checkout_duplicate.
- */
-static ike_sa_t* checkout_duplicate(private_ike_sa_manager_t *this,
- ike_sa_t *ike_sa)
-{
- enumerator_t *enumerator;
- entry_t *entry;
- ike_sa_t *duplicate = NULL;
- identification_t *me, *other;
-
- me = ike_sa->get_my_id(ike_sa);
- other = ike_sa->get_other_id(ike_sa);
-
- this->mutex->lock(this->mutex);
- enumerator = this->ike_sa_list->create_enumerator(this->ike_sa_list);
- while (enumerator->enumerate(enumerator, &entry))
- {
- if (entry->ike_sa == ike_sa)
- { /* self is not a duplicate */
- continue;
- }
- if (entry->my_id && me->equals(me, entry->my_id) &&
- entry->other_id && other->equals(other, entry->other_id))
- {
- /* we are sure that the other entry is not calling
- * checkout_duplicate here, as the identities in entry would not
- * have been set yet. Otherwise we would risk a deadlock. */
- if (wait_for_entry(this, entry))
- {
- duplicate = entry->ike_sa;
- entry->checked_out = TRUE;
- break;
- }
- }
- }
- enumerator->destroy(enumerator);
- this->mutex->unlock(this->mutex);
- return duplicate;
-}
-
-/**
- * enumerator cleanup function
- */
-static void enumerator_unlock(private_ike_sa_manager_t *this)
-{
- this->mutex->unlock(this->mutex);
-}
/**
* enumerator filter function
*/
static bool enumerator_filter(private_ike_sa_manager_t *this,
- entry_t **in, ike_sa_t **out)
+ entry_t **in, ike_sa_t **out, u_int *segment)
{
- if (wait_for_entry(this, *in))
+ if (wait_for_entry(this, *in, *segment))
{
*out = (*in)->ike_sa;
return TRUE;
@@ -784,126 +1248,266 @@ static bool enumerator_filter(private_ike_sa_manager_t *this,
}
/**
- * Implementation of ike_sa_manager_t.create_iterator.
+ * Implementation of ike_sa_manager_t.create_enumerator.
*/
static enumerator_t *create_enumerator(private_ike_sa_manager_t* this)
{
- this->mutex->lock(this->mutex);
return enumerator_create_filter(
- this->ike_sa_list->create_enumerator(this->ike_sa_list),
- (void*)enumerator_filter, this, (void*)enumerator_unlock);
+ create_table_enumerator(this),
+ (void*)enumerator_filter, this, NULL);
}
/**
* Implementation of ike_sa_manager_t.checkin.
*/
-static status_t checkin(private_ike_sa_manager_t *this, ike_sa_t *ike_sa)
+static void checkin(private_ike_sa_manager_t *this, ike_sa_t *ike_sa)
{
/* to check the SA back in, we look for the pointer of the ike_sa
* in all entries.
- * We can't search by SPI's since the MAY have changed (e.g. on reception
- * of a IKE_SA_INIT response). Updating of the SPI MAY be necessary...
+ * The lookup is done by initiator SPI, so even if the SPI has changed (e.g.
+ * on reception of a IKE_SA_INIT response) the lookup will work but
+ * updating of the SPI MAY be necessary...
*/
- status_t retval;
entry_t *entry;
ike_sa_id_t *ike_sa_id;
host_t *other;
identification_t *my_id, *other_id;
+ u_int segment;
ike_sa_id = ike_sa->get_id(ike_sa);
+ my_id = ike_sa->get_my_id(ike_sa);
+ other_id = ike_sa->get_other_id(ike_sa);
+ other = ike_sa->get_other_host(ike_sa);
DBG2(DBG_MGR, "checkin IKE_SA");
- this->mutex->lock(this->mutex);
-
/* look for the entry */
- if (get_entry_by_sa(this, ike_sa, &entry) == SUCCESS)
+ if (get_entry_by_sa(this, ike_sa_id, ike_sa, &entry, &segment) == SUCCESS)
{
/* ike_sa_id must be updated */
entry->ike_sa_id->replace_values(entry->ike_sa_id, ike_sa->get_id(ike_sa));
/* signal waiting threads */
entry->checked_out = FALSE;
entry->message_id = -1;
- /* apply remote address for DoS detection */
- other = ike_sa->get_other_host(ike_sa);
- if (!entry->other || !other->equals(other, entry->other))
+ /* check if this SA is half-open */
+ if (entry->half_open && ike_sa->get_state(ike_sa) != IKE_CONNECTING)
{
- DESTROY_IF(entry->other);
- entry->other = other->clone(other);
+ /* not half open anymore */
+ entry->half_open = FALSE;
+ remove_half_open(this, entry);
}
- /* apply identities for diplicate test */
- my_id = ike_sa->get_my_id(ike_sa);
- other_id = ike_sa->get_other_id(ike_sa);
- if (!entry->my_id ||
- entry->my_id->get_type(entry->my_id) == ID_ANY)
+ else if (entry->half_open && !other->ip_equals(other, entry->other))
{
- DESTROY_IF(entry->my_id);
- entry->my_id = my_id->clone(my_id);
+ /* the other host's IP has changed, we must update the hash table */
+ remove_half_open(this, entry);
+ DESTROY_IF(entry->other);
+ entry->other = other->clone(other);
+ put_half_open(this, entry);
}
- if (!entry->other_id ||
- entry->other_id->get_type(entry->other_id) == ID_ANY)
+ else if (!entry->half_open &&
+ !entry->ike_sa_id->is_initiator(entry->ike_sa_id) &&
+ ike_sa->get_state(ike_sa) == IKE_CONNECTING)
{
- DESTROY_IF(entry->other_id);
- entry->other_id = other_id->clone(other_id);
+ /* this is a new half-open SA */
+ entry->half_open = TRUE;
+ entry->other = other->clone(other);
+ put_half_open(this, entry);
}
DBG2(DBG_MGR, "check-in of IKE_SA successful.");
entry->condvar->signal(entry->condvar);
- retval = SUCCESS;
}
else
{
- DBG2(DBG_MGR, "tried to check in nonexisting IKE_SA");
- /* this SA is no more, this REALLY should not happen */
- retval = NOT_FOUND;
+ entry = entry_create();
+ entry->ike_sa_id = ike_sa_id->clone(ike_sa_id);
+ entry->ike_sa = ike_sa;
+ segment = put_entry(this, entry);
+ }
+
+ /* apply identities for duplicate test (only as responder) */
+ if (!entry->ike_sa_id->is_initiator(entry->ike_sa_id) &&
+ (!entry->my_id || !entry->other_id))
+ {
+ if (!entry->my_id && my_id->get_type(my_id) != ID_ANY)
+ {
+ entry->my_id = my_id->clone(my_id);
+ }
+ if (!entry->other_id && other_id->get_type(other_id) != ID_ANY)
+ {
+ entry->other_id = other_id->clone(other_id);
+ }
+ if (entry->my_id && entry->other_id)
+ {
+ put_connected_peers(this, entry);
+ }
}
- DBG2(DBG_MGR, "%d IKE_SAs in manager now",
- this->ike_sa_list->get_count(this->ike_sa_list));
- this->mutex->unlock(this->mutex);
+ unlock_single_segment(this, segment);
charon->bus->set_sa(charon->bus, NULL);
- return retval;
}
-
/**
* Implementation of ike_sa_manager_t.checkin_and_destroy.
*/
-static status_t checkin_and_destroy(private_ike_sa_manager_t *this, ike_sa_t *ike_sa)
+static void checkin_and_destroy(private_ike_sa_manager_t *this, ike_sa_t *ike_sa)
{
- /* deletion is a bit complex, we must garant that no thread is waiting for
+ /* deletion is a bit complex, we must ensure that no thread is waiting for
* this SA.
- * We take this SA from the list, and start signaling while threads
+ * We take this SA from the table, and start signaling while threads
* are in the condvar.
*/
entry_t *entry;
- status_t retval;
ike_sa_id_t *ike_sa_id;
+ u_int segment;
ike_sa_id = ike_sa->get_id(ike_sa);
+
DBG2(DBG_MGR, "checkin and destroy IKE_SA");
-
- this->mutex->lock(this->mutex);
-
- if (get_entry_by_sa(this, ike_sa, &entry) == SUCCESS)
+
+ if (get_entry_by_sa(this, ike_sa_id, ike_sa, &entry, &segment) == SUCCESS)
{
/* drive out waiting threads, as we are in hurry */
entry->driveout_waiting_threads = TRUE;
+ /* mark it, so no new threads can get this entry */
+ entry->driveout_new_threads = TRUE;
+ /* wait until all workers have done their work */
+ while (entry->waiting_threads)
+ {
+ /* wake up all */
+ entry->condvar->broadcast(entry->condvar);
+ /* they will wake us again when their work is done */
+ entry->condvar->wait(entry->condvar, this->segments[segment].mutex);
+ }
+ remove_entry(this, entry);
+ unlock_single_segment(this, segment);
+
+ if (entry->half_open)
+ {
+ remove_half_open(this, entry);
+ }
+ if (!entry->ike_sa_id->is_initiator(entry->ike_sa_id) &&
+ entry->my_id && entry->other_id)
+ {
+ remove_connected_peers(this, entry);
+ }
- delete_entry(this, entry);
+ entry_destroy(entry);
DBG2(DBG_MGR, "check-in and destroy of IKE_SA successful");
- retval = SUCCESS;
}
else
{
- DBG2(DBG_MGR, "tried to check-in and delete nonexisting IKE_SA");
- retval = NOT_FOUND;
+ DBG1(DBG_MGR, "tried to check-in and delete nonexisting IKE_SA");
+ ike_sa->destroy(ike_sa);
}
charon->bus->set_sa(charon->bus, NULL);
+}
+
+
+/**
+ * Implementation of ike_sa_manager_t.check_uniqueness.
+ */
+static bool check_uniqueness(private_ike_sa_manager_t *this, ike_sa_t *ike_sa)
+{
+ bool cancel = FALSE;
+ peer_cfg_t *peer_cfg;
+ unique_policy_t policy;
+ linked_list_t *list, *duplicate_ids = NULL;
+ enumerator_t *enumerator;
+ ike_sa_id_t *duplicate_id = NULL;
+ identification_t *me, *other;
+ u_int row, segment;
+ rwlock_t *lock;
+
+ peer_cfg = ike_sa->get_peer_cfg(ike_sa);
+ policy = peer_cfg->get_unique_policy(peer_cfg);
+ if (policy == UNIQUE_NO)
+ {
+ return FALSE;
+ }
+
+ me = ike_sa->get_my_id(ike_sa);
+ other = ike_sa->get_other_id(ike_sa);
+
+ row = chunk_hash_inc(other->get_encoding(other),
+ chunk_hash(me->get_encoding(me))) & this->table_mask;
+ segment = row & this->segment_mask;
+
+ lock = this->connected_peers_segments[segment & this->segment_mask].lock;
+ lock->read_lock(lock);
+ if ((list = this->connected_peers_table[row]) != NULL)
+ {
+ connected_peers_t *current;
+
+ if (list->find_first(list, (linked_list_match_t)connected_peers_match,
+ (void**)&current, me, other) == SUCCESS)
+ {
+ /* clone the list, so we can release the lock */
+ duplicate_ids = current->sas->clone_offset(current->sas,
+ offsetof(ike_sa_id_t, clone));
+ }
+ }
+ lock->unlock(lock);
+
+ if (!duplicate_ids)
+ {
+ return FALSE;
+ }
- this->mutex->unlock(this->mutex);
- return retval;
+ enumerator = duplicate_ids->create_enumerator(duplicate_ids);
+ while (enumerator->enumerate(enumerator, &duplicate_id))
+ {
+ status_t status = SUCCESS;
+ ike_sa_t *duplicate;
+
+ duplicate = checkout(this, duplicate_id);
+ if (!duplicate)
+ {
+ continue;
+ }
+ peer_cfg = duplicate->get_peer_cfg(duplicate);
+ if (peer_cfg && peer_cfg->equals(peer_cfg, ike_sa->get_peer_cfg(ike_sa)))
+ {
+ switch (duplicate->get_state(duplicate))
+ {
+ case IKE_ESTABLISHED:
+ case IKE_REKEYING:
+ switch (policy)
+ {
+ case UNIQUE_REPLACE:
+ DBG1(DBG_IKE, "deleting duplicate IKE_SA due"
+ " uniqueness policy");
+ status = duplicate->delete(duplicate);
+ break;
+ case UNIQUE_KEEP:
+ cancel = TRUE;
+ /* we keep the first IKE_SA and delete all
+ * other duplicates that might exist */
+ policy = UNIQUE_REPLACE;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (status == DESTROY_ME)
+ {
+ checkin_and_destroy(this, duplicate);
+ }
+ else
+ {
+ checkin(this, duplicate);
+ }
+ }
+ enumerator->destroy(enumerator);
+ duplicate_ids->destroy_offset(duplicate_ids, offsetof(ike_sa_id_t, destroy));
+ /* reset thread's current IKE_SA after checkin */
+ charon->bus->set_sa(charon->bus, ike_sa);
+ return cancel;
}
/**
@@ -911,35 +1515,43 @@ static status_t checkin_and_destroy(private_ike_sa_manager_t *this, ike_sa_t *ik
*/
static int get_half_open_count(private_ike_sa_manager_t *this, host_t *ip)
{
- enumerator_t *enumerator;
- entry_t *entry;
int count = 0;
- this->mutex->lock(this->mutex);
- enumerator = this->ike_sa_list->create_enumerator(this->ike_sa_list);
- while (enumerator->enumerate(enumerator, &entry))
+ if (ip)
{
- /* we check if we have a responder CONNECTING IKE_SA without checkout */
- if (!entry->ike_sa_id->is_initiator(entry->ike_sa_id) &&
- entry->ike_sa->get_state(entry->ike_sa) == IKE_CONNECTING)
+ linked_list_t *list;
+ chunk_t addr = ip->get_address(ip);
+ u_int row = chunk_hash(addr) & this->table_mask;
+ u_int segment = row & this->segment_mask;
+
+ rwlock_t *lock = this->half_open_segments[segment & this->segment_mask].lock;
+ lock->read_lock(lock);
+ if ((list = this->half_open_table[row]) != NULL)
{
- /* if we have a host, count only matching IKE_SAs */
- if (ip)
- {
- if (entry->other && ip->ip_equals(ip, entry->other))
- {
- count++;
- }
- }
- else
+ half_open_t *current;
+
+ if (list->find_first(list, (linked_list_match_t)half_open_match,
+ (void**)&current, &addr) == SUCCESS)
{
- count++;
+ count = current->count;
}
}
+ lock->unlock(lock);
+ }
+ else
+ {
+ u_int segment;
+
+ for (segment = 0; segment < this->segment_count; ++segment)
+ {
+ rwlock_t *lock;
+ lock = this->half_open_segments[segment & this->segment_mask].lock;
+ lock->read_lock(lock);
+ count += this->half_open_segments[segment].count;
+ lock->unlock(lock);
+ }
}
- enumerator->destroy(enumerator);
- this->mutex->unlock(this->mutex);
return count;
}
@@ -951,13 +1563,14 @@ static void flush(private_ike_sa_manager_t *this)
/* destroy all list entries */
enumerator_t *enumerator;
entry_t *entry;
+ u_int segment;
- this->mutex->lock(this->mutex);
+ lock_all_segments(this);
DBG2(DBG_MGR, "going to destroy IKE_SA manager and all managed IKE_SA's");
/* Step 1: drive out all waiting threads */
DBG2(DBG_MGR, "set driveout flags for all stored IKE_SA's");
- enumerator = this->ike_sa_list->create_enumerator(this->ike_sa_list);
- while (enumerator->enumerate(enumerator, &entry))
+ enumerator = create_table_enumerator(this);
+ while (enumerator->enumerate(enumerator, &entry, &segment))
{
/* do not accept new threads, drive out waiting threads */
entry->driveout_new_threads = TRUE;
@@ -966,22 +1579,22 @@ static void flush(private_ike_sa_manager_t *this)
enumerator->destroy(enumerator);
DBG2(DBG_MGR, "wait for all threads to leave IKE_SA's");
/* Step 2: wait until all are gone */
- enumerator = this->ike_sa_list->create_enumerator(this->ike_sa_list);
- while (enumerator->enumerate(enumerator, &entry))
+ enumerator = create_table_enumerator(this);
+ while (enumerator->enumerate(enumerator, &entry, &segment))
{
- while (entry->waiting_threads)
+ while (entry->waiting_threads || entry->checked_out)
{
/* wake up all */
entry->condvar->broadcast(entry->condvar);
/* go sleeping until they are gone */
- entry->condvar->wait(entry->condvar, this->mutex);
+ entry->condvar->wait(entry->condvar, this->segments[segment].mutex);
}
}
enumerator->destroy(enumerator);
DBG2(DBG_MGR, "delete all IKE_SA's");
/* Step 3: initiate deletion of all IKE_SAs */
- enumerator = this->ike_sa_list->create_enumerator(this->ike_sa_list);
- while (enumerator->enumerate(enumerator, &entry))
+ enumerator = create_table_enumerator(this);
+ while (enumerator->enumerate(enumerator, &entry, &segment))
{
charon->bus->set_sa(charon->bus, entry->ike_sa);
entry->ike_sa->delete(entry->ike_sa);
@@ -990,14 +1603,25 @@ static void flush(private_ike_sa_manager_t *this)
DBG2(DBG_MGR, "destroy all entries");
/* Step 4: destroy all entries */
- while (this->ike_sa_list->remove_last(this->ike_sa_list,
- (void**)&entry) == SUCCESS)
+ enumerator = create_table_enumerator(this);
+ while (enumerator->enumerate(enumerator, &entry, &segment))
{
charon->bus->set_sa(charon->bus, entry->ike_sa);
+ if (entry->half_open)
+ {
+ remove_half_open(this, entry);
+ }
+ if (!entry->ike_sa_id->is_initiator(entry->ike_sa_id) &&
+ entry->my_id && entry->other_id)
+ {
+ remove_connected_peers(this, entry);
+ }
+ remove_entry_at((private_enumerator_t*)enumerator);
entry_destroy(entry);
}
+ enumerator->destroy(enumerator);
charon->bus->set_sa(charon->bus, NULL);
- this->mutex->unlock(this->mutex);
+ unlock_all_segments(this);
}
/**
@@ -1005,18 +1629,67 @@ static void flush(private_ike_sa_manager_t *this)
*/
static void destroy(private_ike_sa_manager_t *this)
{
- this->ike_sa_list->destroy(this->ike_sa_list);
+ u_int i;
+
+ for (i = 0; i < this->table_size; ++i)
+ {
+ linked_list_t *list;
+
+ if ((list = this->ike_sa_table[i]) != NULL)
+ {
+ list->destroy(list);
+ }
+ if ((list = this->half_open_table[i]) != NULL)
+ {
+ list->destroy(list);
+ }
+ if ((list = this->connected_peers_table[i]) != NULL)
+ {
+ list->destroy(list);
+ }
+ }
+ free(this->ike_sa_table);
+ free(this->half_open_table);
+ free(this->connected_peers_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);
+ }
+ free(this->segments);
+ free(this->half_open_segments);
+ free(this->connected_peers_segments);
+
this->rng->destroy(this->rng);
this->hasher->destroy(this->hasher);
- this->mutex->destroy(this->mutex);
free(this);
}
+/**
+ * This function returns the next-highest power of two for the given number.
+ * The algorithm works by setting all bits on the right-hand side of the most
+ * significant 1 to 1 and then increments the whole number so it rolls over
+ * to the nearest power of two. Note: returns 0 for n == 0
+ */
+static u_int get_nearest_powerof2(u_int n)
+{
+ u_int i;
+
+ --n;
+ for (i = 1; i < sizeof(u_int) * 8; i <<= 1)
+ {
+ n |= n >> i;
+ }
+ return ++n;
+}
+
/*
* Described in header.
*/
ike_sa_manager_t *ike_sa_manager_create()
{
+ u_int i;
private_ike_sa_manager_t *this = malloc_thing(private_ike_sa_manager_t);
/* assign public functions */
@@ -1028,10 +1701,10 @@ ike_sa_manager_t *ike_sa_manager_create()
this->public.checkout_by_config = (ike_sa_t*(*)(ike_sa_manager_t*,peer_cfg_t*))checkout_by_config;
this->public.checkout_by_id = (ike_sa_t*(*)(ike_sa_manager_t*,u_int32_t,bool))checkout_by_id;
this->public.checkout_by_name = (ike_sa_t*(*)(ike_sa_manager_t*,char*,bool))checkout_by_name;
- this->public.checkout_duplicate = (ike_sa_t*(*)(ike_sa_manager_t*, ike_sa_t *ike_sa))checkout_duplicate;
+ this->public.check_uniqueness = (bool(*)(ike_sa_manager_t*, ike_sa_t *ike_sa))check_uniqueness;
this->public.create_enumerator = (enumerator_t*(*)(ike_sa_manager_t*))create_enumerator;
- this->public.checkin = (status_t(*)(ike_sa_manager_t*,ike_sa_t*))checkin;
- this->public.checkin_and_destroy = (status_t(*)(ike_sa_manager_t*,ike_sa_t*))checkin_and_destroy;
+ this->public.checkin = (void(*)(ike_sa_manager_t*,ike_sa_t*))checkin;
+ this->public.checkin_and_destroy = (void(*)(ike_sa_manager_t*,ike_sa_t*))checkin_and_destroy;
this->public.get_half_open_count = (int(*)(ike_sa_manager_t*,host_t*))get_half_open_count;
/* initialize private variables */
@@ -1050,10 +1723,44 @@ ike_sa_manager_t *ike_sa_manager_create()
free(this);
return NULL;
}
- this->ike_sa_list = linked_list_create();
- this->mutex = mutex_create(MUTEX_DEFAULT);
+ this->table_size = get_nearest_powerof2(lib->settings->get_int(lib->settings,
+ "charon.ikesa_table_size", DEFAULT_HASHTABLE_SIZE));
+ this->table_size = max(1, min(this->table_size, MAX_HASHTABLE_SIZE));
+ this->table_mask = this->table_size - 1;
+
+ this->segment_count = get_nearest_powerof2(lib->settings->get_int(lib->settings,
+ "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->segments = (segment_t*)calloc(this->segment_count, sizeof(segment_t));
+ for (i = 0; i < this->segment_count; ++i)
+ {
+ this->segments[i].mutex = mutex_create(MUTEX_RECURSIVE);
+ this->segments[i].count = 0;
+ }
+
+ /* we use the same table parameters for the table to track half-open SAs */
+ this->half_open_table = calloc(this->table_size, sizeof(linked_list_t*));
+ this->half_open_segments = calloc(this->segment_count, sizeof(shareable_segment_t));
+ for (i = 0; i < this->segment_count; ++i)
+ {
+ this->half_open_segments[i].lock = rwlock_create(RWLOCK_DEFAULT);
+ this->half_open_segments[i].count = 0;
+ }
+
+ /* also for the hash table used for duplicate tests */
+ this->connected_peers_table = calloc(this->table_size, sizeof(linked_list_t*));
+ this->connected_peers_segments = calloc(this->segment_count, sizeof(shareable_segment_t));
+ for (i = 0; i < this->segment_count; ++i)
+ {
+ this->connected_peers_segments[i].lock = rwlock_create(RWLOCK_DEFAULT);
+ this->connected_peers_segments[i].count = 0;
+ }
+
this->reuse_ikesa = lib->settings->get_bool(lib->settings,
"charon.reuse_ikesa", TRUE);
return &this->public;
}
-