/** * @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 . * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include 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, FALSE); 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; }