/* * Copyright (C) 2007-2011 Tobias Brunner * Copyright (C) 2007-2011 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_v1.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * Number of old messages hashes we keep for retransmission. * * In Main Mode, we must ignore messages from a previous message pair if * we already continued to the next. Otherwise a late retransmission * could be considered as a reply to the newer request. */ #define MAX_OLD_HASHES 2 /** * First sequence number of responding packets. * * To distinguish retransmission jobs for initiating and responding packets, * we split up the sequence counter and use the upper half for responding. */ #define RESPONDING_SEQ INT_MAX 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_v1_t public; /** * associated IKE_SA we are serving */ ike_sa_t *ike_sa; /** * RNG to create message IDs */ rng_t *rng; /** * Exchange we are currently handling as responder */ struct { /** * Message ID of the last response */ u_int32_t mid; /** * Hash of a previously received message */ u_int32_t hash; /** * packet for retransmission */ packet_t *packet; /** * Sequence number of the last sent message */ u_int32_t seqnr; /** * how many times we have retransmitted so far */ u_int retransmitted; } responding; /** * Exchange we are currently handling as initiator */ struct { /** * Message ID of the exchange */ u_int32_t mid; /** * Hashes of old responses we can ignore */ u_int32_t old_hashes[MAX_OLD_HASHES]; /** * Position in old hash array */ int old_hash_pos; /** * Sequence number of the last sent message */ u_int32_t seqnr; /** * 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; /** * Queued messages not yet ready to process */ message_t *queued; /** * Number of times we retransmit messages before giving up */ u_int retransmit_tries; /** * Retransmission timeout */ double retransmit_timeout; /** * Base to calculate retransmission timeout */ double retransmit_base; /** * Sequence number for sending DPD requests */ u_int32_t dpd_send; /** * Sequence number for received DPD requests */ u_int32_t dpd_recv; }; METHOD(task_manager_t, flush_queue, void, private_task_manager_t *this, task_queue_t queue) { linked_list_t *list; task_t *task; if (this->queued) { this->queued->destroy(this->queued); this->queued = NULL; } switch (queue) { case TASK_QUEUE_ACTIVE: list = this->active_tasks; /* cancel pending retransmits */ this->initiating.type = EXCHANGE_TYPE_UNDEFINED; DESTROY_IF(this->initiating.packet); this->initiating.packet = NULL; break; case TASK_QUEUE_PASSIVE: list = this->passive_tasks; break; case TASK_QUEUE_QUEUED: list = this->queued_tasks; break; default: return; } while (list->remove_last(list, (void**)&task) == SUCCESS) { task->destroy(task); } } /** * flush all tasks in the task manager */ static void flush(private_task_manager_t *this) { flush_queue(this, TASK_QUEUE_QUEUED); flush_queue(this, TASK_QUEUE_PASSIVE); flush_queue(this, TASK_QUEUE_ACTIVE); } /** * 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) { enumerator_t *enumerator; task_t *task; bool found = FALSE; enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); while (enumerator->enumerate(enumerator, (void**)&task)) { if (task->get_type(task) == type) { DBG2(DBG_IKE, " activating %N task", task_type_names, type); this->queued_tasks->remove_at(this->queued_tasks, enumerator); this->active_tasks->insert_last(this->active_tasks, task); found = TRUE; break; } } enumerator->destroy(enumerator); return found; } /** * Retransmit a packet, either as initiator or as responder */ static status_t retransmit_packet(private_task_manager_t *this, u_int32_t seqnr, u_int mid, u_int retransmitted, packet_t *packet) { u_int32_t t; if (retransmitted > this->retransmit_tries) { DBG1(DBG_IKE, "giving up after %u retransmits", retransmitted - 1); return DESTROY_ME; } t = (u_int32_t)(this->retransmit_timeout * 1000.0 * pow(this->retransmit_base, retransmitted)); if (retransmitted) { DBG1(DBG_IKE, "sending retransmit %u of %s message ID %u, seq %u", retransmitted, seqnr < RESPONDING_SEQ ? "request" : "response", mid, seqnr < RESPONDING_SEQ ? seqnr : seqnr - RESPONDING_SEQ); } charon->sender->send(charon->sender, packet->clone(packet)); lib->scheduler->schedule_job_ms(lib->scheduler, (job_t*) retransmit_job_create(seqnr, this->ike_sa->get_id(this->ike_sa)), t); return NEED_MORE; } METHOD(task_manager_t, retransmit, status_t, private_task_manager_t *this, u_int32_t seqnr) { status_t status = SUCCESS; if (seqnr == this->initiating.seqnr && this->initiating.packet) { status = retransmit_packet(this, seqnr, this->initiating.mid, this->initiating.retransmitted, this->initiating.packet); if (status == NEED_MORE) { this->initiating.retransmitted++; status = SUCCESS; } } if (seqnr == this->responding.seqnr && this->responding.packet) { status = retransmit_packet(this, seqnr, this->responding.mid, this->responding.retransmitted, this->responding.packet); if (status == NEED_MORE) { this->responding.retransmitted++; status = SUCCESS; } } return status; } /** * Check if we have to wait for a mode config before starting a quick mode */ static bool mode_config_expected(private_task_manager_t *this) { enumerator_t *enumerator; peer_cfg_t *peer_cfg; char *pool; host_t *host; peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); if (peer_cfg) { enumerator = peer_cfg->create_pool_enumerator(peer_cfg); if (!enumerator->enumerate(enumerator, &pool)) { /* no pool configured */ enumerator->destroy(enumerator); return FALSE; } enumerator->destroy(enumerator); enumerator = this->ike_sa->create_virtual_ip_enumerator(this->ike_sa, FALSE); if (!enumerator->enumerate(enumerator, &host)) { /* have a pool, but no VIP assigned yet */ enumerator->destroy(enumerator); return TRUE; } enumerator->destroy(enumerator); } return FALSE; } METHOD(task_manager_t, initiate, status_t, private_task_manager_t *this) { enumerator_t *enumerator; task_t *task; message_t *message; host_t *me, *other; status_t status; exchange_type_t exchange = EXCHANGE_TYPE_UNDEFINED; bool new_mid = FALSE, expect_response = FALSE, cancelled = FALSE, keep = FALSE; if (this->initiating.type != EXCHANGE_TYPE_UNDEFINED && this->initiating.type != INFORMATIONAL_V1) { DBG2(DBG_IKE, "delaying task initiation, %N exchange in progress", exchange_type_names, this->initiating.type); /* 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: activate_task(this, TASK_ISAKMP_VENDOR); activate_task(this, TASK_ISAKMP_CERT_PRE); if (activate_task(this, TASK_MAIN_MODE)) { exchange = ID_PROT; } else if (activate_task(this, TASK_AGGRESSIVE_MODE)) { exchange = AGGRESSIVE; } activate_task(this, TASK_ISAKMP_CERT_POST); activate_task(this, TASK_ISAKMP_NATD); break; case IKE_CONNECTING: if (activate_task(this, TASK_ISAKMP_DELETE)) { exchange = INFORMATIONAL_V1; new_mid = TRUE; break; } if (activate_task(this, TASK_XAUTH)) { exchange = TRANSACTION; new_mid = TRUE; break; } if (activate_task(this, TASK_INFORMATIONAL)) { exchange = INFORMATIONAL_V1; new_mid = TRUE; break; } break; case IKE_ESTABLISHED: if (activate_task(this, TASK_MODE_CONFIG)) { exchange = TRANSACTION; new_mid = TRUE; break; } if (!mode_config_expected(this) && activate_task(this, TASK_QUICK_MODE)) { exchange = QUICK_MODE; new_mid = TRUE; break; } if (activate_task(this, TASK_INFORMATIONAL)) { exchange = INFORMATIONAL_V1; new_mid = TRUE; break; } if (activate_task(this, TASK_QUICK_DELETE)) { exchange = INFORMATIONAL_V1; new_mid = TRUE; break; } if (activate_task(this, TASK_ISAKMP_DELETE)) { exchange = INFORMATIONAL_V1; new_mid = TRUE; break; } if (activate_task(this, TASK_ISAKMP_DPD)) { exchange = INFORMATIONAL_V1; new_mid = TRUE; break; } break; default: break; } } else { DBG2(DBG_IKE, "reinitiating already active tasks"); enumerator = this->active_tasks->create_enumerator(this->active_tasks); while (enumerator->enumerate(enumerator, (void**)&task)) { DBG2(DBG_IKE, " %N task", task_type_names, task->get_type(task)); switch (task->get_type(task)) { case TASK_MAIN_MODE: exchange = ID_PROT; break; case TASK_AGGRESSIVE_MODE: exchange = AGGRESSIVE; break; case TASK_QUICK_MODE: exchange = QUICK_MODE; break; case TASK_XAUTH: exchange = TRANSACTION; new_mid = TRUE; break; default: continue; } break; } enumerator->destroy(enumerator); } if (exchange == EXCHANGE_TYPE_UNDEFINED) { DBG2(DBG_IKE, "nothing to initiate"); /* nothing to do yet... */ return SUCCESS; } me = this->ike_sa->get_my_host(this->ike_sa); other = this->ike_sa->get_other_host(this->ike_sa); if (new_mid) { if (!this->rng->get_bytes(this->rng, sizeof(this->initiating.mid), (void*)&this->initiating.mid)) { DBG1(DBG_IKE, "failed to allocate message ID, destroying IKE_SA"); flush(this); return DESTROY_ME; } } message = message_create(IKEV1_MAJOR_VERSION, IKEV1_MINOR_VERSION); message->set_message_id(message, this->initiating.mid); message->set_source(message, me->clone(me)); message->set_destination(message, other->clone(other)); message->set_exchange_type(message, exchange); this->initiating.type = exchange; this->initiating.retransmitted = 0; enumerator = this->active_tasks->create_enumerator(this->active_tasks); while (enumerator->enumerate(enumerator, (void*)&task)) { switch (task->build(task, message)) { case SUCCESS: /* task completed, remove it */ this->active_tasks->remove_at(this->active_tasks, enumerator); if (task->get_type(task) == TASK_AGGRESSIVE_MODE || task->get_type(task) == TASK_QUICK_MODE) { /* last message of three message exchange */ keep = TRUE; } task->destroy(task); continue; case NEED_MORE: expect_response = TRUE; /* processed, but task needs another exchange */ continue; case ALREADY_DONE: cancelled = TRUE; break; case FAILED: default: if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING) { charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); } /* FALL */ case DESTROY_ME: /* critical failure, destroy IKE_SA */ enumerator->destroy(enumerator); message->destroy(message); flush(this); return DESTROY_ME; } break; } enumerator->destroy(enumerator); if (this->active_tasks->get_count(this->active_tasks) == 0 && (exchange == QUICK_MODE || exchange == AGGRESSIVE)) { /* tasks completed, no exchange active anymore */ this->initiating.type = EXCHANGE_TYPE_UNDEFINED; } if (cancelled) { message->destroy(message); return initiate(this); } DESTROY_IF(this->initiating.packet); status = this->ike_sa->generate_message(this->ike_sa, message, &this->initiating.packet); if (status != SUCCESS) { /* message generation failed. There is nothing more to do than to * close the SA */ message->destroy(message); flush(this); charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); return DESTROY_ME; } this->initiating.seqnr++; if (expect_response) { message->destroy(message); return retransmit(this, this->initiating.seqnr); } if (keep) { /* keep the packet for retransmission, the responder might request it */ charon->sender->send(charon->sender, this->initiating.packet->clone(this->initiating.packet)); } else { charon->sender->send(charon->sender, this->initiating.packet); this->initiating.packet = NULL; } message->destroy(message); if (exchange == INFORMATIONAL_V1) { switch (this->ike_sa->get_state(this->ike_sa)) { case IKE_CONNECTING: /* close after sending an INFORMATIONAL when unestablished */ return FAILED; case IKE_DELETING: /* close after sending a DELETE */ return DESTROY_ME; default: break; } } return initiate(this); } /** * build a response depending on the "passive" task list */ static status_t build_response(private_task_manager_t *this, message_t *request) { enumerator_t *enumerator; task_t *task; message_t *message; host_t *me, *other; bool delete = FALSE, cancelled = FALSE, expect_request = FALSE; status_t status; me = request->get_destination(request); other = request->get_source(request); message = message_create(IKEV1_MAJOR_VERSION, IKEV1_MINOR_VERSION); message->set_exchange_type(message, request->get_exchange_type(request)); /* send response along the path the request came in */ message->set_source(message, me->clone(me)); message->set_destination(message, other->clone(other)); message->set_message_id(message, request->get_message_id(request)); message->set_request(message, FALSE); this->responding.mid = request->get_message_id(request); this->responding.retransmitted = 0; this->responding.seqnr++; enumerator = this->passive_tasks->create_enumerator(this->passive_tasks); while (enumerator->enumerate(enumerator, (void*)&task)) { switch (task->build(task, message)) { case SUCCESS: /* task completed, remove it */ this->passive_tasks->remove_at(this->passive_tasks, enumerator); task->destroy(task); continue; case NEED_MORE: /* processed, but task needs another exchange */ if (task->get_type(task) == TASK_QUICK_MODE || task->get_type(task) == TASK_AGGRESSIVE_MODE) { /* we rely on initiator retransmission, except for * three-message exchanges */ expect_request = TRUE; } continue; case ALREADY_DONE: cancelled = TRUE; break; case FAILED: default: charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); /* FALL */ case DESTROY_ME: /* destroy IKE_SA, but SEND response first */ delete = TRUE; break; } break; } enumerator->destroy(enumerator); DESTROY_IF(this->responding.packet); this->responding.packet = NULL; if (cancelled) { message->destroy(message); return initiate(this); } status = this->ike_sa->generate_message(this->ike_sa, message, &this->responding.packet); message->destroy(message); if (status != SUCCESS) { charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); return DESTROY_ME; } if (expect_request && !delete) { return retransmit(this, this->responding.seqnr); } charon->sender->send(charon->sender, this->responding.packet->clone(this->responding.packet)); if (delete) { return DESTROY_ME; } return SUCCESS; } /** * Send a notify in a separate INFORMATIONAL exchange back to the sender. * The notify protocol_id is set to ISAKMP */ static void send_notify(private_task_manager_t *this, message_t *request, notify_type_t type) { message_t *response; packet_t *packet; host_t *me, *other; u_int32_t mid; if (request->get_exchange_type(request) == INFORMATIONAL_V1) { /* don't respond to INFORMATIONAL requests to avoid a notify war */ DBG1(DBG_IKE, "ignore malformed INFORMATIONAL request"); return; } if (!this->rng->get_bytes(this->rng, sizeof(mid), (void*)&mid)) { DBG1(DBG_IKE, "failed to allocate message ID"); return; } response = message_create(IKEV1_MAJOR_VERSION, IKEV1_MINOR_VERSION); response->set_exchange_type(response, INFORMATIONAL_V1); response->set_request(response, TRUE); response->set_message_id(response, mid); response->add_payload(response, (payload_t*) notify_payload_create_from_protocol_and_type(NOTIFY_V1, PROTO_IKE, type)); me = this->ike_sa->get_my_host(this->ike_sa); if (me->is_anyaddr(me)) { me = request->get_destination(request); this->ike_sa->set_my_host(this->ike_sa, me->clone(me)); } other = this->ike_sa->get_other_host(this->ike_sa); if (other->is_anyaddr(other)) { other = request->get_source(request); this->ike_sa->set_other_host(this->ike_sa, other->clone(other)); } response->set_source(response, me->clone(me)); response->set_destination(response, other->clone(other)); if (this->ike_sa->generate_message(this->ike_sa, response, &packet) == SUCCESS) { charon->sender->send(charon->sender, packet); } response->destroy(response); } /** * Process a DPD request/response */ static bool process_dpd(private_task_manager_t *this, message_t *message) { notify_payload_t *notify; notify_type_t type; u_int32_t seq; chunk_t data; type = DPD_R_U_THERE; notify = message->get_notify(message, type); if (!notify) { type = DPD_R_U_THERE_ACK; notify = message->get_notify(message, type); } if (!notify) { return FALSE; } data = notify->get_notification_data(notify); if (data.len != 4) { return FALSE; } seq = untoh32(data.ptr); if (type == DPD_R_U_THERE) { if (this->dpd_recv == 0 || seq == this->dpd_recv) { /* check sequence validity */ this->dpd_recv = seq + 1; this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND, time_monotonic(NULL)); } /* but respond anyway */ this->ike_sa->queue_task(this->ike_sa, &isakmp_dpd_create(this->ike_sa, DPD_R_U_THERE_ACK, seq)->task); } else /* DPD_R_U_THERE_ACK */ { if (seq == this->dpd_send - 1) { this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND, time_monotonic(NULL)); } else { DBG1(DBG_IKE, "received invalid DPD sequence number %u " "(expected %u), ignored", seq, this->dpd_send - 1); } } return TRUE; } /** * handle an incoming request message */ static status_t process_request(private_task_manager_t *this, message_t *message) { enumerator_t *enumerator; task_t *task = NULL; bool send_response = FALSE, dpd = FALSE; if (message->get_exchange_type(message) == INFORMATIONAL_V1 || this->passive_tasks->get_count(this->passive_tasks) == 0) { /* create tasks depending on request type, if not already some queued */ switch (message->get_exchange_type(message)) { case ID_PROT: task = (task_t *)isakmp_vendor_create(this->ike_sa, FALSE); this->passive_tasks->insert_last(this->passive_tasks, task); task = (task_t*)isakmp_cert_pre_create(this->ike_sa, FALSE); this->passive_tasks->insert_last(this->passive_tasks, task); task = (task_t *)main_mode_create(this->ike_sa, FALSE); this->passive_tasks->insert_last(this->passive_tasks, task); task = (task_t*)isakmp_cert_post_create(this->ike_sa, FALSE); this->passive_tasks->insert_last(this->passive_tasks, task); task = (task_t *)isakmp_natd_create(this->ike_sa, FALSE); this->passive_tasks->insert_last(this->passive_tasks, task); break; case AGGRESSIVE: task = (task_t *)isakmp_vendor_create(this->ike_sa, FALSE); this->passive_tasks->insert_last(this->passive_tasks, task); task = (task_t*)isakmp_cert_pre_create(this->ike_sa, FALSE); this->passive_tasks->insert_last(this->passive_tasks, task); task = (task_t *)aggressive_mode_create(this->ike_sa, FALSE); this->passive_tasks->insert_last(this->passive_tasks, task); task = (task_t*)isakmp_cert_post_create(this->ike_sa, FALSE); this->passive_tasks->insert_last(this->passive_tasks, task); task = (task_t *)isakmp_natd_create(this->ike_sa, FALSE); this->passive_tasks->insert_last(this->passive_tasks, task); break; case QUICK_MODE: if (this->ike_sa->get_state(this->ike_sa) != IKE_ESTABLISHED) { DBG1(DBG_IKE, "received quick mode request for " "unestablished IKE_SA, ignored"); return FAILED; } task = (task_t *)quick_mode_create(this->ike_sa, NULL, NULL, NULL); this->passive_tasks->insert_last(this->passive_tasks, task); break; case INFORMATIONAL_V1: if (process_dpd(this, message)) { dpd = TRUE; } else { task = (task_t *)informational_create(this->ike_sa, NULL); this->passive_tasks->insert_first(this->passive_tasks, task); } break; case TRANSACTION: if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING) { task = (task_t *)mode_config_create(this->ike_sa, FALSE); } else { task = (task_t *)xauth_create(this->ike_sa, FALSE); } this->passive_tasks->insert_last(this->passive_tasks, task); break; default: return FAILED; } } if (dpd) { return initiate(this); } this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND, time_monotonic(NULL)); /* let the tasks process the message */ enumerator = this->passive_tasks->create_enumerator(this->passive_tasks); while (enumerator->enumerate(enumerator, (void*)&task)) { switch (task->process(task, message)) { case SUCCESS: /* task completed, remove it */ this->passive_tasks->remove_at(this->passive_tasks, enumerator); task->destroy(task); continue; case NEED_MORE: /* processed, but task needs at least another call to build() */ send_response = TRUE; continue; case ALREADY_DONE: send_response = FALSE; break; case FAILED: default: charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); /* FALL */ case DESTROY_ME: /* critical failure, destroy IKE_SA */ this->passive_tasks->remove_at(this->passive_tasks, enumerator); enumerator->destroy(enumerator); task->destroy(task); return DESTROY_ME; } break; } enumerator->destroy(enumerator); if (send_response) { if (build_response(this, message) != SUCCESS) { return DESTROY_ME; } } else { /* We don't send a response, so don't retransmit one if we get * the same message again. */ DESTROY_IF(this->responding.packet); this->responding.packet = NULL; } if (this->passive_tasks->get_count(this->passive_tasks) == 0 && this->queued_tasks->get_count(this->queued_tasks) > 0) { /* passive tasks completed, check if an active task has been queued, * such as XAUTH or modeconfig push */ return initiate(this); } return SUCCESS; } /** * handle an incoming response message */ static status_t process_response(private_task_manager_t *this, message_t *message) { enumerator_t *enumerator; message_t *queued; status_t status; 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); charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); return DESTROY_ME; } enumerator = this->active_tasks->create_enumerator(this->active_tasks); while (enumerator->enumerate(enumerator, (void*)&task)) { switch (task->process(task, message)) { case SUCCESS: /* task completed, remove it */ this->active_tasks->remove_at(this->active_tasks, enumerator); task->destroy(task); continue; case NEED_MORE: /* processed, but task needs another exchange */ continue; case ALREADY_DONE: break; case FAILED: default: charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); /* FALL */ case DESTROY_ME: /* critical failure, destroy IKE_SA */ this->active_tasks->remove_at(this->active_tasks, enumerator); enumerator->destroy(enumerator); task->destroy(task); return DESTROY_ME; } break; } enumerator->destroy(enumerator); this->initiating.type = EXCHANGE_TYPE_UNDEFINED; DESTROY_IF(this->initiating.packet); this->initiating.packet = NULL; if (this->queued && this->active_tasks->get_count(this->active_tasks) == 0) { queued = this->queued; this->queued = NULL; status = this->public.task_manager.process_message( &this->public.task_manager, queued); queued->destroy(queued); if (status == DESTROY_ME) { return status; } } return initiate(this); } /** * Parse the given message and verify that it is valid. */ static status_t parse_message(private_task_manager_t *this, message_t *msg) { status_t status; status = msg->parse_body(msg, this->ike_sa->get_keymat(this->ike_sa)); if (status != SUCCESS) { switch (status) { case NOT_SUPPORTED: DBG1(DBG_IKE, "unsupported exchange type"); send_notify(this, msg, INVALID_EXCHANGE_TYPE); break; case PARSE_ERROR: DBG1(DBG_IKE, "message parsing failed"); send_notify(this, msg, PAYLOAD_MALFORMED); break; case VERIFY_ERROR: DBG1(DBG_IKE, "message verification failed"); send_notify(this, msg, PAYLOAD_MALFORMED); break; case FAILED: DBG1(DBG_IKE, "integrity check failed"); send_notify(this, msg, INVALID_HASH_INFORMATION); break; case INVALID_STATE: DBG1(DBG_IKE, "found encrypted message, but no keys available"); send_notify(this, msg, PAYLOAD_MALFORMED); default: break; } DBG1(DBG_IKE, "%N %s with message ID %u processing failed", exchange_type_names, msg->get_exchange_type(msg), msg->get_request(msg) ? "request" : "response", msg->get_message_id(msg)); if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED) { /* invalid initiation attempt, close SA */ return DESTROY_ME; } } return status; } METHOD(task_manager_t, process_message, status_t, private_task_manager_t *this, message_t *msg) { u_int32_t hash, mid, i; host_t *me, *other; status_t status; /* TODO-IKEv1: update hosts more selectively */ me = msg->get_destination(msg); other = msg->get_source(msg); mid = msg->get_message_id(msg); hash = chunk_hash(msg->get_packet_data(msg)); for (i = 0; i < MAX_OLD_HASHES; i++) { if (this->initiating.old_hashes[i] == hash) { if (this->initiating.packet && i == (this->initiating.old_hash_pos % MAX_OLD_HASHES) && (msg->get_exchange_type(msg) == QUICK_MODE || msg->get_exchange_type(msg) == AGGRESSIVE)) { DBG1(DBG_IKE, "received retransmit of response with ID %u, " "resending last request", mid); charon->sender->send(charon->sender, this->initiating.packet->clone(this->initiating.packet)); return SUCCESS; } DBG1(DBG_IKE, "received retransmit of response with ID %u, " "but next request already sent", mid); return SUCCESS; } } if ((mid && mid == this->initiating.mid) || (this->initiating.mid == 0 && msg->get_exchange_type(msg) == this->initiating.type && this->active_tasks->get_count(this->active_tasks))) { msg->set_request(msg, FALSE); charon->bus->message(charon->bus, msg, TRUE, FALSE); status = parse_message(this, msg); if (status != SUCCESS) { return status; } this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND, time_monotonic(NULL)); this->ike_sa->update_hosts(this->ike_sa, me, other, TRUE); charon->bus->message(charon->bus, msg, TRUE, TRUE); if (process_response(this, msg) != SUCCESS) { flush(this); return DESTROY_ME; } this->initiating.old_hashes[(++this->initiating.old_hash_pos) % MAX_OLD_HASHES] = hash; } else { if (hash == this->responding.hash) { if (this->responding.packet) { DBG1(DBG_IKE, "received retransmit of request with ID %u, " "retransmitting response", mid); charon->sender->send(charon->sender, this->responding.packet->clone(this->responding.packet)); } else if (this->initiating.packet && this->initiating.type == INFORMATIONAL_V1) { DBG1(DBG_IKE, "received retransmit of DPD request, " "retransmitting response"); charon->sender->send(charon->sender, this->initiating.packet->clone(this->initiating.packet)); } else { DBG1(DBG_IKE, "received retransmit of request with ID %u, " "but no response to retransmit", mid); } return SUCCESS; } if (msg->get_exchange_type(msg) == TRANSACTION && this->active_tasks->get_count(this->active_tasks)) { /* main mode not yet complete, queue XAuth/Mode config tasks */ if (this->queued) { DBG1(DBG_IKE, "ignoring additional %N request, queue full", exchange_type_names, TRANSACTION); return SUCCESS; } this->queued = message_create_from_packet(msg->get_packet(msg)); if (this->queued->parse_header(this->queued) != SUCCESS) { this->queued->destroy(this->queued); this->queued = NULL; return FAILED; } DBG1(DBG_IKE, "queueing %N request as tasks still active", exchange_type_names, TRANSACTION); return SUCCESS; } msg->set_request(msg, TRUE); charon->bus->message(charon->bus, msg, TRUE, FALSE); status = parse_message(this, msg); if (status != SUCCESS) { return status; } /* if this IKE_SA is virgin, we check for a config */ if (this->ike_sa->get_ike_cfg(this->ike_sa) == NULL) { ike_sa_id_t *ike_sa_id; ike_cfg_t *ike_cfg; job_t *job; ike_cfg = charon->backends->get_ike_cfg(charon->backends, me, other); if (ike_cfg == NULL) { /* no config found for these hosts, destroy */ DBG1(DBG_IKE, "no IKE config found for %H...%H, sending %N", me, other, notify_type_names, NO_PROPOSAL_CHOSEN); send_notify(this, msg, NO_PROPOSAL_CHOSEN); return DESTROY_ME; } this->ike_sa->set_ike_cfg(this->ike_sa, ike_cfg); ike_cfg->destroy(ike_cfg); /* add a timeout if peer does not establish it completely */ ike_sa_id = this->ike_sa->get_id(this->ike_sa); job = (job_t*)delete_ike_sa_job_create(ike_sa_id, FALSE); lib->scheduler->schedule_job(lib->scheduler, job, lib->settings->get_int(lib->settings, "%s.half_open_timeout", HALF_OPEN_IKE_SA_TIMEOUT, charon->name)); } this->ike_sa->update_hosts(this->ike_sa, me, other, TRUE); charon->bus->message(charon->bus, msg, TRUE, TRUE); if (process_request(this, msg) != SUCCESS) { flush(this); return DESTROY_ME; } this->responding.hash = hash; } return SUCCESS; } METHOD(task_manager_t, queue_task, void, 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); } /** * Check if a given task has been queued already */ static bool has_queued(private_task_manager_t *this, task_type_t type) { enumerator_t *enumerator; bool found = FALSE; task_t *task; enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); while (enumerator->enumerate(enumerator, &task)) { if (task->get_type(task) == type) { found = TRUE; break; } } enumerator->destroy(enumerator); return found; } METHOD(task_manager_t, queue_ike, void, private_task_manager_t *this) { peer_cfg_t *peer_cfg; if (!has_queued(this, TASK_ISAKMP_VENDOR)) { queue_task(this, (task_t*)isakmp_vendor_create(this->ike_sa, TRUE)); } if (!has_queued(this, TASK_ISAKMP_CERT_PRE)) { queue_task(this, (task_t*)isakmp_cert_pre_create(this->ike_sa, TRUE)); } peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); if (peer_cfg->use_aggressive(peer_cfg)) { if (!has_queued(this, TASK_AGGRESSIVE_MODE)) { queue_task(this, (task_t*)aggressive_mode_create(this->ike_sa, TRUE)); } } else { if (!has_queued(this, TASK_MAIN_MODE)) { queue_task(this, (task_t*)main_mode_create(this->ike_sa, TRUE)); } } if (!has_queued(this, TASK_ISAKMP_CERT_POST)) { queue_task(this, (task_t*)isakmp_cert_post_create(this->ike_sa, TRUE)); } if (!has_queued(this, TASK_ISAKMP_NATD)) { queue_task(this, (task_t*)isakmp_natd_create(this->ike_sa, TRUE)); } } METHOD(task_manager_t, queue_ike_reauth, void, private_task_manager_t *this) { enumerator_t *enumerator; child_sa_t *child_sa; ike_sa_t *new; host_t *host; new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, this->ike_sa->get_version(this->ike_sa), TRUE); if (!new) { /* shouldn't happen */ return; } new->set_peer_cfg(new, this->ike_sa->get_peer_cfg(this->ike_sa)); host = this->ike_sa->get_other_host(this->ike_sa); new->set_other_host(new, host->clone(host)); host = this->ike_sa->get_my_host(this->ike_sa); new->set_my_host(new, host->clone(host)); enumerator = this->ike_sa->create_virtual_ip_enumerator(this->ike_sa, TRUE); while (enumerator->enumerate(enumerator, &host)) { new->add_virtual_ip(new, TRUE, host); } enumerator->destroy(enumerator); enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa); while (enumerator->enumerate(enumerator, &child_sa)) { this->ike_sa->remove_child_sa(this->ike_sa, enumerator); new->add_child_sa(new, child_sa); } enumerator->destroy(enumerator); if (!new->get_child_count(new)) { /* check if a Quick Mode task is queued (UNITY_LOAD_BALANCE case) */ task_t *task; enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); while (enumerator->enumerate(enumerator, &task)) { if (task->get_type(task) == TASK_QUICK_MODE) { this->queued_tasks->remove_at(this->queued_tasks, enumerator); task->migrate(task, new); new->queue_task(new, task); } } enumerator->destroy(enumerator); } if (new->initiate(new, NULL, 0, NULL, NULL) != DESTROY_ME) { charon->ike_sa_manager->checkin(charon->ike_sa_manager, new); this->ike_sa->set_state(this->ike_sa, IKE_REKEYING); } else { charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, new); DBG1(DBG_IKE, "reauthenticating IKE_SA failed"); } charon->bus->set_sa(charon->bus, this->ike_sa); } METHOD(task_manager_t, queue_ike_rekey, void, private_task_manager_t *this) { queue_ike_reauth(this); } METHOD(task_manager_t, queue_ike_delete, void, private_task_manager_t *this) { enumerator_t *enumerator; child_sa_t *child_sa; enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa); while (enumerator->enumerate(enumerator, &child_sa)) { queue_task(this, (task_t*) quick_delete_create(this->ike_sa, child_sa->get_protocol(child_sa), child_sa->get_spi(child_sa, TRUE), FALSE, FALSE)); } enumerator->destroy(enumerator); queue_task(this, (task_t*)isakmp_delete_create(this->ike_sa, TRUE)); } METHOD(task_manager_t, queue_mobike, void, private_task_manager_t *this, bool roam, bool address) { /* Not supported in IKEv1 */ } METHOD(task_manager_t, queue_child, void, private_task_manager_t *this, child_cfg_t *cfg, u_int32_t reqid, traffic_selector_t *tsi, traffic_selector_t *tsr) { quick_mode_t *task; task = quick_mode_create(this->ike_sa, cfg, tsi, tsr); task->use_reqid(task, reqid); queue_task(this, &task->task); } /** * Check if two CHILD_SAs have the same traffic selector */ static bool have_equal_ts(child_sa_t *a, child_sa_t *b, bool local) { linked_list_t *list; traffic_selector_t *ts_a, *ts_b; list = a->get_traffic_selectors(a, local); if (list->get_first(list, (void**)&ts_a) == SUCCESS) { list = b->get_traffic_selectors(b, local); if (list->get_first(list, (void**)&ts_b) == SUCCESS) { return ts_a->equals(ts_a, ts_b); } } return FALSE; } /** * Check if a CHILD_SA is redundant and we should delete instead of rekey */ static bool is_redundant(private_task_manager_t *this, child_sa_t *child_sa) { enumerator_t *enumerator; child_sa_t *current; bool redundant = FALSE; enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa); while (enumerator->enumerate(enumerator, ¤t)) { if (current->get_state(current) == CHILD_INSTALLED && streq(current->get_name(current), child_sa->get_name(child_sa)) && have_equal_ts(current, child_sa, TRUE) && have_equal_ts(current, child_sa, FALSE) && current->get_lifetime(current, FALSE) > child_sa->get_lifetime(child_sa, FALSE)) { DBG1(DBG_IKE, "deleting redundant CHILD_SA %s{%d}", child_sa->get_name(child_sa), child_sa->get_reqid(child_sa)); redundant = TRUE; break; } } enumerator->destroy(enumerator); return redundant; } /** * Get the first traffic selector of a CHILD_SA, local or remote */ static traffic_selector_t* get_first_ts(child_sa_t *child_sa, bool local) { traffic_selector_t *ts = NULL; linked_list_t *list; list = child_sa->get_traffic_selectors(child_sa, local); if (list->get_first(list, (void**)&ts) == SUCCESS) { return ts; } return NULL; } METHOD(task_manager_t, queue_child_rekey, void, private_task_manager_t *this, protocol_id_t protocol, u_int32_t spi) { child_sa_t *child_sa; child_cfg_t *cfg; quick_mode_t *task; child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol, spi, TRUE); if (!child_sa) { child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol, spi, FALSE); } if (child_sa && child_sa->get_state(child_sa) == CHILD_INSTALLED) { if (is_redundant(this, child_sa)) { queue_task(this, (task_t*)quick_delete_create(this->ike_sa, protocol, spi, FALSE, FALSE)); } else { child_sa->set_state(child_sa, CHILD_REKEYING); cfg = child_sa->get_config(child_sa); task = quick_mode_create(this->ike_sa, cfg->get_ref(cfg), get_first_ts(child_sa, TRUE), get_first_ts(child_sa, FALSE)); task->use_reqid(task, child_sa->get_reqid(child_sa)); task->rekey(task, child_sa->get_spi(child_sa, TRUE)); queue_task(this, &task->task); } } } METHOD(task_manager_t, queue_child_delete, void, private_task_manager_t *this, protocol_id_t protocol, u_int32_t spi, bool expired) { queue_task(this, (task_t*)quick_delete_create(this->ike_sa, protocol, spi, FALSE, expired)); } METHOD(task_manager_t, queue_dpd, void, private_task_manager_t *this) { peer_cfg_t *peer_cfg; u_int32_t t, retransmit; queue_task(this, (task_t*)isakmp_dpd_create(this->ike_sa, DPD_R_U_THERE, this->dpd_send++)); peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); /* compute timeout in milliseconds */ t = 1000 * peer_cfg->get_dpd_timeout(peer_cfg); if (t == 0) { /* use the same timeout as a retransmitting IKE message would have */ for (retransmit = 0; retransmit <= this->retransmit_tries; retransmit++) { t += (u_int32_t)(this->retransmit_timeout * 1000.0 * pow(this->retransmit_base, retransmit)); } } /* schedule DPD timeout job */ lib->scheduler->schedule_job_ms(lib->scheduler, (job_t*)dpd_timeout_job_create(this->ike_sa->get_id(this->ike_sa)), t); } METHOD(task_manager_t, adopt_tasks, void, private_task_manager_t *this, task_manager_t *other_public) { private_task_manager_t *other = (private_task_manager_t*)other_public; 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); } } METHOD(task_manager_t, busy, bool, private_task_manager_t *this) { return (this->active_tasks->get_count(this->active_tasks) > 0); } METHOD(task_manager_t, incr_mid, void, private_task_manager_t *this, bool initiate) { } METHOD(task_manager_t, reset, void, private_task_manager_t *this, u_int32_t initiate, u_int32_t respond) { enumerator_t *enumerator; task_t *task; /* reset message counters and retransmit packets */ DESTROY_IF(this->responding.packet); DESTROY_IF(this->initiating.packet); this->responding.packet = NULL; this->responding.seqnr = RESPONDING_SEQ; this->responding.retransmitted = 0; this->initiating.packet = NULL; this->initiating.mid = 0; this->initiating.seqnr = 0; this->initiating.retransmitted = 0; this->initiating.type = EXCHANGE_TYPE_UNDEFINED; if (initiate != UINT_MAX) { this->dpd_send = initiate; } if (respond != UINT_MAX) { this->dpd_recv = respond; } /* reset queued tasks */ enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); while (enumerator->enumerate(enumerator, &task)) { task->migrate(task, this->ike_sa); } enumerator->destroy(enumerator); /* 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); } } METHOD(task_manager_t, create_task_enumerator, enumerator_t*, private_task_manager_t *this, task_queue_t queue) { switch (queue) { case TASK_QUEUE_ACTIVE: return this->active_tasks->create_enumerator(this->active_tasks); case TASK_QUEUE_PASSIVE: return this->passive_tasks->create_enumerator(this->passive_tasks); case TASK_QUEUE_QUEUED: return this->queued_tasks->create_enumerator(this->queued_tasks); default: return enumerator_create_empty(); } } METHOD(task_manager_t, destroy, void, 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->queued); DESTROY_IF(this->responding.packet); DESTROY_IF(this->initiating.packet); DESTROY_IF(this->rng); free(this); } /* * see header file */ task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa) { private_task_manager_t *this; INIT(this, .public = { .task_manager = { .process_message = _process_message, .queue_task = _queue_task, .queue_ike = _queue_ike, .queue_ike_rekey = _queue_ike_rekey, .queue_ike_reauth = _queue_ike_reauth, .queue_ike_delete = _queue_ike_delete, .queue_mobike = _queue_mobike, .queue_child = _queue_child, .queue_child_rekey = _queue_child_rekey, .queue_child_delete = _queue_child_delete, .queue_dpd = _queue_dpd, .initiate = _initiate, .retransmit = _retransmit, .incr_mid = _incr_mid, .reset = _reset, .adopt_tasks = _adopt_tasks, .busy = _busy, .create_task_enumerator = _create_task_enumerator, .flush_queue = _flush_queue, .destroy = _destroy, }, }, .initiating = { .type = EXCHANGE_TYPE_UNDEFINED, }, .responding = { .seqnr = RESPONDING_SEQ, }, .ike_sa = ike_sa, .rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK), .queued_tasks = linked_list_create(), .active_tasks = linked_list_create(), .passive_tasks = linked_list_create(), .retransmit_tries = lib->settings->get_int(lib->settings, "%s.retransmit_tries", RETRANSMIT_TRIES, charon->name), .retransmit_timeout = lib->settings->get_double(lib->settings, "%s.retransmit_timeout", RETRANSMIT_TIMEOUT, charon->name), .retransmit_base = lib->settings->get_double(lib->settings, "%s.retransmit_base", RETRANSMIT_BASE, charon->name), ); if (!this->rng) { DBG1(DBG_IKE, "no RNG found, unable to create IKE_SA"); destroy(this); return NULL; } if (!this->rng->get_bytes(this->rng, sizeof(this->dpd_send), (void*)&this->dpd_send)) { DBG1(DBG_IKE, "failed to allocate message ID, unable to create IKE_SA"); destroy(this); return NULL; } this->dpd_send &= 0x7FFFFFFF; return &this->public; }