#include "child_rekey.h"
#include <daemon.h>
+#include <encoding/payloads/delete_payload.h>
#include <encoding/payloads/notify_payload.h>
#include <sa/ikev2/tasks/child_create.h>
#include <sa/ikev2/tasks/child_delete.h>
child_sa_t *child_sa;
/**
- * colliding task, may be delete or rekey
+ * Colliding passive rekey task
*/
task_t *collision;
+ /**
+ * SPIs of SAs the peer deleted and we haven't found while this task was
+ * active
+ */
+ array_t *deleted_spis;
+
/**
* State flags
*/
CHILD_REKEY_FOLLOWUP_KE = (1<<0),
/**
- * Set if we adopted a completed passive task, otherwise (i.e. for
- * multi-KE rekeyings) we just reference it.
+ * Set if the passive rekey task is completed and we adopted it,
+ * otherwise (i.e. for multi-KE rekeyings) we just reference it.
+ */
+ CHILD_REKEY_PASSIVE_INSTALLED = (1<<1),
+
+ /**
+ * Indicates that the peer sent a DELETE for its own CHILD_SA of a
+ * collision. In regular rekeyings, this happens if a peer lost and
+ * the delete for the redundant SA gets processed before the active
+ * rekey job is complete. It could also mean the peer deleted its
+ * winning SA.
*/
- CHILD_REKEY_ADOPTED_PASSIVE = (1<<1),
+ CHILD_REKEY_OTHER_DELETED = (1<<2),
/**
- * Indicate that the peer destroyed the redundant child from a
- * collision. This happens if a peer's delete notification for the
- * redundant child gets processed before the active rekey job is
- * complete. If so, we must not touch the child created in the collision
- * since it points to memory already freed.
+ * Indicates that the peer sent a DELETE for the rekeyed/old CHILD_SA.
+ * This happens if the peer has won the rekey collision, but it might
+ * also happen if it incorrectly sent one after it replied to our
+ * CREATE_CHILD_SA request and the DELETE arrived before that response.
*/
- CHILD_REKEY_OTHER_DESTROYED = (1<<2),
+ CHILD_REKEY_OLD_SA_DELETED = (1<<3),
+
+ /**
+ * After handling the collision, this indicates whether the peer deleted
+ * the winning replacement SA (either ours or its own).
+ */
+ CHILD_REKEY_REPLACEMENT_DELETED = (1<<4),
} flags;
};
lib->scheduler->schedule_job(lib->scheduler, job, retry);
}
-/**
- * Implementation of task_t.build for initiator, after rekeying
- */
-static status_t build_i_delete(private_child_rekey_t *this, message_t *message)
+METHOD(task_t, build_i_delete, status_t,
+ private_child_rekey_t *this, message_t *message)
{
/* update exchange type to INFORMATIONAL for the delete */
message->set_exchange_type(message, INFORMATIONAL);
-
return this->child_delete->task.build(&this->child_delete->task, message);
}
-/**
- * Implementation of task_t.process for initiator, after rekeying
- */
-static status_t process_i_delete(private_child_rekey_t *this, message_t *message)
+METHOD(task_t, process_i_delete, status_t,
+ private_child_rekey_t *this, message_t *message)
{
return this->child_delete->task.process(&this->child_delete->task, message);
}
/**
- * find a child using the REKEY_SA notify
+ * In failure cases, we don't use a child_delete task, but handle the deletes
+ * ourselves for more flexibility (in particular, adding multiple DELETE
+ * payloads to a single message).
*/
-static void find_child(private_child_rekey_t *this, message_t *message)
+static void build_delete_old_sa(private_child_rekey_t *this, message_t *message)
{
- notify_payload_t *notify;
+ delete_payload_t *del;
protocol_id_t protocol;
uint32_t spi;
- child_sa_t *child_sa;
- notify = message->get_notify(message, REKEY_SA);
- if (notify)
- {
- protocol = notify->get_protocol_id(notify);
- spi = notify->get_spi(notify);
+ message->set_exchange_type(message, INFORMATIONAL);
- if (protocol == PROTO_ESP || protocol == PROTO_AH)
- {
- child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol,
- spi, FALSE);
- /* ignore rekeyed/deleted CHILD_SAs we keep around */
- if (child_sa &&
- child_sa->get_state(child_sa) != CHILD_DELETED)
- {
- this->child_sa = child_sa;
- }
- }
- if (!this->child_sa)
- {
- this->protocol = protocol;
- this->spi_data = chunk_clone(notify->get_spi_data(notify));
- }
+ protocol = this->child_sa->get_protocol(this->child_sa);
+ spi = this->child_sa->get_spi(this->child_sa, TRUE);
+
+ del = delete_payload_create(PLV2_DELETE, protocol);
+ del->add_spi(del, spi);
+ message->add_payload(message, (payload_t*)del);
+
+ DBG1(DBG_IKE, "sending DELETE for %N CHILD_SA with SPI %.8x",
+ protocol_id_names, protocol, ntohl(spi));
+}
+
+METHOD(task_t, build_i_delete_replacement, status_t,
+ private_child_rekey_t *this, message_t *message)
+{
+ /* add the delete for the replacement we failed to create locally but the
+ * peer probably already has installed */
+ this->child_create->task.build(&this->child_create->task, message);
+ return SUCCESS;
+}
+
+METHOD(task_t, build_i_delete_old_destroy, status_t,
+ private_child_rekey_t *this, message_t *message)
+{
+ /* send the delete but then immediately destroy and possibly recreate the
+ * CHILD_SA as the peer deleted its replacement. treat this like the peer
+ * sent a delete for the original SA */
+ build_delete_old_sa(this, message);
+ child_delete_destroy_and_reestablish(this->ike_sa, this->child_sa);
+ return SUCCESS;
+}
+
+/**
+ * Delete either both or only the replacement SA and then destroy and recreate
+ * the old SA.
+ */
+static status_t build_delete_recreate(private_child_rekey_t *this,
+ message_t *message, bool delete_old)
+{
+ if (delete_old)
+ {
+ build_delete_old_sa(this, message);
}
+ this->child_create->task.build(&this->child_create->task, message);
+ child_delete_destroy_and_force_reestablish(this->ike_sa, this->child_sa);
+ return SUCCESS;
+}
+
+METHOD(task_t, build_i_delete_replacement_recreate, status_t,
+ private_child_rekey_t *this, message_t *message)
+{
+ return build_delete_recreate(this, message, FALSE);
+}
+
+METHOD(task_t, build_i_delete_both_recreate, status_t,
+ private_child_rekey_t *this, message_t *message)
+{
+ return build_delete_recreate(this, message, TRUE);
}
METHOD(task_t, build_i, status_t,
return NEED_MORE;
}
+/**
+ * Find a CHILD_SA using the REKEY_SA notify
+ */
+static void find_child(private_child_rekey_t *this, message_t *message)
+{
+ notify_payload_t *notify;
+ protocol_id_t protocol;
+ uint32_t spi;
+ child_sa_t *child_sa;
+
+ notify = message->get_notify(message, REKEY_SA);
+ if (notify)
+ {
+ protocol = notify->get_protocol_id(notify);
+ spi = notify->get_spi(notify);
+
+ if (protocol == PROTO_ESP || protocol == PROTO_AH)
+ {
+ child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol,
+ spi, FALSE);
+ /* ignore rekeyed/deleted CHILD_SAs we keep around */
+ if (child_sa &&
+ child_sa->get_state(child_sa) != CHILD_DELETED)
+ {
+ this->child_sa = child_sa;
+ }
+ }
+ if (!this->child_sa)
+ {
+ this->protocol = protocol;
+ this->spi_data = chunk_clone(notify->get_spi_data(notify));
+ }
+ }
+}
+
METHOD(task_t, process_r, status_t,
private_child_rekey_t *this, message_t *message)
{
child_sa_t *child_sa;
child_sa_state_t state = CHILD_INSTALLED;
uint32_t reqid;
- bool followup_sent;
+ bool followup_sent = FALSE;
if (!this->child_sa)
{
}
if (this->child_sa->get_state(this->child_sa) == CHILD_DELETING)
{
- DBG1(DBG_IKE, "unable to rekey, we are deleting the CHILD_SA");
+ DBG1(DBG_IKE, "unable to rekey CHILD_SA %s{%u}, we are deleting it",
+ this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa));
message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty);
return SUCCESS;
}
if (child_sa && child_sa->get_state(child_sa) == CHILD_INSTALLED)
{
this->child_sa->set_state(this->child_sa, CHILD_REKEYED);
- this->child_sa->set_rekey_spi(this->child_sa,
- child_sa->get_spi(child_sa, FALSE));
-
- /* FIXME: this might trigger twice if there was a collision */
- charon->bus->child_rekey(charon->bus, this->child_sa, child_sa);
+ /* link the SAs to handle possible collisions */
+ this->child_sa->set_rekey_sa(this->child_sa, child_sa);
+ child_sa->set_rekey_sa(child_sa, this->child_sa);
+ /* like installing the outbound SA, we only trigger the child-rekey
+ * event once the old SA is deleted */
}
else if (this->child_sa->get_state(this->child_sa) == CHILD_REKEYING)
{ /* rekeying failed, reuse old child */
return SUCCESS;
}
+/**
+ * Check if the peer deleted the replacement SA we created while we waited for
+ * its completion.
+ */
+static bool is_our_replacement_deleted(private_child_rekey_t *this)
+{
+ uint32_t spi, peer_spi;
+ int i;
+
+ if (!this->deleted_spis)
+ {
+ return FALSE;
+ }
+
+ peer_spi = this->child_create->get_other_spi(this->child_create);
+ if (!peer_spi)
+ {
+ return FALSE;
+ }
+
+ for (i = 0; i < array_count(this->deleted_spis); i++)
+ {
+ array_get(this->deleted_spis, i, &spi);
+ if (spi == peer_spi)
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
/**
* Remove the passive rekey task that's waiting for IKE_FOLLOWUP_KE requests
- * that will never come.
+ * that will never come if we won the collision.
*/
static void remove_passive_rekey_task(private_child_rekey_t *this)
{
}
/**
- * Handle a rekey collision
+ * Compare the nonces to determine if we lost the rekey collision.
+ * The SA with the lowest nonce should be deleted (if already complete), this
+ * checks if we or the peer created it
*/
-static child_sa_t *handle_collision(private_child_rekey_t *this,
- child_sa_t **to_install, bool multi_ke)
+static bool lost_collision(private_child_rekey_t *this)
{
private_child_rekey_t *other = (private_child_rekey_t*)this->collision;
chunk_t this_nonce, other_nonce;
- child_sa_t *to_delete, *child_sa;
- if (this->collision->get_type(this->collision) == TASK_CHILD_DELETE)
- { /* CHILD_DELETE, which we only adopt if it is for the CHILD_SA we are
- * ourselves rekeying */
- to_delete = this->child_create->get_child(this->child_create);
- if (multi_ke)
- {
- DBG1(DBG_IKE, "CHILD_SA rekey/delete collision, abort incomplete "
- "multi-KE rekeying");
- }
- else
- {
- DBG1(DBG_IKE, "CHILD_SA rekey/delete collision, deleting redundant "
- "child %s{%d}", to_delete->get_name(to_delete),
- to_delete->get_unique_id(to_delete));
- }
- return to_delete;
+ if (!other)
+ {
+ return FALSE;
}
this_nonce = this->child_create->get_lower_nonce(this->child_create);
other_nonce = other->child_create->get_lower_nonce(other->child_create);
- /* the SA with the lowest nonce should be deleted (if already complete),
- * check if we or the peer created it */
- if (memcmp(this_nonce.ptr, other_nonce.ptr,
- min(this_nonce.len, other_nonce.len)) < 0)
+ return memcmp(this_nonce.ptr, other_nonce.ptr,
+ min(this_nonce.len, other_nonce.len)) < 0;
+}
+
+/**
+ * Handle a rekey collision. Returns TRUE if we won the collision or there
+ * wasn't one. Also returns the SA that should be deleted and the winning SA
+ * of the collision, if any.
+ */
+static bool handle_collision(private_child_rekey_t *this,
+ child_sa_t **to_delete, child_sa_t **winning_sa,
+ bool multi_ke)
+{
+ private_child_rekey_t *other = (private_child_rekey_t*)this->collision;
+ child_sa_t *other_sa;
+
+ if (lost_collision(this))
{
- to_delete = this->child_create->get_child(this->child_create);
+ *to_delete = this->child_create->get_child(this->child_create);
if (multi_ke)
{
DBG1(DBG_IKE, "CHILD_SA rekey collision lost, abort incomplete "
else
{
DBG1(DBG_IKE, "CHILD_SA rekey collision lost, deleting "
- "redundant child %s{%d}", to_delete->get_name(to_delete),
- to_delete->get_unique_id(to_delete));
+ "redundant child %s{%u}", (*to_delete)->get_name(*to_delete),
+ (*to_delete)->get_unique_id(*to_delete));
+ }
+ /* check if the passive rekeying is completed */
+ if (this->flags & CHILD_REKEY_PASSIVE_INSTALLED)
+ {
+ *winning_sa = other->child_create->get_child(other->child_create);
+
+ if (this->flags & CHILD_REKEY_OTHER_DELETED)
+ {
+ /* the peer deleted its own replacement SA while we waited
+ * for a response, set a flag to destroy the SA accordingly */
+ this->flags |= CHILD_REKEY_REPLACEMENT_DELETED;
+ /* if the peer has not triggered a rekey event yet by deleting
+ * its own SA before deleting the old SA (if it did so at all),
+ * we trigger that now so listeners can track this properly */
+ if (!(this->flags & CHILD_REKEY_OLD_SA_DELETED) ||
+ (*winning_sa)->get_outbound_state(*winning_sa) != CHILD_OUTBOUND_INSTALLED)
+ {
+ charon->bus->child_rekey(charon->bus, this->child_sa,
+ *winning_sa);
+ }
+ }
+ /* check if the peer already sent a delete for the old SA */
+ if (this->flags & CHILD_REKEY_OLD_SA_DELETED)
+ {
+ child_delete_destroy_rekeyed(this->ike_sa, this->child_sa);
+ }
+ else if (this->flags & CHILD_REKEY_OTHER_DELETED)
+ {
+ /* make sure the old SA is in the correct state if the peer
+ * deleted its own SA but not yet the old one (weird, but who
+ * knows...) */
+ this->child_sa->set_state(this->child_sa, CHILD_REKEYED);
+ }
}
- return to_delete;
+ return FALSE;
}
- *to_install = this->child_create->get_child(this->child_create);
- to_delete = this->child_sa;
+ *winning_sa = this->child_create->get_child(this->child_create);
+ *to_delete = this->child_sa;
+
+ /* regular rekeying without collision (or we already concluded it for a
+ * multi-KE rekeying), check if the peer deleted the new SA already */
+ if (!this->collision)
+ {
+ if (is_our_replacement_deleted(this))
+ {
+ this->flags |= CHILD_REKEY_REPLACEMENT_DELETED;
+ /* since we will destroy the winning SA, we have to trigger a rekey
+ * event before so listeners can track this properly */
+ charon->bus->child_rekey(charon->bus, this->child_sa, *winning_sa);
+ }
+ return TRUE;
+ }
/* the passive rekeying is complete only if it was single-KE. otherwise,
* the peer would either have stopped before sending IKE_FOLLOWUP_KE when
* it noticed it lost, or it responded with TEMPORARY_FAILURE to our
* CREATE_CHILD_SA request if it already started sending them. */
- if (this->flags & CHILD_REKEY_ADOPTED_PASSIVE)
+ if (this->flags & CHILD_REKEY_PASSIVE_INSTALLED)
{
- /* we don't want to install the peer's redundant outbound SA */
- this->child_sa->set_rekey_spi(this->child_sa, 0);
- /* don't touch child other created if it has already been deleted */
- if (!(this->flags & CHILD_REKEY_OTHER_DESTROYED))
- {
- /* disable close action and updown event for redundant child the
- * other is expected to delete */
- child_sa = other->child_create->get_child(other->child_create);
- if (child_sa)
- {
- child_sa->set_close_action(child_sa, ACTION_NONE);
- if (child_sa->get_state(child_sa) != CHILD_REKEYED)
- {
- child_sa->set_state(child_sa, CHILD_REKEYED);
- }
- }
- }
if (multi_ke)
{
DBG1(DBG_IKE, "CHILD_SA rekey collision won, continue with "
else
{
DBG1(DBG_IKE, "CHILD_SA rekey collision won, deleting old child "
- "%s{%d}", to_delete->get_name(to_delete),
- to_delete->get_unique_id(to_delete));
+ "%s{%u}", (*to_delete)->get_name(*to_delete),
+ (*to_delete)->get_unique_id(*to_delete));
+ }
+
+ other_sa = other->child_create->get_child(other->child_create);
+
+ /* check if the peer already sent a delete for our winning SA */
+ if (is_our_replacement_deleted(this))
+ {
+ this->flags |= CHILD_REKEY_REPLACEMENT_DELETED;
+ /* similar to the case above, but here the peer might already have
+ * deleted its redundant SA, and it might have sent an incorrect
+ * delete for the old SA. if it did the latter first, then we will
+ * have concluded the rekeying and there was a rekey event from the
+ * old SA to the redundant one that we have to consider here */
+ if (this->flags & CHILD_REKEY_OLD_SA_DELETED && other_sa &&
+ other_sa->get_outbound_state(other_sa) == CHILD_OUTBOUND_INSTALLED)
+ {
+ charon->bus->child_rekey(charon->bus, other_sa, *winning_sa);
+ }
+ else
+ {
+ charon->bus->child_rekey(charon->bus, this->child_sa,
+ *winning_sa);
+ }
+ }
+
+ /* check if the peer already sent a delete for its redundant SA */
+ if (!(this->flags & CHILD_REKEY_OTHER_DELETED))
+ {
+ /* unlink the redundant SA the peer is expected to delete, disable
+ * events and make sure the outbound SA isn't installed/registered */
+ this->child_sa->set_rekey_sa(this->child_sa, NULL);
+ if (other_sa)
+ {
+ other_sa->set_rekey_sa(other_sa, NULL);
+ other_sa->set_state(other_sa, CHILD_REKEYED);
+ other_sa->remove_outbound(other_sa);
+ }
+ }
+ else if (other_sa)
+ {
+ /* the peer already deleted its redundant SA, but we have not yet
+ * destroyed it, do so now */
+ child_delete_destroy_rekeyed(this->ike_sa, other_sa);
}
this->collision->destroy(this->collision);
}
remove_passive_rekey_task(this);
}
this->collision = NULL;
- return to_delete;
+ return TRUE;
+}
+
+/**
+ * Check if we can ignore a CHILD_SA_NOT_FOUND notify and log appropriate
+ * messages.
+ */
+static bool ignore_child_sa_not_found(private_child_rekey_t *this)
+{
+ private_child_rekey_t *other;
+ child_sa_t *other_sa;
+
+ /* if the peer hasn't explicitly sent a delete for the CHILD_SA it wasn't
+ * able to find now, it might have lost the state, we can't ignore that and
+ * create a replacement */
+ if (!(this->flags & CHILD_REKEY_OLD_SA_DELETED))
+ {
+ DBG1(DBG_IKE, "peer didn't find CHILD_SA %s{%u} we tried to rekey, "
+ "create a replacement",
+ this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa));
+ return FALSE;
+ }
+
+ /* if the peer explicitly deleted the original CHILD_SA before our request
+ * arrived, we adhere to that wish and close the SA.
+ * this is the case where the peer received the DELETE response before
+ * our rekey request, see below for the case where it hasn't received the
+ * response yet and responded with TEMPORARY_FAILURE */
+ if (!this->collision)
+ {
+ DBG1(DBG_IKE, "closing CHILD_SA %s{%u} we tried to rekey because "
+ "the peer deleted it before it received our request",
+ this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa));
+ child_delete_destroy_and_reestablish(this->ike_sa, this->child_sa);
+ return TRUE;
+ }
+
+ /* if there was a rekey collision and the peer deleted the original CHILD_SA
+ * before our request arrived and it has not deleted the new SA, we just
+ * abort our own rekeying and use the peer's replacement */
+ if (!(this->flags & CHILD_REKEY_OTHER_DELETED))
+ {
+ DBG1(DBG_IKE, "abort active rekeying for CHILD_SA %s{%u} because "
+ "it was successfully rekeyed by the peer before it received "
+ "our request", this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa));
+ child_delete_destroy_rekeyed(this->ike_sa, this->child_sa);
+ return TRUE;
+ }
+
+ /* the peer successfully rekeyed the same SA, deleted it, but then also
+ * deleted the CHILD_SA it created as replacement. adhere to that wish and
+ * close the replacement */
+ other = (private_child_rekey_t*)this->collision;
+ other_sa = other->child_create->get_child(other->child_create);
+
+ DBG1(DBG_IKE, "abort active rekeying for CHILD_SA %s{%u} because the other "
+ "peer already deleted its replacement CHILD_SA %s{%u} before "
+ "it received our request", this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa),
+ other_sa->get_name(other_sa), other_sa->get_unique_id(other_sa));
+ child_delete_destroy_rekeyed(this->ike_sa, this->child_sa);
+ child_delete_destroy_and_reestablish(this->ike_sa, other_sa);
+ return TRUE;
+}
+
+/**
+ * Check if we can ignore failures to create the new CHILD_SA e.g. due to an
+ * error notify like TEMPORARY_FAILURE and log appropriate messages.
+ */
+static bool ignore_child_sa_failure(private_child_rekey_t *this)
+{
+ /* we are fine if there was a successful passive rekeying. the peer might
+ * not have detected the collision and responded with a TEMPORARY_FAILURE
+ * notify while deleting the old SA, which conflicted with our request */
+ if (this->collision && (this->flags & CHILD_REKEY_PASSIVE_INSTALLED) &&
+ !(this->flags & CHILD_REKEY_OTHER_DELETED))
+ {
+ DBG1(DBG_IKE, "abort active rekeying for CHILD_SA %s{%u} because "
+ "the peer successfully rekeyed it before receiving our request%s",
+ this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa),
+ this->flags & CHILD_REKEY_OLD_SA_DELETED ? ""
+ : ", waiting for delete");
+
+ /* if the peer already deleted the rekeyed SA, destroy it, otherwise
+ * just wait for the delete */
+ if (this->flags & CHILD_REKEY_OLD_SA_DELETED)
+ {
+ child_delete_destroy_rekeyed(this->ike_sa, this->child_sa);
+ }
+ return TRUE;
+ }
+
+ /* if the peer initiated a delete for the old SA before our rekey request
+ * reached it, the expected response is TEMPORARY_FAILURE. adhere to that
+ * wish and abort the rekeying.
+ * this is the case where the peer has not yet received the DELETE response
+ * when our rekey request arrived, see above for the case where it has
+ * already received the response and responded with CHILD_SA_NOT_FOUND */
+ if (this->flags & CHILD_REKEY_OLD_SA_DELETED)
+ {
+ DBG1(DBG_IKE, "closing CHILD_SA %s{%u} we tried to rekey because "
+ "the peer started to delete it before receiving our request",
+ this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa));
+ child_delete_destroy_and_reestablish(this->ike_sa, this->child_sa);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Check if we can ignore local failures to create the new CHILD_SA e.g. due to
+ * a KE or kernel problem and log an appropriate message.
+ */
+static status_t handle_local_failure(private_child_rekey_t *this)
+{
+ /* if we lost the collision, we are expected to delete the failed SA
+ * anyway, so just do that and rely on the passive rekeying, which
+ * deletes the old SA (or has already done so, in which case we destroy the
+ * SA now) */
+ if (this->collision && lost_collision(this))
+ {
+ if (this->flags & CHILD_REKEY_OLD_SA_DELETED)
+ {
+ child_delete_destroy_rekeyed(this->ike_sa, this->child_sa);
+ }
+ this->public.task.build = _build_i_delete_replacement;
+ return NEED_MORE;
+ }
+
+ /* the peer sent a delete for our winning replacement SA, no need to send a
+ * delete for it again and adhere to this wish to delete the SA.
+ * however, we are expected to send a delete for the original SA, unless,
+ * it was already deleted by the peer as well (which would be incorrect) */
+ if (is_our_replacement_deleted(this))
+ {
+ DBG1(DBG_IKE, "closing CHILD_SA %s{%u} we tried to rekey because "
+ "the peer meanwhile sent a delete for its replacement",
+ this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa));
+ if (this->flags & CHILD_REKEY_OLD_SA_DELETED)
+ {
+ child_delete_destroy_and_reestablish(this->ike_sa, this->child_sa);
+ return SUCCESS;
+ }
+ this->public.task.build = _build_i_delete_old_destroy;
+ return NEED_MORE;
+ }
+
+ /* as the winner of the collision or if there wasn't one, we're expected to
+ * delete the original SA, but we also want to recreate it because we
+ * failed to install the replacement. because the peer already has the
+ * replacement partially installed, we also need to send a delete for the
+ * failed one */
+ this->public.task.build = _build_i_delete_both_recreate;
+
+ if (this->flags & CHILD_REKEY_OLD_SA_DELETED)
+ {
+ /* the peer already sent an incorrect delete for the original SA that
+ * arrived before the response to the rekeying, delete only the failed
+ * replacement and recreate the SA */
+ DBG1(DBG_IKE, "peer sent an incorrect delete for CHILD_SA %s{%u} after "
+ "responding to our rekeying",
+ this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa));
+ this->public.task.build = _build_i_delete_replacement_recreate;
+ }
+ DBG1(DBG_IKE, "closing and recreating CHILD_SA %s{%u} after failing to "
+ "install replacement", this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa));
+ return NEED_MORE;
}
METHOD(task_t, process_i, status_t,
{
protocol_id_t protocol;
uint32_t spi;
- child_sa_t *child_sa, *to_delete = NULL, *to_install = NULL;
+ child_sa_t *child_sa, *to_delete = NULL, *winning_sa = NULL;
+ bool collision_won;
if (message->get_notify(message, NO_ADDITIONAL_SAS))
{
}
if (message->get_notify(message, CHILD_SA_NOT_FOUND))
{
- child_cfg_t *child_cfg;
- child_init_args_t args = {};
- status_t status;
-
- if (this->collision &&
- this->collision->get_type(this->collision) == TASK_CHILD_DELETE)
- { /* ignore this error if we already deleted the CHILD_SA on the
- * peer's behalf (could happen if the other peer does not detect
- * the collision and did not respond with TEMPORARY_FAILURE) */
- return SUCCESS;
- }
- DBG1(DBG_IKE, "peer didn't find the CHILD_SA we tried to rekey");
- /* FIXME: according to RFC 7296 we should only create a new CHILD_SA if
- * it does not exist yet, we currently have no good way of checking for
- * that (we could go by name, but that might be tricky e.g. due to
- * narrowing) */
- spi = this->child_sa->get_spi(this->child_sa, TRUE);
- protocol = this->child_sa->get_protocol(this->child_sa);
- child_cfg = this->child_sa->get_config(this->child_sa);
- child_cfg->get_ref(child_cfg);
- args.reqid = this->child_sa->get_reqid_ref(this->child_sa);
- args.label = this->child_sa->get_label(this->child_sa);
- if (args.label)
+ /* ignore CHILD_SA_NOT_FOUND error notify in some cases, otherwise
+ * create a replacement SA */
+ if (ignore_child_sa_not_found(this))
{
- args.label = args.label->clone(args.label);
- }
- charon->bus->child_updown(charon->bus, this->child_sa, FALSE);
- this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi);
- status = this->ike_sa->initiate(this->ike_sa,
- child_cfg->get_ref(child_cfg), &args);
- if (args.reqid)
- {
- charon->kernel->release_reqid(charon->kernel, args.reqid);
+ return SUCCESS;
}
- DESTROY_IF(args.label);
- return status;
+ return child_delete_destroy_and_force_reestablish(this->ike_sa,
+ this->child_sa);
}
if (this->child_create->task.process(&this->child_create->task,
message) == NEED_MORE)
{
- if (message->get_notify(message, INVALID_KE_PAYLOAD) ||
- !this->child_create->get_child(this->child_create))
- { /* bad key exchange mechanism, retry, or failure requiring delete */
- return NEED_MORE;
+ if (message->get_notify(message, INVALID_KE_PAYLOAD))
+ {
+ /* invalid KE method => retry, unless we can ignore it */
+ return ignore_child_sa_failure(this) ? SUCCESS : NEED_MORE;
+ }
+ else if (!this->child_create->get_child(this->child_create))
+ {
+ /* local failure requiring a delete, check what we have to do */
+ return handle_local_failure(this);
}
+
/* multiple key exchanges */
this->flags |= CHILD_REKEY_FOLLOWUP_KE;
/* there will only be a collision while we process a CREATE_CHILD_SA
- * response, later we just respond with TEMPORARY_FAILURE and ignore
- * the passive task - if we lost, the returned SA is the one we created
- * in this task, since it's not complete yet, we abort the task */
- if (this->collision)
+ * response, later we just respond with TEMPORARY_FAILURE, so handle
+ * it now */
+ if (!handle_collision(this, &to_delete, &winning_sa, TRUE))
{
- to_delete = handle_collision(this, &to_install, TRUE);
+ /* we lost the collision. since the SA is not complete yet, we just
+ * abort the task */
+ return SUCCESS;
}
- return (to_delete && to_delete != this->child_sa) ? SUCCESS : NEED_MORE;
+ return NEED_MORE;
}
child_sa = this->child_create->get_child(this->child_create);
if (!child_sa || child_sa->get_state(child_sa) != CHILD_INSTALLED)
{
- /* establishing new child failed, reuse old and try again. but not when
- * we received a delete in the meantime or passively rekeyed the SA */
- if (!this->collision ||
- (this->collision->get_type(this->collision) != TASK_CHILD_DELETE &&
- !(this->flags & CHILD_REKEY_ADOPTED_PASSIVE)))
+ /* check if we can ignore remote errors like TEMPORARY_FAILURE */
+ if (!ignore_child_sa_failure(this))
{
+ /* otherwise (e.g. for an IKE/CHILD rekey collision), reuse the old
+ * CHILD_SA and try again */
schedule_delayed_rekey(this);
}
return SUCCESS;
/* there won't be a collision if this task is for a multi-KE rekeying, as a
* collision during CREATE_CHILD_SA was cleaned up above */
- if (this->collision)
+ collision_won = handle_collision(this, &to_delete, &winning_sa, FALSE);
+
+ if (this->flags & CHILD_REKEY_REPLACEMENT_DELETED)
{
- to_delete = handle_collision(this, &to_install, FALSE);
+ DBG1(DBG_IKE, "peer meanwhile sent a delete for CHILD_SA %s{%u} with "
+ "SPIs %.8x_i %.8x_o, abort rekeying",
+ winning_sa->get_name(winning_sa),
+ winning_sa->get_unique_id(winning_sa),
+ ntohl(winning_sa->get_spi(winning_sa, TRUE)),
+ ntohl(winning_sa->get_spi(winning_sa, FALSE)));
+ child_delete_destroy_and_reestablish(this->ike_sa, winning_sa);
}
- else
+ else if (collision_won)
{
- to_install = this->child_create->get_child(this->child_create);
- to_delete = this->child_sa;
+ /* only conclude the rekeying here if we won, otherwise, we either
+ * already concluded the rekeying or we will do so when the peer deletes
+ * the old SA */
+ child_rekey_conclude_rekeying(this->child_sa, winning_sa);
}
- if (to_install)
+
+ if (collision_won &&
+ this->flags & CHILD_REKEY_OLD_SA_DELETED)
{
- if (to_install->install_outbound(to_install) != SUCCESS)
- {
- DBG1(DBG_IKE, "unable to install outbound IPsec SA (SAD) in kernel");
- charon->bus->alert(charon->bus, ALERT_INSTALL_CHILD_SA_FAILED,
- to_install);
- /* FIXME: delete the child_sa? fail the task? */
- }
- else
- {
- linked_list_t *my_ts, *other_ts;
-
- my_ts = linked_list_create_from_enumerator(
- to_install->create_ts_enumerator(to_install, TRUE));
- other_ts = linked_list_create_from_enumerator(
- to_install->create_ts_enumerator(to_install, FALSE));
-
- DBG0(DBG_IKE, "outbound CHILD_SA %s{%d} established "
- "with SPIs %.8x_i %.8x_o and TS %#R === %#R",
- to_install->get_name(to_install),
- to_install->get_unique_id(to_install),
- ntohl(to_install->get_spi(to_install, TRUE)),
- ntohl(to_install->get_spi(to_install, FALSE)),
- my_ts, other_ts);
-
- my_ts->destroy(my_ts);
- other_ts->destroy(other_ts);
- }
- }
- if (to_delete->get_state(to_delete) != CHILD_REKEYED)
- { /* disable updown event for old/redundant CHILD_SA */
- to_delete->set_state(to_delete, CHILD_REKEYED);
+ /* the peer already deleted the rekeyed SA we were expected to delete
+ * with an incorrect delete to which we responded as usual but didn't
+ * destroy the SA yet */
+ DBG1(DBG_IKE, "peer sent an incorrect delete for CHILD_SA %s{%u} after "
+ "responding to our rekeying",
+ this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa));
+ child_delete_destroy_rekeyed(this->ike_sa, this->child_sa);
+ return SUCCESS;
}
- if (to_delete == this->child_sa)
- { /* invoke rekey hook if rekeying successful and remove the old
- * outbound SA as we installed the new one already above, but might not
- * be using it yet depending on how SAs/policies are handled */
- this->child_sa->remove_outbound(this->child_sa);
- charon->bus->child_rekey(charon->bus, this->child_sa,
- this->child_create->get_child(this->child_create));
+
+ /* disable updown event for old/redundant CHILD_SA */
+ to_delete->set_state(to_delete, CHILD_REKEYED);
+ /* and make sure the outbound SA is not registered, unless it is still fully
+ * installed, which happens if the rekeying is aborted. we keep it installed
+ * as we can't establish a replacement until the delete is done */
+ if (to_delete->get_outbound_state(to_delete) != CHILD_OUTBOUND_INSTALLED)
+ {
+ to_delete->remove_outbound(to_delete);
}
+
spi = to_delete->get_spi(to_delete, TRUE);
protocol = to_delete->get_protocol(to_delete);
/* rekeying done, delete the obsolete CHILD_SA using a subtask */
this->child_delete = child_delete_create(this->ike_sa, protocol, spi, FALSE);
- this->public.task.build = (status_t(*)(task_t*,message_t*))build_i_delete;
- this->public.task.process = (status_t(*)(task_t*,message_t*))process_i_delete;
+ this->public.task.build = _build_i_delete;
+ this->public.task.process = _process_i_delete;
return NEED_MORE;
}
+/*
+ * Described in header
+ */
+bool child_rekey_conclude_rekeying(child_sa_t *old, child_sa_t *new)
+{
+ linked_list_t *my_ts, *other_ts;
+
+ if (new->install_outbound(new) != SUCCESS)
+ {
+ /* shouldn't happen after we were able to install the inbound SA */
+ DBG1(DBG_IKE, "unable to install outbound IPsec SA (SAD) in kernel");
+ charon->bus->alert(charon->bus, ALERT_INSTALL_CHILD_SA_FAILED,
+ new);
+ return FALSE;
+ }
+
+ my_ts = linked_list_create_from_enumerator(
+ new->create_ts_enumerator(new, TRUE));
+ other_ts = linked_list_create_from_enumerator(
+ new->create_ts_enumerator(new, FALSE));
+
+ DBG0(DBG_IKE, "outbound CHILD_SA %s{%d} established "
+ "with SPIs %.8x_i %.8x_o and TS %#R === %#R",
+ new->get_name(new), new->get_unique_id(new),
+ ntohl(new->get_spi(new, TRUE)), ntohl(new->get_spi(new, FALSE)),
+ my_ts, other_ts);
+
+ my_ts->destroy(my_ts);
+ other_ts->destroy(other_ts);
+
+ /* remove the old outbound SA after we installed the new one. otherwise, it
+ * might not get used yet depending on how SAs/policies are handled in the
+ * kernel */
+ old->remove_outbound(old);
+
+ DBG0(DBG_IKE, "rekeyed CHILD_SA %s{%u} with SPIs %.8x_i %.8x_o with "
+ "%s{%u} with SPIs %.8x_i %.8x_o",
+ old->get_name(old), old->get_unique_id(old),
+ ntohl(old->get_spi(old, TRUE)), ntohl(old->get_spi(old, FALSE)),
+ new->get_name(new), new->get_unique_id(new),
+ ntohl(new->get_spi(new, TRUE)), ntohl(new->get_spi(new, FALSE)));
+ charon->bus->child_rekey(charon->bus, old, new);
+ return TRUE;
+}
+
METHOD(task_t, get_type, task_type_t,
private_child_rekey_t *this)
{
return TASK_CHILD_REKEY;
}
-METHOD(child_rekey_t, is_redundant, bool,
- private_child_rekey_t *this, child_sa_t *child)
+METHOD(child_rekey_t, handle_delete, child_rekey_collision_t,
+ private_child_rekey_t *this, child_sa_t *child, uint32_t spi)
{
- if (this->collision &&
- this->collision->get_type(this->collision) == TASK_CHILD_REKEY)
+ /* if we already completed our active rekeying and are deleting the
+ * old/redundant SA, there is no need to do anything special */
+ if (this->child_delete)
{
- private_child_rekey_t *rekey = (private_child_rekey_t*)this->collision;
- return child == rekey->child_create->get_child(rekey->child_create);
+ return CHILD_REKEY_COLLISION_NONE;
}
- return FALSE;
+
+ if (!child)
+ {
+ /* check later if the SPI is the peer's of the SA we created (i.e.
+ * whether it deleted the new SA immediately after creation and we
+ * received that request before our active rekeying was complete) */
+ array_insert_create_value(&this->deleted_spis, sizeof(uint32_t),
+ ARRAY_TAIL, &spi);
+ }
+ else if (child == this->child_sa)
+ {
+ /* the peer sent a delete for the old SA, might be because it won a
+ * collision, but could also be either because it initiated that before
+ * it received our CREATE_CHILD_SA request, or it incorrectly sent one
+ * as response to our request, we will check once we have the response
+ * to our rekeying */
+ this->flags |= CHILD_REKEY_OLD_SA_DELETED;
+ return CHILD_REKEY_COLLISION_OLD;
+ }
+ else if (this->collision)
+ {
+ private_child_rekey_t *other = (private_child_rekey_t*)this->collision;
+
+ if (child == other->child_create->get_child(other->child_create))
+ {
+ /* the peer deleted the redundant (or in rare cases the winning) SA
+ * it created before our active rekeying was complete, how we handle
+ * this depends on the response to our rekeying */
+ this->flags |= CHILD_REKEY_OTHER_DELETED;
+ return CHILD_REKEY_COLLISION_PEER;
+ }
+ }
+ return CHILD_REKEY_COLLISION_NONE;
}
METHOD(child_rekey_t, collide, bool,
private_child_rekey_t *this, task_t *other)
{
- /* the task manager only detects exchange collision, but not if
- * the collision is for the same child. we check it here. */
- if (other->get_type(other) == TASK_CHILD_REKEY)
- {
- private_child_rekey_t *rekey = (private_child_rekey_t*)other;
- child_sa_t *other_child;
+ private_child_rekey_t *rekey = (private_child_rekey_t*)other;
+ child_sa_t *other_child;
- if (rekey->child_sa != this->child_sa)
- { /* not the same child => no collision */
- return FALSE;
- }
- /* ignore passive tasks that did not successfully create a CHILD_SA */
- other_child = rekey->child_create->get_child(rekey->child_create);
- if (!other_child)
- {
- return FALSE;
- }
- if (other_child->get_state(other_child) != CHILD_INSTALLED)
- {
- DBG1(DBG_IKE, "colliding passive rekeying is not yet complete",
- task_type_names, TASK_CHILD_REKEY);
- /* we do reference the task to check its state later */
- this->collision = other;
- return FALSE;
- }
+ if (rekey->child_sa != this->child_sa)
+ {
+ /* not the same child => no collision */
+ return FALSE;
}
- else if (other->get_type(other) == TASK_CHILD_DELETE)
+
+ other_child = rekey->child_create->get_child(rekey->child_create);
+ if (!other_child)
{
- child_delete_t *del = (child_delete_t*)other;
- if (is_redundant(this, del->get_child(del)))
- {
- this->flags |= CHILD_REKEY_OTHER_DESTROYED;
- return FALSE;
- }
- if (del->get_child(del) != this->child_sa)
- {
- /* not the same child => no collision */
- return FALSE;
- }
+ /* ignore passive tasks that did not successfully create a CHILD_SA */
+ return FALSE;
}
- else
+ if (other_child->get_state(other_child) != CHILD_INSTALLED)
{
- /* shouldn't happen */
+ DBG1(DBG_IKE, "colliding passive rekeying for CHILD_SA %s{%u} is not "
+ "yet complete", this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa));
+ /* we do reference the task to check its state later */
+ this->collision = other;
return FALSE;
}
-
- DBG1(DBG_IKE, "detected %N collision with %N", task_type_names,
- TASK_CHILD_REKEY, task_type_names, other->get_type(other));
-
- if (this->flags & CHILD_REKEY_ADOPTED_PASSIVE)
+ if (this->collision && this->collision != other)
{
- DESTROY_IF(this->collision);
+ DBG1(DBG_IKE, "duplicate rekey collision for CHILD_SA %s{%u}???",
+ this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa));
+ return FALSE;
}
- this->flags |= CHILD_REKEY_ADOPTED_PASSIVE;
+ /* once the passive rekeying is complete, we adopt the task */
+ DBG1(DBG_IKE, "detected rekey collision for CHILD_SA %s{%u}",
+ this->child_sa->get_name(this->child_sa),
+ this->child_sa->get_unique_id(this->child_sa));
+ this->flags |= CHILD_REKEY_PASSIVE_INSTALLED;
this->collision = other;
return TRUE;
}
{
this->child_create->task.migrate(&this->child_create->task, ike_sa);
}
- if (this->flags & CHILD_REKEY_ADOPTED_PASSIVE)
+ if (this->flags & CHILD_REKEY_PASSIVE_INSTALLED)
{
DESTROY_IF(this->collision);
}
+ array_destroy(this->deleted_spis);
this->ike_sa = ike_sa;
this->collision = NULL;
+ this->flags = 0;
}
METHOD(task_t, destroy, void,
{
this->child_delete->task.destroy(&this->child_delete->task);
}
- if (this->flags & CHILD_REKEY_ADOPTED_PASSIVE)
+ if (this->flags & CHILD_REKEY_PASSIVE_INSTALLED)
{
DESTROY_IF(this->collision);
}
+ array_destroy(this->deleted_spis);
chunk_free(&this->spi_data);
free(this);
}
.migrate = _migrate,
.destroy = _destroy,
},
- .is_redundant = _is_redundant,
+ .handle_delete = _handle_delete,
.collide = _collide,
},
.ike_sa = ike_sa,
/*
- * Copyright (C) 2016-2017 Tobias Brunner
+ * Copyright (C) 2016-2022 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
#include "test_suite.h"
#include <daemon.h>
+#include <encoding/payloads/delete_payload.h>
#include <tests/utils/exchange_test_helper.h>
#include <tests/utils/exchange_test_asserts.h>
#include <tests/utils/job_asserts.h>
assert_hook_not_called(child_updown);
/* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
- assert_hook_called(child_rekey);
+ assert_hook_not_called(child_rekey);
assert_notify(IN, REKEY_SA);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, spi_b, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
assert_hook();
/* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
- assert_hook_called(child_rekey);
+ assert_hook_rekey(child_rekey, spi_a, 3);
assert_no_notify(IN, REKEY_SA);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_NONE);
assert_hook();
/* INFORMATIONAL { D } --> */
- assert_hook_not_called(child_rekey);
+ assert_hook_rekey(child_rekey, spi_b, 4);
assert_jobs_scheduled(1);
assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_hook();
/* IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } --> */
- assert_hook_called(child_rekey);
+ assert_hook_not_called(child_rekey);
assert_payload(IN, PLV2_KEY_EXCHANGE);
assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_hook();
/* <-- IKE_FOLLOWUP_KE { KEr } */
- assert_hook_called(child_rekey);
+ assert_hook_rekey(child_rekey, spi_a, 3);
assert_payload(IN, PLV2_KEY_EXCHANGE);
assert_no_notify(IN, ADDITIONAL_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_hook();
/* INFORMATIONAL { D } --> */
- assert_hook_not_called(child_rekey);
+ assert_hook_rekey(child_rekey, spi_b, 4);
assert_jobs_scheduled(1);
assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_hook();
/* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
- assert_hook_called(child_rekey);
+ assert_hook_not_called(child_rekey);
assert_notify(IN, REKEY_SA);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, spi_b, CHILD_REKEYED);
assert_hook();
/* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
- assert_hook_called(child_rekey);
+ assert_hook_rekey(child_rekey, spi_a, 4);
assert_no_notify(IN, REKEY_SA);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_NONE);
assert_hook();
/* INFORMATIONAL { D } --> */
- assert_hook_not_called(child_rekey);
+ assert_hook_rekey(child_rekey, spi_b, 5);
assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, spi_b, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_hook_not_called(child_updown);
/* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
- assert_hook_called(child_rekey);
+ assert_hook_not_called(child_rekey);
assert_notify(IN, REKEY_SA);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 5, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
assert_hook();
/* INFORMATIONAL { D } --> */
- assert_hook_not_called(child_rekey);
+ assert_hook_called(child_rekey);
assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 5, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_hook();
/* IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } --> */
- assert_hook_called(child_rekey);
+ assert_hook_not_called(child_rekey);
assert_payload(IN, PLV2_KEY_EXCHANGE);
assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_hook();
/* <-- IKE_FOLLOWUP_KE { KEr } */
- assert_hook_called(child_rekey);
+ assert_hook_rekey(child_rekey, spi_a, 4);
assert_payload(IN, PLV2_KEY_EXCHANGE);
assert_no_notify(IN, ADDITIONAL_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_hook();
/* INFORMATIONAL { D } --> */
- assert_hook_not_called(child_rekey);
+ assert_hook_rekey(child_rekey, spi_b, 5);
assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, spi_b, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_hook();
/* IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } --> */
- assert_hook_called(child_rekey);
+ assert_hook_not_called(child_rekey);
assert_payload(IN, PLV2_KEY_EXCHANGE);
assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_hook();
/* INFORMATIONAL { D } --> */
- assert_hook_not_called(child_rekey);
+ assert_hook_called(child_rekey);
assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 5, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_hook_not_called(child_updown);
/* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
- assert_hook_called(child_rekey);
+ assert_hook_not_called(child_rekey);
assert_notify(IN, REKEY_SA);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_REKEYED);
assert_hook();
/* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
- assert_hook_called(child_rekey);
+ assert_hook_rekey(child_rekey, 1, 3);
assert_no_notify(IN, REKEY_SA);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
assert_ipsec_sas_installed(a, 1, 3, 4);
assert_hook();
- /* we don't expect this to get called anymore */
- assert_hook_not_called(child_rekey);
/* this should not produce a message, if it does there won't be a delete
* payload below */
call_ikesa(b, rekey_child_sa, PROTO_ESP, 2);
assert_child_sa_state(b, 2, CHILD_REKEYED);
/* INFORMATIONAL { D } --> */
+ assert_hook_rekey(child_rekey, 2, 4);
assert_jobs_scheduled(1);
assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_count(b, 2);
assert_ipsec_sas_installed(b, 2, 3, 4);
assert_scheduler();
+ assert_hook();
+
+ /* we don't expect this to get called anymore */
+ assert_hook_not_called(child_rekey);
/* <-- INFORMATIONAL { D } */
assert_jobs_scheduled(1);
assert_single_payload(IN, PLV2_DELETE);
assert_hook_not_called(child_updown);
/* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
- assert_hook_called(child_rekey);
+ assert_hook_not_called(child_rekey);
assert_notify(IN, REKEY_SA);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_REKEYED);
assert_hook();
/* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
- assert_hook_called(child_rekey);
+ assert_hook_rekey(child_rekey, 1, 3);
assert_no_notify(IN, REKEY_SA);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
assert_ipsec_sas_installed(a, 1, 3, 4);
assert_hook();
- /* we don't expect this to get called anymore */
- assert_hook_not_called(child_rekey);
/* this is similar to a regular delete collision, but we don't actually
- * want to send a delete back as that might conflict with a delayed
- * CREATE_CHILD_SA response */
+ * want to send a delete as that might conflict with a delayed
+ * CREATE_CHILD_SA response and the peer is expected to delete it anyway */
+ assert_hook_rekey(child_rekey, 2, 4);
call_ikesa(b, delete_child_sa, PROTO_ESP, 2, TRUE);
assert_child_sa_count(b, 1);
assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
/* the expire causes the outbound SA to get installed */
assert_ipsec_sas_installed(b, 3, 4);
+ assert_hook();
+ /* we don't expect this to get called anymore */
+ assert_hook_not_called(child_rekey);
/* INFORMATIONAL { D } --> */
assert_no_jobs_scheduled();
assert_single_payload(IN, PLV2_DELETE);
END_TEST
/**
- * Both peers initiate the CHILD_SA rekeying concurrently and should handle
- * the collision properly depending on the nonces.
+ * Check that the responder and initiator handle deletes for the new SA
+ * properly while waiting for the delete after a rekeying (e.g. if a script or
+ * user deletes the new, not fully installed, SA manually).
*/
-START_TEST(test_collision)
+START_TEST(test_regular_responder_delete)
{
ike_sa_t *a, *b;
exchange_test_helper->establish_sa(exchange_test_helper,
&a, &b, NULL);
-
- /* When rekeyings collide we get two CHILD_SAs with a total of four nonces.
- * The CHILD_SA with the lowest nonce SHOULD be deleted by the peer that
- * created that CHILD_SA. The replaced CHILD_SA is deleted by the peer that
- * initiated the surviving SA.
- * Four nonces and SPIs are needed (SPI 1 and 2 are used for the initial
- * CHILD_SA):
- * N1/3 -----\ /----- N2/4
- * \--/-----> N3/5
- * N4/6 <-------/ /----- ...
- * ... -----\
- * We test this four times, each time a different nonce is the lowest.
- */
- struct {
- /* Nonces used at each point */
- u_char nonces[4];
- /* SPIs of the deleted CHILD_SA (either redundant or replaced) */
- uint32_t spi_del_a, spi_del_b;
- /* SPIs of the kept CHILD_SA */
- uint32_t spi_a, spi_b;
- } data[] = {
- { { 0x00, 0xFF, 0xFF, 0xFF }, 3, 2, 6, 4 },
- { { 0xFF, 0x00, 0xFF, 0xFF }, 1, 4, 3, 5 },
- { { 0xFF, 0xFF, 0x00, 0xFF }, 3, 2, 6, 4 },
- { { 0xFF, 0xFF, 0xFF, 0x00 }, 1, 4, 3, 5 },
- };
-
- exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
initiate_rekey(a, 1);
assert_ipsec_sas_installed(a, 1, 2);
- exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
- initiate_rekey(b, 2);
- assert_ipsec_sas_installed(b, 1, 2);
- /* this should never get called as this results in a successful rekeying */
+ /* this should not get called until the new SA is deleted */
assert_hook_not_called(child_updown);
/* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
- exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
- assert_hook_rekey(child_rekey, 2, 5);
+ assert_hook_not_called(child_rekey);
+ assert_notify(IN, REKEY_SA);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
- assert_ipsec_sas_installed(b, 1, 2, 5);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(b, 1, 2, 4);
assert_hook();
- /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
- exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
- assert_hook_rekey(child_rekey, 1, 6);
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
+ assert_hook_rekey(child_rekey, 1, 3);
+ assert_no_notify(IN, REKEY_SA);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
- assert_ipsec_sas_installed(a, 1, 2, 6);
+ assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, 1, 3, 4);
assert_hook();
- /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
- if (data[_i].spi_del_a == 1)
- { /* currently we call this again if we keep our own replacement as we
- * already called it above */
- assert_hook_rekey(child_rekey, 1, data[_i].spi_a);
- exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_hook();
- assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED,
- CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
- CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
- CHILD_OUTBOUND_NONE);
- assert_ipsec_sas_installed(a, 1, 3, 5, 6);
- }
- else
- {
- assert_hook_not_called(child_rekey);
- exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_hook();
- assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED,
- CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
- CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
- CHILD_OUTBOUND_REGISTERED);
- assert_ipsec_sas_installed(a, 1, 2, 3, 6);
- }
- /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
- if (data[_i].spi_del_b == 2)
- {
- assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
- exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_hook();
- assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
- CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
- CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
- CHILD_OUTBOUND_NONE);
- assert_ipsec_sas_installed(b, 2, 4, 5, 6);
- }
- else
- {
- assert_hook_not_called(child_rekey);
- exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_hook();
- assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
- CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
- CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
- CHILD_OUTBOUND_REGISTERED);
- assert_ipsec_sas_installed(b, 1, 2, 4, 5);
- }
+ assert_hook_rekey(child_rekey, 2, 4);
+ call_ikesa(b, delete_child_sa, PROTO_ESP, 4, FALSE);
+ assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 4, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(b, 2);
+ /* the delete causes the outbound SA to get installed/uninstalled */
+ assert_ipsec_sas_installed(b, 2, 3, 4);
+ assert_hook();
- /* we don't expect this hook to get called anymore */
- assert_hook_not_called(child_rekey);
/* INFORMATIONAL { D } --> */
+ assert_hook_not_called(child_rekey);
assert_jobs_scheduled(1);
+ assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
- data[_i].spi_del_b == 2 ? CHILD_OUTBOUND_NONE
- : CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_state(b, data[_i].spi_del_a, CHILD_DELETED,
- CHILD_OUTBOUND_NONE);
- assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
- CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(b, 3);
- if (data[_i].spi_del_b == 2)
- {
- assert_ipsec_sas_installed(b, 2, 4, 5, 6);
- }
- else
- {
- assert_ipsec_sas_installed(b, 2, 3, 4, 5);
- }
+ assert_child_sa_state(b, 2, CHILD_DELETED);
+ assert_child_sa_state(b, 4, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, 2, 3, 4);
assert_scheduler();
+ assert_hook();
+
+ /* child_updown */
+ assert_hook();
+
+ /* we don't expect this to get called anymore */
+ assert_hook_not_called(child_rekey);
+
/* <-- INFORMATIONAL { D } */
- assert_jobs_scheduled(1);
+ assert_hook_updown(child_updown, FALSE);
+ assert_no_jobs_scheduled();
+ assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
- data[_i].spi_del_a == 1 ? CHILD_OUTBOUND_NONE
- : CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETED,
- CHILD_OUTBOUND_NONE);
- assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
- CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(a, 3);
- if (data[_i].spi_del_a == 1)
- {
- assert_ipsec_sas_installed(a, 1, 3, 5, 6);
- }
- else
- {
- assert_ipsec_sas_installed(a, 1, 3, 4, 6);
- }
+ assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+ assert_child_sa_count(a, 1);
+ assert_ipsec_sas_installed(a, 1);
assert_scheduler();
+ assert_hook();
+
/* <-- INFORMATIONAL { D } */
+ assert_hook_not_called(child_updown);
assert_jobs_scheduled(1);
+ assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETED,
- CHILD_OUTBOUND_NONE);
- assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETED,
- CHILD_OUTBOUND_NONE);
- assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
- CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(a, 3);
- assert_ipsec_sas_installed(a, 1, 3, 6,
- data[_i].spi_del_a == 1 ? 5 : 4);
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_count(a, 1);
+ assert_ipsec_sas_installed(a, 1);
assert_scheduler();
+ assert_hook();
+
/* INFORMATIONAL { D } --> */
- assert_jobs_scheduled(1);
+ assert_hook_updown(child_updown, FALSE);
+ assert_no_jobs_scheduled();
+ assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETED,
- CHILD_OUTBOUND_NONE);
- assert_child_sa_state(b, data[_i].spi_del_a, CHILD_DELETED,
- CHILD_OUTBOUND_NONE);
- assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
- CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(b, 3);
- assert_ipsec_sas_installed(b, 2, 4, 5,
- data[_i].spi_del_b == 2 ? 6 : 3);
+ assert_child_sa_state(b, 2, CHILD_DELETED);
+ assert_child_sa_count(b, 1);
+ assert_ipsec_sas_installed(b, 2);
assert_scheduler();
+ assert_hook();
- /* simulate the execution of the scheduled jobs */
- destroy_rekeyed(a, data[_i].spi_del_a);
- destroy_rekeyed(a, data[_i].spi_del_b);
- assert_child_sa_count(a, 1);
- assert_ipsec_sas_installed(a, data[_i].spi_a, data[_i].spi_b);
- destroy_rekeyed(b, data[_i].spi_del_a);
- destroy_rekeyed(b, data[_i].spi_del_b);
- assert_child_sa_count(b, 1);
- assert_ipsec_sas_installed(b, data[_i].spi_a, data[_i].spi_b);
+ /* we don't expect this to get called anymore */
+ assert_hook_not_called(child_updown);
+
+ /* simulate the execution of the scheduled job */
+ destroy_rekeyed(a, 1);
+ assert_child_sa_count(a, 0);
+ assert_ipsec_sas_installed(a);
+ destroy_rekeyed(b, 2);
+ assert_child_sa_count(b, 0);
+ assert_ipsec_sas_installed(b);
/* child_rekey/child_updown */
assert_hook();
END_TEST
/**
- * Both peers initiate a multi-KE CHILD_SA rekeying concurrently and should
- * handle the collision properly depending on the nonces.
+ * This simulates what happens if the responder for some reason lost the
+ * CHILD_SA the initiator is trying to rekey.
*/
-START_TEST(test_collision_multi_ke)
+START_TEST(test_regular_responder_lost_sa)
{
ike_sa_t *a, *b;
exchange_test_helper->establish_sa(exchange_test_helper,
- &a, &b, &multi_ke_conf);
+ &a, &b, NULL);
+ initiate_rekey(a, 1);
+ assert_ipsec_sas_installed(a, 1, 2);
+
+ /* destroy the CHILD_SA on the responder without notification */
+ call_ikesa(b, destroy_child_sa, PROTO_ESP, 2);
+ assert_child_sa_count(b, 0);
+
+ /* this should never get called as there is no successful rekeying on
+ * either side */
+ assert_hook_not_called(child_rekey);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_notify(IN, REKEY_SA);
+ assert_single_notify(OUT, CHILD_SA_NOT_FOUND);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+
+ /* <-- CREATE_CHILD_SA { N(NO_CHILD_SA) } */
+ assert_hook_updown(child_updown, FALSE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 0);
+ assert_ipsec_sas_installed(a);
+ assert_hook();
+
+ /* CREATE_CHILD_SA { SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_updown(child_updown, TRUE);
+ assert_no_notify(IN, REKEY_SA);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, 4, 5);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Ni, [KEi,] TSi, TSr } */
+ assert_hook_updown(child_updown, TRUE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, 4, 5);
+ assert_hook();
+
+ /* child_rekey */
+ assert_hook();
+
+ assert_sa_idle(a);
+ assert_sa_idle(b);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * Helper to add DELETE payload.
+ */
+typedef struct {
+ listener_t listener;
+ uint32_t spi;
+} incorrect_delete_listener_t;
+
+/**
+ * Add a DELETE payload to a message.
+ */
+static bool add_delete(incorrect_delete_listener_t *listener, ike_sa_t *ike_sa,
+ message_t *message, bool incoming, bool plain)
+{
+ delete_payload_t *payload;
+
+ if (plain && !incoming && message->get_request(message))
+ {
+ payload = delete_payload_create(PLV2_DELETE, PROTO_ESP);
+ payload->add_spi(payload, listener->spi);
+ message->add_payload(message, (payload_t*)payload);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * Send a DELETE for the given SPI from an SA.
+ */
+static void send_child_delete(ike_sa_t *sa, uint32_t spi)
+{
+ incorrect_delete_listener_t del = {
+ .listener = { .message = (void*)add_delete, },
+ .spi = spi,
+ };
+
+ exchange_test_helper->add_listener(exchange_test_helper, &del.listener);
+ call_ikesa(sa, send_dpd);
+}
+
+/**
+ * This simulates incorrect behavior by some IKEv2 responders, which send
+ * a delete for the old CHILD_SA even if there was no collision (don't know
+ * how they'd behave if there was a collision, maybe they'd send two).
+ * This is an issue if the DELETE arrives before the CREATE_CHILD_SA response.
+ */
+START_TEST(test_regular_responder_incorrect_delete)
+{
+ ike_sa_t *a, *b;
+ message_t *msg;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+ initiate_rekey(a, 1);
+ assert_ipsec_sas_installed(a, 1, 2);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_not_called(child_rekey);
+ assert_notify(IN, REKEY_SA);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYED);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(b, 1, 2, 4);
+ assert_hook();
+
+ /* delay the CREATE_CHILD_SA response */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* inject an incorrect delete for the old CHILD_SA by the responder,
+ * without messing with its internal state */
+ send_child_delete(b, 2);
+
+ /* <-- INFORMATIONAL { D } (incorrect behavior!) */
+ assert_hook_not_called(child_rekey);
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, 1, 2);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } (delayed) */
+ assert_jobs_scheduled(1);
+ assert_hook_rekey(child_rekey, 1, 3);
+ assert_no_notify(IN, REKEY_SA);
+ exchange_test_helper->process_message(exchange_test_helper, a, msg);
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, 1, 3, 4);
+ assert_hook();
+ assert_scheduler();
+
+ /* INFORMATIONAL { D } (response to incorrect DELETE) -->
+ * this is ignored here because the DPD task doesn't handle the DELETE, so
+ * we simulate handling of the delete via expire, does not delay destroy */
+ assert_no_jobs_scheduled();
+ assert_hook_rekey(child_rekey, 2, 4);
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ call_ikesa(b, delete_child_sa, PROTO_ESP, 2, TRUE);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, 3, 4);
+ assert_hook();
+ assert_scheduler();
+
+ /* we don't expect this to get called anymore */
+ assert_hook_not_called(child_rekey);
+
+ /* simulate the execution of the scheduled job */
+ destroy_rekeyed(a, 1);
+ assert_child_sa_count(a, 1);
+ assert_ipsec_sas_installed(a, 3, 4);
+ assert_child_sa_count(b, 1);
+ assert_ipsec_sas_installed(b, 3, 4);
+
+ /* child_rekey/child_updown */
+ assert_hook();
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * Both peers initiate the CHILD_SA rekeying concurrently and should handle
+ * the collision properly depending on the nonces.
+ */
+START_TEST(test_collision)
+{
+ ike_sa_t *a, *b;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ /* When rekeyings collide we get two CHILD_SAs with a total of four nonces.
+ * The CHILD_SA with the lowest nonce SHOULD be deleted by the peer that
+ * created that CHILD_SA. The replaced CHILD_SA is deleted by the peer that
+ * initiated the surviving SA.
+ * Four nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * CHILD_SA):
+ * N1/3 -----\ /----- N2/4
+ * \--/-----> N3/5
+ * N4/6 <-------/ /----- ...
+ * ... -----\
+ * We test this four times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[4];
+ /* SPIs of the deleted CHILD_SA (either redundant or replaced) */
+ uint32_t spi_del_a, spi_del_b;
+ /* SPIs of the kept CHILD_SA */
+ uint32_t spi_a, spi_b;
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF, 0xFF }, 3, 2, 6, 4 },
+ { { 0xFF, 0x00, 0xFF, 0xFF }, 1, 4, 3, 5 },
+ { { 0xFF, 0xFF, 0x00, 0xFF }, 3, 2, 6, 4 },
+ { { 0xFF, 0xFF, 0xFF, 0x00 }, 1, 4, 3, 5 },
+ };
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a, 1);
+ assert_ipsec_sas_installed(a, 1, 2);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b, 2);
+ assert_ipsec_sas_installed(b, 1, 2);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(b, 1, 2, 5);
+ assert_hook();
+ /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(a, 1, 2, 6);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
+ if (data[_i].spi_del_a == 1)
+ {
+ assert_hook_rekey(child_rekey, 1, data[_i].spi_a);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_hook();
+ assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, 1, 3, 5, 6);
+ }
+ else
+ {
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_hook();
+ assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+ CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(a, 1, 2, 3, 6);
+ }
+ assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
+ CHILD_OUTBOUND_NONE);
+ /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
+ if (data[_i].spi_del_b == 2)
+ {
+ assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_hook();
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, 2, 4, 5, 6);
+ }
+ else
+ {
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_hook();
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+ CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(b, 1, 2, 4, 5);
+ }
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+ CHILD_OUTBOUND_NONE);
+
+ /* INFORMATIONAL { D } --> */
+ assert_jobs_scheduled(1);
+ if (data[_i].spi_del_b == 2)
+ {
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_hook();
+ }
+ else
+ {
+ assert_hook_rekey(child_rekey, 2, 5);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_hook();
+ }
+ assert_scheduler();
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(b, 3);
+ if (data[_i].spi_del_b == 2)
+ {
+ assert_ipsec_sas_installed(b, 2, 4, 5, 6);
+ }
+ else
+ {
+ assert_ipsec_sas_installed(b, 2, 3, 4, 5);
+ }
+ /* <-- INFORMATIONAL { D } */
+ assert_jobs_scheduled(1);
+ if (data[_i].spi_del_a == 1)
+ {
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_hook();
+ }
+ else
+ {
+ assert_hook_rekey(child_rekey, 1, 6);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_hook();
+ }
+ assert_scheduler();
+ assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 3);
+ if (data[_i].spi_del_a == 1)
+ {
+ assert_ipsec_sas_installed(a, 1, 3, 5, 6);
+ }
+ else
+ {
+ assert_ipsec_sas_installed(a, 1, 3, 4, 6);
+ }
+
+ /* we don't expect this to get called anymore */
+ assert_hook_not_called(child_rekey);
+
+ /* <-- INFORMATIONAL { D } */
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 3);
+ assert_ipsec_sas_installed(a, 1, 3, 6,
+ data[_i].spi_del_a == 1 ? 5 : 4);
+ assert_scheduler();
+ /* INFORMATIONAL { D } --> */
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(b, 3);
+ assert_ipsec_sas_installed(b, 2, 4, 5,
+ data[_i].spi_del_b == 2 ? 6 : 3);
+ assert_scheduler();
+
+ /* simulate the execution of the scheduled jobs */
+ destroy_rekeyed(a, data[_i].spi_del_a);
+ destroy_rekeyed(a, data[_i].spi_del_b);
+ assert_child_sa_count(a, 1);
+ assert_ipsec_sas_installed(a, data[_i].spi_a, data[_i].spi_b);
+ destroy_rekeyed(b, data[_i].spi_del_a);
+ destroy_rekeyed(b, data[_i].spi_del_b);
+ assert_child_sa_count(b, 1);
+ assert_ipsec_sas_installed(b, data[_i].spi_a, data[_i].spi_b);
+
+ /* child_rekey/child_updown */
+ assert_hook();
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * Both peers initiate a multi-KE CHILD_SA rekeying concurrently and should
+ * handle the collision properly depending on the nonces.
+ */
+START_TEST(test_collision_multi_ke)
+{
+ ike_sa_t *a, *b;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, &multi_ke_conf);
+
+ /* When rekeyings collide we get two CHILD_SAs with a total of four nonces.
+ * The CHILD_SA with the lowest nonce SHOULD be deleted by the peer that
+ * created that CHILD_SA. However, with multiple key exchanges, no CHILD_SA
+ * has yet been established, so the losing peer just doesn't continue with
+ * IKE_FOLLOWUP_KE exchanges (i.e. that SA is not explicitly deleted later).
+ * The replaced CHILD_SA is deleted by the peer that initiated the
+ * surviving SA. Four nonces and SPIs are needed (SPI 1 and 2 are used for
+ * the initial CHILD_SA):
+ * N1/3 -----\ /----- N2/4
+ * \--/-----> N3/5
+ * N4/6 <-------/ /----- ...
+ * ... -----\
+ * We test this four times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[4];
+ /* SPIs of the deleted CHILD_SA (either redundant or replaced) */
+ uint32_t spi_del_a, spi_del_b;
+ /* SPIs of the kept CHILD_SA */
+ uint32_t spi_a, spi_b;
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF, 0xFF }, 3, 2, 6, 4 },
+ { { 0xFF, 0x00, 0xFF, 0xFF }, 1, 4, 3, 5 },
+ { { 0xFF, 0xFF, 0x00, 0xFF }, 3, 2, 6, 4 },
+ { { 0xFF, 0xFF, 0xFF, 0x00 }, 1, 4, 3, 5 },
+ };
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a, 1);
+ assert_ipsec_sas_installed(a, 1, 2);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b, 2);
+ assert_ipsec_sas_installed(b, 1, 2);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, KEi, TSi, TSr } --> */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, 1, 2);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, KEi, TSi, TSr } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, 1, 2);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, KEr, TSi, TSr, N(ADD_KE) } */
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ /* if a won, it must remove the passive task, otherwise the active task,
+ * no new SA is yet created */
+ assert_num_tasks(a, data[_i].spi_del_a == 1 ? 0 : 1, TASK_QUEUE_PASSIVE);
+ assert_num_tasks(a, data[_i].spi_del_a == 1 ? 1 : 0, TASK_QUEUE_ACTIVE);
+ assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, 1, 2);
+ assert_hook();
+
+ /* CREATE_CHILD_SA { SA, Nr, KEr, TSi, TSr, N(ADD_KE) } --> */
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_num_tasks(b, data[_i].spi_del_b == 2 ? 0 : 1, TASK_QUEUE_PASSIVE);
+ assert_num_tasks(b, data[_i].spi_del_b == 2 ? 1 : 0, TASK_QUEUE_ACTIVE);
+ assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, 1, 2);
+ assert_hook();
+
+ if (data[_i].spi_del_a == 1)
+ {
+ /* IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } --> */
+ assert_hook_not_called(child_rekey);
+ assert_payload(IN, PLV2_KEY_EXCHANGE);
+ assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(b, 1, 2, 5);
+ assert_hook();
+
+ /* <-- IKE_FOLLOWUP_KE { KEr } */
+ assert_hook_rekey(child_rekey, 1, data[_i].spi_a);
+ assert_payload(IN, PLV2_KEY_EXCHANGE);
+ assert_no_notify(IN, ADDITIONAL_KEY_EXCHANGE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, 1, 3, 5);
+ assert_hook();
+ }
+ else
+ {
+ /* <-- IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } */
+ assert_hook_not_called(child_rekey);
+ assert_payload(IN, PLV2_KEY_EXCHANGE);
+ assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(a, 1, 2, 6);
+ assert_hook();
+
+ /* IKE_FOLLOWUP_KE { KEr } --> */
+ assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
+ assert_payload(IN, PLV2_KEY_EXCHANGE);
+ assert_no_notify(IN, ADDITIONAL_KEY_EXCHANGE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, 2, 4, 6);
+ assert_hook();
+ }
+
+ if (data[_i].spi_del_a == 1)
+ {
+ /* INFORMATIONAL { D } --> */
+ assert_hook_rekey(child_rekey, 2, 5);
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(b, 2);
+ assert_ipsec_sas_installed(b, 2, 3, 5);
+ assert_scheduler();
+ assert_hook();
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_not_called(child_rekey);
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, 1, 3, 5);
+ assert_scheduler();
+ assert_hook();
+ }
+ else
+ {
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_rekey(child_rekey, 1, 6);
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, 1, 4, 6);
+ assert_scheduler();
+ assert_hook();
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_not_called(child_rekey);
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(b, 2);
+ assert_ipsec_sas_installed(b, 2, 4, 6);
+ assert_scheduler();
+ assert_hook();
+ }
+
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(child_rekey);
+
+ /* simulate the execution of the scheduled jobs */
+ destroy_rekeyed(a, data[_i].spi_del_a == 1 ? data[_i].spi_del_a
+ : data[_i].spi_del_b);
+ destroy_rekeyed(b, data[_i].spi_del_a == 1 ? data[_i].spi_del_a
+ : data[_i].spi_del_b);
+
+ assert_child_sa_count(a, 1);
+ assert_ipsec_sas_installed(a, data[_i].spi_a, data[_i].spi_b);
+ assert_child_sa_count(b, 1);
+ assert_ipsec_sas_installed(b, data[_i].spi_a, data[_i].spi_b);
+
+ /* child_rekey/child_updown */
+ assert_hook();
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * Both peers initiate a CHILD_SA rekeying concurrently, but only one of them
+ * proposes multiple key exchanges, they should still handle the collision
+ * properly.
+ */
+START_TEST(test_collision_mixed)
+{
+ exchange_test_sa_conf_t conf = {
+ .initiator = {
+ .esp = "aes256-sha256-modp3072-ke1_ecp256,aes256-sha256-modp3072",
+ },
+ .responder = {
+ .esp = "aes256-sha256-modp3072,aes256-sha256-modp3072-ke1_ecp256",
+ },
+ };
+ ike_sa_t *a, *b;
+
+ /* let's accept what the peer proposes first */
+ lib->settings->set_bool(lib->settings, "%s.prefer_configured_proposals",
+ FALSE, lib->ns);
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, &conf);
/* When rekeyings collide we get two CHILD_SAs with a total of four nonces.
* The CHILD_SA with the lowest nonce SHOULD be deleted by the peer that
exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
- assert_ipsec_sas_installed(a, 1, 2);
+ assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(a, 1, 2, 6);
assert_hook();
/* <-- CREATE_CHILD_SA { SA, Nr, KEr, TSi, TSr, N(ADD_KE) } */
- assert_hook_not_called(child_rekey);
- exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- /* if a won, it must remove the passive task, otherwise the active task,
- * no new SA is yet created */
- assert_num_tasks(a, data[_i].spi_del_a == 1 ? 0 : 1, TASK_QUEUE_PASSIVE);
- assert_num_tasks(a, data[_i].spi_del_a == 1 ? 1 : 0, TASK_QUEUE_ACTIVE);
- assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
- assert_ipsec_sas_installed(a, 1, 2);
- assert_hook();
-
- /* CREATE_CHILD_SA { SA, Nr, KEr, TSi, TSr, N(ADD_KE) } --> */
- assert_hook_not_called(child_rekey);
- exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_num_tasks(b, data[_i].spi_del_b == 2 ? 0 : 1, TASK_QUEUE_PASSIVE);
- assert_num_tasks(b, data[_i].spi_del_b == 2 ? 1 : 0, TASK_QUEUE_ACTIVE);
- assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
- assert_ipsec_sas_installed(b, 1, 2);
- assert_hook();
+ if (data[_i].spi_del_a == 1)
+ { /* a's multi-KE SA is the winner */
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ /* the single-KE passive task was completed and adopted */
+ assert_num_tasks(a, 0, TASK_QUEUE_PASSIVE);
+ assert_num_tasks(a, 1, TASK_QUEUE_ACTIVE);
+ assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, 6, CHILD_REKEYED, CHILD_OUTBOUND_NONE);
+ assert_hook();
+ }
+ else
+ { /* b's single-KE SA is the winner */
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ /* the single-KE passive task was completed and adopted */
+ assert_num_tasks(a, 0, TASK_QUEUE_PASSIVE);
+ assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE);
+ assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_hook();
+ }
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, 1, 2, 6);
if (data[_i].spi_del_a == 1)
{
+ /* CREATE_CHILD_SA { SA, Nr, KEr, TSi, TSr, N(ADD_KE) } --> */
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_num_tasks(b, 1, TASK_QUEUE_PASSIVE);
+ assert_num_tasks(b, 1, TASK_QUEUE_ACTIVE);
+ assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, 4, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+ assert_child_sa_count(b, 2);
+ assert_ipsec_sas_installed(b, 1, 2, 4);
+ assert_hook();
+
/* IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } --> */
- assert_hook_rekey(child_rekey, 2, 5);
+ assert_hook_not_called(child_rekey);
assert_payload(IN, PLV2_KEY_EXCHANGE);
assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, 4, CHILD_DELETING, CHILD_OUTBOUND_NONE);
assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
- assert_ipsec_sas_installed(b, 1, 2, 5);
+ assert_child_sa_count(b, 3);
+ assert_ipsec_sas_installed(b, 1, 2, 4, 5);
+ assert_hook();
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_not_called(child_rekey);
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, 6, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, 1, 2, 6);
+ assert_scheduler();
assert_hook();
/* <-- IKE_FOLLOWUP_KE { KEr } */
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_ipsec_sas_installed(a, 1, 3, 5);
+ assert_child_sa_state(a, 6, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_ipsec_sas_installed(a, 1, 3, 5, 6);
assert_hook();
}
else
{
- /* <-- IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } */
- assert_hook_rekey(child_rekey, 1, 6);
- assert_payload(IN, PLV2_KEY_EXCHANGE);
- assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
- exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
- assert_ipsec_sas_installed(a, 1, 2, 6);
- assert_hook();
-
- /* IKE_FOLLOWUP_KE { KEr } --> */
- assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
- assert_payload(IN, PLV2_KEY_EXCHANGE);
- assert_no_notify(IN, ADDITIONAL_KEY_EXCHANGE);
+ /* CREATE_CHILD_SA { SA, Nr, KEr, TSi, TSr, N(ADD_KE) } --> */
+ assert_hook_rekey(child_rekey, 2, 4);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_num_tasks(b, 0, TASK_QUEUE_PASSIVE);
+ assert_num_tasks(b, 1, TASK_QUEUE_ACTIVE);
assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_NONE);
assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 2);
assert_ipsec_sas_installed(b, 2, 4, 6);
assert_hook();
}
- /* we don't expect this hook to get called anymore */
- assert_hook_not_called(child_rekey);
-
if (data[_i].spi_del_a == 1)
{
/* INFORMATIONAL { D } --> */
+ assert_hook_not_called(child_rekey);
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, 4, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_child_sa_count(b, 3);
+ assert_ipsec_sas_installed(b, 1, 2, 4, 5);
+ assert_scheduler();
+ assert_hook();
+
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_rekey(child_rekey, 2, 5);
assert_jobs_scheduled(1);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 4, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(b, 2);
- assert_ipsec_sas_installed(b, 2, 3, 5);
+ assert_child_sa_count(b, 3);
+ assert_ipsec_sas_installed(b, 2, 4, 3, 5);
assert_scheduler();
+ assert_hook();
+
+ assert_hook_not_called(child_rekey);
/* <-- INFORMATIONAL { D } */
assert_jobs_scheduled(1);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(a, 2);
- assert_ipsec_sas_installed(a, 1, 3, 5);
+ assert_child_sa_state(a, 6, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_count(a, 3);
+ assert_ipsec_sas_installed(a, 1, 3, 5, 6);
assert_scheduler();
/* simulate the execution of the scheduled jobs */
destroy_rekeyed(a, data[_i].spi_del_a);
+ destroy_rekeyed(a, data[_i].spi_del_b);
destroy_rekeyed(b, data[_i].spi_del_a);
+ destroy_rekeyed(b, data[_i].spi_del_b);
+
+ assert_hook();
}
else
{
/* <-- INFORMATIONAL { D } */
+ assert_hook_rekey(child_rekey, 1, 6);
assert_jobs_scheduled(1);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_child_sa_count(a, 2);
assert_ipsec_sas_installed(a, 1, 4, 6);
assert_scheduler();
+ assert_hook();
+
+ assert_hook_not_called(child_rekey);
/* INFORMATIONAL { D } --> */
assert_jobs_scheduled(1);
/* simulate the execution of the scheduled jobs */
destroy_rekeyed(a, data[_i].spi_del_b);
destroy_rekeyed(b, data[_i].spi_del_b);
+
+ assert_hook();
}
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(child_rekey);
+
assert_child_sa_count(a, 1);
assert_ipsec_sas_installed(a, data[_i].spi_a, data[_i].spi_b);
assert_child_sa_count(b, 1);
END_TEST
/**
- * Both peers initiate a CHILD_SA rekeying concurrently, but only one of them
- * proposes multiple key exchanges, they should still handle the collision
- * properly.
+ * This is like the rekey collision above, but one peer deletes the
+ * redundant/old SA before the other peer receives the CREATE_CHILD_SA
+ * response:
+ *
+ * rekey ----\ /---- rekey
+ * \-----/----> detect collision
+ * detect collision <---------/ /----
+ * ----\ /
+ * \----/----->
+ * handle delete <--------/------- delete SA
+ * --------/------->
+ * handle rekey <------/
+ * delete SA ---------------->
+ * <----------------
*/
-START_TEST(test_collision_mixed)
+START_TEST(test_collision_delayed_response)
{
- exchange_test_sa_conf_t conf = {
- .initiator = {
- .esp = "aes256-sha256-modp3072-ke1_ecp256,aes256-sha256-modp3072",
- },
- .responder = {
- .esp = "aes256-sha256-modp3072,aes256-sha256-modp3072-ke1_ecp256",
- },
- };
ike_sa_t *a, *b;
-
- /* let's accept what the peer proposes first */
- lib->settings->set_bool(lib->settings, "%s.prefer_configured_proposals",
- FALSE, lib->ns);
+ message_t *msg;
exchange_test_helper->establish_sa(exchange_test_helper,
- &a, &b, &conf);
+ &a, &b, NULL);
- /* When rekeyings collide we get two CHILD_SAs with a total of four nonces.
- * The CHILD_SA with the lowest nonce SHOULD be deleted by the peer that
- * created that CHILD_SA. However, with multiple key exchanges, no CHILD_SA
- * has yet been established, so the losing peer just doesn't continue with
- * IKE_FOLLOWUP_KE exchanges (i.e. that SA is not explicitly deleted later).
- * The replaced CHILD_SA is deleted by the peer that initiated the
- * surviving SA. Four nonces and SPIs are needed (SPI 1 and 2 are used for
- * the initial CHILD_SA):
+ /* Four nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * CHILD_SA):
* N1/3 -----\ /----- N2/4
* \--/-----> N3/5
* N4/6 <-------/ /----- ...
/* this should never get called as this results in a successful rekeying */
assert_hook_not_called(child_updown);
- /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, KEi, TSi, TSr } --> */
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
- assert_ipsec_sas_installed(b, 1, 2);
+ assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(b, 1, 2, 5);
assert_hook();
-
- /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, KEi, TSi, TSr } */
+ /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
- assert_hook_rekey(child_rekey, 1, 6);
+ assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
assert_ipsec_sas_installed(a, 1, 2, 6);
assert_hook();
- /* <-- CREATE_CHILD_SA { SA, Nr, KEr, TSi, TSr, N(ADD_KE) } */
- assert_hook_not_called(child_rekey);
- exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- /* the single-KE passive task was completed and adopted above */
- assert_num_tasks(a, 0, TASK_QUEUE_PASSIVE);
- if (data[_i].spi_del_a == 1)
+ /* delay the CREATE_CHILD_SA response from b to a */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
+ if (data[_i].spi_del_b == 2)
+ {
+ assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, 2, 4, 5, 6);
+ assert_hook();
+ }
+ else
+ {
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+ CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(b, 1, 2, 4, 5);
+ assert_hook();
+ }
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+ CHILD_OUTBOUND_NONE);
+
+ /* <-- INFORMATIONAL { D } */
+ assert_no_jobs_scheduled();
+ if (data[_i].spi_del_b == 2)
+ {
+ assert_hook_rekey(child_rekey, 1, 6);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, 1, 4, 6);
+ assert_hook();
+ }
+ else
+ {
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETING,
+ CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(a, 1, 2, 6);
+ assert_hook();
+ }
+ assert_scheduler();
+ assert_child_sa_count(a, 2);
+ /* INFORMATIONAL { D } --> */
+ assert_hook_not_called(child_rekey);
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ if (data[_i].spi_del_b == 2)
{
- assert_num_tasks(a, 1, TASK_QUEUE_ACTIVE);
- assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(a, 6, CHILD_REKEYED, CHILD_OUTBOUND_REGISTERED);
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, 2, 4, 5, 6);
}
else
{
- assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE);
- assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+ CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(b, 1, 2, 4, 5);
}
- assert_child_sa_count(a, 2);
- assert_ipsec_sas_installed(a, 1, 2, 6);
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_count(b, 3);
+ assert_scheduler();
assert_hook();
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } (delayed) */
+ /* the second job here is for the retransmit of the delete */
+ assert_jobs_scheduled(2);
if (data[_i].spi_del_a == 1)
{
- /* CREATE_CHILD_SA { SA, Nr, KEr, TSi, TSr, N(ADD_KE) } --> */
- assert_hook_not_called(child_rekey);
- exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_num_tasks(b, 1, TASK_QUEUE_PASSIVE);
- assert_num_tasks(b, 1, TASK_QUEUE_ACTIVE);
- assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(b, 4, CHILD_DELETING, CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_count(b, 2);
- assert_ipsec_sas_installed(b, 1, 2, 4);
- assert_hook();
-
- /* IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } --> */
- assert_hook_rekey(child_rekey, 2, 5);
- assert_payload(IN, PLV2_KEY_EXCHANGE);
- assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
- exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(b, 4, CHILD_DELETING, CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_count(b, 3);
- assert_ipsec_sas_installed(b, 1, 2, 4, 5);
- assert_hook();
-
- /* <-- INFORMATIONAL { D } */
- assert_jobs_scheduled(1);
- exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(a, 6, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_child_sa_count(a, 2);
- assert_ipsec_sas_installed(a, 1, 2, 6);
- assert_scheduler();
-
- /* <-- IKE_FOLLOWUP_KE { KEr } */
- /* currently we call this again if we keep our own replacement as we
- * already called it above */
assert_hook_rekey(child_rekey, 1, data[_i].spi_a);
- assert_payload(IN, PLV2_KEY_EXCHANGE);
- assert_no_notify(IN, ADDITIONAL_KEY_EXCHANGE);
- exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(a, 6, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_ipsec_sas_installed(a, 1, 3, 5, 6);
+ exchange_test_helper->process_message(exchange_test_helper, a, msg);
assert_hook();
}
else
{
- /* CREATE_CHILD_SA { SA, Nr, KEr, TSi, TSr, N(ADD_KE) } --> */
- assert_hook_rekey(child_rekey, 2, 4);
- exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_num_tasks(b, 0, TASK_QUEUE_PASSIVE);
- assert_num_tasks(b, 1, TASK_QUEUE_ACTIVE);
- assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(a, 2);
- assert_ipsec_sas_installed(b, 2, 4, 6);
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, msg);
assert_hook();
}
+ assert_scheduler();
+ assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 3);
+ assert_ipsec_sas_installed(a, 1, 3, 6,
+ data[_i].spi_del_a == 1 ? 5 : 4);
- /* we don't expect this hook to get called anymore */
- assert_hook_not_called(child_rekey);
-
- if (data[_i].spi_del_a == 1)
+ /* INFORMATIONAL { D } --> */
+ assert_jobs_scheduled(1);
+ if (data[_i].spi_del_b == 2)
{
- /* INFORMATIONAL { D } --> */
- assert_jobs_scheduled(1);
- exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(b, 4, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_count(b, 3);
- assert_ipsec_sas_installed(b, 1, 2, 4, 5);
- assert_scheduler();
-
- /* INFORMATIONAL { D } --> */
- assert_jobs_scheduled(1);
+ assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_child_sa_state(b, 2, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(b, 4, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(b, 3);
- assert_ipsec_sas_installed(b, 2, 4, 3, 5);
- assert_scheduler();
-
- /* <-- INFORMATIONAL { D } */
- assert_jobs_scheduled(1);
- exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(a, 6, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_child_sa_count(a, 3);
- assert_ipsec_sas_installed(a, 1, 3, 5, 6);
- assert_scheduler();
-
- /* simulate the execution of the scheduled jobs */
- destroy_rekeyed(a, data[_i].spi_del_a);
- destroy_rekeyed(a, data[_i].spi_del_b);
- destroy_rekeyed(b, data[_i].spi_del_a);
- destroy_rekeyed(b, data[_i].spi_del_b);
+ assert_hook();
}
else
{
- /* <-- INFORMATIONAL { D } */
- assert_jobs_scheduled(1);
- exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(a, 2);
- assert_ipsec_sas_installed(a, 1, 4, 6);
- assert_scheduler();
-
- /* INFORMATIONAL { D } --> */
- assert_jobs_scheduled(1);
+ assert_hook_rekey(child_rekey, 2, 5);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_child_sa_state(b, 2, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(b, 2);
- assert_ipsec_sas_installed(b, 2, 4, 6);
- assert_scheduler();
-
- /* simulate the execution of the scheduled jobs */
- destroy_rekeyed(a, data[_i].spi_del_b);
- destroy_rekeyed(b, data[_i].spi_del_b);
+ assert_hook();
}
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, 2, 4, 5,
+ data[_i].spi_del_b == 2 ? 6 : 3);
+ assert_child_sa_count(b, 3);
+ assert_scheduler();
+
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(child_rekey);
+ /* <-- INFORMATIONAL { D } */
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, 1, 3, 6,
+ data[_i].spi_del_a == 1 ? 5 : 4);
+ assert_child_sa_count(a, 3);
+ assert_scheduler();
+ /* simulate the execution of the scheduled jobs */
+ destroy_rekeyed(a, data[_i].spi_del_a);
+ destroy_rekeyed(a, data[_i].spi_del_b);
assert_child_sa_count(a, 1);
assert_ipsec_sas_installed(a, data[_i].spi_a, data[_i].spi_b);
+ destroy_rekeyed(b, data[_i].spi_del_a);
+ destroy_rekeyed(b, data[_i].spi_del_b);
assert_child_sa_count(b, 1);
assert_ipsec_sas_installed(b, data[_i].spi_a, data[_i].spi_b);
/**
* This is like the rekey collision above, but one peer deletes the
- * redundant/old SA before the other peer receives the CREATE_CHILD_SA
- * response:
+ * redundant/old SA and then also the new one before the other peer receives
+ * the CREATE_CHILD_SA response:
*
* rekey ----\ /---- rekey
* \-----/----> detect collision
* detect collision <---------/ /----
* ----\ /
* \----/----->
- * handle delete <--------/------- delete SA
+ * handle delete <--------/------- delete old SA
* --------/------->
- * handle rekey <------/
- * delete SA ---------------->
- * <----------------
+ * handle delete <------/--------- delete new SA
+ * ------/--------->
+ * ignore rekey <----/
*/
-START_TEST(test_collision_delayed_response)
+START_TEST(test_collision_delayed_response_delete)
{
ike_sa_t *a, *b;
message_t *msg;
initiate_rekey(b, 2);
assert_ipsec_sas_installed(b, 1, 2);
- /* this should never get called as this results in a successful rekeying */
+ /* this should not get called until the replacement SA is deleted */
assert_hook_not_called(child_updown);
/* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
- assert_hook_rekey(child_rekey, 2, 5);
+ assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
assert_hook();
/* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
- assert_hook_rekey(child_rekey, 1, 6);
+ assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
{
assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_hook();
assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
- CHILD_OUTBOUND_REGISTERED);
+ CHILD_OUTBOUND_NONE);
assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
- CHILD_OUTBOUND_NONE);
assert_ipsec_sas_installed(b, 2, 4, 5, 6);
+ assert_hook();
}
else
{
assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_hook();
assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
CHILD_OUTBOUND_INSTALLED);
assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
- CHILD_OUTBOUND_REGISTERED);
assert_ipsec_sas_installed(b, 1, 2, 4, 5);
+ assert_hook();
}
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+ CHILD_OUTBOUND_NONE);
/* <-- INFORMATIONAL { D } */
- assert_hook_not_called(child_rekey);
- assert_jobs_scheduled(1);
- exchange_test_helper->process_message(exchange_test_helper, a, NULL);
if (data[_i].spi_del_b == 2)
{
+ assert_hook_rekey(child_rekey, 1, 6);
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
CHILD_OUTBOUND_INSTALLED);
assert_ipsec_sas_installed(a, 1, 4, 6);
+ assert_scheduler();
+ assert_hook();
}
else
{
- assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETED,
- CHILD_OUTBOUND_NONE);
+ assert_hook_not_called(child_rekey);
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETING,
+ CHILD_OUTBOUND_REGISTERED);
assert_ipsec_sas_installed(a, 1, 2, 6);
+ assert_scheduler();
+ assert_hook();
}
assert_child_sa_count(a, 2);
- assert_scheduler();
/* INFORMATIONAL { D } --> */
+ assert_hook_not_called(child_rekey);
assert_jobs_scheduled(1);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
if (data[_i].spi_del_b == 2)
{
- assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
- CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, 2, 4, 5, 6);
+ }
+ else
+ {
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+ CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(b, 1, 2, 4, 5);
+ }
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_count(b, 3);
+ assert_scheduler();
+ assert_hook();
+
+ /* trigger a delete for the new CHILD_SA */
+ call_ikesa(b, delete_child_sa, PROTO_ESP, data[_i].spi_b, FALSE);
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETED,
+ CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, data[_i].spi_b, CHILD_DELETING,
+ CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(b, 3);
+ assert_ipsec_sas_installed(b, 2, 4, 5,
+ data[_i].spi_del_b == 2 ? 6 : 3);
+
+ /* child_updown */
+ assert_hook();
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_not_called(child_rekey);
+ assert_hook_not_called(child_updown);
+ if (data[_i].spi_del_b == 2)
+ {
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, data[_i].spi_a, CHILD_DELETING,
CHILD_OUTBOUND_INSTALLED);
- assert_ipsec_sas_installed(b, 2, 4, 5, 6);
+ assert_ipsec_sas_installed(a, 1, 4, 6);
+ assert_scheduler();
}
else
{
- assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
- CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETING,
CHILD_OUTBOUND_REGISTERED);
- assert_ipsec_sas_installed(b, 1, 2, 4, 5);
+ assert_ipsec_sas_installed(a, 1, 2, 6);
+ assert_scheduler();
}
+ assert_child_sa_count(a, 2);
+ assert_hook();
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_updown(child_updown, FALSE);
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+ CHILD_OUTBOUND_NONE);
assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETED,
CHILD_OUTBOUND_NONE);
- assert_child_sa_count(b, 3);
+ assert_child_sa_count(b, 2);
+ assert_ipsec_sas_installed(b, 2, data[_i].spi_del_b == 2 ? 5 : 4);
assert_scheduler();
assert_hook();
+ /* child_rekey */
+ assert_hook();
/* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } (delayed) */
+ assert_hook_updown(child_updown, FALSE);
if (data[_i].spi_del_a == 1)
{
- assert_hook_rekey(child_rekey, 1, data[_i].spi_a);
+ assert_hook_rekey(child_rekey, 1, 3);
exchange_test_helper->process_message(exchange_test_helper, a, msg);
- assert_hook();
assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
- CHILD_OUTBOUND_NONE);
- assert_ipsec_sas_installed(a, 1, 3, 5, 6);
+ CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, 1, 2, 6);
+ assert_hook();
}
else
{
assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, a, msg);
- assert_hook();
assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
- CHILD_OUTBOUND_REGISTERED);
- assert_ipsec_sas_installed(a, 1, 3, 4, 6);
+ CHILD_OUTBOUND_NONE);
+ assert_ipsec_sas_installed(a, 1, 3);
+ assert_hook();
}
assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETED,
CHILD_OUTBOUND_NONE);
- assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
- CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(a, 3);
+ assert_child_sa_count(a, 2);
+ assert_hook();
- /* we don't expect this hook to get called anymore */
+ /* we don't expect these hooks to get called anymore */
+ assert_hook_not_called(child_updown);
assert_hook_not_called(child_rekey);
/* INFORMATIONAL { D } --> */
assert_jobs_scheduled(1);
CHILD_OUTBOUND_NONE);
assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETED,
CHILD_OUTBOUND_NONE);
- assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
- CHILD_OUTBOUND_INSTALLED);
- assert_ipsec_sas_installed(b, 2, 4, 5,
- data[_i].spi_del_b == 2 ? 6 : 3);
- assert_child_sa_count(b, 3);
+ assert_ipsec_sas_installed(b, 2, data[_i].spi_del_b == 2 ? 5 : 4);
+ assert_child_sa_count(b, 2);
assert_scheduler();
/* <-- INFORMATIONAL { D } */
assert_jobs_scheduled(1);
CHILD_OUTBOUND_NONE);
assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETED,
CHILD_OUTBOUND_NONE);
- assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
- CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(a, 3);
- assert_ipsec_sas_installed(a, 1, 3, 6,
- data[_i].spi_del_a == 1 ? 5 : 4);
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, 1, data[_i].spi_del_a == 1 ? 6 : 3);
assert_scheduler();
/* simulate the execution of the scheduled jobs */
destroy_rekeyed(a, data[_i].spi_del_a);
destroy_rekeyed(a, data[_i].spi_del_b);
- assert_child_sa_count(a, 1);
- assert_ipsec_sas_installed(a, data[_i].spi_a, data[_i].spi_b);
+ assert_child_sa_count(a, 0);
+ assert_ipsec_sas_installed(a);
destroy_rekeyed(b, data[_i].spi_del_a);
destroy_rekeyed(b, data[_i].spi_del_b);
- assert_child_sa_count(b, 1);
- assert_ipsec_sas_installed(b, data[_i].spi_a, data[_i].spi_b);
+ assert_child_sa_count(b, 0);
+ assert_ipsec_sas_installed(b);
/* child_rekey/child_updown */
assert_hook();
assert_hook();
/* <-- IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } */
- assert_hook_rekey(child_rekey, 1, 6);
+ assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
assert_ipsec_sas_installed(b, 2, 4, 6);
assert_hook();
- /* we don't expect this hook to get called anymore */
- assert_hook_not_called(child_rekey);
-
if (!after_delete)
{ /* a receives the response right after the IKE_FOLLOWUP_KE, the passive
* rekeying is completed and the active aborted */
/* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } (delayed) */
+ assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, a, msg);
assert_num_tasks(a, 0, TASK_QUEUE_PASSIVE);
assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE);
assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
assert_ipsec_sas_installed(a, 1, 2, 6);
+ assert_hook();
+ }
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_rekey(child_rekey, 1, 6);
+ assert_jobs_scheduled(after_delete ? 0 : 1);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, 1, 4, 6);
+ assert_child_sa_count(a, 2);
+ assert_scheduler();
+ assert_hook();
+
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(child_rekey);
+
+ /* INFORMATIONAL { D } --> */
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, 2, 4, 6);
+ assert_child_sa_count(b, 2);
+ assert_scheduler();
+
+ if (after_delete)
+ {
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } (delayed) */
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, a, msg);
+ assert_num_tasks(a, 0, TASK_QUEUE_PASSIVE);
+ assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE);
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(a, 1, 4, 6);
+ assert_scheduler();
+ }
+
+ /* simulate the execution of the scheduled jobs */
+ destroy_rekeyed(a, data[_i].spi_del_b);
+ assert_child_sa_count(a, 1);
+ assert_ipsec_sas_installed(a, data[_i].spi_a, data[_i].spi_b);
+ destroy_rekeyed(b, data[_i].spi_del_b);
+ assert_child_sa_count(b, 1);
+ assert_ipsec_sas_installed(b, data[_i].spi_a, data[_i].spi_b);
+
+ /* child_rekey/child_updown */
+ assert_hook();
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
+/**
+ * In this scenario one of the peers does not notice that there is a
+ * rekey collision:
+ *
+ * rekey ----\ /---- rekey
+ * \ /
+ * detect collision <-----\---/
+ * -------\-------->
+ * \ /---- delete old SA
+ * \-/----> detect collision
+ * handle delete <---------/ /---- TEMP_FAIL
+ * -----------/---->
+ * aborts rekeying <---------/
+ *
+ * Besides the scenario depicted above, i.e. where the response arrives after
+ * handling B's delete request, we also test when it arrives before that:
+ *
+ * ...
+ * \ /---- delete old SA
+ * \-/----> detect collision
+ * aborts rekeying <---------/------ TEMP_FAIL
+ * handle delete <--------/
+ * ---------------->
+ */
+START_TEST(test_collision_delayed_request)
+{
+ ike_sa_t *a, *b;
+ message_t *msg;
+ bool before_delete = _i >= 3;
+
+ _i %= 3;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ /* Three nonces and SPIs are needed (SPI 1 and 2 are used for the initial
+ * CHILD_SA):
+ * N1/3 -----\ /----- N2/4
+ * N3/5 <-----\--/
+ * ... -----\ \-------> ...
+ * We test this three times, each time a different nonce is the lowest.
+ */
+ struct {
+ /* Nonces used at each point */
+ u_char nonces[3];
+ } data[] = {
+ { { 0x00, 0xFF, 0xFF } },
+ { { 0xFF, 0x00, 0xFF } },
+ { { 0xFF, 0xFF, 0x00 } },
+ };
+
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
+ initiate_rekey(a, 1);
+ assert_ipsec_sas_installed(a, 1, 2);
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
+ initiate_rekey(b, 2);
+ assert_ipsec_sas_installed(b, 1, 2);
+
+ /* delay the CREATE_CHILD_SA request from a to b */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
+ exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(a, 1, 2, 5);
+ assert_hook();
+ /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
+ assert_hook_rekey(child_rekey, 2, 4);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, 2, 4, 5);
+ assert_hook();
+
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> (delayed) */
+ assert_hook_not_called(child_rekey);
+ assert_single_notify(OUT, TEMPORARY_FAILURE);
+ exchange_test_helper->process_message(exchange_test_helper, b, msg);
+ assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_hook();
+
+ if (before_delete)
+ {
+ /* delay the DELETE request from b to a so TEMP_FAIL arrives before */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+ }
+ else
+ {
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_rekey(child_rekey, 1, 5);
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, 1, 4, 5);
+ assert_scheduler();
+ assert_hook();
+ }
+
+ /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ assert_hook_not_called(child_rekey);
+ if (before_delete)
+ {
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, 1, 2, 5);
+ assert_scheduler();
+ }
+ else
+ {
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, 1, 4, 5);
+ assert_scheduler();
+ }
+ assert_hook();
+
+ if (before_delete)
+ {
+ /* <-- INFORMATIONAL { D } (delayed) */
+ assert_hook_rekey(child_rekey, 1, 5);
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, a, msg);
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, 1, 4, 5);
+ assert_scheduler();
+ assert_hook();
}
- /* <-- INFORMATIONAL { D } */
- assert_jobs_scheduled(1);
- exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_ipsec_sas_installed(a, 1, 4, 6);
- assert_child_sa_count(a, 2);
- assert_scheduler();
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(child_rekey);
/* INFORMATIONAL { D } --> */
assert_jobs_scheduled(1);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_ipsec_sas_installed(b, 2, 4, 6);
assert_child_sa_count(b, 2);
+ assert_ipsec_sas_installed(b, 2, 4, 5);
assert_scheduler();
- if (after_delete)
- {
- /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } (delayed) */
- exchange_test_helper->process_message(exchange_test_helper, a, msg);
- assert_num_tasks(a, 0, TASK_QUEUE_PASSIVE);
- assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE);
- assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_ipsec_sas_installed(a, 1, 4, 6);
- }
-
/* simulate the execution of the scheduled jobs */
- destroy_rekeyed(a, data[_i].spi_del_b);
+ destroy_rekeyed(a, 1);
assert_child_sa_count(a, 1);
- assert_ipsec_sas_installed(a, data[_i].spi_a, data[_i].spi_b);
- destroy_rekeyed(b, data[_i].spi_del_b);
+ assert_ipsec_sas_installed(a, 4, 5);
+ destroy_rekeyed(b, 2);
assert_child_sa_count(b, 1);
- assert_ipsec_sas_installed(b, data[_i].spi_a, data[_i].spi_b);
+ assert_ipsec_sas_installed(b, 4, 5);
/* child_rekey/child_updown */
assert_hook();
assert_hook();
+ assert_sa_idle(a);
+ assert_sa_idle(b);
+
call_ikesa(a, destroy);
call_ikesa(b, destroy);
}
END_TEST
/**
- * In this scenario one of the peers does not notice that there is a
- * rekey collision:
+ * Similar to above one peer fails to notice the collision but the
+ * CREATE_CHILD_SA request is even more delayed:
*
* rekey ----\ /---- rekey
* \ /
* detect collision <-----\---/
* -------\-------->
- * \ /---- delete old SA
- * \-/----> detect collision
- * detect collision <---------/ /---- TEMP_FAIL
- * delete -----------/---->
- * aborts rekeying <---------/
+ * detect collision <-------\-------- delete old SA
+ * delete ---------\------>
+ * \----->
+ * /---- CHILD_SA_NOT_FOUND
+ * aborts rekeying <----------/
*/
-START_TEST(test_collision_delayed_request)
+START_TEST(test_collision_delayed_request_more)
{
ike_sa_t *a, *b;
message_t *msg;
/* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
- assert_hook_rekey(child_rekey, 1, 5);
+ assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
assert_ipsec_sas_installed(b, 2, 4, 5);
assert_hook();
- /* we don't expect this hook to get called anymore */
- assert_hook_not_called(child_rekey);
-
- /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> (delayed) */
- assert_single_notify(OUT, TEMPORARY_FAILURE);
- exchange_test_helper->process_message(exchange_test_helper, b, msg);
- assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
-
/* <-- INFORMATIONAL { D } */
- assert_jobs_scheduled(1);
- exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(a, 2);
- assert_ipsec_sas_installed(a, 1, 4, 5);
- assert_scheduler();
-
- /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
+ assert_hook_rekey(child_rekey, 1, 5);
assert_no_jobs_scheduled();
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_child_sa_count(a, 2);
assert_ipsec_sas_installed(a, 1, 4, 5);
assert_scheduler();
+ assert_hook();
+
+ /* we don't expect this to get called anymore */
+ assert_hook_not_called(child_rekey);
/* INFORMATIONAL { D } --> */
assert_jobs_scheduled(1);
assert_ipsec_sas_installed(b, 2, 4, 5);
assert_scheduler();
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_single_notify(OUT, CHILD_SA_NOT_FOUND);
+ exchange_test_helper->process_message(exchange_test_helper, b, msg);
+ assert_child_sa_state(b, 2, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(b, 2);
+ assert_ipsec_sas_installed(b, 2, 4, 5);
+
+ /* <-- CREATE_CHILD_SA { N(NO_CHILD_SA) } */
+ assert_jobs_scheduled(1);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, 1, 4, 5);
+ assert_scheduler();
+
/* simulate the execution of the scheduled jobs */
destroy_rekeyed(a, 1);
assert_child_sa_count(a, 1);
* \ /
* detect collision <-----\---/
* -------\-------->
- * detect collision <-------\-------- delete old SA
- * delete ---------\------>
- * \----->
+ * handle delete <-------\-------- delete old SA
+ * ---------\------>
+ * handle delete <---------\------ delete new SA
+ * -----------\---->
+ * \--->
* /---- CHILD_SA_NOT_FOUND
* aborts rekeying <----------/
*/
-START_TEST(test_collision_delayed_request_more)
+START_TEST(test_collision_delayed_request_more_delete)
{
ike_sa_t *a, *b;
message_t *msg;
/* delay the CREATE_CHILD_SA request from a to b */
msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
- /* this should never get called as this results in a successful rekeying */
+ /* this should not get called until the new SA is deleted */
assert_hook_not_called(child_updown);
/* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
- assert_hook_rekey(child_rekey, 1, 5);
+ assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
assert_ipsec_sas_installed(b, 2, 4, 5);
assert_hook();
- /* we don't expect this hook to get called anymore */
- assert_hook_not_called(child_rekey);
-
/* <-- INFORMATIONAL { D } */
- assert_jobs_scheduled(1);
+ assert_hook_rekey(child_rekey, 1, 5);
+ assert_no_jobs_scheduled();
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(a, 2);
assert_ipsec_sas_installed(a, 1, 4, 5);
assert_scheduler();
+ assert_hook();
+
+ /* child_updown() */
+ assert_hook();
+
+ /* we don't expect this to get called anymore */
+ assert_hook_not_called(child_rekey);
+ /* this is expected later */
+ assert_hook_not_called(child_updown);
+
+
/* INFORMATIONAL { D } --> */
assert_jobs_scheduled(1);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_ipsec_sas_installed(b, 2, 4, 5);
assert_scheduler();
+ /* trigger a delete for the new CHILD_SA */
+ call_ikesa(b, delete_child_sa, PROTO_ESP, 5, FALSE);
+
+ /* <-- INFORMATIONAL { D } */
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, 5, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, 1, 4, 5);
+ assert_scheduler();
+
+ /* child_updown() */
+ assert_hook();
+
+ /* INFORMATIONAL { D } --> */
+ assert_hook_updown(child_updown, FALSE);
+ assert_no_jobs_scheduled();
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_count(b, 1);
+ assert_ipsec_sas_installed(b, 2);
+ assert_scheduler();
+ assert_hook();
+
/* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_not_called(child_updown);
assert_single_notify(OUT, CHILD_SA_NOT_FOUND);
exchange_test_helper->process_message(exchange_test_helper, b, msg);
assert_child_sa_state(b, 2, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(b, 2);
- assert_ipsec_sas_installed(b, 2, 4, 5);
+ assert_child_sa_count(b, 1);
+ assert_ipsec_sas_installed(b, 2);
+ assert_hook();
+
/* <-- CREATE_CHILD_SA { N(NO_CHILD_SA) } */
- assert_no_jobs_scheduled();
+ assert_hook_updown(child_updown, FALSE);
+ assert_jobs_scheduled(1);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
- assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_count(a, 2);
- assert_ipsec_sas_installed(a, 1, 4, 5);
+ assert_child_sa_count(a, 1);
+ assert_ipsec_sas_installed(a, 1);
assert_scheduler();
+ assert_hook();
+
+ /* we don't expect this to get called anymore */
+ assert_hook_not_called(child_updown);
/* simulate the execution of the scheduled jobs */
destroy_rekeyed(a, 1);
- assert_child_sa_count(a, 1);
- assert_ipsec_sas_installed(a, 4, 5);
+ assert_child_sa_count(a, 0);
+ assert_ipsec_sas_installed(a);
destroy_rekeyed(b, 2);
- assert_child_sa_count(b, 1);
- assert_ipsec_sas_installed(b, 4, 5);
+ assert_child_sa_count(b, 0);
+ assert_ipsec_sas_installed(b);
/* child_rekey/child_updown */
assert_hook();
* aborts rekeying <-------/
*
* In a variation of this scenario, the TEMP_FAIL notify arrives before the
- * delete does.
+ * delete does, similar to the non-multi-KE scenario above.
*/
START_TEST(test_collision_delayed_request_multi_ke)
{
exchange_test_helper->process_message(exchange_test_helper, b, msg);
/* <-- IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } */
- assert_hook_rekey(child_rekey, 1, 5);
+ assert_hook_not_called(child_rekey);
assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_num_tasks(a, 0, TASK_QUEUE_PASSIVE);
assert_ipsec_sas_installed(b, 2, 4, 5);
assert_hook();
- /* we don't expect this hook to get called anymore */
- assert_hook_not_called(child_rekey);
-
/* <-- INFORMATIONAL { D } */
- assert_jobs_scheduled(1);
+ assert_hook_rekey(child_rekey, 1, 5);
+ assert_jobs_scheduled(after_delete ? 0 : 1);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(a, 2);
assert_ipsec_sas_installed(a, 1, 4, 5);
assert_scheduler();
+ assert_hook();
+
+ assert_hook_not_called(child_rekey);
/* INFORMATIONAL { D } --> */
assert_jobs_scheduled(1);
if (after_delete)
{
/* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
- assert_no_jobs_scheduled();
+ assert_jobs_scheduled(1);
exchange_test_helper->process_message(exchange_test_helper, a, msg);
assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE);
assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
/* this should never get called as this results in a successful rekeying */
assert_hook_not_called(child_updown);
+ /* this should not be called until the active rekeyings are concluded */
+ assert_hook_not_called(child_rekey);
/* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
- assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(b, 1);
- assert_hook();
/* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
- assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(a, 1);
- assert_hook();
/* <-- CREATE_CHILD_SA { N(INVAL_KE) } */
exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
- assert_hook_not_called(child_rekey);
assert_single_notify(IN, INVALID_KE_PAYLOAD);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(a, 1);
- assert_hook();
/* CREATE_CHILD_SA { N(INVAL_KE) } --> */
exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
- assert_hook_not_called(child_rekey);
assert_single_notify(IN, INVALID_KE_PAYLOAD);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(b, 1);
- assert_hook();
/* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
- assert_hook_rekey(child_rekey, 2, 7);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_state(b, 7, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
- assert_hook();
/* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
- assert_hook_rekey(child_rekey, 1, 8);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_state(a, 8, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+
+ /* child_rekey */
assert_hook();
/* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
if (data[_i].spi_del_a == 1)
- { /* currently we call this again if we keep our own replacement as we
- * already called it above */
+ {
assert_hook_rekey(child_rekey, 1, data[_i].spi_a);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_hook();
assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED,
- CHILD_OUTBOUND_REGISTERED);
+ CHILD_OUTBOUND_NONE);
assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
- CHILD_OUTBOUND_NONE);
+ assert_hook();
}
else
{
+ assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED,
CHILD_OUTBOUND_INSTALLED);
assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
- CHILD_OUTBOUND_REGISTERED);
+ assert_hook();
}
+ assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
+ CHILD_OUTBOUND_NONE);
+
/* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
if (data[_i].spi_del_b == 2)
{
assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_hook();
assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
- CHILD_OUTBOUND_REGISTERED);
+ CHILD_OUTBOUND_NONE);
assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
CHILD_OUTBOUND_INSTALLED);
- assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
- CHILD_OUTBOUND_NONE);
+ assert_hook();
}
else
{
+ assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
CHILD_OUTBOUND_INSTALLED);
assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
CHILD_OUTBOUND_REGISTERED);
- assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
- CHILD_OUTBOUND_REGISTERED);
+ assert_hook();
}
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+ CHILD_OUTBOUND_NONE);
-
- /* we don't expect this hook to get called anymore */
- assert_hook_not_called(child_rekey);
/* INFORMATIONAL { D } --> */
assert_jobs_scheduled(1);
- exchange_test_helper->process_message(exchange_test_helper, b, NULL);
- assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
- data[_i].spi_del_b == 2 ? CHILD_OUTBOUND_NONE
- : CHILD_OUTBOUND_REGISTERED);
+ if (data[_i].spi_del_b == 2)
+ {
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_hook();
+ }
+ else
+ {
+ assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_hook();
+ }
+ assert_scheduler();
assert_child_sa_state(b, data[_i].spi_del_a, CHILD_DELETED,
CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+ CHILD_OUTBOUND_NONE);
assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(b, 3);
- assert_scheduler();
/* <-- INFORMATIONAL { D } */
assert_jobs_scheduled(1);
- exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ if (data[_i].spi_del_a == 1)
+ {
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_hook();
+ }
+ else
+ {
+ assert_hook_rekey(child_rekey, 1, data[_i].spi_a);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_hook();
+ }
+ assert_scheduler();
assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
- data[_i].spi_del_a == 1 ? CHILD_OUTBOUND_NONE
- : CHILD_OUTBOUND_REGISTERED);
+ CHILD_OUTBOUND_NONE);
assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETED,
CHILD_OUTBOUND_NONE);
assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(a, 3);
- assert_scheduler();
+
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(child_rekey);
+
/* <-- INFORMATIONAL { D } */
assert_jobs_scheduled(1);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
/* this should never get called as this results in a successful rekeying */
assert_hook_not_called(child_updown);
+ /* this should not be called until b doesn't notice a collision */
+ assert_hook_not_called(child_rekey);
/* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
- assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(b, 1);
- assert_hook();
/* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
- assert_hook_not_called(child_rekey);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(a, 1);
- assert_hook();
/* <-- CREATE_CHILD_SA { N(INVAL_KE) } */
exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
- assert_hook_not_called(child_rekey);
assert_single_notify(IN, INVALID_KE_PAYLOAD);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(a, 1);
- assert_hook();
/* CREATE_CHILD_SA { N(INVAL_KE) } --> */
exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
- assert_hook_not_called(child_rekey);
assert_single_notify(IN, INVALID_KE_PAYLOAD);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(b, 1);
- assert_hook();
/* delay the CREATE_CHILD_SA request from a to b */
msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
/* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
- assert_hook_rekey(child_rekey, 1, 7);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_state(a, 7, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+
+ /* child_rekey */
assert_hook();
+
/* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
assert_hook_rekey(child_rekey, 2, 6);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 6, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
assert_hook();
- /* we don't expect this hook to get called anymore */
- assert_hook_not_called(child_rekey);
-
/* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> (delayed) */
+ assert_hook_not_called(child_rekey);
assert_single_notify(OUT, TEMPORARY_FAILURE);
exchange_test_helper->process_message(exchange_test_helper, b, msg);
assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_NONE);
assert_child_sa_state(b, 6, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_hook();
/* <-- INFORMATIONAL { D } */
- assert_jobs_scheduled(1);
+ assert_hook_rekey(child_rekey, 1, 7);
+ assert_no_jobs_scheduled();
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_child_sa_state(a, 7, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(a, 2);
assert_scheduler();
+ assert_hook();
/* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
- assert_no_jobs_scheduled();
+ assert_hook_not_called(child_rekey);
+ assert_jobs_scheduled(1);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
assert_child_sa_state(a, 7, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
assert_child_sa_count(a, 2);
assert_scheduler();
+ assert_hook();
+
+ /* we don't expect this hook to get called anymore */
+ assert_hook_not_called(child_rekey);
/* INFORMATIONAL { D } --> */
assert_jobs_scheduled(1);
}
END_TEST
+/**
+ * This simulates incorrect behavior by a hypothetical IKEv2 responder, which
+ * might send a delete for the old CHILD_SA even if it lost the collision
+ * (compared to the incorrect delete without collision, see above, this hasn't
+ * been observed in the wild).
+ * This is an issue if the DELETE arrives before the CREATE_CHILD_SA response.
+ */
+START_TEST(test_collision_responder_incorrect_delete)
+{
+ ike_sa_t *a, *b;
+ message_t *msg;
+
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+
+ /* make sure the responder looses the collision */
+ exchange_test_helper->nonce_first_byte = 0xff;
+ initiate_rekey(a, 1);
+ assert_ipsec_sas_installed(a, 1, 2);
+ exchange_test_helper->nonce_first_byte = 0x00;
+ initiate_rekey(b, 2);
+ assert_ipsec_sas_installed(b, 1, 2);
+
+ /* this should never get called as this results in a successful rekeying */
+ assert_hook_not_called(child_updown);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ exchange_test_helper->nonce_first_byte = 0xff;
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(b, 1, 2, 5);
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
+ exchange_test_helper->nonce_first_byte = 0xff;
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(a, 1, 2, 6);
+ assert_hook();
+
+ /* delay the CREATE_CHILD_SA response */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_hook();
+ assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, 4, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(b, 1, 2, 4, 5);
+
+ /* <-- INFORMATIONAL { D } */
+ assert_no_jobs_scheduled();
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_hook();
+ assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, 6, CHILD_DELETING, CHILD_OUTBOUND_REGISTERED);
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, 1, 2, 6);
+ assert_scheduler();
+
+ /* INFORMATIONAL { D } --> */
+ assert_jobs_scheduled(1);
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_hook();
+ assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, 4, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_child_sa_count(b, 3);
+ assert_ipsec_sas_installed(b, 1, 2, 4, 5);
+ assert_scheduler();
+
+ /* inject an incorrect delete for the old CHILD_SA by the responder,
+ * without messing with its internal state */
+ send_child_delete(b, 2);
+
+ /* <-- INFORMATIONAL { D } */
+ assert_no_jobs_scheduled();
+ assert_hook_not_called(child_rekey);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_hook();
+ assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, 6, CHILD_DELETING, CHILD_OUTBOUND_REGISTERED);
+ assert_child_sa_count(a, 2);
+ assert_ipsec_sas_installed(a, 1, 2, 6);
+ assert_scheduler();
+
+
+ /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } (delayed) */
+ assert_jobs_scheduled(2);
+ assert_hook_rekey(child_rekey, 1, 3);
+ exchange_test_helper->process_message(exchange_test_helper, a, msg);
+ assert_hook();
+ assert_child_sa_state(a, 1, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(a, 6, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_count(a, 3);
+ assert_ipsec_sas_installed(a, 1, 3, 5, 6);
+ assert_scheduler();
+
+ /* INFORMATIONAL { D } (response to incorrect DELETE) --> */
+ assert_no_jobs_scheduled();
+ assert_hook_rekey(child_rekey, 2, 5);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ /* simulate handling of the delete via expire, does not delay destroy */
+ call_ikesa(b, delete_child_sa, PROTO_ESP, 2, TRUE);
+ assert_child_sa_state(b, 4, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(b, 2);
+ assert_ipsec_sas_installed(b, 3, 4, 5);
+ assert_hook();
+ assert_scheduler();
+
+ /* we don't expect this to get called anymore */
+ assert_hook_not_called(child_rekey);
+
+ /* simulate the execution of the scheduled jobs */
+ destroy_rekeyed(a, 1);
+ destroy_rekeyed(a, 6);
+ assert_child_sa_count(a, 1);
+ assert_ipsec_sas_installed(a, 3, 5);
+ destroy_rekeyed(b, 4);
+ assert_child_sa_count(b, 1);
+ assert_ipsec_sas_installed(b, 3, 5);
+
+ /* child_rekey/child_updown */
+ assert_hook();
+ assert_hook();
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
+
/**
* One of the hosts initiates a DELETE of the CHILD_SA the other peer is
* concurrently trying to rekey.
*/
/* <-- INFORMATIONAL { D } */
- assert_hook_updown(child_updown, FALSE);
+ assert_hook_not_called(child_updown);
assert_single_payload(IN, PLV2_DELETE);
assert_single_payload(OUT, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_count(a, 0);
+ /* the SA is not destroyed until we get the CREATE_CHILD_SA response */
+ assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 1);
assert_hook();
/* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
- assert_hook_not_called(child_updown);
+ assert_hook_updown(child_updown, FALSE);
/* we don't expect a job to retry the rekeying */
assert_no_jobs_scheduled();
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 0);
assert_scheduler();
assert_hook();
assert_hook();
/* <-- INFORMATIONAL { D } */
- assert_hook_updown(child_updown, FALSE);
+ assert_hook_not_called(child_updown);
assert_single_payload(IN, PLV2_DELETE);
assert_single_payload(OUT, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_count(a, 0);
+ assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 1);
assert_hook();
/* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
- assert_hook_not_called(child_updown);
+ assert_hook_updown(child_updown, FALSE);
/* we don't expect a job to retry the rekeying */
assert_no_jobs_scheduled();
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 0);
assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE);
assert_scheduler();
assert_hook();
* /---- CHILD_SA_NOT_FOUND
* aborts rekeying <----------/
*/
- START_TEST(test_collision_delete_drop_rekey)
+START_TEST(test_collision_delete_drop_rekey)
{
ike_sa_t *a, *b;
message_t *msg;
*/
/* <-- INFORMATIONAL { D } */
- assert_hook_updown(child_updown, FALSE);
+ assert_hook_not_called(child_updown);
assert_single_payload(IN, PLV2_DELETE);
assert_single_payload(OUT, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
- assert_child_sa_count(a, 0);
+ assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 1);
assert_hook();
/* INFORMATIONAL { D } --> */
assert_hook();
/* <-- CREATE_CHILD_SA { N(NO_CHILD_SA) } */
- assert_hook_not_called(child_updown);
+ assert_hook_updown(child_updown, FALSE);
/* no jobs or tasks should get scheduled/queued */
assert_no_jobs_scheduled();
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 0);
assert_scheduler();
assert_hook();
END_TEST
/**
- * FIXME: Not sure what we can do about the following:
- *
* One of the hosts initiates a rekeying of a CHILD_SA and after responding to
* it the other peer deletes the new SA. However, the rekey response is
* delayed or dropped, so the peer doing the rekeying receives a delete for an
- * unknown CHILD_SA and then has a rekeyed CHILD_SA that should not exist.
+ * unknown CHILD_SA and has to consider this when processing the rekey response.
*
* rekey ---------------->
* /---- rekey
* unknown SA <----------/----- delete new SA
* ----------/----->
- * <--------/
- *
- * The peers' states are now out of sync.
- *
- * Perhaps the rekey initiator could keep track of deletes for non-existing SAs
- * while rekeying and then check against the SPIs when handling the
- * CREATE_CHILD_SA response.
+ * delete SA <--------/
*/
+START_TEST(test_collision_delete_delayed_response)
+{
+ ike_sa_t *a, *b;
+ message_t *msg;
+ uint32_t spi_a = _i+1, spi_b = 2-_i;
+
+ if (_i)
+ { /* responder rekeys the CHILD_SA (SPI 2) */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &b, &a, NULL);
+ }
+ else
+ { /* initiator rekeys the CHILD_SA (SPI 1) */
+ exchange_test_helper->establish_sa(exchange_test_helper,
+ &a, &b, NULL);
+ }
+ initiate_rekey(a, spi_a);
+
+ /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
+ assert_hook_not_called(child_rekey);
+ assert_notify(IN, REKEY_SA);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, spi_b, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+ assert_ipsec_sas_installed(b, spi_a, spi_b, 4);
+ assert_hook();
+
+
+ /* delay the CREATE_CHILD_SA response */
+ msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
+
+ assert_hook_rekey(child_rekey, spi_b, 4);
+ assert_hook_not_called(child_updown);
+ call_ikesa(b, delete_child_sa, PROTO_ESP, 4, FALSE);
+ assert_child_sa_state(b, spi_b, CHILD_REKEYED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_state(b, 4, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+ assert_ipsec_sas_installed(b, spi_b, 3, 4);
+ assert_child_sa_count(b, 2);
+ assert_hook();
+ assert_hook();
+
+ /* this is not expected to get called until the response is processed */
+ assert_hook_not_called(child_rekey);
+
+ /* <-- INFORMATIONAL { D } */
+ assert_hook_not_called(child_updown);
+ assert_single_payload(IN, PLV2_DELETE);
+ assert_message_empty(OUT);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_count(a, 1);
+ assert_ipsec_sas_installed(a, spi_a, spi_b);
+ assert_hook();
+
+ /* INFORMATIONAL { } --> */
+ assert_hook_updown(child_updown, FALSE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, spi_b, CHILD_REKEYED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_count(b, 1);
+ assert_ipsec_sas_installed(b, spi_b);
+ assert_hook();
+
+ /* child_rekey */
+ assert_hook();
+
+ /* <-- CREATE_CHILD_SA { SA, Ni, [KEi,] TSi, TSr } (delayed) */
+ assert_hook_rekey(child_rekey, spi_a, 3);
+ assert_hook_updown(child_updown, FALSE);
+ /* the job scheduled here is for the retransmit of the delete */
+ assert_jobs_scheduled(1);
+ assert_no_notify(IN, REKEY_SA);
+ exchange_test_helper->process_message(exchange_test_helper, a, msg);
+ assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+ assert_child_sa_count(a, 1);
+ assert_ipsec_sas_installed(a, spi_a, spi_b);
+ assert_scheduler();
+ assert_hook();
+ assert_hook();
+
+ /* this is not expected to get called anymore */
+ assert_hook_not_called(child_rekey);
+
+ /* INFORMATIONAL { D } --> */
+ assert_jobs_scheduled(1);
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+ assert_child_sa_state(b, spi_b, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_count(b, 1);
+ assert_ipsec_sas_installed(b, spi_b);
+ assert_scheduler();
+ /* <-- INFORMATIONAL { D } */
+ assert_jobs_scheduled(1);
+ assert_single_payload(IN, PLV2_DELETE);
+ exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+ assert_child_sa_state(a, spi_a, CHILD_DELETED, CHILD_OUTBOUND_NONE);
+ assert_child_sa_count(a, 1);
+ assert_ipsec_sas_installed(a, spi_a);
+ assert_scheduler();
+ /* simulate the execution of the scheduled jobs */
+ destroy_rekeyed(a, spi_a);
+ assert_child_sa_count(a, 0);
+ assert_ipsec_sas_installed(a);
+ destroy_rekeyed(b, spi_b);
+ assert_child_sa_count(b, 0);
+ assert_ipsec_sas_installed(a);
+
+ /* child_rekey */
+ assert_hook();
+
+ assert_sa_idle(a);
+ assert_sa_idle(b);
+
+ call_ikesa(a, destroy);
+ call_ikesa(b, destroy);
+}
+END_TEST
/**
* One of the hosts initiates a rekey of the IKE_SA of the CHILD_SA the other
tcase_add_loop_test(tc, test_regular_ke_invalid_multi_ke, 0, 2);
tcase_add_test(tc, test_regular_responder_ignore_soft_expire);
tcase_add_test(tc, test_regular_responder_handle_hard_expire);
+ tcase_add_test(tc, test_regular_responder_delete);
+ tcase_add_test(tc, test_regular_responder_lost_sa);
+ tcase_add_test(tc, test_regular_responder_incorrect_delete);
suite_add_tcase(s, tc);
tc = tcase_create("collisions rekey");
tcase_add_loop_test(tc, test_collision_multi_ke, 0, 4);
tcase_add_loop_test(tc, test_collision_mixed, 0, 4);
tcase_add_loop_test(tc, test_collision_delayed_response, 0, 4);
+ tcase_add_loop_test(tc, test_collision_delayed_response_delete, 0, 4);
tcase_add_loop_test(tc, test_collision_delayed_response_multi_ke, 0, 4);
- tcase_add_loop_test(tc, test_collision_delayed_request, 0, 3);
+ tcase_add_loop_test(tc, test_collision_delayed_request, 0, 6);
tcase_add_loop_test(tc, test_collision_delayed_request_more, 0, 3);
+ tcase_add_loop_test(tc, test_collision_delayed_request_more_delete, 0, 3);
tcase_add_loop_test(tc, test_collision_delayed_request_multi_ke, 0, 6);
tcase_add_loop_test(tc, test_collision_ke_invalid, 0, 4);
tcase_add_loop_test(tc, test_collision_ke_invalid_delayed_retry, 0, 3);
+ tcase_add_test(tc, test_collision_responder_incorrect_delete);
suite_add_tcase(s, tc);
tc = tcase_create("collisions delete");
tcase_add_loop_test(tc, test_collision_delete_multi_ke, 0, 2);
tcase_add_loop_test(tc, test_collision_delete_drop_delete, 0, 2);
tcase_add_loop_test(tc, test_collision_delete_drop_rekey, 0, 2);
+ tcase_add_loop_test(tc, test_collision_delete_delayed_response, 0, 2);
suite_add_tcase(s, tc);
tc = tcase_create("collisions ike rekey");