summaryrefslogtreecommitdiff
path: root/src/libcharon/sa/ikev2/tasks/ike_rekey.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcharon/sa/ikev2/tasks/ike_rekey.c')
-rw-r--r--src/libcharon/sa/ikev2/tasks/ike_rekey.c261
1 files changed, 169 insertions, 92 deletions
diff --git a/src/libcharon/sa/ikev2/tasks/ike_rekey.c b/src/libcharon/sa/ikev2/tasks/ike_rekey.c
index eaba04e3a..2f0552a33 100644
--- a/src/libcharon/sa/ikev2/tasks/ike_rekey.c
+++ b/src/libcharon/sa/ikev2/tasks/ike_rekey.c
@@ -1,7 +1,8 @@
/*
+ * Copyright (C) 2015-2016 Tobias Brunner
* Copyright (C) 2005-2008 Martin Willi
* Copyright (C) 2005 Jan Hutter
- * Hochschule fuer Technik Rapperswil
+ * 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
@@ -66,9 +67,30 @@ struct private_ike_rekey_t {
* colliding task detected by the task manager
*/
task_t *collision;
+
+ /**
+ * TRUE if rekeying can't be handled temporarily
+ */
+ bool failed_temporarily;
};
/**
+ * Schedule a retry if rekeying temporary failed
+ */
+static void schedule_delayed_rekey(private_ike_rekey_t *this)
+{
+ uint32_t retry;
+ job_t *job;
+
+ retry = RETRY_INTERVAL - (random() % RETRY_JITTER);
+ job = (job_t*)rekey_ike_sa_job_create(
+ this->ike_sa->get_id(this->ike_sa), FALSE);
+ DBG1(DBG_IKE, "IKE_SA rekeying failed, trying again in %d seconds", retry);
+ this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
+ lib->scheduler->schedule_job(lib->scheduler, job, retry);
+}
+
+/**
* Check if an IKE_SA has any queued tasks, return initiation job
*/
static job_t* check_queued_tasks(ike_sa_t *ike_sa)
@@ -83,7 +105,6 @@ static job_t* check_queued_tasks(ike_sa_t *ike_sa)
job = (job_t*)initiate_tasks_job_create(ike_sa->get_id(ike_sa));
}
enumerator->destroy(enumerator);
-
return job;
}
@@ -117,20 +138,9 @@ static void establish_new(private_ike_rekey_t *this)
}
this->new_sa = NULL;
charon->bus->set_sa(charon->bus, this->ike_sa);
- }
-}
-METHOD(task_t, process_r_delete, status_t,
- private_ike_rekey_t *this, message_t *message)
-{
- establish_new(this);
- return this->ike_delete->task.process(&this->ike_delete->task, message);
-}
-
-METHOD(task_t, build_r_delete, status_t,
- private_ike_rekey_t *this, message_t *message)
-{
- return this->ike_delete->task.build(&this->ike_delete->task, message);
+ this->ike_sa->set_state(this->ike_sa, IKE_REKEYED);
+ }
}
METHOD(task_t, build_i_delete, status_t,
@@ -172,36 +182,59 @@ METHOD(task_t, build_i, status_t,
return NEED_MORE;
}
-METHOD(task_t, process_r, status_t,
- private_ike_rekey_t *this, message_t *message)
+/**
+ * Check if there are any half-open children
+ */
+static bool have_half_open_children(private_ike_rekey_t *this)
{
enumerator_t *enumerator;
child_sa_t *child_sa;
-
- if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
- {
- DBG1(DBG_IKE, "peer initiated rekeying, but we are deleting");
- return NEED_MORE;
- }
+ task_t *task;
enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa);
while (enumerator->enumerate(enumerator, (void**)&child_sa))
{
switch (child_sa->get_state(child_sa))
{
- case CHILD_CREATED:
case CHILD_REKEYING:
case CHILD_RETRYING:
case CHILD_DELETING:
- /* we do not allow rekeying while we have children in-progress */
- DBG1(DBG_IKE, "peer initiated rekeying, but a child is half-open");
enumerator->destroy(enumerator);
- return NEED_MORE;
+ return TRUE;
default:
break;
}
}
enumerator->destroy(enumerator);
+ enumerator = this->ike_sa->create_task_enumerator(this->ike_sa,
+ TASK_QUEUE_ACTIVE);
+ while (enumerator->enumerate(enumerator, (void**)&task))
+ {
+ if (task->get_type(task) == TASK_CHILD_CREATE)
+ {
+ enumerator->destroy(enumerator);
+ return TRUE;
+ }
+ }
+ enumerator->destroy(enumerator);
+ return FALSE;
+}
+
+METHOD(task_t, process_r, status_t,
+ private_ike_rekey_t *this, message_t *message)
+{
+ if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
+ {
+ DBG1(DBG_IKE, "peer initiated rekeying, but we are deleting");
+ this->failed_temporarily = TRUE;
+ return NEED_MORE;
+ }
+ if (have_half_open_children(this))
+ {
+ DBG1(DBG_IKE, "peer initiated rekeying, but a child is half-open");
+ this->failed_temporarily = TRUE;
+ return NEED_MORE;
+ }
this->new_sa = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager,
this->ike_sa->get_version(this->ike_sa), FALSE);
@@ -219,33 +252,57 @@ METHOD(task_t, process_r, status_t,
METHOD(task_t, build_r, status_t,
private_ike_rekey_t *this, message_t *message)
{
+ if (this->failed_temporarily)
+ {
+ message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty);
+ return SUCCESS;
+ }
if (this->new_sa == NULL)
{
/* IKE_SA/a CHILD_SA is in an inacceptable state, deny rekeying */
message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
return SUCCESS;
}
-
if (this->ike_init->task.build(&this->ike_init->task, message) == FAILED)
{
+ this->ike_init->task.destroy(&this->ike_init->task);
+ this->ike_init = NULL;
charon->bus->set_sa(charon->bus, this->ike_sa);
return SUCCESS;
}
charon->bus->set_sa(charon->bus, this->ike_sa);
- this->ike_sa->set_state(this->ike_sa, IKE_REKEYING);
- /* rekeying successful, delete the IKE_SA using a subtask */
- this->ike_delete = ike_delete_create(this->ike_sa, FALSE);
- this->public.task.build = _build_r_delete;
- this->public.task.process = _process_r_delete;
-
- /* the peer does have to delete the IKE_SA. If it does not, we get a
- * unusable IKE_SA in REKEYING state without a replacement. We consider
- * this a timeout condition by the peer, and trigger a delete actively. */
- lib->scheduler->schedule_job(lib->scheduler, (job_t*)
- delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE), 90);
+ if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING)
+ { /* in case of a collision we let the initiating task handle this */
+ establish_new(this);
+ /* make sure the IKE_SA is gone in case the peer fails to delete it */
+ lib->scheduler->schedule_job(lib->scheduler, (job_t*)
+ delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE),
+ 90);
+ }
+ return SUCCESS;
+}
- return NEED_MORE;
+/**
+ * Conclude any undetected rekey collision.
+ *
+ * If the peer does not detect the collision it will delete this IKE_SA.
+ * Depending on when our request reaches the peer and we receive the delete
+ * this may get called at different times.
+ *
+ * Returns TRUE if there was a collision, FALSE otherwise.
+ */
+static bool conclude_undetected_collision(private_ike_rekey_t *this)
+{
+ if (this->collision &&
+ this->collision->get_type(this->collision) == TASK_IKE_REKEY)
+ {
+ DBG1(DBG_IKE, "peer did not notice IKE_SA rekey collision, abort "
+ "active rekeying");
+ establish_new((private_ike_rekey_t*)this->collision);
+ return TRUE;
+ }
+ return FALSE;
}
METHOD(task_t, process_i, status_t,
@@ -266,18 +323,9 @@ METHOD(task_t, process_i, status_t,
{
case FAILED:
/* rekeying failed, fallback to old SA */
- if (!(this->collision && (
- this->collision->get_type(this->collision) == TASK_IKE_DELETE ||
- this->collision->get_type(this->collision) == TASK_IKE_REAUTH)))
+ if (!conclude_undetected_collision(this))
{
- job_t *job;
- u_int32_t retry = RETRY_INTERVAL - (random() % RETRY_JITTER);
- job = (job_t*)rekey_ike_sa_job_create(
- this->ike_sa->get_id(this->ike_sa), FALSE);
- DBG1(DBG_IKE, "IKE_SA rekeying failed, "
- "trying again in %d seconds", retry);
- this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
- lib->scheduler->schedule_job(lib->scheduler, job, retry);
+ schedule_delayed_rekey(this);
}
return SUCCESS;
case NEED_MORE:
@@ -293,55 +341,53 @@ METHOD(task_t, process_i, status_t,
this->collision->get_type(this->collision) == TASK_IKE_REKEY)
{
private_ike_rekey_t *other = (private_ike_rekey_t*)this->collision;
+ host_t *host;
+ chunk_t this_nonce, other_nonce;
- /* ike_init can be NULL, if child_sa is half-open */
- if (other->ike_init)
- {
- host_t *host;
- chunk_t this_nonce, other_nonce;
-
- this_nonce = this->ike_init->get_lower_nonce(this->ike_init);
- other_nonce = other->ike_init->get_lower_nonce(other->ike_init);
+ this_nonce = this->ike_init->get_lower_nonce(this->ike_init);
+ other_nonce = other->ike_init->get_lower_nonce(other->ike_init);
- /* if we have the lower nonce, delete rekeyed SA. If not, delete
- * the redundant. */
- if (memcmp(this_nonce.ptr, other_nonce.ptr,
- min(this_nonce.len, other_nonce.len)) > 0)
+ /* if we have the lower nonce, delete rekeyed SA. If not, delete
+ * the redundant. */
+ if (memcmp(this_nonce.ptr, other_nonce.ptr,
+ min(this_nonce.len, other_nonce.len)) < 0)
+ {
+ DBG1(DBG_IKE, "IKE_SA rekey collision lost, deleting redundant "
+ "IKE_SA %s[%d]", this->new_sa->get_name(this->new_sa),
+ this->new_sa->get_unique_id(this->new_sa));
+ /* apply host for a proper delete */
+ host = this->ike_sa->get_my_host(this->ike_sa);
+ this->new_sa->set_my_host(this->new_sa, host->clone(host));
+ host = this->ike_sa->get_other_host(this->ike_sa);
+ this->new_sa->set_other_host(this->new_sa, host->clone(host));
+ /* IKE_SAs in state IKE_REKEYED are silently deleted, so we use
+ * IKE_REKEYING */
+ this->new_sa->set_state(this->new_sa, IKE_REKEYING);
+ if (this->new_sa->delete(this->new_sa) == DESTROY_ME)
{
- /* peer should delete this SA. Add a timeout just in case. */
- job_t *job = (job_t*)delete_ike_sa_job_create(
- other->new_sa->get_id(other->new_sa), TRUE);
- lib->scheduler->schedule_job(lib->scheduler, job, 10);
- DBG1(DBG_IKE, "IKE_SA rekey collision won, waiting for delete");
- charon->ike_sa_manager->checkin(charon->ike_sa_manager, other->new_sa);
- other->new_sa = NULL;
+ this->new_sa->destroy(this->new_sa);
}
else
{
- DBG1(DBG_IKE, "IKE_SA rekey collision lost, "
- "deleting redundant IKE_SA");
- /* apply host for a proper delete */
- host = this->ike_sa->get_my_host(this->ike_sa);
- this->new_sa->set_my_host(this->new_sa, host->clone(host));
- host = this->ike_sa->get_other_host(this->ike_sa);
- this->new_sa->set_other_host(this->new_sa, host->clone(host));
- this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
- this->new_sa->set_state(this->new_sa, IKE_REKEYING);
- if (this->new_sa->delete(this->new_sa) == DESTROY_ME)
- {
- this->new_sa->destroy(this->new_sa);
- }
- else
- {
- charon->ike_sa_manager->checkin(
- charon->ike_sa_manager, this->new_sa);
- }
- charon->bus->set_sa(charon->bus, this->ike_sa);
- this->new_sa = NULL;
- establish_new(other);
- return SUCCESS;
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager,
+ this->new_sa);
}
+ charon->bus->set_sa(charon->bus, this->ike_sa);
+ this->new_sa = NULL;
+ establish_new(other);
+ return SUCCESS;
}
+ /* peer should delete this SA. Add a timeout just in case. */
+ job_t *job = (job_t*)delete_ike_sa_job_create(
+ other->new_sa->get_id(other->new_sa), TRUE);
+ lib->scheduler->schedule_job(lib->scheduler, job,
+ HALF_OPEN_IKE_SA_TIMEOUT);
+ DBG1(DBG_IKE, "IKE_SA rekey collision won, waiting for delete for "
+ "redundant IKE_SA %s[%d]", other->new_sa->get_name(other->new_sa),
+ other->new_sa->get_unique_id(other->new_sa));
+ other->new_sa->set_state(other->new_sa, IKE_REKEYED);
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager, other->new_sa);
+ other->new_sa = NULL;
charon->bus->set_sa(charon->bus, this->ike_sa);
}
@@ -361,11 +407,41 @@ METHOD(task_t, get_type, task_type_t,
return TASK_IKE_REKEY;
}
+METHOD(ike_rekey_t, did_collide, bool,
+ private_ike_rekey_t *this)
+{
+ return this->collision &&
+ this->collision->get_type(this->collision) == TASK_IKE_REKEY;
+}
+
METHOD(ike_rekey_t, collide, void,
private_ike_rekey_t* this, task_t *other)
{
DBG1(DBG_IKE, "detected %N collision with %N", task_type_names,
TASK_IKE_REKEY, task_type_names, other->get_type(other));
+
+ switch (other->get_type(other))
+ {
+ case TASK_IKE_DELETE:
+ conclude_undetected_collision(this);
+ other->destroy(other);
+ return;
+ case TASK_IKE_REKEY:
+ {
+ private_ike_rekey_t *rekey = (private_ike_rekey_t*)other;
+
+ if (!rekey->ike_init)
+ {
+ DBG1(DBG_IKE, "colliding exchange did not result in an IKE_SA, "
+ "ignore");
+ other->destroy(other);
+ return;
+ }
+ break;
+ }
+ default:
+ break;
+ }
DESTROY_IF(this->collision);
this->collision = other;
}
@@ -425,6 +501,7 @@ ike_rekey_t *ike_rekey_create(ike_sa_t *ike_sa, bool initiator)
.migrate = _migrate,
.destroy = _destroy,
},
+ .did_collide = _did_collide,
.collide = _collide,
},
.ike_sa = ike_sa,