/** * @file ike_sa_manager.c * * @brief Central point for managing IKE-SAs (creation, locking, deleting...) * */ /* * Copyright (C) 2005 Jan Hutter, Martin Willi * Hochschule fuer Technik Rapperswil * * 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 . * * 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 #include #include "ike_sa_manager.h" #include "globals.h" #include "ike_sa_id.h" #include "utils/allocator.h" #include "utils/logger.h" #include "utils/logger_manager.h" #include "utils/linked_list.h" /** * @brief An entry in the linked list, contains IKE_SA, locking and lookup data. */ typedef struct ike_sa_entry_s ike_sa_entry_t; struct ike_sa_entry_s { /** * destructor, also destroys ike_sa */ status_t (*destroy) (ike_sa_entry_t *this); /** * Number of threads waiting for this ike_sa */ int waiting_threads; /** * condvar where threads can wait until it's free again */ pthread_cond_t condvar; /** * is this ike_sa currently checked out? */ bool checked_out; /** * Does this SA drives out new threads? */ bool driveout_new_threads; /** * Does this SA drives out waiting threads? */ bool driveout_waiting_threads; /** * identifiaction of ike_sa (SPIs) */ ike_sa_id_t *ike_sa_id; /** * the contained ike_sa */ ike_sa_t *ike_sa; }; /** * @see ike_sa_entry_t.destroy */ static status_t ike_sa_entry_destroy(ike_sa_entry_t *this) { this->ike_sa->destroy(this->ike_sa); this->ike_sa_id->destroy(this->ike_sa_id); allocator_free(this); return SUCCESS; } /** * @brief creates a new entry for the ike_sa list * * This constructor additionaly creates a new and empty SA * * @param ike_sa_id the associated ike_sa_id_t, will be cloned * @return created entry, with ike_sa and ike_sa_id */ static ike_sa_entry_t *ike_sa_entry_create(ike_sa_id_t *ike_sa_id) { ike_sa_entry_t *this = allocator_alloc_thing(ike_sa_entry_t); /* destroy function */ this->destroy = ike_sa_entry_destroy; this->waiting_threads = 0; 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; /* ike_sa_id is always cloned */ ike_sa_id->clone(ike_sa_id, &(this->ike_sa_id)); this->ike_sa = ike_sa_create(ike_sa_id); return this; } /** * Additional private members to ike_sa_manager_t */ typedef struct private_ike_sa_manager_s private_ike_sa_manager_t; struct private_ike_sa_manager_s { /** * Public members */ ike_sa_manager_t public; /** * @brief get next spi * * we give out SPIs incremental * * @param this the ike_sa_manager * @param spi[out] spi will be written here * @return SUCCESS or, * OUT_OF_RES when we already served 2^64 SPIs ;-) */ status_t (*get_next_spi) (private_ike_sa_manager_t *this, spi_t *spi); /** * @brief find the ike_sa_entry in the list by SPIs * * This function simply iterates over the linked list. A hash-table * would be more efficient when storing a lot of IKE_SAs... * * @param this the ike_sa_manager containing the list * @param ike_sa_id id of the ike_sa, containing SPIs * @param entry[out] pointer to set to the found entry * @return * - SUCCESS when found, * - NOT_FOUND when no such ike_sa_id in list * - OUT_OF_RES */ status_t (*get_entry_by_id) (private_ike_sa_manager_t *this, ike_sa_id_t *ike_sa_id, ike_sa_entry_t **entry); /** * @brief find the ike_sa_entry in the list by pointer to SA. * * This function simply iterates over the linked list. A hash-table * would be more efficient when storing a lot of IKE_SAs... * * @param this the ike_sa_manager containing the list * @param ike_sa pointer to the ike_sa * @param entry[out] pointer to set to the found entry * @return * - SUCCESS when found, * - NOT_FOUND when no such ike_sa_id in list * - OUT_OF_RES */ status_t (*get_entry_by_sa) (private_ike_sa_manager_t *this, ike_sa_t *ike_sa, ike_sa_entry_t **entry); /** * @brief delete an entry from the linked list * * @param this the ike_sa_manager containing the list * @param entry entry to delete * @return * - SUCCESS when found, * - NOT_FOUND when no such ike_sa_id in list */ status_t (*delete_entry) (private_ike_sa_manager_t *this, ike_sa_entry_t *entry); /** * lock for exclusivly accessing the manager */ pthread_mutex_t mutex; /** * Logger used for this IKE SA Manager */ logger_t *logger; /** * Linked list with entries for the ike_sa */ linked_list_t *list; /** * Next SPI, needed for incremental creation of SPIs */ spi_t next_spi; }; /** * @see private_ike_sa_manager_t.get_entry_by_id */ static status_t get_entry_by_id(private_ike_sa_manager_t *this, ike_sa_id_t *ike_sa_id, ike_sa_entry_t **entry) { linked_list_t *list = this->list; linked_list_iterator_t *iterator; status_t status; status = list->create_iterator(list, &iterator, TRUE); if (status != SUCCESS) { return status; } /* default status */ status = NOT_FOUND; while (iterator->has_next(iterator)) { ike_sa_entry_t *current; bool are_equal = FALSE; iterator->current(iterator, (void**)¤t); current->ike_sa_id->equals(current->ike_sa_id, ike_sa_id, &are_equal); if (are_equal) { *entry = current; status = SUCCESS; break; } } iterator->destroy(iterator); return status; } /** * @see private_ike_sa_manager_t.get_entry_by_sa */ static status_t get_entry_by_sa(private_ike_sa_manager_t *this, ike_sa_t *ike_sa, ike_sa_entry_t **entry) { linked_list_t *list = this->list; linked_list_iterator_t *iterator; status_t status; status = list->create_iterator(list, &iterator, TRUE); if (status != SUCCESS) { return status; } /* default status */ status = NOT_FOUND; while (iterator->has_next(iterator)) { ike_sa_entry_t *current; iterator->current(iterator, (void**)¤t); /* only pointers are compared */ if (current->ike_sa == ike_sa) { *entry = current; status = SUCCESS; break; } } iterator->destroy(iterator); return status; } /** * @see private_ike_sa_manager_s.delete_entry */ static status_t delete_entry(private_ike_sa_manager_t *this, ike_sa_entry_t *entry) { linked_list_t *list = this->list; linked_list_iterator_t *iterator; list->create_iterator(list, &iterator, TRUE); while (iterator->has_next(iterator)) { ike_sa_entry_t *current; iterator->current(iterator, (void**)¤t); if (current == entry) { list->remove(list, iterator); entry->destroy(entry); iterator->destroy(iterator); return SUCCESS; } } iterator->destroy(iterator); return NOT_FOUND; } /** * @see private_ike_sa_manager_t.get_next_spi */ static status_t get_next_spi(private_ike_sa_manager_t *this, spi_t *spi) { this->next_spi.low ++; if (this->next_spi.low == 0) { /* overflow of lower int in spi */ this->next_spi.high ++; if (this->next_spi.high == 0) { /* our software ran so incredible stable, we have no more * SPIs to give away :-/. */ return OUT_OF_RES; } } *spi = this->next_spi; return SUCCESS; } /** * @see ike_sa_manager_s.checkout_ike_sa */ static status_t checkout(private_ike_sa_manager_t *this, ike_sa_id_t *ike_sa_id, ike_sa_t **ike_sa) { bool responder_spi_set; bool initiator_spi_set; status_t retval; pthread_mutex_lock(&(this->mutex)); responder_spi_set = ike_sa_id->responder_spi_is_set(ike_sa_id); initiator_spi_set = ike_sa_id->initiator_spi_is_set(ike_sa_id); if (initiator_spi_set && responder_spi_set) { /* we SHOULD have an IKE_SA for these SPIs in the list, * if not, we can't handle the request... */ ike_sa_entry_t *entry; /* look for the entry */ if (this->get_entry_by_id(this, ike_sa_id, &entry) == SUCCESS) { /* can we give this out to new requesters?*/ if (entry->driveout_new_threads) { this->logger->log(this->logger,CONTROL_MORE,"Drive out new thread for existing IKE_SA"); /* no we can't */ retval = NOT_FOUND; } else { /* is this IKE_SA already checked out ?? * are we welcome to get this SA ? */ while (entry->checked_out && !entry->driveout_waiting_threads) { /* so wait until we can get it for us. * we register us as waiting. */ entry->waiting_threads++; pthread_cond_wait(&(entry->condvar), &(this->mutex)); entry->waiting_threads--; } /* hm, a deletion request forbids us to get this SA, go home */ if (entry->driveout_waiting_threads) { /* we must signal here, others are interested that we leave */ pthread_cond_signal(&(entry->condvar)); this->logger->log(this->logger,CONTROL_MORE,"Drive out waiting thread for existing IKE_SA"); retval = NOT_FOUND; } else { /* ok, this IKE_SA is finally ours */ entry->checked_out = TRUE; *ike_sa = entry->ike_sa; /* DON'T use return, we must unlock the mutex! */ retval = SUCCESS; } } } else { /* looks like there is no such IKE_SA, better luck next time... */ /* DON'T use return, we must unlock the mutex! */ retval = NOT_FOUND; } } else if (initiator_spi_set && !responder_spi_set) { /* 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... */ spi_t responder_spi; ike_sa_entry_t *new_ike_sa_entry; /* set SPIs, we are the responder */ retval = this->get_next_spi(this, &responder_spi); if (retval == SUCCESS) { /* next SPI could be created */ /* we also set arguments spi, so its still valid */ ike_sa_id->set_responder_spi(ike_sa_id, responder_spi); /* create entry */ new_ike_sa_entry = ike_sa_entry_create(ike_sa_id); if (new_ike_sa_entry != NULL) { retval = this->list->insert_last(this->list, new_ike_sa_entry); if (retval == SUCCESS) { /* check ike_sa out */ new_ike_sa_entry->checked_out = TRUE; *ike_sa = new_ike_sa_entry->ike_sa; /* DON'T use return, we must unlock the mutex! */ } else { /* ike_sa_entry could not be added to list*/ this->logger->log(this->logger,CONTROL,"Fatal error: ike_sa_entry could not be added to list"); } } else { /* new ike_sa_entry could not be created */ this->logger->log(this->logger,CONTROL,"Fatal error: ike_sa_entry could not be created"); } } else { /* next SPI could not be created */ this->logger->log(this->logger,CONTROL,"Fatal error: Next SPI could not be created"); } } else if (!initiator_spi_set && !responder_spi_set) { /* creation of an IKE_SA from local site, * we are the initiator! */ spi_t initiator_spi; ike_sa_entry_t *new_ike_sa_entry; retval = this->get_next_spi(this, &initiator_spi); if (retval == SUCCESS) { /* we also set arguments SPI, so its still valid */ ike_sa_id->set_initiator_spi(ike_sa_id, initiator_spi); /* create entry */ new_ike_sa_entry = ike_sa_entry_create(ike_sa_id); if (new_ike_sa_entry != NULL) { retval = this->list->insert_last(this->list, new_ike_sa_entry); if (retval == SUCCESS) { /* check ike_sa out */ new_ike_sa_entry->checked_out = TRUE; *ike_sa = new_ike_sa_entry->ike_sa; /* DON'T use return, we must unlock the mutex! */ } else { /* ike_sa_entry could not be added to list*/ this->logger->log(this->logger,CONTROL,"Fatal error: ike_sa_entry could not be added to list"); } } else { /* new ike_sa_entry could not be created */ this->logger->log(this->logger,CONTROL,"Fatal error: ike_sa_entry could not be created"); } } else { /* next SPI could not be created */ this->logger->log(this->logger,CONTROL,"Fatal error: Next SPI could not be created"); } } else { /* responder set, initiator not: here is something seriously wrong! */ this->logger->log(this->logger,CONTROL_MORE,"Invalid IKE_SA SPI's"); /* DON'T use return, we must unlock the mutex! */ retval = INVALID_ARG; } pthread_mutex_unlock(&(this->mutex)); /* OK, unlocked... */ return retval; } /** * Implements ike_sa_manager_t-function checkin. * @see ike_sa_manager_t.checkin. */ static status_t 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... */ status_t retval; ike_sa_entry_t *entry; pthread_mutex_lock(&(this->mutex)); /* look for the entry */ if (this->get_entry_by_sa(this, ike_sa, &entry) == 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; pthread_cond_signal(&(entry->condvar)); retval = SUCCESS; } else { this->logger->log(this->logger,CONTROL,"Fatal Error: Tried to checkin nonexisting IKE_SA"); /* this SA is no more, this REALLY should not happen */ retval = NOT_FOUND; } pthread_mutex_unlock(&(this->mutex)); return retval; } /** * Implements ike_sa_manager_t-function checkin_and_delete. * @see ike_sa_manager_t.checkin_and_delete. */ static status_t checkin_and_delete(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 * this SA. * We take this SA from the list, and start signaling while threads * are in the condvar. */ ike_sa_entry_t *entry; status_t retval; pthread_mutex_lock(&(this->mutex)); if (this->get_entry_by_sa(this, ike_sa, &entry) == SUCCESS) { /* mark it, so now new threads can acquire this SA */ entry->driveout_new_threads = TRUE; /* additionaly, drive out waiting threads */ entry->driveout_waiting_threads = TRUE; /* wait until all workers have done their work */ while (entry->waiting_threads > 0) { /* let the other threads do some work*/ pthread_cond_signal(&(entry->condvar)); /* and the nice thing, they will wake us again when their work is done */ pthread_cond_wait(&(entry->condvar), &(this->mutex)); } /* ok, we are alone now, no threads waiting in the entry's condvar */ this->delete_entry(this, entry); retval = SUCCESS; } else { this->logger->log(this->logger,CONTROL,"Fatal Error: Tried to checkin and delete nonexisting IKE_SA"); retval = NOT_FOUND; } pthread_mutex_unlock(&(this->mutex)); return retval; } /** * Implements ike_sa_manager_t-function delete. * @see ike_sa_manager_t.delete. */ static status_t delete(private_ike_sa_manager_t *this, ike_sa_id_t *ike_sa_id) { /* deletion is a bit complex, we must garant that no thread is waiting for * this SA. * We take this SA from the list, and start signaling while threads * are in the condvar. */ ike_sa_entry_t *entry; status_t retval; pthread_mutex_lock(&(this->mutex)); if (this->get_entry_by_id(this, ike_sa_id, &entry) == SUCCESS) { /* mark it, so now new threads can acquire this SA */ entry->driveout_new_threads = TRUE; /* wait until all workers have done their work */ while (entry->waiting_threads) { /* wake up all */ pthread_cond_signal(&(entry->condvar)); /* and the nice thing, they will wake us again when their work is done */ pthread_cond_wait(&(entry->condvar), &(this->mutex)); } /* ok, we are alone now, no threads waiting in the entry's condvar */ this->delete_entry(this, entry); retval = SUCCESS; } else { this->logger->log(this->logger,CONTROL,"Fatal Error: Tried to delete nonexisting IKE_SA"); retval = NOT_FOUND; } pthread_mutex_unlock(&(this->mutex)); return retval; } /** * Implements ike_sa_manager_t-function destroy. * @see ike_sa_manager_t.destroy. */ static status_t destroy(private_ike_sa_manager_t *this) { /* destroy all list entries */ linked_list_t *list = this->list; linked_list_iterator_t *iterator; status_t status; pthread_mutex_lock(&(this->mutex)); /* Step 1: drive out all waiting threads */ status = list->create_iterator(list, &iterator, TRUE); if (status != SUCCESS) { this->logger->log(this->logger,CONTROL,"Fatal Error: Out of Ressources to greate interator while destroying IKE_SA-Manager"); return FAILED; } while (iterator->has_next(iterator)) { ike_sa_entry_t *entry; iterator->current(iterator, (void**)&entry); /* do not accept new threads, drive out waiting threads */ entry->driveout_new_threads = TRUE; entry->driveout_waiting_threads = TRUE; } /* Step 2: wait until all are gone */ iterator->reset(iterator); while (iterator->has_next(iterator)) { ike_sa_entry_t *entry; iterator->current(iterator, (void**)&entry); while (entry->waiting_threads) { /* wake up all */ pthread_cond_signal(&(entry->condvar)); /* go sleeping until they are gone */ pthread_cond_wait(&(entry->condvar), &(this->mutex)); } } /* Step 3: delete all entries */ iterator->reset(iterator); while (iterator->has_next(iterator)) { ike_sa_entry_t *entry; iterator->current(iterator, (void**)&entry); this->delete_entry(this, entry); } iterator->destroy(iterator); list->destroy(list); pthread_mutex_unlock(&(this->mutex)); allocator_free(this); return SUCCESS; } /* * Described in header */ ike_sa_manager_t *ike_sa_manager_create() { private_ike_sa_manager_t *this = allocator_alloc_thing(private_ike_sa_manager_t); /* assign public functions */ this->public.destroy = (status_t(*)(ike_sa_manager_t*))destroy; this->public.checkout = (status_t(*)(ike_sa_manager_t*, ike_sa_id_t *sa_id, ike_sa_t **sa))checkout; this->public.checkin = (status_t(*)(ike_sa_manager_t*, ike_sa_t *sa))checkin; this->public.delete = (status_t(*)(ike_sa_manager_t*, ike_sa_id_t *sa_id))delete; this->public.checkin_and_delete = (status_t(*)(ike_sa_manager_t*, ike_sa_t *ike_sa))checkin_and_delete; /* initialize private functions */ this->get_next_spi = get_next_spi; this->get_entry_by_sa = get_entry_by_sa; this->get_entry_by_id = get_entry_by_id; this->delete_entry = delete_entry; /* initialize private variables */ this->list = linked_list_create(); if (this->list == NULL) { allocator_free(this); return NULL; } this->logger = global_logger_manager->create_logger(global_logger_manager,IKE_SA_MANAGER,NULL); if (this->logger == NULL) { this->list->destroy(this->list); allocator_free(this); return NULL; } pthread_mutex_init(&(this->mutex), NULL); this->next_spi.low = 1; this->next_spi.high = 0; return (ike_sa_manager_t*)this; }