2164 lines
56 KiB
C
2164 lines
56 KiB
C
/*
|
|
* Copyright (C) 2007-2019 Tobias Brunner
|
|
* Copyright (C) 2007-2011 Martin Willi
|
|
* HSR 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_v1.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include <daemon.h>
|
|
#include <sa/ikev1/tasks/main_mode.h>
|
|
#include <sa/ikev1/tasks/aggressive_mode.h>
|
|
#include <sa/ikev1/tasks/quick_mode.h>
|
|
#include <sa/ikev1/tasks/quick_delete.h>
|
|
#include <sa/ikev1/tasks/xauth.h>
|
|
#include <sa/ikev1/tasks/mode_config.h>
|
|
#include <sa/ikev1/tasks/informational.h>
|
|
#include <sa/ikev1/tasks/isakmp_natd.h>
|
|
#include <sa/ikev1/tasks/isakmp_vendor.h>
|
|
#include <sa/ikev1/tasks/isakmp_cert_pre.h>
|
|
#include <sa/ikev1/tasks/isakmp_cert_post.h>
|
|
#include <sa/ikev1/tasks/isakmp_delete.h>
|
|
#include <sa/ikev1/tasks/isakmp_dpd.h>
|
|
|
|
#include <processing/jobs/retransmit_job.h>
|
|
#include <processing/jobs/delete_ike_sa_job.h>
|
|
#include <processing/jobs/dpd_timeout_job.h>
|
|
#include <processing/jobs/process_message_job.h>
|
|
|
|
#include <collections/array.h>
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
uint32_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
|
|
*/
|
|
uint32_t mid;
|
|
|
|
/**
|
|
* Hash of a previously received message
|
|
*/
|
|
uint32_t hash;
|
|
|
|
/**
|
|
* packet(s) for retransmission
|
|
*/
|
|
array_t *packets;
|
|
|
|
/**
|
|
* Sequence number of the last sent message
|
|
*/
|
|
uint32_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
|
|
*/
|
|
uint32_t mid;
|
|
|
|
/**
|
|
* Hashes of old responses we can ignore
|
|
*/
|
|
uint32_t old_hashes[MAX_OLD_HASHES];
|
|
|
|
/**
|
|
* Position in old hash array
|
|
*/
|
|
int old_hash_pos;
|
|
|
|
/**
|
|
* Sequence number of the last sent message
|
|
*/
|
|
uint32_t seqnr;
|
|
|
|
/**
|
|
* how many times we have retransmitted so far
|
|
*/
|
|
u_int retransmitted;
|
|
|
|
/**
|
|
* packet(s) for retransmission
|
|
*/
|
|
array_t *packets;
|
|
|
|
/**
|
|
* type of the initiated exchange
|
|
*/
|
|
exchange_type_t type;
|
|
|
|
} initiating;
|
|
|
|
/**
|
|
* Message we are currently defragmenting, if any (only one at a time)
|
|
*/
|
|
message_t *defrag;
|
|
|
|
/**
|
|
* List of queued tasks not yet in action
|
|
*/
|
|
linked_list_t *queued_tasks;
|
|
|
|
/**
|
|
* List of active tasks, initiated by ourselves
|
|
*/
|
|
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;
|
|
|
|
/**
|
|
* Maximum number of tries possible with current retransmission settings
|
|
* before overflowing the range of uint32_t, which we use for the timeout.
|
|
* Note that UINT32_MAX milliseconds equal nearly 50 days, so that doesn't
|
|
* make much sense without retransmit_limit anyway.
|
|
*/
|
|
u_int retransmit_tries_max;
|
|
|
|
/**
|
|
* Retransmission timeout
|
|
*/
|
|
double retransmit_timeout;
|
|
|
|
/**
|
|
* Base to calculate retransmission timeout
|
|
*/
|
|
double retransmit_base;
|
|
|
|
/**
|
|
* Jitter to apply to calculated retransmit timeout (in percent)
|
|
*/
|
|
u_int retransmit_jitter;
|
|
|
|
/**
|
|
* Limit retransmit timeout to this value
|
|
*/
|
|
uint32_t retransmit_limit;
|
|
|
|
/**
|
|
* Sequence number for sending DPD requests
|
|
*/
|
|
uint32_t dpd_send;
|
|
|
|
/**
|
|
* Sequence number for received DPD requests
|
|
*/
|
|
uint32_t dpd_recv;
|
|
};
|
|
|
|
/**
|
|
* Reset retransmission packet list
|
|
*/
|
|
static void clear_packets(array_t *array)
|
|
{
|
|
packet_t *packet;
|
|
|
|
while (array_remove(array, ARRAY_TAIL, &packet))
|
|
{
|
|
packet->destroy(packet);
|
|
}
|
|
}
|
|
|
|
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;
|
|
clear_packets(this->initiating.packets);
|
|
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);
|
|
}
|
|
}
|
|
|
|
METHOD(task_manager_t, flush, void,
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Send packets in the given array (they get cloned)
|
|
*/
|
|
static void send_packets(private_task_manager_t *this, array_t *packets)
|
|
{
|
|
enumerator_t *enumerator;
|
|
packet_t *packet;
|
|
|
|
enumerator = array_create_enumerator(packets);
|
|
while (enumerator->enumerate(enumerator, &packet))
|
|
{
|
|
charon->sender->send(charon->sender, packet->clone(packet));
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
}
|
|
|
|
/**
|
|
* Generates the given message and stores packet(s) in the given array
|
|
*/
|
|
static bool generate_message(private_task_manager_t *this, message_t *message,
|
|
array_t **packets)
|
|
{
|
|
enumerator_t *fragments;
|
|
packet_t *fragment;
|
|
|
|
if (this->ike_sa->generate_message_fragmented(this->ike_sa, message,
|
|
&fragments) != SUCCESS)
|
|
{
|
|
return FALSE;
|
|
}
|
|
while (fragments->enumerate(fragments, &fragment))
|
|
{
|
|
array_insert_create(packets, ARRAY_TAIL, fragment);
|
|
}
|
|
fragments->destroy(fragments);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Retransmit a packet (or its fragments)
|
|
*/
|
|
static status_t retransmit_packet(private_task_manager_t *this, uint32_t seqnr,
|
|
u_int mid, u_int retransmitted, array_t *packets)
|
|
{
|
|
packet_t *packet;
|
|
uint32_t t = UINT32_MAX, max_jitter;
|
|
|
|
array_get(packets, 0, &packet);
|
|
if (retransmitted > this->retransmit_tries)
|
|
{
|
|
DBG1(DBG_IKE, "giving up after %u retransmits", retransmitted - 1);
|
|
charon->bus->alert(charon->bus, ALERT_RETRANSMIT_SEND_TIMEOUT, packet);
|
|
return DESTROY_ME;
|
|
}
|
|
if (!this->retransmit_tries_max ||
|
|
retransmitted <= this->retransmit_tries_max)
|
|
{
|
|
t = (uint32_t)(this->retransmit_timeout * 1000.0 *
|
|
pow(this->retransmit_base, retransmitted));
|
|
}
|
|
if (this->retransmit_limit)
|
|
{
|
|
t = min(t, this->retransmit_limit);
|
|
}
|
|
if (this->retransmit_jitter)
|
|
{
|
|
max_jitter = (t / 100.0) * this->retransmit_jitter;
|
|
t -= max_jitter * (random() / (RAND_MAX + 1.0));
|
|
}
|
|
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->bus->alert(charon->bus, ALERT_RETRANSMIT_SEND, packet,
|
|
retransmitted);
|
|
}
|
|
send_packets(this, packets);
|
|
lib->scheduler->schedule_job_ms(lib->scheduler, (job_t*)
|
|
retransmit_job_create(seqnr, this->ike_sa->get_id(this->ike_sa)), t);
|
|
return SUCCESS;
|
|
}
|
|
|
|
METHOD(task_manager_t, retransmit, status_t,
|
|
private_task_manager_t *this, uint32_t seqnr)
|
|
{
|
|
status_t status = INVALID_STATE;
|
|
|
|
if (seqnr == this->initiating.seqnr &&
|
|
array_count(this->initiating.packets))
|
|
{
|
|
status = retransmit_packet(this, seqnr, this->initiating.mid,
|
|
this->initiating.retransmitted, this->initiating.packets);
|
|
if (status == SUCCESS)
|
|
{
|
|
this->initiating.retransmitted++;
|
|
}
|
|
}
|
|
if (seqnr == this->responding.seqnr &&
|
|
array_count(this->responding.packets))
|
|
{
|
|
status = retransmit_packet(this, seqnr, this->responding.mid,
|
|
this->responding.retransmitted, this->responding.packets);
|
|
if (status == SUCCESS)
|
|
{
|
|
this->responding.retransmitted++;
|
|
}
|
|
}
|
|
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;
|
|
bool local;
|
|
host_t *host;
|
|
|
|
peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa);
|
|
if (peer_cfg)
|
|
{
|
|
if (peer_cfg->use_pull_mode(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);
|
|
|
|
local = FALSE;
|
|
}
|
|
else
|
|
{
|
|
enumerator = peer_cfg->create_virtual_ip_enumerator(peer_cfg);
|
|
if (!enumerator->enumerate(enumerator, &host))
|
|
{ /* not requesting a vip */
|
|
enumerator->destroy(enumerator);
|
|
return FALSE;
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
|
|
local = TRUE;
|
|
}
|
|
enumerator = this->ike_sa->create_virtual_ip_enumerator(this->ike_sa,
|
|
local);
|
|
if (!enumerator->enumerate(enumerator, &host))
|
|
{ /* expecting a VIP exchange, 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;
|
|
exchange_type_t exchange = EXCHANGE_TYPE_UNDEFINED;
|
|
bool new_mid = FALSE, expect_response = FALSE, canceled = 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 (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;
|
|
}
|
|
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;
|
|
}
|
|
break;
|
|
case IKE_REKEYING:
|
|
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:
|
|
canceled = 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 (canceled)
|
|
{
|
|
message->destroy(message);
|
|
return initiate(this);
|
|
}
|
|
|
|
clear_packets(this->initiating.packets);
|
|
if (!generate_message(this, message, &this->initiating.packets))
|
|
{
|
|
/* 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);
|
|
}
|
|
send_packets(this, this->initiating.packets);
|
|
if (!keep)
|
|
{
|
|
clear_packets(this->initiating.packets);
|
|
}
|
|
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 */
|
|
charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE);
|
|
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, canceled = FALSE, expect_request = FALSE;
|
|
|
|
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:
|
|
canceled = TRUE;
|
|
break;
|
|
case INVALID_ARG:
|
|
if (task->get_type(task) == TASK_QUICK_MODE)
|
|
{ /* not responsible for this exchange */
|
|
continue;
|
|
}
|
|
/* FALL */
|
|
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);
|
|
|
|
clear_packets(this->responding.packets);
|
|
if (canceled)
|
|
{
|
|
message->destroy(message);
|
|
return initiate(this);
|
|
}
|
|
if (!generate_message(this, message, &this->responding.packets))
|
|
{
|
|
message->destroy(message);
|
|
charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE);
|
|
return DESTROY_ME;
|
|
}
|
|
message->destroy(message);
|
|
|
|
if (expect_request && !delete)
|
|
{
|
|
return retransmit(this, this->responding.seqnr);
|
|
}
|
|
send_packets(this, this->responding.packets);
|
|
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;
|
|
array_t *packets = NULL;
|
|
host_t *me, *other;
|
|
uint32_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(PLV1_NOTIFY,
|
|
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 (generate_message(this, response, &packets))
|
|
{
|
|
send_packets(this, packets);
|
|
}
|
|
clear_packets(packets);
|
|
array_destroy(packets);
|
|
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;
|
|
uint32_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)
|
|
{
|
|
this->dpd_send++;
|
|
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);
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Check if we already have a quick mode task queued for the exchange with the
|
|
* given message ID
|
|
*/
|
|
static bool have_quick_mode_task(private_task_manager_t *this, uint32_t mid)
|
|
{
|
|
enumerator_t *enumerator;
|
|
quick_mode_t *qm;
|
|
task_t *task;
|
|
bool found = FALSE;
|
|
|
|
enumerator = this->passive_tasks->create_enumerator(this->passive_tasks);
|
|
while (enumerator->enumerate(enumerator, &task))
|
|
{
|
|
if (task->get_type(task) == TASK_QUICK_MODE)
|
|
{
|
|
qm = (quick_mode_t*)task;
|
|
if (qm->get_mid(qm) == mid)
|
|
{
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
return found;
|
|
}
|
|
|
|
/**
|
|
* Check if we still have a specific task queued
|
|
*/
|
|
static bool have_task_queued(private_task_manager_t *this, task_type_t type)
|
|
{
|
|
enumerator_t *enumerator;
|
|
task_t *task;
|
|
bool found = FALSE;
|
|
|
|
enumerator = this->passive_tasks->create_enumerator(this->passive_tasks);
|
|
while (enumerator->enumerate(enumerator, &task))
|
|
{
|
|
if (task->get_type(task) == type)
|
|
{
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
return found;
|
|
}
|
|
|
|
/**
|
|
* 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 ||
|
|
message->get_exchange_type(message) == QUICK_MODE ||
|
|
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;
|
|
}
|
|
if (have_quick_mode_task(this, message->get_message_id(message)))
|
|
{
|
|
break;
|
|
}
|
|
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, TRUE);
|
|
}
|
|
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 INVALID_ARG:
|
|
if (task->get_type(task) == TASK_QUICK_MODE)
|
|
{ /* not responsible for this exchange */
|
|
continue;
|
|
}
|
|
/* FALL */
|
|
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
|
|
{
|
|
if (this->responding.retransmitted > 1)
|
|
{
|
|
packet_t *packet = NULL;
|
|
array_get(this->responding.packets, 0, &packet);
|
|
charon->bus->alert(charon->bus, ALERT_RETRANSMIT_SEND_CLEARED,
|
|
packet);
|
|
}
|
|
/* We don't send a response, so don't retransmit one if we get
|
|
* the same message again. */
|
|
clear_packets(this->responding.packets);
|
|
}
|
|
if (this->queued &&
|
|
this->queued->get_exchange_type(this->queued) == INFORMATIONAL_V1)
|
|
{
|
|
message_t *queued;
|
|
status_t status;
|
|
|
|
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;
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
/* Windows server sends a fourth quick mode message having an initial
|
|
* contact notify. Ignore this message for compatibility. */
|
|
if (this->initiating.type == EXCHANGE_TYPE_UNDEFINED &&
|
|
message->get_exchange_type(message) == QUICK_MODE &&
|
|
message->get_notify(message, INITIAL_CONTACT))
|
|
{
|
|
DBG1(DBG_IKE, "ignoring fourth Quick Mode message");
|
|
return SUCCESS;
|
|
}
|
|
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);
|
|
|
|
if (this->initiating.retransmitted > 1)
|
|
{
|
|
packet_t *packet = NULL;
|
|
array_get(this->initiating.packets, 0, &packet);
|
|
charon->bus->alert(charon->bus, ALERT_RETRANSMIT_SEND_CLEARED, packet);
|
|
}
|
|
this->initiating.type = EXCHANGE_TYPE_UNDEFINED;
|
|
clear_packets(this->initiating.packets);
|
|
|
|
if (this->queued && !this->active_tasks->get_count(this->active_tasks) &&
|
|
this->queued->get_exchange_type(this->queued) == TRANSACTION)
|
|
{
|
|
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);
|
|
}
|
|
|
|
static status_t handle_fragment(private_task_manager_t *this, message_t *msg)
|
|
{
|
|
status_t status;
|
|
|
|
if (!this->defrag)
|
|
{
|
|
this->defrag = message_create_defrag(msg);
|
|
if (!this->defrag)
|
|
{
|
|
return FAILED;
|
|
}
|
|
}
|
|
status = this->defrag->add_fragment(this->defrag, msg);
|
|
if (status == SUCCESS)
|
|
{
|
|
lib->processor->queue_job(lib->processor,
|
|
(job_t*)process_message_job_create(this->defrag));
|
|
this->defrag = NULL;
|
|
/* do not process the last fragment */
|
|
status = NEED_MORE;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* 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));
|
|
|
|
charon->bus->alert(charon->bus, ALERT_PARSE_ERROR_BODY, msg, status);
|
|
|
|
if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED)
|
|
{ /* invalid initiation attempt, close SA */
|
|
return DESTROY_ME;
|
|
}
|
|
}
|
|
|
|
if (msg->get_first_payload_type(msg) == PLV1_FRAGMENT)
|
|
{
|
|
return handle_fragment(this, msg);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Queue the given message if possible
|
|
*/
|
|
static status_t queue_message(private_task_manager_t *this, message_t *msg)
|
|
{
|
|
if (this->queued)
|
|
{
|
|
DBG1(DBG_IKE, "ignoring %N request, queue full",
|
|
exchange_type_names, msg->get_exchange_type(msg));
|
|
return FAILED;
|
|
}
|
|
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, msg->get_exchange_type(msg));
|
|
return SUCCESS;
|
|
}
|
|
|
|
METHOD(task_manager_t, process_message, status_t,
|
|
private_task_manager_t *this, message_t *msg)
|
|
{
|
|
uint32_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 (array_count(this->initiating.packets) &&
|
|
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);
|
|
send_packets(this, this->initiating.packets);
|
|
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 == NEED_MORE)
|
|
{
|
|
return SUCCESS;
|
|
}
|
|
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, UPDATE_HOSTS_FORCE_ADDRS);
|
|
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 (array_count(this->responding.packets))
|
|
{
|
|
DBG1(DBG_IKE, "received retransmit of request with ID %u, "
|
|
"retransmitting response", mid);
|
|
send_packets(this, this->responding.packets);
|
|
}
|
|
else if (array_count(this->initiating.packets) &&
|
|
this->initiating.type == INFORMATIONAL_V1)
|
|
{
|
|
DBG1(DBG_IKE, "received retransmit of DPD request, "
|
|
"retransmitting response");
|
|
send_packets(this, this->initiating.packets);
|
|
}
|
|
else
|
|
{
|
|
DBG1(DBG_IKE, "received retransmit of request with ID %u, "
|
|
"but no response to retransmit", mid);
|
|
}
|
|
charon->bus->alert(charon->bus, ALERT_RETRANSMIT_RECEIVE, msg);
|
|
return SUCCESS;
|
|
}
|
|
|
|
/* reject Main/Aggressive Modes once established */
|
|
if (msg->get_exchange_type(msg) == ID_PROT ||
|
|
msg->get_exchange_type(msg) == AGGRESSIVE)
|
|
{
|
|
if (this->ike_sa->get_state(this->ike_sa) != IKE_CREATED &&
|
|
this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING &&
|
|
msg->get_first_payload_type(msg) != PLV1_FRAGMENT)
|
|
{
|
|
DBG1(DBG_IKE, "ignoring %N in established IKE_SA state",
|
|
exchange_type_names, msg->get_exchange_type(msg));
|
|
return FAILED;
|
|
}
|
|
}
|
|
|
|
/* drop XAuth/Mode Config/Quick Mode messages until we received the last
|
|
* Aggressive Mode message. since Informational messages are not
|
|
* retransmitted we queue them. */
|
|
if (have_task_queued(this, TASK_AGGRESSIVE_MODE))
|
|
{
|
|
if (msg->get_exchange_type(msg) == INFORMATIONAL_V1)
|
|
{
|
|
return queue_message(this, msg);
|
|
}
|
|
else if (msg->get_exchange_type(msg) != AGGRESSIVE)
|
|
{
|
|
DBG1(DBG_IKE, "ignoring %N request while phase 1 is incomplete",
|
|
exchange_type_names, msg->get_exchange_type(msg));
|
|
return FAILED;
|
|
}
|
|
}
|
|
|
|
/* queue XAuth/Mode Config messages unless the Main Mode exchange we
|
|
* initiated is complete */
|
|
if (msg->get_exchange_type(msg) == TRANSACTION &&
|
|
this->active_tasks->get_count(this->active_tasks))
|
|
{
|
|
return queue_message(this, msg);
|
|
}
|
|
|
|
/* some peers send INITIAL_CONTACT notifies during XAuth, cache it */
|
|
if (have_task_queued(this, TASK_XAUTH) &&
|
|
msg->get_exchange_type(msg) == INFORMATIONAL_V1)
|
|
{
|
|
return queue_message(this, msg);
|
|
}
|
|
|
|
msg->set_request(msg, TRUE);
|
|
charon->bus->message(charon->bus, msg, TRUE, FALSE);
|
|
status = parse_message(this, msg);
|
|
if (status == NEED_MORE)
|
|
{
|
|
return SUCCESS;
|
|
}
|
|
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, IKEV1);
|
|
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,
|
|
lib->ns));
|
|
}
|
|
this->ike_sa->update_hosts(this->ike_sa, me, other, UPDATE_HOSTS_FORCE_ADDRS);
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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_task_delayed, void,
|
|
private_task_manager_t *this, task_t *task, uint32_t delay)
|
|
{
|
|
task_type_t type = task->get_type(task);
|
|
|
|
switch (type)
|
|
{
|
|
case TASK_MODE_CONFIG:
|
|
case TASK_XAUTH:
|
|
if (has_queued(this, type))
|
|
{
|
|
task->destroy(task);
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
DBG2(DBG_IKE, "queueing %N task", task_type_names, task->get_type(task));
|
|
this->queued_tasks->insert_last(this->queued_tasks, task);
|
|
}
|
|
|
|
METHOD(task_manager_t, queue_task, void,
|
|
private_task_manager_t *this, task_t *task)
|
|
{
|
|
queue_task_delayed(this, task, 0);
|
|
}
|
|
|
|
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->create_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);
|
|
|
|
charon->bus->children_migrate(charon->bus, new->get_id(new),
|
|
new->get_unique_id(new));
|
|
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);
|
|
charon->bus->set_sa(charon->bus, new);
|
|
charon->bus->children_migrate(charon->bus, NULL, 0);
|
|
charon->bus->set_sa(charon->bus, this->ike_sa);
|
|
|
|
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;
|
|
|
|
/* cancel any currently active task to get the DELETE done quickly */
|
|
flush_queue(this, TASK_QUEUE_ACTIVE);
|
|
|
|
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, uint32_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 *child1, child_sa_t *child2, bool local)
|
|
{
|
|
enumerator_t *e1, *e2;
|
|
traffic_selector_t *ts1, *ts2;
|
|
bool equal = FALSE;
|
|
|
|
e1 = child1->create_ts_enumerator(child1, local);
|
|
e2 = child2->create_ts_enumerator(child2, local);
|
|
if (e1->enumerate(e1, &ts1) && e2->enumerate(e2, &ts2))
|
|
{
|
|
equal = ts1->equals(ts1, ts2);
|
|
}
|
|
e2->destroy(e2);
|
|
e1->destroy(e1);
|
|
|
|
return equal;
|
|
}
|
|
|
|
/*
|
|
* Described in header
|
|
*/
|
|
bool ikev1_child_sa_is_redundant(ike_sa_t *ike_sa, child_sa_t *child_sa,
|
|
bool (*cmp)(child_sa_t*,child_sa_t*))
|
|
{
|
|
enumerator_t *enumerator;
|
|
child_sa_t *current;
|
|
bool redundant = FALSE;
|
|
|
|
enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
|
|
while (enumerator->enumerate(enumerator, ¤t))
|
|
{
|
|
if (current != child_sa &&
|
|
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) &&
|
|
(!cmp || cmp(child_sa, current)))
|
|
{
|
|
DBG1(DBG_IKE, "detected redundant CHILD_SA %s{%d}",
|
|
child_sa->get_name(child_sa),
|
|
child_sa->get_unique_id(child_sa));
|
|
redundant = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
|
|
return redundant;
|
|
}
|
|
|
|
/**
|
|
* Compare the rekey times of two CHILD_SAs, a CHILD_SA is redundant if it is
|
|
* rekeyed sooner than another.
|
|
*/
|
|
static bool is_rekeyed_sooner(child_sa_t *is_redundant, child_sa_t *other)
|
|
{
|
|
return other->get_lifetime(other, FALSE) >
|
|
is_redundant->get_lifetime(is_redundant, FALSE);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
enumerator_t *enumerator;
|
|
|
|
enumerator = child_sa->create_ts_enumerator(child_sa, local);
|
|
enumerator->enumerate(enumerator, &ts);
|
|
enumerator->destroy(enumerator);
|
|
|
|
return ts;
|
|
}
|
|
|
|
METHOD(task_manager_t, queue_child_rekey, void,
|
|
private_task_manager_t *this, protocol_id_t protocol, uint32_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 (ikev1_child_sa_is_redundant(this->ike_sa, child_sa,
|
|
is_rekeyed_sooner))
|
|
{
|
|
child_sa->set_state(child_sa, CHILD_REKEYED);
|
|
if (lib->settings->get_bool(lib->settings, "%s.delete_rekeyed",
|
|
FALSE, lib->ns))
|
|
{
|
|
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->use_marks(task, child_sa->get_mark(child_sa, TRUE).value,
|
|
child_sa->get_mark(child_sa, FALSE).value);
|
|
task->use_if_ids(task, child_sa->get_if_id(child_sa, TRUE),
|
|
child_sa->get_if_id(child_sa, FALSE));
|
|
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, uint32_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;
|
|
uint32_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 += (uint32_t)(this->retransmit_timeout * 1000.0 *
|
|
pow(this->retransmit_base, retransmit));
|
|
}
|
|
}
|
|
/* compensate for the already elapsed dpd delay */
|
|
t -= 1000 * peer_cfg->get_dpd(peer_cfg);
|
|
|
|
/* 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, get_mid, uint32_t,
|
|
private_task_manager_t *this, bool initiate)
|
|
{
|
|
return initiate ? this->initiating.mid : this->responding.mid;
|
|
}
|
|
|
|
METHOD(task_manager_t, reset, void,
|
|
private_task_manager_t *this, uint32_t initiate, uint32_t respond)
|
|
{
|
|
enumerator_t *enumerator;
|
|
task_t *task;
|
|
|
|
/* reset message counters and retransmit packets */
|
|
clear_packets(this->responding.packets);
|
|
clear_packets(this->initiating.packets);
|
|
this->responding.seqnr = RESPONDING_SEQ;
|
|
this->responding.retransmitted = 0;
|
|
this->initiating.mid = 0;
|
|
this->initiating.seqnr = 0;
|
|
this->initiating.retransmitted = 0;
|
|
this->initiating.type = EXCHANGE_TYPE_UNDEFINED;
|
|
DESTROY_IF(this->defrag);
|
|
this->defrag = NULL;
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Data for a task queue enumerator
|
|
*/
|
|
typedef struct {
|
|
enumerator_t public;
|
|
task_queue_t queue;
|
|
enumerator_t *inner;
|
|
} task_enumerator_t;
|
|
|
|
METHOD(enumerator_t, task_enumerator_destroy, void,
|
|
task_enumerator_t *this)
|
|
{
|
|
this->inner->destroy(this->inner);
|
|
free(this);
|
|
}
|
|
|
|
METHOD(enumerator_t, task_enumerator_enumerate, bool,
|
|
task_enumerator_t *this, va_list args)
|
|
{
|
|
task_t **task;
|
|
|
|
VA_ARGS_VGET(args, task);
|
|
return this->inner->enumerate(this->inner, task);
|
|
}
|
|
|
|
METHOD(task_manager_t, create_task_enumerator, enumerator_t*,
|
|
private_task_manager_t *this, task_queue_t queue)
|
|
{
|
|
task_enumerator_t *enumerator;
|
|
|
|
INIT(enumerator,
|
|
.public = {
|
|
.enumerate = enumerator_enumerate_default,
|
|
.venumerate = _task_enumerator_enumerate,
|
|
.destroy = _task_enumerator_destroy,
|
|
},
|
|
.queue = queue,
|
|
);
|
|
switch (queue)
|
|
{
|
|
case TASK_QUEUE_ACTIVE:
|
|
enumerator->inner = this->active_tasks->create_enumerator(
|
|
this->active_tasks);
|
|
break;
|
|
case TASK_QUEUE_PASSIVE:
|
|
enumerator->inner = this->passive_tasks->create_enumerator(
|
|
this->passive_tasks);
|
|
break;
|
|
case TASK_QUEUE_QUEUED:
|
|
enumerator->inner = this->queued_tasks->create_enumerator(
|
|
this->queued_tasks);
|
|
break;
|
|
default:
|
|
enumerator->inner = enumerator_create_empty();
|
|
break;
|
|
}
|
|
return &enumerator->public;
|
|
}
|
|
|
|
METHOD(task_manager_t, remove_task, void,
|
|
private_task_manager_t *this, enumerator_t *enumerator_public)
|
|
{
|
|
task_enumerator_t *enumerator = (task_enumerator_t*)enumerator_public;
|
|
|
|
switch (enumerator->queue)
|
|
{
|
|
case TASK_QUEUE_ACTIVE:
|
|
this->active_tasks->remove_at(this->active_tasks,
|
|
enumerator->inner);
|
|
break;
|
|
case TASK_QUEUE_PASSIVE:
|
|
this->passive_tasks->remove_at(this->passive_tasks,
|
|
enumerator->inner);
|
|
break;
|
|
case TASK_QUEUE_QUEUED:
|
|
this->queued_tasks->remove_at(this->queued_tasks,
|
|
enumerator->inner);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
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->defrag);
|
|
|
|
DESTROY_IF(this->queued);
|
|
clear_packets(this->responding.packets);
|
|
array_destroy(this->responding.packets);
|
|
clear_packets(this->initiating.packets);
|
|
array_destroy(this->initiating.packets);
|
|
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_task_delayed = _queue_task_delayed,
|
|
.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,
|
|
.get_mid = _get_mid,
|
|
.reset = _reset,
|
|
.adopt_tasks = _adopt_tasks,
|
|
.busy = _busy,
|
|
.create_task_enumerator = _create_task_enumerator,
|
|
.remove_task = _remove_task,
|
|
.flush = _flush,
|
|
.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, lib->ns),
|
|
.retransmit_timeout = lib->settings->get_double(lib->settings,
|
|
"%s.retransmit_timeout", RETRANSMIT_TIMEOUT, lib->ns),
|
|
.retransmit_base = lib->settings->get_double(lib->settings,
|
|
"%s.retransmit_base", RETRANSMIT_BASE, lib->ns),
|
|
.retransmit_jitter = min(lib->settings->get_int(lib->settings,
|
|
"%s.retransmit_jitter", 0, lib->ns), RETRANSMIT_JITTER_MAX),
|
|
.retransmit_limit = lib->settings->get_int(lib->settings,
|
|
"%s.retransmit_limit", 0, lib->ns) * 1000,
|
|
);
|
|
|
|
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;
|
|
|
|
if (this->retransmit_base > 1)
|
|
{ /* based on 1000 * timeout * base^try */
|
|
this->retransmit_tries_max = log(UINT32_MAX/
|
|
(1000.0 * this->retransmit_timeout))/
|
|
log(this->retransmit_base);
|
|
}
|
|
return &this->public;
|
|
}
|