diff options
Diffstat (limited to 'src/charon/sa/task_manager.c')
-rw-r--r-- | src/charon/sa/task_manager.c | 854 |
1 files changed, 854 insertions, 0 deletions
diff --git a/src/charon/sa/task_manager.c b/src/charon/sa/task_manager.c new file mode 100644 index 000000000..844300735 --- /dev/null +++ b/src/charon/sa/task_manager.c @@ -0,0 +1,854 @@ +/** + * @file task_manager.c + * + * @brief Implementation of task_manager_t. + * + */ + +/* + * Copyright (C) 2007 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 <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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 "task_manager.h" + +#include <daemon.h> +#include <sa/tasks/ike_init.h> +#include <sa/tasks/ike_natd.h> +#include <sa/tasks/ike_auth.h> +#include <sa/tasks/ike_cert.h> +#include <sa/tasks/ike_rekey.h> +#include <sa/tasks/ike_delete.h> +#include <sa/tasks/ike_config.h> +#include <sa/tasks/ike_dpd.h> +#include <sa/tasks/child_create.h> +#include <sa/tasks/child_rekey.h> +#include <sa/tasks/child_delete.h> +#include <encoding/payloads/delete_payload.h> +#include <queues/jobs/retransmit_job.h> + +typedef struct exchange_t exchange_t; + +/** + * An exchange in the air, used do detect and handle retransmission + */ +struct exchange_t { + + /** + * Message ID used for this transaction + */ + u_int32_t mid; + + /** + * generated packet for retransmission + */ + packet_t *packet; +}; + +typedef struct private_task_manager_t private_task_manager_t; + +/** + * private data of the task manager + */ +struct private_task_manager_t { + + /** + * public functions + */ + task_manager_t public; + + /** + * associated IKE_SA we are serving + */ + ike_sa_t *ike_sa; + + /** + * Exchange we are currently handling as responder + */ + struct { + /** + * Message ID of the exchange + */ + u_int32_t mid; + + /** + * packet for retransmission + */ + packet_t *packet; + + } responding; + + /** + * Exchange we are currently handling as initiator + */ + struct { + /** + * Message ID of the exchange + */ + u_int32_t mid; + + /** + * how many times we have retransmitted so far + */ + u_int retransmitted; + + /** + * packet for retransmission + */ + packet_t *packet; + + /** + * type of the initated exchange + */ + exchange_type_t type; + + } initiating; + + /** + * List of queued tasks not yet in action + */ + linked_list_t *queued_tasks; + + /** + * List of active tasks, initiated by ourselve + */ + linked_list_t *active_tasks; + + /** + * List of tasks initiated by peer + */ + linked_list_t *passive_tasks; +}; + +/** + * flush all tasks in the task manager + */ +static void flush(private_task_manager_t *this) +{ + task_t *task; + + this->queued_tasks->destroy_offset(this->queued_tasks, + offsetof(task_t, destroy)); + this->passive_tasks->destroy_offset(this->passive_tasks, + offsetof(task_t, destroy)); + + /* emmit outstanding signals for tasks */ + while (this->active_tasks->remove_last(this->active_tasks, + (void**)&task) == SUCCESS) + { + switch (task->get_type(task)) + { + case IKE_AUTH: + SIG(IKE_UP_FAILED, "establishing IKE_SA failed"); + break; + case IKE_DELETE: + SIG(IKE_DOWN_FAILED, "IKE_SA deleted"); + break; + case IKE_REKEY: + SIG(IKE_REKEY_FAILED, "rekeying IKE_SA failed"); + break; + case CHILD_CREATE: + SIG(CHILD_UP_FAILED, "establishing CHILD_SA failed"); + break; + case CHILD_DELETE: + SIG(CHILD_DOWN_FAILED, "deleting CHILD_SA failed"); + break; + case CHILD_REKEY: + SIG(IKE_REKEY_FAILED, "rekeying CHILD_SA failed"); + break; + default: + break; + } + task->destroy(task); + } + this->queued_tasks = linked_list_create(); + this->passive_tasks = linked_list_create(); +} + +/** + * move a task of a specific type from the queue to the active list + */ +static bool activate_task(private_task_manager_t *this, task_type_t type) +{ + iterator_t *iterator; + task_t *task; + bool found = FALSE; + + iterator = this->queued_tasks->create_iterator(this->queued_tasks, TRUE); + while (iterator->iterate(iterator, (void**)&task)) + { + if (task->get_type(task) == type) + { + DBG2(DBG_IKE, " activating %N task", task_type_names, type); + iterator->remove(iterator); + this->active_tasks->insert_last(this->active_tasks, task); + found = TRUE; + break; + } + } + iterator->destroy(iterator); + return found; +} + +/** + * Implementation of task_manager_t.retransmit + */ +static status_t retransmit(private_task_manager_t *this, u_int32_t message_id) +{ + if (message_id == this->initiating.mid) + { + u_int32_t timeout; + job_t *job; + + timeout = charon->configuration->get_retransmit_timeout( + charon->configuration, this->initiating.retransmitted); + if (timeout == 0) + { + DBG1(DBG_IKE, "giving up after %d retransmits", + this->initiating.retransmitted - 1); + return DESTROY_ME; + } + + if (this->initiating.retransmitted) + { + DBG1(DBG_IKE, "retransmit %d of request with message ID %d", + this->initiating.retransmitted, message_id); + } + this->initiating.retransmitted++; + + charon->sender->send(charon->sender, + this->initiating.packet->clone(this->initiating.packet)); + job = (job_t*)retransmit_job_create(this->initiating.mid, + this->ike_sa->get_id(this->ike_sa)); + charon->event_queue->add_relative(charon->event_queue, job, timeout); + } + return SUCCESS; +} + +/** + * build a request using the active task list + * Implementation of task_manager_t.initiate + */ +static status_t build_request(private_task_manager_t *this) +{ + iterator_t *iterator; + task_t *task; + message_t *message; + status_t status; + exchange_type_t exchange = 0; + + if (this->initiating.type != EXCHANGE_TYPE_UNDEFINED) + { + DBG2(DBG_IKE, "delaying task initiation, exchange in progress"); + /* do not initiate if we already have a message in the air */ + return SUCCESS; + } + + if (this->active_tasks->get_count(this->active_tasks) == 0) + { + DBG2(DBG_IKE, "activating new tasks"); + switch (this->ike_sa->get_state(this->ike_sa)) + { + case IKE_CREATED: + if (activate_task(this, IKE_INIT)) + { + exchange = IKE_SA_INIT; + activate_task(this, IKE_NATD); + activate_task(this, IKE_CERT); + activate_task(this, IKE_AUTHENTICATE); + activate_task(this, IKE_CONFIG); + activate_task(this, CHILD_CREATE); + } + break; + case IKE_ESTABLISHED: + if (activate_task(this, CHILD_CREATE)) + { + exchange = CREATE_CHILD_SA; + activate_task(this, IKE_CONFIG); + break; + } + if (activate_task(this, CHILD_DELETE)) + { + exchange = INFORMATIONAL; + break; + } + if (activate_task(this, CHILD_REKEY)) + { + exchange = CREATE_CHILD_SA; + break; + } + if (activate_task(this, IKE_DELETE)) + { + exchange = INFORMATIONAL; + break; + } + if (activate_task(this, IKE_REKEY)) + { + exchange = CREATE_CHILD_SA; + break; + } + if (activate_task(this, IKE_DEADPEER)) + { + exchange = INFORMATIONAL; + break; + } + case IKE_REKEYING: + if (activate_task(this, IKE_DELETE)) + { + exchange = INFORMATIONAL; + break; + } + case IKE_DELETING: + default: + break; + } + } + else + { + DBG2(DBG_IKE, "reinitiating already active tasks"); + iterator = this->active_tasks->create_iterator(this->active_tasks, TRUE); + while (iterator->iterate(iterator, (void**)&task)) + { + DBG2(DBG_IKE, " %N task", task_type_names, task->get_type(task)); + switch (task->get_type(task)) + { + case IKE_INIT: + exchange = IKE_SA_INIT; + break; + case IKE_AUTHENTICATE: + exchange = IKE_AUTH; + break; + default: + continue; + } + break; + } + iterator->destroy(iterator); + } + + if (exchange == 0) + { + DBG2(DBG_IKE, "nothing to initiate"); + /* nothing to do yet... */ + return SUCCESS; + } + + message = message_create(); + message->set_message_id(message, this->initiating.mid); + message->set_exchange_type(message, exchange); + this->initiating.type = exchange; + this->initiating.retransmitted = 0; + + iterator = this->active_tasks->create_iterator(this->active_tasks, TRUE); + while (iterator->iterate(iterator, (void*)&task)) + { + switch (task->build(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + iterator->remove(iterator); + task->destroy(task); + break; + case NEED_MORE: + /* processed, but task needs another exchange */ + break; + case FAILED: + default: + /* critical failure, destroy IKE_SA */ + iterator->destroy(iterator); + message->destroy(message); + flush(this); + return DESTROY_ME; + } + } + iterator->destroy(iterator); + + DESTROY_IF(this->initiating.packet); + status = this->ike_sa->generate_message(this->ike_sa, message, + &this->initiating.packet); + message->destroy(message); + if (status != SUCCESS) + { + /* message generation failed. There is nothing more to do than to + * close the SA */ + flush(this); + return DESTROY_ME; + } + + return retransmit(this, this->initiating.mid); +} + +/** + * handle an incoming response message + */ +static status_t process_response(private_task_manager_t *this, + message_t *message) +{ + iterator_t *iterator; + task_t *task; + + if (message->get_exchange_type(message) != this->initiating.type) + { + DBG1(DBG_IKE, "received %N response, but expected %N", + exchange_type_names, message->get_exchange_type(message), + exchange_type_names, this->initiating.type); + return DESTROY_ME; + } + + iterator = this->active_tasks->create_iterator(this->active_tasks, TRUE); + while (iterator->iterate(iterator, (void*)&task)) + { + switch (task->process(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + iterator->remove(iterator); + task->destroy(task); + break; + case NEED_MORE: + /* processed, but task needs another exchange */ + break; + case FAILED: + default: + /* critical failure, destroy IKE_SA */ + iterator->destroy(iterator); + return DESTROY_ME; + } + } + iterator->destroy(iterator); + + this->initiating.mid++; + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + + return build_request(this); +} + +/** + * handle exchange collisions + */ +static void handle_collisions(private_task_manager_t *this, task_t *task) +{ + iterator_t *iterator; + task_t *active; + task_type_t type; + + type = task->get_type(task); + + /* do we have to check */ + if (type == IKE_REKEY || type == CHILD_REKEY || + type == CHILD_DELETE || type == IKE_DELETE) + { + /* find an exchange collision, and notify these tasks */ + iterator = this->active_tasks->create_iterator(this->active_tasks, TRUE); + while (iterator->iterate(iterator, (void**)&active)) + { + switch (active->get_type(active)) + { + case IKE_REKEY: + if (type == IKE_REKEY || type == IKE_DELETE) + { + ike_rekey_t *rekey = (ike_rekey_t*)active; + rekey->collide(rekey, task); + break; + } + continue; + case CHILD_REKEY: + if (type == CHILD_REKEY || type == CHILD_DELETE) + { + child_rekey_t *rekey = (child_rekey_t*)active; + rekey->collide(rekey, task); + break; + } + continue; + default: + continue; + } + iterator->destroy(iterator); + return; + } + iterator->destroy(iterator); + } + /* destroy task if not registered in any active task */ + task->destroy(task); +} + +/** + * build a response depending on the "passive" task list + */ +static status_t build_response(private_task_manager_t *this, + exchange_type_t exchange) +{ + iterator_t *iterator; + task_t *task; + message_t *message; + bool delete = FALSE; + status_t status; + + message = message_create(); + message->set_exchange_type(message, exchange); + message->set_message_id(message, this->responding.mid); + message->set_request(message, FALSE); + + iterator = this->passive_tasks->create_iterator(this->passive_tasks, TRUE); + while (iterator->iterate(iterator, (void*)&task)) + { + switch (task->build(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + iterator->remove(iterator); + handle_collisions(this, task); + case NEED_MORE: + /* processed, but task needs another exchange */ + break; + case FAILED: + default: + /* destroy IKE_SA, but SEND response first */ + delete = TRUE; + break; + } + if (delete) + { + break; + } + } + iterator->destroy(iterator); + + /* remove resonder SPI if IKE_SA_INIT failed */ + if (delete && exchange == IKE_SA_INIT) + { + ike_sa_id_t *id = this->ike_sa->get_id(this->ike_sa); + id->set_responder_spi(id, 0); + } + + /* message complete, send it */ + DESTROY_IF(this->responding.packet); + status = this->ike_sa->generate_message(this->ike_sa, message, + &this->responding.packet); + message->destroy(message); + if (status != SUCCESS) + { + return DESTROY_ME; + } + + charon->sender->send(charon->sender, + this->responding.packet->clone(this->responding.packet)); + if (delete) + { + return DESTROY_ME; + } + return SUCCESS; +} + +/** + * handle an incoming request message + */ +static status_t process_request(private_task_manager_t *this, + message_t *message) +{ + iterator_t *iterator; + task_t *task = NULL; + exchange_type_t exchange; + payload_t *payload; + notify_payload_t *notify; + + exchange = message->get_exchange_type(message); + + /* create tasks depending on request type */ + switch (exchange) + { + case IKE_SA_INIT: + { + task = (task_t*)ike_init_create(this->ike_sa, FALSE, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_natd_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_cert_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_auth_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_config_create(this->ike_sa, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)child_create_create(this->ike_sa, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + } + case CREATE_CHILD_SA: + { + bool notify_found = FALSE, ts_found = FALSE; + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&payload)) + { + switch (payload->get_type(payload)) + { + case NOTIFY: + { + /* if we find a rekey notify, its CHILD_SA rekeying */ + notify = (notify_payload_t*)payload; + if (notify->get_notify_type(notify) == REKEY_SA && + (notify->get_protocol_id(notify) == PROTO_AH || + notify->get_protocol_id(notify) == PROTO_ESP)) + { + notify_found = TRUE; + } + break; + } + case TRAFFIC_SELECTOR_INITIATOR: + case TRAFFIC_SELECTOR_RESPONDER: + { + /* if we don't find a TS, its IKE rekeying */ + ts_found = TRUE; + break; + } + default: + break; + } + } + iterator->destroy(iterator); + + if (ts_found) + { + if (notify_found) + { + task = (task_t*)child_rekey_create(this->ike_sa, NULL); + } + else + { + task = (task_t*)child_create_create(this->ike_sa, NULL); + } + } + else + { + task = (task_t*)ike_rekey_create(this->ike_sa, FALSE); + } + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + } + case INFORMATIONAL: + { + delete_payload_t *delete; + + delete = (delete_payload_t*)message->get_payload(message, DELETE); + if (delete) + { + if (delete->get_protocol_id(delete) == PROTO_IKE) + { + task = (task_t*)ike_delete_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + } + else + { + task = (task_t*)child_delete_create(this->ike_sa, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + } + } + else + { + task = (task_t*)ike_dpd_create(FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + } + break; + } + default: + break; + } + + /* let the tasks process the message */ + iterator = this->passive_tasks->create_iterator(this->passive_tasks, TRUE); + while (iterator->iterate(iterator, (void*)&task)) + { + switch (task->process(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + iterator->remove(iterator); + task->destroy(task); + break; + case NEED_MORE: + /* processed, but task needs at least another call to build() */ + break; + case FAILED: + default: + /* critical failure, destroy IKE_SA */ + iterator->destroy(iterator); + return DESTROY_ME; + } + } + iterator->destroy(iterator); + + return build_response(this, exchange); +} + +/** + * Implementation of task_manager_t.process_message + */ +static status_t process_message(private_task_manager_t *this, message_t *msg) +{ + u_int32_t mid = msg->get_message_id(msg); + + if (msg->get_request(msg)) + { + if (mid == this->responding.mid) + { + if (process_request(this, msg) != SUCCESS) + { + flush(this); + return DESTROY_ME; + } + this->responding.mid++; + } + else if ((mid == this->responding.mid - 1) && this->responding.packet) + { + DBG1(DBG_IKE, "received retransmit of request with ID %d, " + "retransmitting response", mid); + charon->sender->send(charon->sender, + this->responding.packet->clone(this->responding.packet)); + } + else + { + DBG1(DBG_IKE, "received message ID %d, excepted %d. Ignored", + mid, this->responding.mid); + } + } + else + { + if (mid == this->initiating.mid) + { + if (process_response(this, msg) != SUCCESS) + { + flush(this); + return DESTROY_ME; + } + } + else + { + DBG1(DBG_IKE, "received message ID %d, excepted %d. Ignored", + mid, this->initiating.mid); + return SUCCESS; + } + } + return SUCCESS; +} + +/** + * Implementation of task_manager_t.queue_task + */ +static void queue_task(private_task_manager_t *this, task_t *task) +{ + DBG2(DBG_IKE, "queueing %N task", task_type_names, task->get_type(task)); + this->queued_tasks->insert_last(this->queued_tasks, task); +} + +/** + * Implementation of task_manager_t.adopt_tasks + */ +static void adopt_tasks(private_task_manager_t *this, private_task_manager_t *other) +{ + task_t *task; + + /* move queued tasks from other to this */ + while (other->queued_tasks->remove_last(other->queued_tasks, + (void**)&task) == SUCCESS) + { + DBG2(DBG_IKE, "migrating %N task", task_type_names, task->get_type(task)); + task->migrate(task, this->ike_sa); + this->queued_tasks->insert_first(this->queued_tasks, task); + } + + /* reset active tasks and move them to others queued tasks */ + while (other->active_tasks->remove_last(other->active_tasks, + (void**)&task) == SUCCESS) + { + DBG2(DBG_IKE, "migrating %N task", task_type_names, task->get_type(task)); + task->migrate(task, this->ike_sa); + this->queued_tasks->insert_first(this->queued_tasks, task); + } +} + +/** + * Implementation of task_manager_t.busy + */ +static bool busy(private_task_manager_t *this) +{ + return (this->active_tasks->get_count(this->active_tasks) > 0); +} + +/** + * Implementation of task_manager_t.reset + */ +static void reset(private_task_manager_t *this) +{ + task_t *task; + + /* reset message counters and retransmit packets */ + DESTROY_IF(this->responding.packet); + DESTROY_IF(this->initiating.packet); + this->responding.packet = NULL; + this->initiating.packet = NULL; + this->responding.mid = 0; + this->initiating.mid = -1; + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + + /* reset active tasks */ + while (this->active_tasks->remove_last(this->active_tasks, + (void**)&task) == SUCCESS) + { + task->migrate(task, this->ike_sa); + this->queued_tasks->insert_first(this->queued_tasks, task); + } +} + +/** + * Implementation of task_manager_t.destroy + */ +static void destroy(private_task_manager_t *this) +{ + flush(this); + + this->active_tasks->destroy(this->active_tasks); + this->queued_tasks->destroy(this->queued_tasks); + this->passive_tasks->destroy(this->passive_tasks); + + DESTROY_IF(this->responding.packet); + DESTROY_IF(this->initiating.packet); + free(this); +} + +/* + * see header file + */ +task_manager_t *task_manager_create(ike_sa_t *ike_sa) +{ + private_task_manager_t *this = malloc_thing(private_task_manager_t); + + this->public.process_message = (status_t(*)(task_manager_t*,message_t*))process_message; + this->public.queue_task = (void(*)(task_manager_t*,task_t*))queue_task; + this->public.initiate = (status_t(*)(task_manager_t*))build_request; + this->public.retransmit = (status_t(*)(task_manager_t*,u_int32_t))retransmit; + this->public.reset = (void(*)(task_manager_t*))reset; + this->public.adopt_tasks = (void(*)(task_manager_t*,task_manager_t*))adopt_tasks; + this->public.busy = (bool(*)(task_manager_t*))busy; + this->public.destroy = (void(*)(task_manager_t*))destroy; + + this->ike_sa = ike_sa; + this->responding.packet = NULL; + this->initiating.packet = NULL; + this->responding.mid = 0; + this->initiating.mid = 0; + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + this->queued_tasks = linked_list_create(); + this->active_tasks = linked_list_create(); + this->passive_tasks = linked_list_create(); + + return &this->public; +} |