* The contained ike_sa_t object.
*/
ike_sa_t *ike_sa;
+
+ /**
+ * hash of the IKE_SA_INIT message, used to detect retransmissions
+ */
+ chunk_t init_hash;
+
+ /**
+ * message ID currently processing, if any
+ */
+ u_int32_t message_id;
};
/**
/* also destroy IKE SA */
this->ike_sa->destroy(this->ike_sa);
this->ike_sa_id->destroy(this->ike_sa_id);
+ chunk_free(&this->init_hash);
free(this);
return SUCCESS;
}
entry_t *this = malloc_thing(entry_t);
this->waiting_threads = 0;
- pthread_cond_init(&(this->condvar), NULL);
+ pthread_cond_init(&this->condvar, NULL);
/* we set checkout flag when we really give it out */
this->checked_out = FALSE;
this->driveout_new_threads = FALSE;
this->driveout_waiting_threads = FALSE;
+ this->message_id = -1;
+ this->init_hash = chunk_empty;
/* ike_sa_id is always cloned */
this->ike_sa_id = ike_sa_id->clone(ike_sa_id);
* A randomizer, to get random SPIs for our side
*/
randomizer_t *randomizer;
+
+ /**
+ * SHA1 hasher for IKE_SA_INIT retransmit detection
+ */
+ hasher_t *hasher;
};
/**
while (entry->checked_out && !entry->driveout_waiting_threads)
{
/* so wait until we can get it for us.
- * we register us as waiting. */
+ * we register us as waiting. */
entry->waiting_threads++;
pthread_cond_wait(&(entry->condvar), &(this->mutex));
entry->waiting_threads--;
{
u_int64_t spi;
- this->randomizer->get_pseudo_random_bytes(this->randomizer, 8, (u_int8_t*)&spi);
-
+ this->randomizer->get_pseudo_random_bytes(this->randomizer, sizeof(spi),
+ (u_int8_t*)&spi);
return spi;
}
*/
static ike_sa_t* checkout(private_ike_sa_manager_t *this, ike_sa_id_t *ike_sa_id)
{
- bool responder_spi_set;
- bool initiator_spi_set;
- bool original_initiator;
ike_sa_t *ike_sa = NULL;
+ entry_t *entry;
- DBG2(DBG_MGR, "checkout IKE_SA: %J", ike_sa_id);
-
- DBG2(DBG_MGR, "%d IKE_SAs in manager",
- this->ike_sa_list->get_count(this->ike_sa_list));
+ DBG2(DBG_MGR, "checkout IKE_SA: %J, %d IKE_SAs in manager",
+ ike_sa_id, this->ike_sa_list->get_count(this->ike_sa_list));
- /* each access is locked */
pthread_mutex_lock(&(this->mutex));
+ if (get_entry_by_id(this, ike_sa_id, &entry) == SUCCESS)
+ {
+ if (wait_for_entry(this, entry))
+ {
+ DBG2(DBG_MGR, "IKE_SA successfully checked out");
+ entry->checked_out = TRUE;
+ ike_sa = entry->ike_sa;
+ }
+ }
+ pthread_mutex_unlock(&this->mutex);
+ charon->bus->set_sa(charon->bus, ike_sa);
+ return ike_sa;
+}
+
+/**
+ * Implementation of of ike_sa_manager.checkout_new.
+ */
+static ike_sa_t *checkout_new(private_ike_sa_manager_t* this, bool initiator)
+{
+ entry_t *entry;
+ ike_sa_id_t *id;
- responder_spi_set = ike_sa_id->get_responder_spi(ike_sa_id);
- initiator_spi_set = ike_sa_id->get_initiator_spi(ike_sa_id);
- original_initiator = ike_sa_id->is_initiator(ike_sa_id);
+ if (initiator)
+ {
+ id = ike_sa_id_create(get_next_spi(this), 0, TRUE);
+ }
+ else
+ {
+ id = ike_sa_id_create(0, get_next_spi(this), FALSE);
+ }
+ entry = entry_create(id);
+ pthread_mutex_lock(&this->mutex);
+ this->ike_sa_list->insert_last(this->ike_sa_list, entry);
+ entry->checked_out = TRUE;
+ pthread_mutex_unlock(&this->mutex);
+ DBG2(DBG_MGR, "created IKE_SA: %J, %d IKE_SAs in manager",
+ id, this->ike_sa_list->get_count(this->ike_sa_list));
+ return entry->ike_sa;
+}
+
+/**
+ * Implementation of of ike_sa_manager.checkout_by_id.
+ */
+static ike_sa_t* checkout_by_message(private_ike_sa_manager_t* this,
+ message_t *message)
+{
+ 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: %J by message, %d IKE_SAs in manager",
+ id, this->ike_sa_list->get_count(this->ike_sa_list));
- if ((initiator_spi_set && responder_spi_set) ||
- ((initiator_spi_set && !responder_spi_set) && (original_initiator)))
+ if (message->get_request(message) &&
+ message->get_exchange_type(message) == IKE_SA_INIT)
{
- /* we SHOULD have an IKE_SA for these SPIs in the list,
- * if not, we can't handle the request...
- */
- entry_t *entry;
- /* look for the entry */
- if (get_entry_by_id(this, ike_sa_id, &entry) == SUCCESS)
+ /* IKE_SA_INIT request. Check for an IKE_SA with such a message hash. */
+ iterator_t *iterator;
+ chunk_t data, hash;
+ bool occupied = FALSE;
+
+ data = message->get_packet_data(message);
+ this->hasher->allocate_hash(this->hasher, data, &hash);
+ chunk_free(&data);
+
+ pthread_mutex_lock(&this->mutex);
+ iterator = this->ike_sa_list->create_iterator(this->ike_sa_list, TRUE);
+ while (iterator->iterate(iterator, (void**)&entry))
{
- if (wait_for_entry(this, entry))
+ if (chunk_equals(hash, entry->init_hash))
{
- DBG2(DBG_MGR, "IKE_SA successfully checked out");
- /* ok, this IKE_SA is finally ours */
- entry->checked_out = TRUE;
- ike_sa = entry->ike_sa;
- /* update responder SPI when it's not set */
- if (entry->ike_sa_id->get_responder_spi(entry->ike_sa_id) == 0)
+ if (entry->message_id == 0)
{
- ike_sa_id_t *ike_sa_ike_sa_id = ike_sa->get_id(ike_sa);
- u_int64_t spi = ike_sa_id->get_responder_spi(ike_sa_id);
-
- ike_sa_ike_sa_id->set_responder_spi(ike_sa_ike_sa_id, spi);
- entry->ike_sa_id->set_responder_spi(entry->ike_sa_id, spi);
+ occupied = TRUE;
}
- }
- else
- {
- DBG2(DBG_MGR, "IKE_SA found, but not allowed to check it out");
+ 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;
}
}
- else
+ iterator->destroy(iterator);
+ pthread_mutex_unlock(&this->mutex);
+ if (occupied)
{
- DBG2(DBG_MGR, "IKE_SA not stored in list");
- /* looks like there is no such IKE_SA, better luck next time... */
+ /* already processing this message ID, discard */
+ chunk_free(&hash);
+ id->destroy(id);
+ return NULL;
}
- }
- else if ((initiator_spi_set && !responder_spi_set) && (!original_initiator))
- {
- /* an IKE_SA_INIT from an another endpoint,
- * he is the initiator.
- * For simplicity, we do NOT check for retransmitted
- * IKE_SA_INIT-Requests here, so EVERY single IKE_SA_INIT-
- * Request (even a retransmitted one) will result in a
- * IKE_SA. This could be improved...
- */
- u_int64_t responder_spi;
- entry_t *new_entry;
-
- /* set SPIs, we are the responder */
- responder_spi = get_next_spi(this);
-
- /* we also set arguments spi, so its still valid */
- ike_sa_id->set_responder_spi(ike_sa_id, responder_spi);
-
- /* create entry */
- new_entry = entry_create(ike_sa_id);
-
- this->ike_sa_list->insert_last(this->ike_sa_list, new_entry);
-
- /* check ike_sa out */
- DBG2(DBG_MGR, "IKE_SA added to list of known IKE_SAs");
- new_entry->checked_out = TRUE;
- ike_sa = new_entry->ike_sa;
- }
- else if (!initiator_spi_set && !responder_spi_set)
- {
- /* checkout of a new and unused IKE_SA, used for rekeying */
- entry_t *new_entry;
-
- if (original_initiator)
+ if (ike_sa == NULL)
{
- ike_sa_id->set_initiator_spi(ike_sa_id, get_next_spi(this));
+ /* no IKE_SA found, create a new one */
+ id->set_responder_spi(id, get_next_spi(this));
+ entry = entry_create(id);
+
+ pthread_mutex_lock(&this->mutex);
+ this->ike_sa_list->insert_last(this->ike_sa_list, entry);
+ entry->checked_out = TRUE;
+ entry->message_id = message->get_message_id(message);
+ pthread_mutex_unlock(&this->mutex);
+ entry->init_hash = hash;
+ ike_sa = entry->ike_sa;
}
else
{
- ike_sa_id->set_responder_spi(ike_sa_id, get_next_spi(this));
+ chunk_free(&hash);
}
- /* create entry */
- new_entry = entry_create(ike_sa_id);
- DBG2(DBG_MGR, "created IKE_SA: %J", ike_sa_id);
-
- this->ike_sa_list->insert_last(this->ike_sa_list, new_entry);
-
- /* check ike_sa out */
- new_entry->checked_out = TRUE;
- ike_sa = new_entry->ike_sa;
+ id->destroy(id);
+ charon->bus->set_sa(charon->bus, ike_sa);
+ return ike_sa;
}
- else
+
+ pthread_mutex_lock(&(this->mutex));
+ if (get_entry_by_id(this, id, &entry) == SUCCESS)
{
- /* responder set, initiator not: here is something seriously wrong! */
- DBG2(DBG_MGR, "invalid IKE_SA SPIs");
+ /* only check out if we are not processing this request */
+ if (message->get_request(message) &&
+ message->get_message_id(message) == entry->message_id)
+ {
+ DBG2(DBG_MGR, "not checking out, message already processing");
+ }
+ else if (wait_for_entry(this, entry))
+ {
+ ike_sa_id_t *ike_id = entry->ike_sa->get_id(entry->ike_sa);
+ DBG2(DBG_MGR, "IKE_SA successfully checked out");
+ entry->checked_out = TRUE;
+ entry->message_id = message->get_message_id(message);
+ if (ike_id->get_responder_spi(ike_id) == 0)
+ {
+ ike_id->set_responder_spi(ike_id, id->get_responder_spi(id));
+ }
+ ike_sa = entry->ike_sa;
+ }
}
-
- pthread_mutex_unlock(&(this->mutex));
-
+ pthread_mutex_unlock(&this->mutex);
+ id->destroy(id);
charon->bus->set_sa(charon->bus, ike_sa);
return ike_sa;
}
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;
DBG2(DBG_MGR, "check-in of IKE_SA successful.");
pthread_cond_signal(&(entry->condvar));
retval = SUCCESS;
pthread_mutex_unlock(&(this->mutex));
this->randomizer->destroy(this->randomizer);
+ this->hasher->destroy(this->hasher);
free(this);
}
/* assign public functions */
this->public.destroy = (void(*)(ike_sa_manager_t*))destroy;
this->public.checkout = (ike_sa_t*(*)(ike_sa_manager_t*, ike_sa_id_t*))checkout;
+ this->public.checkout_new = (ike_sa_t*(*)(ike_sa_manager_t*,bool))checkout_new;
+ this->public.checkout_by_message = (ike_sa_t*(*)(ike_sa_manager_t*,message_t*))checkout_by_message;
this->public.checkout_by_peer = (ike_sa_t*(*)(ike_sa_manager_t*,host_t*,host_t*,identification_t*,identification_t*))checkout_by_peer;
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->ike_sa_list = linked_list_create();
pthread_mutex_init(&this->mutex, NULL);
this->randomizer = randomizer_create();
+ this->hasher = hasher_create(HASH_SHA1);
return &this->public;
}
#include <library.h>
#include <sa/ike_sa.h>
+#include <encoding/message.h>
/**
* @brief The IKE_SA-Manager is responsible for managing all initiated and responded IKE_SA's.
* @ingroup sa
*/
struct ike_sa_manager_t {
+
/**
- * @brief Checkout an IKE_SA, create it when necesarry.
- *
- * Checks out a SA by its ID. An SA will be created, when the responder
- * SPI is not set (when received an IKE_SA_INIT from initiator).
- * Management of SPIs is the managers job, he will set it.
- * This function blocks until SA is available for checkout.
- *
- * @warning checking out two times without checking in will
- * result in a deadlock!
+ * @brief Checkout an existing IKE_SA.
*
* @param this the manager object
* @param ike_sa_id the SA identifier, will be updated
* @returns
* - checked out IKE_SA if found
- * - NULL, if no such IKE_SA available
+ * - NULL, if specified IKE_SA is not found.
*/
ike_sa_t* (*checkout) (ike_sa_manager_t* this, ike_sa_id_t *sa_id);
/**
+ * @brief Create and check out a new IKE_SA.
+ *
+ * @param this the manager object
+ * @param initiator TRUE for initiator, FALSE otherwise
+ * @returns created andchecked out IKE_SA
+ */
+ ike_sa_t* (*checkout_new) (ike_sa_manager_t* this, bool initiator);
+
+ /**
+ * @brief Checkout an IKE_SA by a message.
+ *
+ * In some situations, it is necessary that the manager knows the
+ * message to use for the checkout. This has the folloing reasons:
+ *
+ * 1. If the targeted IKE_SA is already processing a message, we do not
+ * check it out if the message ID is the same.
+ * 2. If it is an IKE_SA_INIT request, we have to check if it is a
+ * retransmission. If so, we have to drop the message, we would
+ * create another unneded IKE_SA for each retransmitted packet.
+ *
+ * A call to checkout_by_message() returns a (maybe new created) IKE_SA.
+ * If processing the message does not make sense (for the reasons above),
+ * NULL is returned.
+ *
+ * @param this the manager object
+ * @param ike_sa_id the SA identifier, will be updated
+ * @returns
+ * - checked out/created IKE_SA
+ * - NULL to not process message further
+ */
+ ike_sa_t* (*checkout_by_message) (ike_sa_manager_t* this, message_t *message);
+
+ /**
* @brief Checkout an existing IKE_SA by hosts and identifications.
*
* Allows the lookup of an IKE_SA by user IDs and hosts. It returns the