Merge branch 'exchange-collisions'

Improves the handling of IKEv2 exchange collisions in several corner
cases.  TEMPORARY_FAILURE and CHILD_SA_NOT_FOUND notifies that were defined
with RFC 7296 are now handled and sent as appropriate.

The behavior in these situations is tested with new unit tests.

Fixes #379, #464, #876, #1293.
This commit is contained in:
Tobias Brunner
2016-06-17 18:53:51 +02:00
54 changed files with 6480 additions and 330 deletions
+5
View File
@@ -253,6 +253,11 @@ charon.port_nat_t = 4500
allocated. Has to be different from **charon.port**, otherwise a random
port will be allocated.
charon.prefer_configured_proposals = yes
Prefer locally configured proposals for IKE/IPsec over supplied ones as
responder (disabling this can avoid keying retries due to INVALID_KE_PAYLOAD
notifies).
charon.prefer_temporary_addrs = no
By default public IPv6 addresses are preferred over temporary ones (RFC
4941), to make connections more stable. Enable this option to reverse this.
+3
View File
@@ -154,6 +154,9 @@ AM_CPPFLAGS = \
-DIPSEC_DIR=\"${ipsecdir}\" \
-DIPSEC_PIDDIR=\"${piddir}\"
AM_CFLAGS = \
@COVERAGE_CFLAGS@
AM_LDFLAGS = \
-no-undefined
+31 -18
View File
@@ -211,25 +211,40 @@ METHOD(child_cfg_t, get_proposals, linked_list_t*,
METHOD(child_cfg_t, select_proposal, proposal_t*,
private_child_cfg_t*this, linked_list_t *proposals, bool strip_dh,
bool private)
bool private, bool prefer_self)
{
enumerator_t *stored_enum, *supplied_enum;
proposal_t *stored, *supplied, *selected = NULL;
enumerator_t *prefer_enum, *match_enum;
proposal_t *proposal, *match, *selected = NULL;
stored_enum = this->proposals->create_enumerator(this->proposals);
supplied_enum = proposals->create_enumerator(proposals);
/* compare all stored proposals with all supplied. Stored ones are preferred. */
while (stored_enum->enumerate(stored_enum, &stored))
if (prefer_self)
{
stored = stored->clone(stored);
while (supplied_enum->enumerate(supplied_enum, &supplied))
prefer_enum = this->proposals->create_enumerator(this->proposals);
match_enum = proposals->create_enumerator(proposals);
}
else
{
prefer_enum = proposals->create_enumerator(proposals);
match_enum = this->proposals->create_enumerator(this->proposals);
}
while (prefer_enum->enumerate(prefer_enum, &proposal))
{
proposal = proposal->clone(proposal);
if (prefer_self)
{
proposals->reset_enumerator(proposals, match_enum);
}
else
{
this->proposals->reset_enumerator(this->proposals, match_enum);
}
while (match_enum->enumerate(match_enum, &match))
{
if (strip_dh)
{
stored->strip_dh(stored, MODP_NONE);
proposal->strip_dh(proposal, MODP_NONE);
}
selected = stored->select(stored, supplied, private);
selected = proposal->select(proposal, match, private);
if (selected)
{
DBG2(DBG_CFG, "received proposals: %#P", proposals);
@@ -238,17 +253,15 @@ METHOD(child_cfg_t, select_proposal, proposal_t*,
break;
}
}
stored->destroy(stored);
proposal->destroy(proposal);
if (selected)
{
break;
}
supplied_enum->destroy(supplied_enum);
supplied_enum = proposals->create_enumerator(proposals);
}
stored_enum->destroy(stored_enum);
supplied_enum->destroy(supplied_enum);
if (selected == NULL)
prefer_enum->destroy(prefer_enum);
match_enum->destroy(match_enum);
if (!selected)
{
DBG1(DBG_CFG, "received proposals: %#P", proposals);
DBG1(DBG_CFG, "configured proposals: %#P", this->proposals);
+3 -1
View File
@@ -100,10 +100,12 @@ struct child_cfg_t {
* @param proposals list from which proposals are selected
* @param strip_dh TRUE strip out diffie hellman groups
* @param private accept algorithms from a private range
* @param prefer_self whether to prefer configured or supplied proposals
* @return selected proposal, or NULL if nothing matches
*/
proposal_t* (*select_proposal)(child_cfg_t*this, linked_list_t *proposals,
bool strip_dh, bool private);
bool strip_dh, bool private,
bool prefer_self);
/**
* Add a traffic selector to the config.
+39 -24
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2012-2015 Tobias Brunner
* Copyright (C) 2012-2016 Tobias Brunner
* Copyright (C) 2005-2007 Martin Willi
* Copyright (C) 2005 Jan Hutter
* Hochschule fuer Technik Rapperswil
@@ -310,42 +310,57 @@ METHOD(ike_cfg_t, get_proposals, linked_list_t*,
}
METHOD(ike_cfg_t, select_proposal, proposal_t*,
private_ike_cfg_t *this, linked_list_t *proposals, bool private)
private_ike_cfg_t *this, linked_list_t *proposals, bool private,
bool prefer_self)
{
enumerator_t *stored_enum, *supplied_enum;
proposal_t *stored, *supplied, *selected;
enumerator_t *prefer_enum, *match_enum;
proposal_t *proposal, *match, *selected = NULL;
stored_enum = this->proposals->create_enumerator(this->proposals);
supplied_enum = proposals->create_enumerator(proposals);
/* compare all stored proposals with all supplied. Stored ones are preferred.*/
while (stored_enum->enumerate(stored_enum, (void**)&stored))
if (prefer_self)
{
proposals->reset_enumerator(proposals, supplied_enum);
prefer_enum = this->proposals->create_enumerator(this->proposals);
match_enum = proposals->create_enumerator(proposals);
}
else
{
prefer_enum = proposals->create_enumerator(proposals);
match_enum = this->proposals->create_enumerator(this->proposals);
}
while (supplied_enum->enumerate(supplied_enum, (void**)&supplied))
while (prefer_enum->enumerate(prefer_enum, (void**)&proposal))
{
if (prefer_self)
{
selected = stored->select(stored, supplied, private);
proposals->reset_enumerator(proposals, match_enum);
}
else
{
this->proposals->reset_enumerator(this->proposals, match_enum);
}
while (match_enum->enumerate(match_enum, (void**)&match))
{
selected = proposal->select(proposal, match, private);
if (selected)
{
/* they match, return */
stored_enum->destroy(stored_enum);
supplied_enum->destroy(supplied_enum);
DBG2(DBG_CFG, "received proposals: %#P", proposals);
DBG2(DBG_CFG, "configured proposals: %#P", this->proposals);
DBG2(DBG_CFG, "selected proposal: %P", selected);
return selected;
break;
}
}
if (selected)
{
break;
}
}
/* no proposal match :-(, will result in a NO_PROPOSAL_CHOSEN... */
stored_enum->destroy(stored_enum);
supplied_enum->destroy(supplied_enum);
DBG1(DBG_CFG, "received proposals: %#P", proposals);
DBG1(DBG_CFG, "configured proposals: %#P", this->proposals);
return NULL;
prefer_enum->destroy(prefer_enum);
match_enum->destroy(match_enum);
if (!selected)
{
DBG1(DBG_CFG, "received proposals: %#P", proposals);
DBG1(DBG_CFG, "configured proposals: %#P", this->proposals);
}
return selected;
}
METHOD(ike_cfg_t, get_dh_group, diffie_hellman_group_t,
+4 -3
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2012-2015 Tobias Brunner
* Copyright (C) 2012-2016 Tobias Brunner
* Copyright (C) 2005-2007 Martin Willi
* Copyright (C) 2005 Jan Hutter
* Hochschule fuer Technik Rapperswil
@@ -165,16 +165,17 @@ struct ike_cfg_t {
linked_list_t* (*get_proposals) (ike_cfg_t *this);
/**
* Select a proposed from suggested proposals.
* Select a proposal from a list of supplied proposals.
*
* Returned proposal must be destroyed after use.
*
* @param proposals list of proposals to select from
* @param private accept algorithms from a private range
* @param prefer_self whether to prefer configured or supplied proposals
* @return selected proposal, or NULL if none matches.
*/
proposal_t *(*select_proposal) (ike_cfg_t *this, linked_list_t *proposals,
bool private);
bool private, bool prefer_self);
/**
* Should we send a certificate request in IKE_SA_INIT?
+24 -6
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2014 Tobias Brunner
* Copyright (C) 2008-2016 Tobias Brunner
* Copyright (C) 2006-2010 Martin Willi
* Copyright (C) 2013-2015 Andreas Steffen
* Hochschule fuer Technik Rapperswil
@@ -210,7 +210,7 @@ static bool select_algo(private_proposal_t *this, proposal_t *other,
e1 = create_enumerator(this, type);
e2 = other->create_enumerator(other, type);
if (!e1->enumerate(e1, NULL, NULL))
if (!e1->enumerate(e1, &alg1, NULL))
{
if (!e2->enumerate(e2, &alg2, NULL))
{
@@ -219,12 +219,23 @@ static bool select_algo(private_proposal_t *this, proposal_t *other,
else if (optional)
{
do
{ /* if the other peer proposes NONE, we accept the proposal */
{ /* if NONE is proposed, we accept the proposal */
found = !alg2;
}
while (!found && e2->enumerate(e2, &alg2, NULL));
}
}
else if (!e2->enumerate(e2, NULL, NULL))
{
if (optional)
{
do
{ /* if NONE is proposed, we accept the proposal */
found = !alg1;
}
while (!found && e1->enumerate(e1, &alg1, NULL));
}
}
e1->destroy(e1);
e1 = create_enumerator(this, type);
@@ -244,7 +255,6 @@ static bool select_algo(private_proposal_t *this, proposal_t *other,
"but peer implementation is unknown, skipped");
continue;
}
/* ok, we have an algorithm */
selected->add_algorithm(selected, type, alg1, ks1);
found = TRUE;
break;
@@ -288,9 +298,7 @@ METHOD(proposal_t, select_proposal, proposal_t*,
}
DBG2(DBG_CFG, " proposal matches");
selected->set_spi(selected, other->get_spi(other));
return selected;
}
@@ -445,6 +453,16 @@ static void check_proposal(private_proposal_t *this)
}
}
e->destroy(e);
/* remove MODP_NONE from IKE proposal */
e = array_create_enumerator(this->transforms);
while (e->enumerate(e, &entry))
{
if (entry->type == DIFFIE_HELLMAN_GROUP && !entry->alg)
{
array_remove_at(this->transforms, e);
}
}
e->destroy(e);
}
if (this->protocol == PROTO_ESP)
+13 -3
View File
@@ -1,9 +1,9 @@
/*
* Copyright (C) 2006-2015 Tobias Brunner
* Copyright (C) 2006-2016 Tobias Brunner
* Copyright (C) 2006 Daniel Roethlisberger
* Copyright (C) 2005-2009 Martin Willi
* Copyright (C) 2005 Jan Hutter
* Hochschule fuer Technik Rapperswil
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -71,6 +71,7 @@ ENUM(ike_sa_state_names, IKE_CREATED, IKE_DESTROYING,
"ESTABLISHED",
"PASSIVE",
"REKEYING",
"REKEYED",
"DELETING",
"DESTROYING",
);
@@ -2356,7 +2357,8 @@ METHOD(ike_sa_t, retransmit, status_t,
reestablish(this);
break;
}
if (this->state != IKE_CONNECTING)
if (this->state != IKE_CONNECTING &&
this->state != IKE_REKEYED)
{
charon->bus->ike_updown(charon->bus, &this->public, FALSE);
}
@@ -2508,6 +2510,7 @@ METHOD(ike_sa_t, roam, status_t,
case IKE_DELETING:
case IKE_DESTROYING:
case IKE_PASSIVE:
case IKE_REKEYED:
return SUCCESS;
default:
break;
@@ -2617,6 +2620,12 @@ METHOD(ike_sa_t, queue_task, void,
this->task_manager->queue_task(this->task_manager, task);
}
METHOD(ike_sa_t, queue_task_delayed, void,
private_ike_sa_t *this, task_t *task, uint32_t delay)
{
this->task_manager->queue_task_delayed(this->task_manager, task, delay);
}
METHOD(ike_sa_t, inherit_pre, void,
private_ike_sa_t *this, ike_sa_t *other_public)
{
@@ -2935,6 +2944,7 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator,
.create_task_enumerator = _create_task_enumerator,
.flush_queue = _flush_queue,
.queue_task = _queue_task,
.queue_task_delayed = _queue_task_delayed,
#ifdef ME
.act_as_mediation_server = _act_as_mediation_server,
.get_server_reflexive_host = _get_server_reflexive_host,
+18 -4
View File
@@ -1,9 +1,9 @@
/*
* Copyright (C) 2006-2015 Tobias Brunner
* Copyright (C) 2006-2016 Tobias Brunner
* Copyright (C) 2006 Daniel Roethlisberger
* Copyright (C) 2005-2009 Martin Willi
* Copyright (C) 2005 Jan Hutter
* Hochschule fuer Technik Rapperswil
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -58,12 +58,12 @@ typedef struct ike_sa_t ike_sa_t;
/**
* After which time rekeying should be retried if it failed, in seconds.
*/
#define RETRY_INTERVAL 30
#define RETRY_INTERVAL 15
/**
* Jitter to subtract from RETRY_INTERVAL to randomize rekey retry.
*/
#define RETRY_JITTER 20
#define RETRY_JITTER 10
/**
* Number of redirects allowed within REDIRECT_LOOP_DETECT_PERIOD.
@@ -308,6 +308,11 @@ enum ike_sa_state_t {
*/
IKE_REKEYING,
/**
* IKE_SA has been rekeyed (or is redundant)
*/
IKE_REKEYED,
/**
* IKE_SA is in progress of deletion
*/
@@ -1118,6 +1123,15 @@ struct ike_sa_t {
*/
void (*queue_task)(ike_sa_t *this, task_t *task);
/**
* Queue a task in the manager, but delay its initiation for at least the
* given number of seconds.
*
* @param task task to queue
* @param delay minimum delay in s before initiating the task
*/
void (*queue_task_delayed)(ike_sa_t *this, task_t *task, uint32_t delay);
/**
* Inherit required attributes to new SA before rekeying.
*
+3 -2
View File
@@ -1415,7 +1415,8 @@ METHOD(ike_sa_manager_t, checkout_by_config, ike_sa_t*,
{
continue;
}
if (entry->ike_sa->get_state(entry->ike_sa) == IKE_DELETING)
if (entry->ike_sa->get_state(entry->ike_sa) == IKE_DELETING ||
entry->ike_sa->get_state(entry->ike_sa) == IKE_REKEYED)
{ /* skip IKE_SAs which are not usable, wake other waiting threads */
entry->condvar->signal(entry->condvar);
continue;
@@ -2188,7 +2189,7 @@ METHOD(ike_sa_manager_t, flush, void,
unlock_all_segments(this);
this->spi_lock->write_lock(this->spi_lock);
this->rng->destroy(this->rng);
DESTROY_IF(this->rng);
this->rng = NULL;
this->spi_cb.cb = NULL;
this->spi_cb.data = NULL;
+10 -3
View File
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2007-2016 Tobias Brunner
* Copyright (C) 2007-2011 Martin Willi
* Hochschule fuer Technik Rapperswil
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -1513,8 +1513,8 @@ static bool has_queued(private_task_manager_t *this, task_type_t type)
return found;
}
METHOD(task_manager_t, queue_task, void,
private_task_manager_t *this, task_t *task)
METHOD(task_manager_t, queue_task_delayed, void,
private_task_manager_t *this, task_t *task, uint32_t delay)
{
task_type_t type = task->get_type(task);
@@ -1535,6 +1535,12 @@ METHOD(task_manager_t, queue_task, void,
this->queued_tasks->insert_last(this->queued_tasks, task);
}
METHOD(task_manager_t, queue_task, void,
private_task_manager_t *this, task_t *task)
{
queue_task_delayed(this, task, 0);
}
METHOD(task_manager_t, queue_ike, void,
private_task_manager_t *this)
{
@@ -1975,6 +1981,7 @@ task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa)
.task_manager = {
.process_message = _process_message,
.queue_task = _queue_task,
.queue_task_delayed = _queue_task_delayed,
.queue_ike = _queue_ike,
.queue_ike_rekey = _queue_ike_rekey,
.queue_ike_reauth = _queue_ike_reauth,
@@ -378,6 +378,7 @@ METHOD(task_t, process_r, status_t,
identification_t *id;
linked_list_t *list;
uint16_t group;
bool prefer_configured;
this->ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
DBG0(DBG_IKE, "%H is initiating a Aggressive Mode IKE_SA",
@@ -401,8 +402,10 @@ METHOD(task_t, process_r, status_t,
}
list = sa_payload->get_proposals(sa_payload);
prefer_configured = lib->settings->get_bool(lib->settings,
"%s.prefer_configured_proposals", TRUE, lib->ns);
this->proposal = this->ike_cfg->select_proposal(this->ike_cfg,
list, FALSE);
list, FALSE, prefer_configured);
list->destroy_offset(list, offsetof(proposal_t, destroy));
if (!this->proposal)
{
@@ -640,7 +643,7 @@ METHOD(task_t, process_i, status_t,
}
list = sa_payload->get_proposals(sa_payload);
this->proposal = this->ike_cfg->select_proposal(this->ike_cfg,
list, FALSE);
list, FALSE, TRUE);
list->destroy_offset(list, offsetof(proposal_t, destroy));
if (!this->proposal)
{
+6 -4
View File
@@ -367,7 +367,7 @@ METHOD(task_t, process_r, status_t,
{
linked_list_t *list;
sa_payload_t *sa_payload;
bool private;
bool private, prefer_configured;
this->ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
DBG0(DBG_IKE, "%H is initiating a Main Mode IKE_SA",
@@ -392,9 +392,11 @@ METHOD(task_t, process_r, status_t,
list = sa_payload->get_proposals(sa_payload);
private = this->ike_sa->supports_extension(this->ike_sa,
EXT_STRONGSWAN);
EXT_STRONGSWAN);
prefer_configured = lib->settings->get_bool(lib->settings,
"%s.prefer_configured_proposals", TRUE, lib->ns);
this->proposal = this->ike_cfg->select_proposal(this->ike_cfg,
list, private);
list, private, prefer_configured);
list->destroy_offset(list, offsetof(proposal_t, destroy));
if (!this->proposal)
{
@@ -641,7 +643,7 @@ METHOD(task_t, process_i, status_t,
private = this->ike_sa->supports_extension(this->ike_sa,
EXT_STRONGSWAN);
this->proposal = this->ike_cfg->select_proposal(this->ike_cfg,
list, private);
list, private, TRUE);
list->destroy_offset(list, offsetof(proposal_t, destroy));
if (!this->proposal)
{
+7 -5
View File
@@ -1051,7 +1051,7 @@ METHOD(task_t, process_r, status_t,
linked_list_t *tsi, *tsr, *hostsi, *hostsr, *list = NULL;
peer_cfg_t *peer_cfg;
uint16_t group;
bool private;
bool private, prefer_configured;
sa_payload = (sa_payload_t*)message->get_payload(message,
PLV1_SECURITY_ASSOCIATION);
@@ -1109,8 +1109,10 @@ METHOD(task_t, process_r, status_t,
}
private = this->ike_sa->supports_extension(this->ike_sa,
EXT_STRONGSWAN);
this->proposal = this->config->select_proposal(this->config,
list, FALSE, private);
prefer_configured = lib->settings->get_bool(lib->settings,
"%s.prefer_configured_proposals", TRUE, lib->ns);
this->proposal = this->config->select_proposal(this->config, list,
FALSE, private, prefer_configured);
list->destroy_offset(list, offsetof(proposal_t, destroy));
get_lifetimes(this);
@@ -1323,8 +1325,8 @@ METHOD(task_t, process_i, status_t,
}
private = this->ike_sa->supports_extension(this->ike_sa,
EXT_STRONGSWAN);
this->proposal = this->config->select_proposal(this->config,
list, FALSE, private);
this->proposal = this->config->select_proposal(this->config, list,
FALSE, private, TRUE);
list->destroy_offset(list, offsetof(proposal_t, destroy));
if (!this->proposal)
{
+178 -72
View File
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2007-2015 Tobias Brunner
* Copyright (C) 2007-2016 Tobias Brunner
* Copyright (C) 2007-2010 Martin Willi
* Hochschule fuer Technik Rapperswil
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -43,30 +43,14 @@
#include <encoding/payloads/unknown_payload.h>
#include <processing/jobs/retransmit_job.h>
#include <processing/jobs/delete_ike_sa_job.h>
#include <processing/jobs/initiate_tasks_job.h>
#ifdef ME
#include <sa/ikev2/tasks/ike_me.h>
#endif
typedef struct exchange_t exchange_t;
/**
* An exchange in the air, used do detect and handle retransmission
*/
struct exchange_t {
/**
* Message ID used for this transaction
*/
uint32_t mid;
/**
* generated packet for retransmission
*/
packet_t *packet;
};
typedef struct private_task_manager_t private_task_manager_t;
typedef struct queued_task_t queued_task_t;
/**
* private data of the task manager
@@ -181,6 +165,22 @@ struct private_task_manager_t {
bool make_before_break;
};
/**
* Queued tasks
*/
struct queued_task_t {
/**
* Queued task
*/
task_t *task;
/**
* Time before which the task is not to be initiated
*/
timeval_t time;
};
/**
* Reset retransmission packet list
*/
@@ -216,6 +216,12 @@ METHOD(task_manager_t, flush_queue, void,
}
while (array_remove(array, ARRAY_TAIL, &task))
{
if (queue == TASK_QUEUE_QUEUED)
{
queued_task_t *queued = (queued_task_t*)task;
task = queued->task;
free(queued);
}
task->destroy(task);
}
}
@@ -229,22 +235,28 @@ METHOD(task_manager_t, flush, void,
}
/**
* move a task of a specific type from the queue to the active list
* Move a task of a specific type from the queue to the active list, if it is
* not delayed.
*/
static bool activate_task(private_task_manager_t *this, task_type_t type)
{
enumerator_t *enumerator;
task_t *task;
queued_task_t *queued;
timeval_t now;
bool found = FALSE;
time_monotonic(&now);
enumerator = array_create_enumerator(this->queued_tasks);
while (enumerator->enumerate(enumerator, (void**)&task))
while (enumerator->enumerate(enumerator, (void**)&queued))
{
if (task->get_type(task) == type)
if (queued->task->get_type(queued->task) == type &&
!timercmp(&now, &queued->time, <))
{
DBG2(DBG_IKE, " activating %N task", task_type_names, type);
array_remove_at(this->queued_tasks, enumerator);
array_insert(this->active_tasks, ARRAY_TAIL, task);
array_insert(this->active_tasks, ARRAY_TAIL, queued->task);
free(queued);
found = TRUE;
break;
}
@@ -535,6 +547,7 @@ METHOD(task_manager_t, initiate, status_t,
break;
}
case IKE_REKEYING:
case IKE_REKEYED:
if (activate_task(this, TASK_IKE_DELETE))
{
exchange = INFORMATIONAL;
@@ -611,7 +624,8 @@ METHOD(task_manager_t, initiate, status_t,
case FAILED:
default:
this->initiating.type = EXCHANGE_TYPE_UNDEFINED;
if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING)
if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING &&
this->ike_sa->get_state(this->ike_sa) != IKE_REKEYED)
{
charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE);
}
@@ -759,8 +773,7 @@ static bool handle_collisions(private_task_manager_t *this, task_t *task)
/* do we have to check */
if (type == TASK_IKE_REKEY || type == TASK_CHILD_REKEY ||
type == TASK_CHILD_DELETE || type == TASK_IKE_DELETE ||
type == TASK_IKE_REAUTH)
type == TASK_CHILD_DELETE || type == TASK_IKE_DELETE)
{
/* find an exchange collision, and notify these tasks */
enumerator = array_create_enumerator(this->active_tasks);
@@ -769,8 +782,7 @@ static bool handle_collisions(private_task_manager_t *this, task_t *task)
switch (active->get_type(active))
{
case TASK_IKE_REKEY:
if (type == TASK_IKE_REKEY || type == TASK_IKE_DELETE ||
type == TASK_IKE_REAUTH)
if (type == TASK_IKE_REKEY || type == TASK_IKE_DELETE)
{
ike_rekey_t *rekey = (ike_rekey_t*)active;
rekey->collide(rekey, task);
@@ -847,6 +859,10 @@ static status_t build_response(private_task_manager_t *this, message_t *request)
/* FALL */
case DESTROY_ME:
/* destroy IKE_SA, but SEND response first */
if (handle_collisions(this, task))
{
array_remove_at(this->passive_tasks, enumerator);
}
delete = TRUE;
break;
}
@@ -909,9 +925,11 @@ static status_t process_request(private_task_manager_t *this,
payload_t *payload;
notify_payload_t *notify;
delete_payload_t *delete;
ike_sa_state_t state;
if (array_count(this->passive_tasks) == 0)
{ /* create tasks depending on request type, if not already some queued */
state = this->ike_sa->get_state(this->ike_sa);
switch (message->get_exchange_type(message))
{
case IKE_SA_INIT:
@@ -947,8 +965,8 @@ static status_t process_request(private_task_manager_t *this,
{ /* FIXME: we should prevent this on mediation connections */
bool notify_found = FALSE, ts_found = FALSE;
if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED ||
this->ike_sa->get_state(this->ike_sa) == IKE_CONNECTING)
if (state == IKE_CREATED ||
state == IKE_CONNECTING)
{
DBG1(DBG_IKE, "received CREATE_CHILD_SA request for "
"unestablished IKE_SA, rejected");
@@ -1013,6 +1031,14 @@ static status_t process_request(private_task_manager_t *this,
case PLV2_NOTIFY:
{
notify = (notify_payload_t*)payload;
if (state == IKE_REKEYED)
{
DBG1(DBG_IKE, "received unexpected notify %N "
"for rekeyed IKE_SA, ignored",
notify_type_names,
notify->get_notify_type(notify));
break;
}
switch (notify->get_notify_type(notify))
{
case ADDITIONAL_IP4_ADDRESS:
@@ -1355,6 +1381,8 @@ METHOD(task_manager_t, process_message, status_t,
status_t status;
uint32_t mid;
bool schedule_delete_job = FALSE;
ike_sa_state_t state;
exchange_type_t type;
charon->bus->message(charon->bus, msg, TRUE, FALSE);
status = parse_message(this, msg);
@@ -1395,15 +1423,16 @@ METHOD(task_manager_t, process_message, status_t,
{
if (mid == this->responding.mid)
{
/* reject initial messages if not received in specific states */
if ((msg->get_exchange_type(msg) == IKE_SA_INIT &&
this->ike_sa->get_state(this->ike_sa) != IKE_CREATED) ||
(msg->get_exchange_type(msg) == IKE_AUTH &&
this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING))
/* reject initial messages if not received in specific states,
* after rekeying we only expect a DELETE in an INFORMATIONAL */
type = msg->get_exchange_type(msg);
state = this->ike_sa->get_state(this->ike_sa);
if ((type == IKE_SA_INIT && state != IKE_CREATED) ||
(type == IKE_AUTH && state != IKE_CONNECTING) ||
(state == IKE_REKEYED && type != INFORMATIONAL))
{
DBG1(DBG_IKE, "ignoring %N in IKE_SA state %N",
exchange_type_names, msg->get_exchange_type(msg),
ike_sa_state_names, this->ike_sa->get_state(this->ike_sa));
exchange_type_names, type, ike_sa_state_names, state);
return FAILED;
}
if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE))
@@ -1507,18 +1536,19 @@ METHOD(task_manager_t, process_message, status_t,
return SUCCESS;
}
METHOD(task_manager_t, queue_task, void,
private_task_manager_t *this, task_t *task)
METHOD(task_manager_t, queue_task_delayed, void,
private_task_manager_t *this, task_t *task, uint32_t delay)
{
enumerator_t *enumerator;
queued_task_t *queued;
timeval_t time;
if (task->get_type(task) == TASK_IKE_MOBIKE)
{ /* there is no need to queue more than one mobike task */
enumerator_t *enumerator;
task_t *current;
enumerator = array_create_enumerator(this->queued_tasks);
while (enumerator->enumerate(enumerator, &current))
while (enumerator->enumerate(enumerator, &queued))
{
if (current->get_type(current) == TASK_IKE_MOBIKE)
if (queued->task->get_type(queued->task) == TASK_IKE_MOBIKE)
{
enumerator->destroy(enumerator);
task->destroy(task);
@@ -1527,8 +1557,35 @@ METHOD(task_manager_t, queue_task, void,
}
enumerator->destroy(enumerator);
}
DBG2(DBG_IKE, "queueing %N task", task_type_names, task->get_type(task));
array_insert(this->queued_tasks, ARRAY_TAIL, task);
time_monotonic(&time);
if (delay)
{
job_t *job;
DBG2(DBG_IKE, "queueing %N task (delayed by %us)", task_type_names,
task->get_type(task), delay);
time.tv_sec += delay;
job = (job_t*)initiate_tasks_job_create(
this->ike_sa->get_id(this->ike_sa));
lib->scheduler->schedule_job_tv(lib->scheduler, job, time);
}
else
{
DBG2(DBG_IKE, "queueing %N task", task_type_names,
task->get_type(task));
}
INIT(queued,
.task = task,
.time = time,
);
array_insert(this->queued_tasks, ARRAY_TAIL, queued);
}
METHOD(task_manager_t, queue_task, void,
private_task_manager_t *this, task_t *task)
{
queue_task_delayed(this, task, 0);
}
/**
@@ -1538,12 +1595,12 @@ static bool has_queued(private_task_manager_t *this, task_type_t type)
{
enumerator_t *enumerator;
bool found = FALSE;
task_t *task;
queued_task_t *queued;
enumerator = array_create_enumerator(this->queued_tasks);
while (enumerator->enumerate(enumerator, &task))
while (enumerator->enumerate(enumerator, &queued))
{
if (task->get_type(task) == type)
if (queued->task->get_type(queued->task) == type)
{
found = TRUE;
break;
@@ -1622,7 +1679,7 @@ static void trigger_mbb_reauth(private_task_manager_t *this)
child_cfg_t *cfg;
ike_sa_t *new;
host_t *host;
task_t *task;
queued_task_t *queued;
new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager,
this->ike_sa->get_version(this->ike_sa), TRUE);
@@ -1653,13 +1710,14 @@ static void trigger_mbb_reauth(private_task_manager_t *this)
enumerator->destroy(enumerator);
enumerator = array_create_enumerator(this->queued_tasks);
while (enumerator->enumerate(enumerator, &task))
while (enumerator->enumerate(enumerator, &queued))
{
if (task->get_type(task) == TASK_CHILD_CREATE)
if (queued->task->get_type(queued->task) == TASK_CHILD_CREATE)
{
task->migrate(task, new);
new->queue_task(new, task);
queued->task->migrate(queued->task, new);
new->queue_task(new, queued->task);
array_remove_at(this->queued_tasks, enumerator);
free(queued);
}
}
enumerator->destroy(enumerator);
@@ -1784,34 +1842,62 @@ METHOD(task_manager_t, adopt_tasks, void,
private_task_manager_t *this, task_manager_t *other_public)
{
private_task_manager_t *other = (private_task_manager_t*)other_public;
task_t *task;
queued_task_t *queued;
timeval_t now;
time_monotonic(&now);
/* move queued tasks from other to this */
while (array_remove(other->queued_tasks, ARRAY_TAIL, &task))
while (array_remove(other->queued_tasks, ARRAY_TAIL, &queued))
{
DBG2(DBG_IKE, "migrating %N task", task_type_names, task->get_type(task));
task->migrate(task, this->ike_sa);
array_insert(this->queued_tasks, ARRAY_HEAD, task);
DBG2(DBG_IKE, "migrating %N task", task_type_names,
queued->task->get_type(queued->task));
queued->task->migrate(queued->task, this->ike_sa);
/* don't delay tasks on the new IKE_SA */
queued->time = now;
array_insert(this->queued_tasks, ARRAY_HEAD, queued);
}
}
/**
* Migrates child-creating tasks from src to dst
* Migrates child-creating tasks from other to this
*/
static void migrate_child_tasks(private_task_manager_t *this,
array_t *src, array_t *dst)
private_task_manager_t *other,
task_queue_t queue)
{
enumerator_t *enumerator;
array_t *array;
task_t *task;
enumerator = array_create_enumerator(src);
switch (queue)
{
case TASK_QUEUE_ACTIVE:
array = other->active_tasks;
break;
case TASK_QUEUE_QUEUED:
array = other->queued_tasks;
break;
default:
return;
}
enumerator = array_create_enumerator(array);
while (enumerator->enumerate(enumerator, &task))
{
queued_task_t *queued = NULL;
if (queue == TASK_QUEUE_QUEUED)
{
queued = (queued_task_t*)task;
task = queued->task;
}
if (task->get_type(task) == TASK_CHILD_CREATE)
{
array_remove_at(src, enumerator);
array_remove_at(array, enumerator);
task->migrate(task, this->ike_sa);
array_insert(dst, ARRAY_TAIL, task);
queue_task(this, task);
free(queued);
}
}
enumerator->destroy(enumerator);
@@ -1823,9 +1909,9 @@ METHOD(task_manager_t, adopt_child_tasks, void,
private_task_manager_t *other = (private_task_manager_t*)other_public;
/* move active child tasks from other to this */
migrate_child_tasks(this, other->active_tasks, this->queued_tasks);
migrate_child_tasks(this, other, TASK_QUEUE_ACTIVE);
/* do the same for queued tasks */
migrate_child_tasks(this, other->queued_tasks, this->queued_tasks);
migrate_child_tasks(this, other, TASK_QUEUE_QUEUED);
}
METHOD(task_manager_t, busy, bool,
@@ -1838,7 +1924,9 @@ METHOD(task_manager_t, reset, void,
private_task_manager_t *this, uint32_t initiate, uint32_t respond)
{
enumerator_t *enumerator;
queued_task_t *queued;
task_t *task;
timeval_t now;
/* reset message counters and retransmit packets */
clear_packets(this->responding.packets);
@@ -1857,11 +1945,13 @@ METHOD(task_manager_t, reset, void,
}
this->initiating.type = EXCHANGE_TYPE_UNDEFINED;
time_monotonic(&now);
/* reset queued tasks */
enumerator = array_create_enumerator(this->queued_tasks);
while (enumerator->enumerate(enumerator, &task))
while (enumerator->enumerate(enumerator, &queued))
{
task->migrate(task, this->ike_sa);
queued->time = now;
queued->task->migrate(queued->task, this->ike_sa);
}
enumerator->destroy(enumerator);
@@ -1869,12 +1959,25 @@ METHOD(task_manager_t, reset, void,
while (array_remove(this->active_tasks, ARRAY_TAIL, &task))
{
task->migrate(task, this->ike_sa);
array_insert(this->queued_tasks, ARRAY_HEAD, task);
INIT(queued,
.task = task,
.time = now,
);
array_insert(this->queued_tasks, ARRAY_HEAD, queued);
}
this->reset = TRUE;
}
/**
* Filter queued tasks
*/
static bool filter_queued(void *unused, queued_task_t **queued, task_t **task)
{
*task = (*queued)->task;
return TRUE;
}
METHOD(task_manager_t, create_task_enumerator, enumerator_t*,
private_task_manager_t *this, task_queue_t queue)
{
@@ -1885,7 +1988,9 @@ METHOD(task_manager_t, create_task_enumerator, enumerator_t*,
case TASK_QUEUE_PASSIVE:
return array_create_enumerator(this->passive_tasks);
case TASK_QUEUE_QUEUED:
return array_create_enumerator(this->queued_tasks);
return enumerator_create_filter(
array_create_enumerator(this->queued_tasks),
(void*)filter_queued, NULL, NULL);
default:
return enumerator_create_empty();
}
@@ -1921,6 +2026,7 @@ task_manager_v2_t *task_manager_v2_create(ike_sa_t *ike_sa)
.task_manager = {
.process_message = _process_message,
.queue_task = _queue_task,
.queue_task_delayed = _queue_task_delayed,
.queue_ike = _queue_ike,
.queue_ike_rekey = _queue_ike_rekey,
.queue_ike_reauth = _queue_ike_reauth,
+39 -7
View File
@@ -1,8 +1,8 @@
/*
* Copyright (C) 2008 Tobias Brunner
* Copyright (C) 2008-2016 Tobias Brunner
* Copyright (C) 2005-2008 Martin Willi
* Copyright (C) 2005 Jan Hutter
* Hochschule fuer Technik Rapperswil
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -29,7 +29,7 @@
#include <encoding/payloads/delete_payload.h>
#include <processing/jobs/delete_ike_sa_job.h>
#include <processing/jobs/inactivity_job.h>
#include <processing/jobs/initiate_tasks_job.h>
typedef struct private_child_create_t private_child_create_t;
@@ -204,6 +204,25 @@ struct private_child_create_t {
bool retry;
};
/**
* Schedule a retry if creating the CHILD_SA temporary failed
*/
static void schedule_delayed_retry(private_child_create_t *this)
{
child_create_t *task;
uint32_t retry;
retry = RETRY_INTERVAL - (random() % RETRY_JITTER);
task = child_create_create(this->ike_sa,
this->config->get_ref(this->config), FALSE,
this->packet_tsi, this->packet_tsr);
task->use_reqid(task, this->reqid);
DBG1(DBG_IKE, "creating CHILD_SA failed, trying again in %d seconds",
retry);
this->ike_sa->queue_task_delayed(this->ike_sa, (task_t*)task, retry);
}
/**
* get the nonce from a message
*/
@@ -464,7 +483,7 @@ static status_t select_and_install(private_child_create_t *this,
chunk_t integ_i = chunk_empty, integ_r = chunk_empty;
linked_list_t *my_ts, *other_ts;
host_t *me, *other;
bool private;
bool private, prefer_configured;
if (this->proposals == NULL)
{
@@ -481,8 +500,10 @@ static status_t select_and_install(private_child_create_t *this,
other = this->ike_sa->get_other_host(this->ike_sa);
private = this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN);
prefer_configured = lib->settings->get_bool(lib->settings,
"%s.prefer_configured_proposals", TRUE, lib->ns);
this->proposal = this->config->select_proposal(this->config,
this->proposals, no_dh, private);
this->proposals, no_dh, private, prefer_configured);
if (this->proposal == NULL)
{
DBG1(DBG_IKE, "no acceptable proposal found");
@@ -1232,13 +1253,13 @@ METHOD(task_t, build_r, status_t,
if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING)
{
DBG1(DBG_IKE, "unable to create CHILD_SA while rekeying IKE_SA");
message->add_notify(message, TRUE, NO_ADDITIONAL_SAS, chunk_empty);
message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty);
return SUCCESS;
}
if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
{
DBG1(DBG_IKE, "unable to create CHILD_SA while deleting IKE_SA");
message->add_notify(message, TRUE, NO_ADDITIONAL_SAS, chunk_empty);
message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty);
return SUCCESS;
}
@@ -1441,6 +1462,17 @@ METHOD(task_t, process_i, status_t,
/* an error in CHILD_SA creation is not critical */
return SUCCESS;
}
case TEMPORARY_FAILURE:
{
DBG1(DBG_IKE, "received %N notify, will retry later",
notify_type_names, type);
enumerator->destroy(enumerator);
if (!this->rekey)
{ /* the rekey task will retry itself if necessary */
schedule_delayed_retry(this);
}
return SUCCESS;
}
case INVALID_KE_PAYLOAD:
{
chunk_t data;
+46 -15
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2009-2016 Tobias Brunner
* Copyright (C) 2006-2007 Martin Willi
* Hochschule fuer Technik Rapperswil
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -18,7 +19,7 @@
#include <daemon.h>
#include <encoding/payloads/delete_payload.h>
#include <sa/ikev2/tasks/child_create.h>
#include <sa/ikev2/tasks/child_rekey.h>
typedef struct private_child_delete_t private_child_delete_t;
@@ -118,6 +119,33 @@ static void build_payloads(private_child_delete_t *this, message_t *message)
enumerator->destroy(enumerator);
}
/**
* Check if the given CHILD_SA is the redundant SA created in a rekey collision.
*/
static bool is_redundant(private_child_delete_t *this, child_sa_t *child)
{
enumerator_t *tasks;
task_t *task;
tasks = this->ike_sa->create_task_enumerator(this->ike_sa,
TASK_QUEUE_ACTIVE);
while (tasks->enumerate(tasks, &task))
{
if (task->get_type(task) == TASK_CHILD_REKEY)
{
child_rekey_t *rekey = (child_rekey_t*)task;
if (rekey->is_redundant(rekey, child))
{
tasks->destroy(tasks);
return TRUE;
}
}
}
tasks->destroy(tasks);
return FALSE;
}
/**
* read in payloads and find the children to delete
*/
@@ -157,24 +185,31 @@ static void process_payloads(private_child_delete_t *this, message_t *message)
switch (child_sa->get_state(child_sa))
{
case CHILD_REKEYING:
case CHILD_REKEYED:
this->rekeyed = TRUE;
/* we reply as usual, rekeying will fail */
break;
case CHILD_DELETING:
/* we don't send back a delete if we initiated ourself */
if (!this->initiator)
{
this->ike_sa->destroy_child_sa(this->ike_sa,
protocol, spi);
continue;
}
/* fall through */
case CHILD_REKEYING:
/* we reply as usual, rekeying will fail */
case CHILD_INSTALLED:
if (!this->initiator)
{ /* reestablish installed children if required */
this->check_delete_action = TRUE;
{
if (is_redundant(this, child_sa))
{
this->rekeyed = TRUE;
}
else
{
this->check_delete_action = TRUE;
}
}
break;
default:
break;
}
@@ -206,7 +241,7 @@ static status_t destroy_and_reestablish(private_child_delete_t *this)
enumerator = this->child_sas->create_enumerator(this->child_sas);
while (enumerator->enumerate(enumerator, (void**)&child_sa))
{
/* signal child down event if we are not rekeying */
/* signal child down event if we weren't rekeying */
if (!this->rekeyed)
{
charon->bus->child_updown(charon->bus, child_sa, FALSE);
@@ -308,7 +343,7 @@ METHOD(task_t, build_i, status_t,
this->spi = child_sa->get_spi(child_sa, TRUE);
}
this->child_sas->insert_last(this->child_sas, child_sa);
if (child_sa->get_state(child_sa) == CHILD_REKEYING)
if (child_sa->get_state(child_sa) == CHILD_REKEYED)
{
this->rekeyed = TRUE;
}
@@ -347,11 +382,7 @@ METHOD(task_t, process_r, status_t,
METHOD(task_t, build_r, status_t,
private_child_delete_t *this, message_t *message)
{
/* if we are rekeying, we send an empty informational */
if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING)
{
build_payloads(this, message);
}
build_payloads(this, message);
DBG1(DBG_IKE, "CHILD_SA closed");
return destroy_and_reestablish(this);
}
+95 -37
View File
@@ -1,7 +1,8 @@
/*
* Copyright (C) 2009-2016 Tobias Brunner
* Copyright (C) 2005-2007 Martin Willi
* Copyright (C) 2005 Jan Hutter
* Hochschule fuer Technik Rapperswil
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -159,14 +160,21 @@ METHOD(task_t, build_i, status_t,
{ /* check if it is an outbound CHILD_SA */
this->child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol,
this->spi, FALSE);
if (!this->child_sa)
{ /* CHILD_SA is gone, unable to rekey. As an empty CREATE_CHILD_SA
* exchange is invalid, we fall back to an INFORMATIONAL exchange.*/
message->set_exchange_type(message, INFORMATIONAL);
return SUCCESS;
if (this->child_sa)
{
/* we work only with the inbound SPI */
this->spi = this->child_sa->get_spi(this->child_sa, TRUE);
}
/* we work only with the inbound SPI */
this->spi = this->child_sa->get_spi(this->child_sa, TRUE);
}
if (!this->child_sa ||
(!this->child_create &&
this->child_sa->get_state(this->child_sa) != CHILD_INSTALLED) ||
(this->child_create &&
this->child_sa->get_state(this->child_sa) != CHILD_REKEYING))
{
/* CHILD_SA is gone or in the wrong state, unable to rekey */
message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
return SUCCESS;
}
config = this->child_sa->get_config(this->child_sa);
@@ -218,12 +226,18 @@ METHOD(task_t, build_r, status_t,
{
child_cfg_t *config;
uint32_t reqid;
child_sa_state_t state;
if (this->child_sa == NULL ||
this->child_sa->get_state(this->child_sa) == CHILD_DELETING)
if (!this->child_sa)
{
DBG1(DBG_IKE, "unable to rekey, CHILD_SA not found");
message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
message->add_notify(message, TRUE, CHILD_SA_NOT_FOUND, chunk_empty);
return SUCCESS;
}
if (this->child_sa->get_state(this->child_sa) == CHILD_DELETING)
{
DBG1(DBG_IKE, "unable to rekey, we are deleting the CHILD_SA");
message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty);
return SUCCESS;
}
@@ -237,14 +251,16 @@ METHOD(task_t, build_r, status_t,
this->child_create->set_config(this->child_create, config->get_ref(config));
this->child_create->task.build(&this->child_create->task, message);
state = this->child_sa->get_state(this->child_sa);
this->child_sa->set_state(this->child_sa, CHILD_REKEYING);
if (message->get_payload(message, PLV2_SECURITY_ASSOCIATION) == NULL)
{
/* rekeying failed, reuse old child */
this->child_sa->set_state(this->child_sa, CHILD_INSTALLED);
{ /* rekeying failed, reuse old child */
this->child_sa->set_state(this->child_sa, state);
return SUCCESS;
}
this->child_sa->set_state(this->child_sa, CHILD_REKEYING);
this->child_sa->set_state(this->child_sa, CHILD_REKEYED);
/* invoke rekey hook */
charon->bus->child_rekey(charon->bus, this->child_sa,
@@ -284,9 +300,9 @@ static child_sa_t *handle_collision(private_child_rekey_t *this)
if (child_sa)
{
child_sa->set_close_action(child_sa, ACTION_NONE);
if (child_sa->get_state(child_sa) != CHILD_REKEYING)
if (child_sa->get_state(child_sa) != CHILD_REKEYED)
{
child_sa->set_state(child_sa, CHILD_REKEYING);
child_sa->set_state(child_sa, CHILD_REKEYED);
}
}
}
@@ -337,6 +353,34 @@ METHOD(task_t, process_i, status_t,
this->ike_sa->get_id(this->ike_sa), TRUE));
return SUCCESS;
}
if (message->get_notify(message, CHILD_SA_NOT_FOUND))
{
child_cfg_t *child_cfg;
uint32_t reqid;
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);
reqid = this->child_sa->get_reqid(this->child_sa);
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);
charon->bus->child_updown(charon->bus, this->child_sa, FALSE);
this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi);
return this->ike_sa->initiate(this->ike_sa,
child_cfg->get_ref(child_cfg), reqid,
NULL, NULL);
}
if (this->child_create->task.process(&this->child_create->task,
message) == NEED_MORE)
@@ -346,10 +390,10 @@ METHOD(task_t, process_i, status_t,
}
if (message->get_payload(message, PLV2_SECURITY_ASSOCIATION) == NULL)
{
/* establishing new child failed, reuse old. but not when we
* received a delete in the meantime */
if (!(this->collision &&
this->collision->get_type(this->collision) == TASK_CHILD_DELETE))
/* establishing new child failed, reuse old and try again. but not when
* we received a delete in the meantime */
if (!this->collision ||
this->collision->get_type(this->collision) != TASK_CHILD_DELETE)
{
schedule_delayed_rekey(this);
}
@@ -377,9 +421,9 @@ METHOD(task_t, process_i, status_t,
return SUCCESS;
}
/* disable updown event for redundant CHILD_SA */
if (to_delete->get_state(to_delete) != CHILD_REKEYING)
if (to_delete->get_state(to_delete) != CHILD_REKEYED)
{
to_delete->set_state(to_delete, CHILD_REKEYING);
to_delete->set_state(to_delete, CHILD_REKEYED);
}
spi = to_delete->get_spi(to_delete, TRUE);
protocol = to_delete->get_protocol(to_delete);
@@ -398,6 +442,18 @@ METHOD(task_t, get_type, task_type_t,
return TASK_CHILD_REKEY;
}
METHOD(child_rekey_t, is_redundant, bool,
private_child_rekey_t *this, child_sa_t *child)
{
if (this->collision &&
this->collision->get_type(this->collision) == TASK_CHILD_REKEY)
{
private_child_rekey_t *rekey = (private_child_rekey_t*)this->collision;
return child == rekey->child_create->get_child(rekey->child_create);
}
return FALSE;
}
METHOD(child_rekey_t, collide, void,
private_child_rekey_t *this, task_t *other)
{
@@ -406,9 +462,18 @@ METHOD(child_rekey_t, collide, void,
if (other->get_type(other) == TASK_CHILD_REKEY)
{
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 */
other->destroy(other);
return;
}
/* ignore passive tasks that did not successfully create a CHILD_SA */
other_child = rekey->child_create->get_child(rekey->child_create);
if (!other_child ||
other_child->get_state(other_child) != CHILD_INSTALLED)
{
/* not the same child => no collision */
other->destroy(other);
return;
}
@@ -416,19 +481,11 @@ METHOD(child_rekey_t, collide, void,
else if (other->get_type(other) == TASK_CHILD_DELETE)
{
child_delete_t *del = (child_delete_t*)other;
if (this->collision &&
this->collision->get_type(this->collision) == TASK_CHILD_REKEY)
if (is_redundant(this, del->get_child(del)))
{
private_child_rekey_t *rekey;
rekey = (private_child_rekey_t*)this->collision;
if (del->get_child(del) == rekey->child_create->get_child(rekey->child_create))
{
/* peer deletes redundant child created in collision */
this->other_child_destroyed = TRUE;
other->destroy(other);
return;
}
this->other_child_destroyed = TRUE;
other->destroy(other);
return;
}
if (del->get_child(del) != this->child_sa)
{
@@ -439,7 +496,7 @@ METHOD(child_rekey_t, collide, void,
}
else
{
/* any other task is not critical for collisisions, ignore */
/* any other task is not critical for collisions, ignore */
other->destroy(other);
return;
}
@@ -496,6 +553,7 @@ child_rekey_t *child_rekey_create(ike_sa_t *ike_sa, protocol_id_t protocol,
.migrate = _migrate,
.destroy = _destroy,
},
.is_redundant = _is_redundant,
.collide = _collide,
},
.ike_sa = ike_sa,
+17 -4
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2016 Tobias Brunner
* Copyright (C) 2007 Martin Willi
* Hochschule fuer Technik Rapperswil
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -39,13 +40,25 @@ struct child_rekey_t {
task_t task;
/**
* Register a rekeying task which collides with this one
* Check if the given SA is the redundant CHILD_SA created during a rekey
* collision.
*
* This is called if the other peer deletes the redundant SA before we were
* able to handle the CREATE_CHILD_SA response.
*
* @param child CHILD_SA to check
* @return TRUE if the SA is the redundant CHILD_SA
*/
bool (*is_redundant)(child_rekey_t *this, child_sa_t *child);
/**
* Register a rekeying/delete task which collides with this one
*
* If two peers initiate rekeying at the same time, the collision must
* be handled gracefully. The task manager is aware of what exchanges
* are going on and notifies the outgoing task by passing the incoming.
* are going on and notifies the active task by passing the passive.
*
* @param other incoming task
* @param other passive task (adopted)
*/
void (*collide)(child_rekey_t* this, task_t *other);
};
+44 -18
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2016 Tobias Brunner
* Copyright (C) 2006-2007 Martin Willi
* Hochschule fuer Technik Rapperswil
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -17,7 +18,7 @@
#include <daemon.h>
#include <encoding/payloads/delete_payload.h>
#include <sa/ikev2/tasks/ike_rekey.h>
typedef struct private_ike_delete_t private_ike_delete_t;
@@ -45,11 +46,6 @@ struct private_ike_delete_t {
* are we deleting a rekeyed SA?
*/
bool rekeyed;
/**
* are we responding to a delete, but have initated our own?
*/
bool simultaneous;
};
METHOD(task_t, build_i, status_t,
@@ -68,7 +64,8 @@ METHOD(task_t, build_i, status_t,
delete_payload = delete_payload_create(PLV2_DELETE, PROTO_IKE);
message->add_payload(message, (payload_t*)delete_payload);
if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING)
if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING ||
this->ike_sa->get_state(this->ike_sa) == IKE_REKEYED)
{
this->rekeyed = TRUE;
}
@@ -93,6 +90,33 @@ METHOD(task_t, process_i, status_t,
return DESTROY_ME;
}
/**
* Check if this delete happened after a rekey collsion
*/
static bool after_rekey_collision(private_ike_delete_t *this)
{
enumerator_t *tasks;
task_t *task;
tasks = this->ike_sa->create_task_enumerator(this->ike_sa,
TASK_QUEUE_ACTIVE);
while (tasks->enumerate(tasks, &task))
{
if (task->get_type(task) == TASK_IKE_REKEY)
{
ike_rekey_t *rekey = (ike_rekey_t*)task;
if (rekey->did_collide(rekey))
{
tasks->destroy(tasks);
return TRUE;
}
}
}
tasks->destroy(tasks);
return FALSE;
}
METHOD(task_t, process_r, status_t,
private_ike_delete_t *this, message_t *message)
{
@@ -119,16 +143,24 @@ METHOD(task_t, process_r, status_t,
switch (this->ike_sa->get_state(this->ike_sa))
{
case IKE_REKEYING:
/* if the peer concurrently deleted the IKE_SA we treat this as
* regular delete. however, in case the peer did not detect a rekey
* collision it will delete the replaced IKE_SA if we are still in
* state IKE_REKEYING */
if (after_rekey_collision(this))
{
this->rekeyed = TRUE;
break;
}
/* fall-through */
case IKE_ESTABLISHED:
this->ike_sa->set_state(this->ike_sa, IKE_DELETING);
this->ike_sa->reestablish(this->ike_sa);
return NEED_MORE;
case IKE_REKEYING:
case IKE_REKEYED:
this->rekeyed = TRUE;
break;
case IKE_DELETING:
this->simultaneous = TRUE;
break;
default:
break;
}
@@ -141,11 +173,6 @@ METHOD(task_t, build_r, status_t,
{
DBG0(DBG_IKE, "IKE_SA deleted");
if (this->simultaneous)
{
/* wait for peer's response for our delete request */
return SUCCESS;
}
if (!this->rekeyed)
{ /* invoke ike_down() hook if SA has not been rekeyed */
charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE);
@@ -164,7 +191,6 @@ METHOD(task_t, migrate, void,
private_ike_delete_t *this, ike_sa_t *ike_sa)
{
this->ike_sa = ike_sa;
this->simultaneous = FALSE;
}
METHOD(task_t, destroy, void,
+4 -2
View File
@@ -373,13 +373,15 @@ static void process_payloads(private_ike_init_t *this, message_t *message)
{
sa_payload_t *sa_payload = (sa_payload_t*)payload;
linked_list_t *proposal_list;
bool private;
bool private, prefer_configured;
proposal_list = sa_payload->get_proposals(sa_payload);
private = this->ike_sa->supports_extension(this->ike_sa,
EXT_STRONGSWAN);
prefer_configured = lib->settings->get_bool(lib->settings,
"%s.prefer_configured_proposals", TRUE, lib->ns);
this->proposal = this->config->select_proposal(this->config,
proposal_list, private);
proposal_list, private, prefer_configured);
if (!this->proposal)
{
charon->bus->alert(charon->bus, ALERT_PROPOSAL_MISMATCH_IKE,
+169 -92
View File
@@ -1,7 +1,8 @@
/*
* Copyright (C) 2015-2016 Tobias Brunner
* Copyright (C) 2005-2008 Martin Willi
* Copyright (C) 2005 Jan Hutter
* Hochschule fuer Technik Rapperswil
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -66,8 +67,29 @@ struct private_ike_rekey_t {
* colliding task detected by the task manager
*/
task_t *collision;
/**
* TRUE if rekeying can't be handled temporarily
*/
bool failed_temporarily;
};
/**
* Schedule a retry if rekeying temporary failed
*/
static void schedule_delayed_rekey(private_ike_rekey_t *this)
{
uint32_t retry;
job_t *job;
retry = RETRY_INTERVAL - (random() % RETRY_JITTER);
job = (job_t*)rekey_ike_sa_job_create(
this->ike_sa->get_id(this->ike_sa), FALSE);
DBG1(DBG_IKE, "IKE_SA rekeying failed, trying again in %d seconds", retry);
this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
lib->scheduler->schedule_job(lib->scheduler, job, retry);
}
/**
* Check if an IKE_SA has any queued tasks, return initiation job
*/
@@ -83,7 +105,6 @@ static job_t* check_queued_tasks(ike_sa_t *ike_sa)
job = (job_t*)initiate_tasks_job_create(ike_sa->get_id(ike_sa));
}
enumerator->destroy(enumerator);
return job;
}
@@ -117,22 +138,11 @@ static void establish_new(private_ike_rekey_t *this)
}
this->new_sa = NULL;
charon->bus->set_sa(charon->bus, this->ike_sa);
this->ike_sa->set_state(this->ike_sa, IKE_REKEYED);
}
}
METHOD(task_t, process_r_delete, status_t,
private_ike_rekey_t *this, message_t *message)
{
establish_new(this);
return this->ike_delete->task.process(&this->ike_delete->task, message);
}
METHOD(task_t, build_r_delete, status_t,
private_ike_rekey_t *this, message_t *message)
{
return this->ike_delete->task.build(&this->ike_delete->task, message);
}
METHOD(task_t, build_i_delete, status_t,
private_ike_rekey_t *this, message_t *message)
{
@@ -172,36 +182,59 @@ METHOD(task_t, build_i, status_t,
return NEED_MORE;
}
METHOD(task_t, process_r, status_t,
private_ike_rekey_t *this, message_t *message)
/**
* Check if there are any half-open children
*/
static bool have_half_open_children(private_ike_rekey_t *this)
{
enumerator_t *enumerator;
child_sa_t *child_sa;
if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
{
DBG1(DBG_IKE, "peer initiated rekeying, but we are deleting");
return NEED_MORE;
}
task_t *task;
enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa);
while (enumerator->enumerate(enumerator, (void**)&child_sa))
{
switch (child_sa->get_state(child_sa))
{
case CHILD_CREATED:
case CHILD_REKEYING:
case CHILD_RETRYING:
case CHILD_DELETING:
/* we do not allow rekeying while we have children in-progress */
DBG1(DBG_IKE, "peer initiated rekeying, but a child is half-open");
enumerator->destroy(enumerator);
return NEED_MORE;
return TRUE;
default:
break;
}
}
enumerator->destroy(enumerator);
enumerator = this->ike_sa->create_task_enumerator(this->ike_sa,
TASK_QUEUE_ACTIVE);
while (enumerator->enumerate(enumerator, (void**)&task))
{
if (task->get_type(task) == TASK_CHILD_CREATE)
{
enumerator->destroy(enumerator);
return TRUE;
}
}
enumerator->destroy(enumerator);
return FALSE;
}
METHOD(task_t, process_r, status_t,
private_ike_rekey_t *this, message_t *message)
{
if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
{
DBG1(DBG_IKE, "peer initiated rekeying, but we are deleting");
this->failed_temporarily = TRUE;
return NEED_MORE;
}
if (have_half_open_children(this))
{
DBG1(DBG_IKE, "peer initiated rekeying, but a child is half-open");
this->failed_temporarily = TRUE;
return NEED_MORE;
}
this->new_sa = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager,
this->ike_sa->get_version(this->ike_sa), FALSE);
@@ -219,33 +252,57 @@ METHOD(task_t, process_r, status_t,
METHOD(task_t, build_r, status_t,
private_ike_rekey_t *this, message_t *message)
{
if (this->failed_temporarily)
{
message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty);
return SUCCESS;
}
if (this->new_sa == NULL)
{
/* IKE_SA/a CHILD_SA is in an inacceptable state, deny rekeying */
message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
return SUCCESS;
}
if (this->ike_init->task.build(&this->ike_init->task, message) == FAILED)
{
this->ike_init->task.destroy(&this->ike_init->task);
this->ike_init = NULL;
charon->bus->set_sa(charon->bus, this->ike_sa);
return SUCCESS;
}
charon->bus->set_sa(charon->bus, this->ike_sa);
this->ike_sa->set_state(this->ike_sa, IKE_REKEYING);
/* rekeying successful, delete the IKE_SA using a subtask */
this->ike_delete = ike_delete_create(this->ike_sa, FALSE);
this->public.task.build = _build_r_delete;
this->public.task.process = _process_r_delete;
if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING)
{ /* in case of a collision we let the initiating task handle this */
establish_new(this);
/* make sure the IKE_SA is gone in case the peer fails to delete it */
lib->scheduler->schedule_job(lib->scheduler, (job_t*)
delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE),
90);
}
return SUCCESS;
}
/* the peer does have to delete the IKE_SA. If it does not, we get a
* unusable IKE_SA in REKEYING state without a replacement. We consider
* this a timeout condition by the peer, and trigger a delete actively. */
lib->scheduler->schedule_job(lib->scheduler, (job_t*)
delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE), 90);
return NEED_MORE;
/**
* Conclude any undetected rekey collision.
*
* If the peer does not detect the collision it will delete this IKE_SA.
* Depending on when our request reaches the peer and we receive the delete
* this may get called at different times.
*
* Returns TRUE if there was a collision, FALSE otherwise.
*/
static bool conclude_undetected_collision(private_ike_rekey_t *this)
{
if (this->collision &&
this->collision->get_type(this->collision) == TASK_IKE_REKEY)
{
DBG1(DBG_IKE, "peer did not notice IKE_SA rekey collision, abort "
"active rekeying");
establish_new((private_ike_rekey_t*)this->collision);
return TRUE;
}
return FALSE;
}
METHOD(task_t, process_i, status_t,
@@ -266,18 +323,9 @@ METHOD(task_t, process_i, status_t,
{
case FAILED:
/* rekeying failed, fallback to old SA */
if (!(this->collision && (
this->collision->get_type(this->collision) == TASK_IKE_DELETE ||
this->collision->get_type(this->collision) == TASK_IKE_REAUTH)))
if (!conclude_undetected_collision(this))
{
job_t *job;
uint32_t retry = RETRY_INTERVAL - (random() % RETRY_JITTER);
job = (job_t*)rekey_ike_sa_job_create(
this->ike_sa->get_id(this->ike_sa), FALSE);
DBG1(DBG_IKE, "IKE_SA rekeying failed, "
"trying again in %d seconds", retry);
this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
lib->scheduler->schedule_job(lib->scheduler, job, retry);
schedule_delayed_rekey(this);
}
return SUCCESS;
case NEED_MORE:
@@ -293,55 +341,53 @@ METHOD(task_t, process_i, status_t,
this->collision->get_type(this->collision) == TASK_IKE_REKEY)
{
private_ike_rekey_t *other = (private_ike_rekey_t*)this->collision;
host_t *host;
chunk_t this_nonce, other_nonce;
/* ike_init can be NULL, if child_sa is half-open */
if (other->ike_init)
this_nonce = this->ike_init->get_lower_nonce(this->ike_init);
other_nonce = other->ike_init->get_lower_nonce(other->ike_init);
/* if we have the lower nonce, delete rekeyed SA. If not, delete
* the redundant. */
if (memcmp(this_nonce.ptr, other_nonce.ptr,
min(this_nonce.len, other_nonce.len)) < 0)
{
host_t *host;
chunk_t this_nonce, other_nonce;
this_nonce = this->ike_init->get_lower_nonce(this->ike_init);
other_nonce = other->ike_init->get_lower_nonce(other->ike_init);
/* if we have the lower nonce, delete rekeyed SA. If not, delete
* the redundant. */
if (memcmp(this_nonce.ptr, other_nonce.ptr,
min(this_nonce.len, other_nonce.len)) > 0)
DBG1(DBG_IKE, "IKE_SA rekey collision lost, deleting redundant "
"IKE_SA %s[%d]", this->new_sa->get_name(this->new_sa),
this->new_sa->get_unique_id(this->new_sa));
/* apply host for a proper delete */
host = this->ike_sa->get_my_host(this->ike_sa);
this->new_sa->set_my_host(this->new_sa, host->clone(host));
host = this->ike_sa->get_other_host(this->ike_sa);
this->new_sa->set_other_host(this->new_sa, host->clone(host));
/* IKE_SAs in state IKE_REKEYED are silently deleted, so we use
* IKE_REKEYING */
this->new_sa->set_state(this->new_sa, IKE_REKEYING);
if (this->new_sa->delete(this->new_sa) == DESTROY_ME)
{
/* peer should delete this SA. Add a timeout just in case. */
job_t *job = (job_t*)delete_ike_sa_job_create(
other->new_sa->get_id(other->new_sa), TRUE);
lib->scheduler->schedule_job(lib->scheduler, job, 10);
DBG1(DBG_IKE, "IKE_SA rekey collision won, waiting for delete");
charon->ike_sa_manager->checkin(charon->ike_sa_manager, other->new_sa);
other->new_sa = NULL;
this->new_sa->destroy(this->new_sa);
}
else
{
DBG1(DBG_IKE, "IKE_SA rekey collision lost, "
"deleting redundant IKE_SA");
/* apply host for a proper delete */
host = this->ike_sa->get_my_host(this->ike_sa);
this->new_sa->set_my_host(this->new_sa, host->clone(host));
host = this->ike_sa->get_other_host(this->ike_sa);
this->new_sa->set_other_host(this->new_sa, host->clone(host));
this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
this->new_sa->set_state(this->new_sa, IKE_REKEYING);
if (this->new_sa->delete(this->new_sa) == DESTROY_ME)
{
this->new_sa->destroy(this->new_sa);
}
else
{
charon->ike_sa_manager->checkin(
charon->ike_sa_manager, this->new_sa);
}
charon->bus->set_sa(charon->bus, this->ike_sa);
this->new_sa = NULL;
establish_new(other);
return SUCCESS;
charon->ike_sa_manager->checkin(charon->ike_sa_manager,
this->new_sa);
}
charon->bus->set_sa(charon->bus, this->ike_sa);
this->new_sa = NULL;
establish_new(other);
return SUCCESS;
}
/* peer should delete this SA. Add a timeout just in case. */
job_t *job = (job_t*)delete_ike_sa_job_create(
other->new_sa->get_id(other->new_sa), TRUE);
lib->scheduler->schedule_job(lib->scheduler, job,
HALF_OPEN_IKE_SA_TIMEOUT);
DBG1(DBG_IKE, "IKE_SA rekey collision won, waiting for delete for "
"redundant IKE_SA %s[%d]", other->new_sa->get_name(other->new_sa),
other->new_sa->get_unique_id(other->new_sa));
other->new_sa->set_state(other->new_sa, IKE_REKEYED);
charon->ike_sa_manager->checkin(charon->ike_sa_manager, other->new_sa);
other->new_sa = NULL;
charon->bus->set_sa(charon->bus, this->ike_sa);
}
@@ -361,11 +407,41 @@ METHOD(task_t, get_type, task_type_t,
return TASK_IKE_REKEY;
}
METHOD(ike_rekey_t, did_collide, bool,
private_ike_rekey_t *this)
{
return this->collision &&
this->collision->get_type(this->collision) == TASK_IKE_REKEY;
}
METHOD(ike_rekey_t, collide, void,
private_ike_rekey_t* this, task_t *other)
{
DBG1(DBG_IKE, "detected %N collision with %N", task_type_names,
TASK_IKE_REKEY, task_type_names, other->get_type(other));
switch (other->get_type(other))
{
case TASK_IKE_DELETE:
conclude_undetected_collision(this);
other->destroy(other);
return;
case TASK_IKE_REKEY:
{
private_ike_rekey_t *rekey = (private_ike_rekey_t*)other;
if (!rekey->ike_init)
{
DBG1(DBG_IKE, "colliding exchange did not result in an IKE_SA, "
"ignore");
other->destroy(other);
return;
}
break;
}
default:
break;
}
DESTROY_IF(this->collision);
this->collision = other;
}
@@ -425,6 +501,7 @@ ike_rekey_t *ike_rekey_create(ike_sa_t *ike_sa, bool initiator)
.migrate = _migrate,
.destroy = _destroy,
},
.did_collide = _did_collide,
.collide = _collide,
},
.ike_sa = ike_sa,
+9 -1
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2016 Tobias Brunner
* Copyright (C) 2007 Martin Willi
* Hochschule fuer Technik Rapperswil
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -37,6 +38,13 @@ struct ike_rekey_t {
*/
task_t task;
/**
* Check if there was a rekey collision.
*
* @return TRUE if there was a rekey collision before
*/
bool (*did_collide)(ike_rekey_t *this);
/**
* Register a rekeying task which collides with this one.
*
+13 -2
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2013-2016 Tobias Brunner
* Copyright (C) 2006 Martin Willi
* Hochschule fuer Technik Rapperswil
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -122,7 +123,17 @@ struct task_manager_t {
*
* @param task task to queue
*/
void (*queue_task) (task_manager_t *this, task_t *task);
void (*queue_task)(task_manager_t *this, task_t *task);
/**
* Queue a task in the manager, but delay its initiation for at least the
* given number of seconds.
*
* @param task task to queue
* @param delay minimum delay in s before initiating the task
*/
void (*queue_task_delayed)(task_manager_t *this, task_t *task,
uint32_t delay);
/**
* Queue IKE_SA establishing tasks.
+33 -1
View File
@@ -1,8 +1,9 @@
TESTS = libcharon_tests
TESTS = libcharon_tests exchange_tests
check_PROGRAMS = $(TESTS)
libcharon_tests_SOURCES = \
suites/test_proposal.c \
suites/test_ike_cfg.c \
suites/test_mem_pool.c \
suites/test_message_chapoly.c \
@@ -21,3 +22,34 @@ libcharon_tests_LDADD = \
$(top_builddir)/src/libcharon/libcharon.la \
$(top_builddir)/src/libstrongswan/libstrongswan.la \
$(top_builddir)/src/libstrongswan/tests/libtest.la
exchange_tests_SOURCES = \
suites/test_child_create.c \
suites/test_child_delete.c \
suites/test_child_rekey.c \
suites/test_ike_delete.c \
suites/test_ike_rekey.c \
utils/exchange_test_asserts.h utils/exchange_test_asserts.c \
utils/exchange_test_helper.h utils/exchange_test_helper.c \
utils/job_asserts.h \
utils/mock_dh.h utils/mock_dh.c \
utils/mock_ipsec.h utils/mock_ipsec.c \
utils/mock_nonce_gen.h utils/mock_nonce_gen.c \
utils/mock_sender.h utils/mock_sender.c \
utils/sa_asserts.h \
exchange_tests.h exchange_tests.c
exchange_tests_CFLAGS = \
-I$(top_srcdir)/src/libcharon \
-I$(top_srcdir)/src/libstrongswan \
-I$(top_srcdir)/src/libstrongswan/tests \
-DPLUGINDIR=\""$(abs_top_builddir)/src/libstrongswan/plugins\"" \
-DPLUGINS=\""${s_plugins}\"" \
@COVERAGE_CFLAGS@
exchange_tests_LDFLAGS = @COVERAGE_LDFLAGS@
exchange_tests_LDADD = \
$(top_builddir)/src/libcharon/libcharon.la \
$(top_builddir)/src/libstrongswan/libstrongswan.la \
$(top_builddir)/src/libstrongswan/tests/libtest.la
+64
View File
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <test_runner.h>
#include <daemon.h>
#include "utils/exchange_test_helper.h"
/* declare test suite constructors */
#define TEST_SUITE(x) test_suite_t* x();
#define TEST_SUITE_DEPEND(x, ...) TEST_SUITE(x)
#include "exchange_tests.h"
#undef TEST_SUITE
#undef TEST_SUITE_DEPEND
static test_configuration_t tests[] = {
#define TEST_SUITE(x) \
{ .suite = x, },
#define TEST_SUITE_DEPEND(x, type, ...) \
{ .suite = x, .feature = PLUGIN_DEPENDS(type, __VA_ARGS__) },
#include "exchange_tests.h"
{ .suite = NULL, }
};
static bool test_runner_init(bool init)
{
if (init)
{
char *plugins, *plugindir;
libcharon_init();
plugins = getenv("TESTS_PLUGINS") ?:
lib->settings->get_str(lib->settings,
"tests.load", PLUGINS);
plugindir = lib->settings->get_str(lib->settings,
"tests.plugindir", PLUGINDIR);
plugin_loader_add_plugindirs(plugindir, plugins);
exchange_test_helper_init(plugins);
}
else
{
exchange_test_helper_deinit();
libcharon_deinit();
}
return TRUE;
}
int main(int argc, char *argv[])
{
return test_runner_run("exchanges", tests, test_runner_init);
}
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
TEST_SUITE(ike_delete_suite_create)
TEST_SUITE(ike_rekey_suite_create)
TEST_SUITE(child_create_suite_create)
TEST_SUITE(child_delete_suite_create)
TEST_SUITE(child_rekey_suite_create)
-3
View File
@@ -53,9 +53,6 @@ static bool test_runner_init(bool init)
}
else
{
lib->processor->set_threads(lib->processor, 0);
lib->processor->cancel(lib->processor);
lib->plugins->unload(lib->plugins);
libcharon_deinit();
}
return TRUE;
+12
View File
@@ -1,4 +1,7 @@
/*
* Copyright (C) 2014-2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* Copyright (C) 2014 Martin Willi
* Copyright (C) 2014 revosec AG
*
@@ -13,6 +16,15 @@
* for more details.
*/
/**
* @defgroup libcharon-tests tests
* @ingroup libcharon
*
* @defgroup test_utils_c test_utils
* @ingroup libcharon-tests
*/
TEST_SUITE(proposal_suite_create)
TEST_SUITE(ike_cfg_suite_create)
TEST_SUITE(mem_pool_suite_create)
TEST_SUITE_DEPEND(message_chapoly_suite_create, AEAD, ENCR_CHACHA20_POLY1305, 32)
@@ -0,0 +1,106 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "test_suite.h"
#include <daemon.h>
#include <tests/utils/exchange_test_helper.h>
#include <tests/utils/exchange_test_asserts.h>
#include <tests/utils/job_asserts.h>
#include <tests/utils/sa_asserts.h>
/**
* One of the peers tries to create a new CHILD_SA while the other concurrently
* started to rekey the IKE_SA. TEMPORARY_FAILURE should be returned on both
* sides and the peers should prepare to retry.
*/
START_TEST(test_collision_ike_rekey)
{
child_cfg_t *child_cfg;
child_cfg_create_t child = {
.mode = MODE_TUNNEL,
};
ike_sa_t *a, *b;
exchange_test_helper->establish_sa(exchange_test_helper,
&a, &b, NULL);
assert_hook_not_called(child_updown);
child_cfg = child_cfg_create("child", &child);
child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP));
child_cfg->add_traffic_selector(child_cfg, TRUE,
traffic_selector_create_dynamic(0, 0, 65535));
child_cfg->add_traffic_selector(child_cfg, FALSE,
traffic_selector_create_dynamic(0, 0, 65535));
call_ikesa(a, initiate, child_cfg, 0, NULL, NULL);
assert_child_sa_count(a, 1);
assert_hook();
call_ikesa(b, rekey);
/* CREATE_CHILD_SA { SA, Ni, [KEi,] TSi, TSr } --> */
assert_hook_not_called(child_updown);
assert_single_notify(OUT, TEMPORARY_FAILURE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_count(b, 1);
assert_hook();
/* <-- CREATE_CHILD_SA { SA, Ni, KEi } */
assert_single_notify(OUT, TEMPORARY_FAILURE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
/* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
assert_hook_not_called(child_updown);
assert_jobs_scheduled(1);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_count(a, 1);
assert_scheduler();
assert_hook();
/* CREATE_CHILD_SA { N(TEMP_FAIL) } --> */
assert_jobs_scheduled(1);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_ike_sa_state(b, IKE_ESTABLISHED);
assert_scheduler();
/* make sure no message was sent after handling the TEMPORARY_FAILURE and
* that the task to retry creating the CHILD_SA is queued and not active
* and it can't be initiated immediately */
ck_assert(!exchange_test_helper->sender->dequeue(exchange_test_helper->sender));
assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE);
assert_num_tasks(a, 1, TASK_QUEUE_QUEUED);
call_ikesa(a, initiate, NULL, 0, NULL, NULL);
assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE);
assert_sa_idle(b);
call_ikesa(a, destroy);
call_ikesa(b, destroy);
}
END_TEST
Suite *child_create_suite_create()
{
Suite *s;
TCase *tc;
s = suite_create("child create");
tc = tcase_create("collisions ike rekey");
tcase_add_test(tc, test_collision_ike_rekey);
suite_add_tcase(s, tc);
return s;
}
@@ -0,0 +1,366 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "test_suite.h"
#include <daemon.h>
#include <tests/utils/exchange_test_helper.h>
#include <tests/utils/exchange_test_asserts.h>
#include <tests/utils/job_asserts.h>
#include <tests/utils/sa_asserts.h>
/**
* Regular CHILD_SA deletion either initiated by the original initiator or
* responder of the IKE_SA.
*/
START_TEST(test_regular)
{
ike_sa_t *a, *b;
if (_i)
{ /* responder deletes the CHILD_SA (SPI 2) */
exchange_test_helper->establish_sa(exchange_test_helper,
&b, &a, NULL);
}
else
{ /* initiator deletes the CHILD_SA (SPI 1) */
exchange_test_helper->establish_sa(exchange_test_helper,
&a, &b, NULL);
}
assert_hook_not_called(child_updown);
call_ikesa(a, delete_child_sa, PROTO_ESP, _i+1, FALSE);
assert_child_sa_state(a, _i+1, CHILD_DELETING);
assert_hook();
/* INFORMATIONAL { D } --> */
assert_hook_updown(child_updown, FALSE);
assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_count(b, 0);
assert_hook();
/* <-- INFORMATIONAL { D } */
assert_hook_updown(child_updown, FALSE);
assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_count(a, 0);
assert_hook();
call_ikesa(a, destroy);
call_ikesa(b, destroy);
}
END_TEST
/**
* Both peers initiate the CHILD_SA deletion concurrently and should handle
* the collision properly.
*/
START_TEST(test_collision)
{
ike_sa_t *a, *b;
exchange_test_helper->establish_sa(exchange_test_helper,
&a, &b, NULL);
/* both peers delete the CHILD_SA concurrently */
assert_hook_not_called(child_updown);
call_ikesa(a, delete_child_sa, PROTO_ESP, 1, FALSE);
assert_child_sa_state(a, 1, CHILD_DELETING);
call_ikesa(b, delete_child_sa, PROTO_ESP, 2, FALSE);
assert_child_sa_state(b, 2, CHILD_DELETING);
assert_hook();
/* RFC 7296 says:
*
* Normally, the response in the INFORMATIONAL exchange will contain
* Delete payloads for the paired SAs going in the other direction.
* There is one exception. If, by chance, both ends of a set of SAs
* independently decide to close them, each may send a Delete payload
* and the two requests may cross in the network. If a node receives a
* delete request for SAs for which it has already issued a delete
* request, it MUST delete the outgoing SAs while processing the request
* and the incoming SAs while processing the response. In that case,
* the responses MUST NOT include Delete payloads for the deleted SAs,
* since that would result in duplicate deletion and could in theory
* delete the wrong SA.
*
* We don't handle SAs separately so we expect both are still installed,
* but the INFORMATIONAL response should not contain a DELETE payload.
*/
/* INFORMATIONAL { D } --> */
assert_hook_not_called(child_updown);
assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_DELETING);
/* <-- INFORMATIONAL { D } */
assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, 1, CHILD_DELETING);
assert_hook();
/* <-- INFORMATIONAL { } */
assert_hook_updown(child_updown, FALSE);
assert_message_empty(IN);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_count(a, 0);
assert_hook();
/* INFORMATIONAL { } --> */
assert_hook_updown(child_updown, FALSE);
assert_message_empty(IN);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_count(b, 0);
assert_hook();
call_ikesa(a, destroy);
call_ikesa(b, destroy);
}
END_TEST
/**
* This is like the collision above but one of the DELETEs is dropped or delayed
* so the other peer is not aware that there is a collision.
*/
START_TEST(test_collision_drop)
{
ike_sa_t *a, *b;
message_t *msg;
exchange_test_helper->establish_sa(exchange_test_helper,
&a, &b, NULL);
/* both peers delete the CHILD_SA concurrently */
assert_hook_not_called(child_updown);
call_ikesa(a, delete_child_sa, PROTO_ESP, 1, FALSE);
assert_child_sa_state(a, 1, CHILD_DELETING);
call_ikesa(b, delete_child_sa, PROTO_ESP, 2, FALSE);
assert_child_sa_state(b, 2, CHILD_DELETING);
assert_hook();
/* INFORMATIONAL { D } --> */
assert_hook_not_called(child_updown);
assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_state(b, 2, CHILD_DELETING);
assert_hook();
/* drop/delay the responder's message */
msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
/* <-- INFORMATIONAL { } */
assert_hook_updown(child_updown, FALSE);
assert_message_empty(IN);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_count(a, 0);
assert_hook();
/* <-- INFORMATIONAL { D } (delayed/retransmitted) */
assert_hook_not_called(child_updown);
assert_single_payload(IN, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, a, msg);
assert_hook();
/* INFORMATIONAL { } --> */
assert_hook_updown(child_updown, FALSE);
assert_message_empty(IN);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_count(b, 0);
assert_hook();
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
* peer is concurrently trying to delete.
*
* delete ----\ /---- rekey IKE
* \-----/----> detect collision
* detect collision <---------/ /---- delete
* TEMP_FAIL ----\ /
* \----/----->
* <--------/
*/
START_TEST(test_collision_ike_rekey)
{
ike_sa_t *a, *b;
uint32_t spi_a = _i+1;
if (_i)
{ /* responder deletes the CHILD_SA (SPI 2) */
exchange_test_helper->establish_sa(exchange_test_helper,
&b, &a, NULL);
}
else
{ /* initiator deletes the CHILD_SA (SPI 1) */
exchange_test_helper->establish_sa(exchange_test_helper,
&a, &b, NULL);
}
call_ikesa(a, delete_child_sa, PROTO_ESP, spi_a, FALSE);
assert_child_sa_state(a, spi_a, CHILD_DELETING);
call_ikesa(b, rekey);
assert_ike_sa_state(b, IKE_REKEYING);
/* this should never get called as there is no successful rekeying */
assert_hook_not_called(ike_rekey);
/* RFC 7296, 2.25.2: If a peer receives a request to delete a Child SA when
* it is currently rekeying the IKE SA, it SHOULD reply as usual, with a
* Delete payload.
*/
/* INFORMATIONAL { D } --> */
assert_hook_updown(child_updown, FALSE);
assert_single_payload(OUT, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_ike_sa_state(b, IKE_REKEYING);
assert_child_sa_count(b, 0);
assert_hook();
/* RFC 7296, 2.25.1: If a peer receives a request to rekey the IKE SA, and
* it is currently, rekeying, or closing a Child SA of that IKE SA, it
* SHOULD reply with TEMPORARY_FAILURE.
*/
/* <-- CREATE_CHILD_SA { SA, Ni, KEi } */
assert_single_notify(OUT, TEMPORARY_FAILURE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_state(a, spi_a, CHILD_DELETING);
/* <-- INFORMATIONAL { D } */
assert_hook_updown(child_updown, FALSE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_count(a, 0);
assert_hook();
/* CREATE_CHILD_SA { N(TEMP_FAIL) } --> */
/* we expect a job to retry the rekeying is scheduled */
assert_jobs_scheduled(1);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_ike_sa_state(b, IKE_ESTABLISHED);
assert_scheduler();
/* ike_rekey */
assert_hook();
call_ikesa(a, destroy);
call_ikesa(b, destroy);
}
END_TEST
/**
* One of the hosts initiates a delete of the IKE_SA of the CHILD_SA the other
* peer is concurrently trying to delete.
*
* delete ----\ /---- delete IKE
* \-----/----> detect collision
* <---------/ /---- delete
* delete ----\ /
* \----/----->
* sa already gone <--------/
*/
START_TEST(test_collision_ike_delete)
{
ike_sa_t *a, *b;
uint32_t spi_a = _i+1;
message_t *msg;
status_t s;
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);
}
call_ikesa(a, delete_child_sa, PROTO_ESP, spi_a, FALSE);
assert_child_sa_state(a, spi_a, CHILD_DELETING);
call_ikesa(b, delete);
assert_ike_sa_state(b, IKE_DELETING);
/* RFC 7296, 2.25.2 does not explicitly state what the behavior SHOULD be if
* a peer receives a request to delete a CHILD_SA when it is currently
* closing the IKE SA. We expect a regular response.
*/
/* INFORMATIONAL { D } --> */
assert_hook_updown(child_updown, FALSE);
assert_single_payload(OUT, PLV2_DELETE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_ike_sa_state(b, IKE_DELETING);
assert_child_sa_count(b, 0);
assert_hook();
/* RFC 7296, 2.25.1 does not explicitly state what the behavior SHOULD be if
* a peer receives a request to close the IKE SA if it is currently deleting
* a Child SA of that IKE SA. Let's just close the IKE_SA and forget the
* delete.
*/
/* <-- INFORMATIONAL { D } */
assert_hook_updown(ike_updown, FALSE);
assert_hook_updown(child_updown, FALSE);
assert_message_empty(OUT);
s = exchange_test_helper->process_message(exchange_test_helper, a, NULL);
ck_assert_int_eq(DESTROY_ME, s);
call_ikesa(a, destroy);
assert_hook();
assert_hook();
/* <-- INFORMATIONAL { D } */
/* the SA is already gone */
msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
msg->destroy(msg);
/* INFORMATIONAL { } --> */
assert_hook_updown(ike_updown, FALSE);
assert_hook_not_called(child_updown);
s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
ck_assert_int_eq(DESTROY_ME, s);
call_ikesa(b, destroy);
assert_hook();
assert_hook();
}
END_TEST
Suite *child_delete_suite_create()
{
Suite *s;
TCase *tc;
s = suite_create("child delete");
tc = tcase_create("regular");
tcase_add_loop_test(tc, test_regular, 0, 2);
suite_add_tcase(s, tc);
tc = tcase_create("collisions");
tcase_add_test(tc, test_collision);
tcase_add_test(tc, test_collision_drop);
suite_add_tcase(s, tc);
tc = tcase_create("collisions ike rekey");
tcase_add_loop_test(tc, test_collision_ike_rekey, 0, 2);
suite_add_tcase(s, tc);
tc = tcase_create("collisions ike delete");
tcase_add_loop_test(tc, test_collision_ike_delete, 0, 2);
suite_add_tcase(s, tc);
return s;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,137 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "test_suite.h"
#include <tests/utils/exchange_test_helper.h>
#include <tests/utils/exchange_test_asserts.h>
#include <tests/utils/sa_asserts.h>
/**
* Regular IKE_SA delete either initiated by the original initiator or
* responder of the IKE_SA.
*/
START_TEST(test_regular)
{
ike_sa_t *a, *b;
status_t s;
if (_i)
{ /* responder deletes the IKE_SA */
exchange_test_helper->establish_sa(exchange_test_helper,
&b, &a, NULL);
}
else
{ /* initiator deletes the IKE_SA */
exchange_test_helper->establish_sa(exchange_test_helper,
&a, &b, NULL);
}
assert_hook_not_called(ike_updown);
assert_hook_not_called(child_updown);
call_ikesa(a, delete);
assert_ike_sa_state(a, IKE_DELETING);
assert_hook();
assert_hook();
/* INFORMATIONAL { D } --> */
assert_hook_updown(ike_updown, FALSE);
assert_hook_updown(child_updown, FALSE);
assert_single_payload(IN, PLV2_DELETE);
s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
ck_assert_int_eq(DESTROY_ME, s);
call_ikesa(b, destroy);
assert_hook();
assert_hook();
/* <-- INFORMATIONAL { } */
assert_hook_updown(ike_updown, FALSE);
assert_hook_updown(child_updown, FALSE);
assert_message_empty(IN);
s = exchange_test_helper->process_message(exchange_test_helper, a, NULL);
ck_assert_int_eq(DESTROY_ME, s);
call_ikesa(a, destroy);
assert_hook();
assert_hook();
}
END_TEST
/**
* Both peers initiate the IKE_SA deletion concurrently and should handle the
* collision properly.
*/
START_TEST(test_collision)
{
ike_sa_t *a, *b;
status_t s;
exchange_test_helper->establish_sa(exchange_test_helper,
&a, &b, NULL);
assert_hook_not_called(ike_updown);
assert_hook_not_called(child_updown);
call_ikesa(a, delete);
assert_ike_sa_state(a, IKE_DELETING);
call_ikesa(b, delete);
assert_ike_sa_state(b, IKE_DELETING);
assert_hook();
assert_hook();
/* RFC 7296 says: If a peer receives a request to close an IKE SA that it
* is currently trying to close, it SHOULD reply as usual, and forget about
* its own close request.
* So we expect the SA to just get closed with an empty response still sent.
*/
/* INFORMATIONAL { D } --> */
assert_hook_updown(ike_updown, FALSE);
assert_hook_updown(child_updown, FALSE);
assert_single_payload(IN, PLV2_DELETE);
assert_message_empty(OUT);
s = exchange_test_helper->process_message(exchange_test_helper, b, NULL);
ck_assert_int_eq(DESTROY_ME, s);
call_ikesa(b, destroy);
assert_hook();
assert_hook();
/* <-- INFORMATIONAL { D } */
assert_hook_updown(ike_updown, FALSE);
assert_hook_updown(child_updown, FALSE);
assert_single_payload(IN, PLV2_DELETE);
assert_message_empty(OUT);
s = exchange_test_helper->process_message(exchange_test_helper, a, NULL);
ck_assert_int_eq(DESTROY_ME, s);
call_ikesa(a, destroy);
assert_hook();
assert_hook();
}
END_TEST
Suite *ike_delete_suite_create()
{
Suite *s;
TCase *tc;
s = suite_create("ike delete");
tc = tcase_create("regular");
tcase_add_loop_test(tc, test_regular, 0, 2);
suite_add_tcase(s, tc);
tc = tcase_create("collisions");
tcase_add_test(tc, test_collision);
suite_add_tcase(s, tc);
return s;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "test_suite.h"
#include <config/proposal.h>
static struct {
char *self;
char *other;
char *expected;
} select_data[] = {
{ "aes128", "aes128", "aes128" },
{ "aes128", "aes256", NULL },
{ "aes128-aes256", "aes256-aes128", "aes128" },
{ "aes256-aes128", "aes128-aes256", "aes256" },
{ "aes128-aes256-sha1-sha256", "aes256-aes128-sha256-sha1", "aes128-sha1" },
{ "aes256-aes128-sha256-sha1", "aes128-aes256-sha1-sha256", "aes256-sha256" },
{ "aes128-sha256-modp3072", "aes128-sha256", NULL },
{ "aes128-sha256", "aes128-sha256-modp3072", NULL },
{ "aes128-sha256-modp3072", "aes128-sha256-modpnone", NULL },
{ "aes128-sha256-modpnone", "aes128-sha256-modp3072", NULL },
{ "aes128-sha256-modp3072-modpnone", "aes128-sha256", "aes128-sha256" },
{ "aes128-sha256", "aes128-sha256-modp3072-modpnone", "aes128-sha256" },
{ "aes128-sha256-modp3072-modpnone", "aes128-sha256-modpnone-modp3072", "aes128-sha256-modp3072" },
{ "aes128-sha256-modpnone-modp3072", "aes128-sha256-modp3072-modpnone", "aes128-sha256-modpnone" },
};
START_TEST(test_select)
{
proposal_t *self, *other, *selected, *expected;
self = proposal_create_from_string(PROTO_ESP,
select_data[_i].self);
other = proposal_create_from_string(PROTO_ESP,
select_data[_i].other);
selected = self->select(self, other, FALSE);
if (select_data[_i].expected)
{
expected = proposal_create_from_string(PROTO_ESP,
select_data[_i].expected);
ck_assert(selected);
ck_assert_msg(expected->equals(expected, selected), "proposal %P does "
"not match expected %P", selected, expected);
expected->destroy(expected);
}
else
{
ck_assert(!selected);
}
DESTROY_IF(selected);
other->destroy(other);
self->destroy(self);
}
END_TEST
Suite *proposal_suite_create()
{
Suite *s;
TCase *tc;
s = suite_create("proposal");
tc = tcase_create("select");
tcase_add_loop_test(tc, test_select, 0, countof(select_data));
suite_add_tcase(s, tc);
return s;
}
@@ -0,0 +1,182 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <inttypes.h>
#include <test_suite.h>
#include "exchange_test_asserts.h"
/*
* Described in header
*/
bool exchange_test_asserts_hook(listener_t *listener)
{
listener_hook_assert_t *this = (listener_hook_assert_t*)listener;
this->count++;
return TRUE;
}
/*
* Described in header
*/
bool exchange_test_asserts_ike_updown(listener_t *listener, ike_sa_t *ike_sa,
bool up)
{
listener_hook_assert_t *this = (listener_hook_assert_t*)listener;
this->count++;
assert_listener_msg(this->up == up, this, "IKE_SA not '%s'",
this->up ? "up" : "down");
return TRUE;
}
/*
* Described in header
*/
bool exchange_test_asserts_child_updown(listener_t *listener, ike_sa_t *ike_sa,
child_sa_t *child_sa, bool up)
{
listener_hook_assert_t *this = (listener_hook_assert_t*)listener;
this->count++;
assert_listener_msg(this->up == up, this, "CHILD_SA not '%s'",
this->up ? "up" : "down");
return TRUE;
}
/*
* Described in header
*/
bool exchange_test_asserts_ike_rekey(listener_t *listener, ike_sa_t *old,
ike_sa_t *new)
{
listener_hook_assert_t *this = (listener_hook_assert_t*)listener;
ike_sa_id_t *id;
uint64_t spi;
this->count++;
id = old->get_id(old);
spi = id->get_initiator_spi(id);
assert_listener_msg(this->spi_old == spi, this, "unexpected old IKE_SA "
"%.16"PRIx64"_i instead of %.16"PRIx64"_i",
be64toh(spi), be64toh(this->spi_old));
id = new->get_id(new);
spi = id->get_initiator_spi(id);
assert_listener_msg(this->spi_new == spi, this, "unexpected new IKE_SA "
"%.16"PRIx64"_i instead of %.16"PRIx64"_i",
be64toh(spi), be64toh(this->spi_new));
return TRUE;
}
/*
* Described in header
*/
bool exchange_test_asserts_child_rekey(listener_t *listener, ike_sa_t *ike_sa,
child_sa_t *old, child_sa_t *new)
{
listener_hook_assert_t *this = (listener_hook_assert_t*)listener;
uint32_t spi, expected;
this->count++;
spi = old->get_spi(old, TRUE);
expected = this->spi_old;
assert_listener_msg(expected == spi, this, "unexpected old CHILD_SA %.8x "
"instead of %.8x", spi, expected);
spi = new->get_spi(new, TRUE);
expected = this->spi_new;
assert_listener_msg(expected == spi, this, "unexpected new CHILD_SA %.8x "
"instead of %.8x", spi, expected);
return TRUE;
}
/**
* Assert a given message rule
*/
static void assert_message_rule(listener_message_assert_t *this, message_t *msg,
listener_message_rule_t *rule)
{
if (rule->expected)
{
if (rule->payload)
{
assert_listener_msg(msg->get_payload(msg, rule->payload),
this, "expected payload (%N) not found",
payload_type_names, rule->payload);
}
if (rule->notify)
{
assert_listener_msg(msg->get_notify(msg, rule->notify),
this, "expected notify payload (%N) not found",
notify_type_names, rule->notify);
}
}
else
{
if (rule->payload)
{
assert_listener_msg(!msg->get_payload(msg, rule->payload),
this, "unexpected payload (%N) found",
payload_type_names, rule->payload);
}
if (rule->notify)
{
assert_listener_msg(!msg->get_notify(msg, rule->notify),
this, "unexpected notify payload (%N) found",
notify_type_names, rule->notify);
}
}
}
/*
* Described in header
*/
bool exchange_test_asserts_message(listener_t *listener, ike_sa_t *ike_sa,
message_t *message, bool incoming, bool plain)
{
listener_message_assert_t *this = (listener_message_assert_t*)listener;
if (plain && this->incoming == incoming)
{
if (this->count >= 0)
{
enumerator_t *enumerator;
int count = 0;
enumerator = message->create_payload_enumerator(message);
while (enumerator->enumerate(enumerator, NULL))
{
count++;
}
enumerator->destroy(enumerator);
assert_listener_msg(this->count == count, this, "unexpected payload "
"count in message (%d != %d)", this->count,
count);
}
if (this->num_rules)
{
int i;
for (i = 0; i < this->num_rules; i++)
{
assert_message_rule(this, message, &this->rules[i]);
}
}
return FALSE;
}
return TRUE;
}
@@ -0,0 +1,343 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* Special assertions using listener_t.
*
* @defgroup exchange_test_asserts exchange_test_asserts
* @{ @ingroup test_utils_c
*/
#ifndef EXCHANGE_TEST_ASSERTS_H_
#define EXCHANGE_TEST_ASSERTS_H_
#include <bus/listeners/listener.h>
typedef struct listener_hook_assert_t listener_hook_assert_t;
typedef struct listener_message_assert_t listener_message_assert_t;
typedef struct listener_message_rule_t listener_message_rule_t;
struct listener_hook_assert_t {
/**
* Implemented interface
*/
listener_t listener;
/**
* Original source file
*/
const char *file;
/**
* Source line
*/
int line;
/**
* Name of the hook
*/
const char *name;
/**
* Expected number of calls (-1 to ignore)
*/
int expected;
/**
* Number of times the hook was called
*/
int count;
/**
* Expected updown result
*/
bool up;
/**
* Initiator/Inbound SPIs to expect in rekey event
*/
uint64_t spi_old, spi_new;
};
/**
* Basic callback for methods on listener_t, counting the number of calls.
*/
bool exchange_test_asserts_hook(listener_t *this);
/**
* Implementation of listener_t::ike_updown.
*/
bool exchange_test_asserts_ike_updown(listener_t *this, ike_sa_t *ike_sa,
bool up);
/**
* Implementation of listener_t::child_updown.
*/
bool exchange_test_asserts_child_updown(listener_t *this, ike_sa_t *ike_sa,
child_sa_t *child_sa, bool up);
/**
* Implementation of listener_t::ike_rekey.
*/
bool exchange_test_asserts_ike_rekey(listener_t *this, ike_sa_t *old,
ike_sa_t *new);
/**
* Implementation of listener_t::child_rekey.
*/
bool exchange_test_asserts_child_rekey(listener_t *this, ike_sa_t *ike_sa,
child_sa_t *old, child_sa_t *new);
/**
* Check if a statement evaluates to TRUE, use original source file and line
* in the error message if not.
*
* @param x statement to evaluate
* @param l listener providing original source file and line
* @param fmt printf format string
* @param ... arguments for fmt
*/
#define assert_listener_msg(x, l, fmt, ...) ({ \
test_fail_if_worker_failed(); \
if (!(x)) \
{ \
test_fail_msg((l)->file, (l)->line, "%s: " fmt, #x, ##__VA_ARGS__); \
} \
})
/**
* Initialize an assertion that enforces that the given hook was called.
* Must be matched by a call to assert_hook().
*
* @param name name of the hook
*/
#define assert_hook_called(name) \
_assert_hook_init(name, exchange_test_asserts_hook, .expected = 1)
/**
* Initialize an assertion that enforces that the given hook was not called.
* Must be matched by a call to assert_hook().
*
* @param name name of the hook
*/
#define assert_hook_not_called(name) \
_assert_hook_init(name, exchange_test_asserts_hook, .expected = 0)
/**
* Initialize an assertion that enforces that the given updown hook was called
* with the expected result.
* Must be matched by a call to assert_hook().
*
* @param name name of the hook
* @param e whether to expect up in the hook to be TRUE or not
*/
#define assert_hook_updown(name, e) \
_assert_hook_init(name, \
streq(#name, "ike_updown") ? (void*)exchange_test_asserts_ike_updown \
: (void*)exchange_test_asserts_child_updown, \
.expected = 1, \
.up = e, \
)
/**
* Initialize an assertion that enforces that the given rekey hook was called
* with the SAs with the matching initiator/inbound SPIs.
* Must be matched by a call to assert_hook().
*
* @param name name of the hook
* @param old SPI of the old SA
* @param new SPI of the new SA
*/
#define assert_hook_rekey(name, old, new) \
_assert_hook_init(name, \
streq(#name, "ike_rekey") ? (void*)exchange_test_asserts_ike_rekey \
: (void*)exchange_test_asserts_child_rekey, \
.expected = 1, \
.spi_old = old, \
.spi_new = new, \
)
/**
* Initialize assertions against invocations of listener_t hooks. Each call
* must be matched by a call to assert_hook().
*/
#define _assert_hook_init(n, callback, ...) \
do { \
listener_hook_assert_t _hook_listener = { \
.listener = { .n = (void*)callback, }, \
.file = __FILE__, \
.line = __LINE__, \
.name = #n, \
##__VA_ARGS__ \
}; \
exchange_test_helper->add_listener(exchange_test_helper, &_hook_listener.listener)
/**
* Enforce the most recently initialized hook assertion.
*/
#define assert_hook() \
charon->bus->remove_listener(charon->bus, &_hook_listener.listener); \
if (_hook_listener.expected > 0) { \
if (_hook_listener.count > 0) { \
assert_listener_msg(_hook_listener.expected == _hook_listener.count, \
&_hook_listener, "hook '%s' was called %d times " \
"instead of %d", _hook_listener.name, \
_hook_listener.count, _hook_listener.expected); \
} else { \
assert_listener_msg(_hook_listener.count, &_hook_listener, \
"hook '%s' was not called (expected %d)", _hook_listener.name, \
_hook_listener.expected); \
} \
} else if (_hook_listener.expected == 0) { \
assert_listener_msg(_hook_listener.count == 0, &_hook_listener, \
"hook '%s' was called unexpectedly", _hook_listener.name); \
} \
} while(FALSE)
/**
* Rules regarding payloads/notifies to expect/not expect in a message
*/
struct listener_message_rule_t {
/**
* Whether the payload/notify is expected in the message, FALSE to fail if
* it is found
*/
bool expected;
/**
* Payload type to expect/not expect
*/
payload_type_t payload;
/**
* Notify type to expect/not expect (paylod type does not have to be
* specified)
*/
notify_type_t notify;
};
/**
* Data used to check plaintext messages via listener_t
*/
struct listener_message_assert_t {
/**
* Implemented interface
*/
listener_t listener;
/**
* Original source file
*/
const char *file;
/**
* Source line
*/
int line;
/**
* Whether to check the next inbound or outbound message
*/
bool incoming;
/**
* Payload count to expect (-1 to ignore the count)
*/
int count;
/**
* Payloads to expect or not expect in a message
*/
listener_message_rule_t *rules;
/**
* Number of rules
*/
int num_rules;
};
/**
* Implementation of listener_t::message collecting data and asserting
* certain things.
*/
bool exchange_test_asserts_message(listener_t *this, ike_sa_t *ike_sa,
message_t *message, bool incoming, bool plain);
/**
* Assert that the next in- or outbound plaintext message is empty.
*
* @param dir IN or OUT to check the next in- or outbound message
*/
#define assert_message_empty(dir) \
_assert_payload(dir, 0)
/**
* Assert that the next in- or outbound plaintext message contains exactly
* one payload of the given type.
*
* @param dir IN or OUT to check the next in- or outbound message
* @param expected expected payload type
*/
#define assert_single_payload(dir, expected) \
_assert_payload(dir, 1, { TRUE, expected, 0 })
/**
* Assert that the next in- or outbound plaintext message contains exactly
* one notify of the given type.
*
* @param dir IN or OUT to check the next in- or outbound message
* @param expected expected notify type
*/
#define assert_single_notify(dir, expected) \
_assert_payload(dir, 1, { TRUE, 0, expected })
/**
* Assert that the next in- or outbound plaintext message contains a notify
* of the given type.
*
* @param dir IN or OUT to check the next in- or outbound message
* @param expected expected notify type
*/
#define assert_notify(dir, expected) \
_assert_payload(dir, -1, { TRUE, 0, expected })
/**
* Assert that the next in- or outbound plaintext message does not contain a
* notify of the given type.
*
* @param dir IN or OUT to check the next in- or outbound message
* @param unexpected not expected notify type
*/
#define assert_no_notify(dir, unexpected) \
_assert_payload(dir, -1, { FALSE, 0, unexpected })
#define _assert_payload(dir, c, ...) ({ \
listener_message_rule_t _rules[] = { __VA_ARGS__ }; \
listener_message_assert_t _listener = { \
.listener = { .message = exchange_test_asserts_message, }, \
.file = __FILE__, \
.line = __LINE__, \
.incoming = streq(#dir, "IN") ? TRUE : FALSE, \
.count = c, \
.rules = _rules, \
.num_rules = countof(_rules), \
}; \
exchange_test_helper->add_listener(exchange_test_helper, &_listener.listener); \
})
#endif /** EXCHANGE_TEST_ASSERTS_H_ @}*/
@@ -0,0 +1,372 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "exchange_test_helper.h"
#include "mock_dh.h"
#include "mock_ipsec.h"
#include "mock_nonce_gen.h"
#include <collections/array.h>
#include <credentials/sets/mem_cred.h>
typedef struct private_exchange_test_helper_t private_exchange_test_helper_t;
typedef struct private_backend_t private_backend_t;
/**
* Private data
*/
struct private_exchange_test_helper_t {
/**
* Public interface
*/
exchange_test_helper_t public;
/**
* Credentials
*/
mem_cred_t *creds;
/**
* IKE_SA SPI counter
*/
refcount_t ike_spi;
/**
* List of registered listeners
*/
array_t *listeners;
};
/**
* Custom backend_t implementation
*/
struct private_backend_t {
/**
* Public interface
*/
backend_t public;
/**
* Responder ike_cfg
*/
ike_cfg_t *ike_cfg;
/**
* Responder peer_cfg/child_cfg
*/
peer_cfg_t *peer_cfg;
};
CALLBACK(get_ike_spi, uint64_t,
private_exchange_test_helper_t *this)
{
return (uint64_t)ref_get(&this->ike_spi);
}
/*
* Described in header
*/
exchange_test_helper_t *exchange_test_helper;
static ike_cfg_t *create_ike_cfg(bool initiator, exchange_test_sa_conf_t *conf)
{
ike_cfg_t *ike_cfg;
char *proposal = NULL;
ike_cfg = ike_cfg_create(IKEV2, TRUE, FALSE, "127.0.0.1", IKEV2_UDP_PORT,
"127.0.0.1", IKEV2_UDP_PORT, FRAGMENTATION_NO, 0);
if (conf)
{
proposal = initiator ? conf->initiator.ike : conf->responder.ike;
}
if (proposal)
{
ike_cfg->add_proposal(ike_cfg,
proposal_create_from_string(PROTO_IKE, proposal));
}
else
{
ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE));
}
return ike_cfg;
}
static child_cfg_t *create_child_cfg(bool initiator,
exchange_test_sa_conf_t *conf)
{
child_cfg_t *child_cfg;
child_cfg_create_t child = {
.mode = MODE_TUNNEL,
};
char *proposal = NULL;
child_cfg = child_cfg_create(initiator ? "init" : "resp", &child);
if (conf)
{
proposal = initiator ? conf->initiator.esp : conf->responder.esp;
}
if (proposal)
{
child_cfg->add_proposal(child_cfg,
proposal_create_from_string(PROTO_ESP, proposal));
}
else
{
child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP));
}
child_cfg->add_traffic_selector(child_cfg, TRUE,
traffic_selector_create_dynamic(0, 0, 65535));
child_cfg->add_traffic_selector(child_cfg, FALSE,
traffic_selector_create_dynamic(0, 0, 65535));
return child_cfg;
}
static void add_auth_cfg(peer_cfg_t *peer_cfg, bool initiator, bool local)
{
auth_cfg_t *auth;
char *id = "init";
auth = auth_cfg_create();
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PSK);
if (initiator ^ local)
{
id = "resp";
}
auth->add(auth, AUTH_RULE_IDENTITY, identification_create_from_string(id));
peer_cfg->add_auth_cfg(peer_cfg, auth, local);
}
static peer_cfg_t *create_peer_cfg(bool initiator,
exchange_test_sa_conf_t *conf)
{
peer_cfg_t *peer_cfg;
peer_cfg_create_t peer = {
.cert_policy = CERT_SEND_IF_ASKED,
.unique = UNIQUE_REPLACE,
.keyingtries = 1,
};
peer_cfg = peer_cfg_create(initiator ? "init" : "resp",
create_ike_cfg(initiator, conf), &peer);
add_auth_cfg(peer_cfg, initiator, TRUE);
add_auth_cfg(peer_cfg, initiator, FALSE);
return peer_cfg;
}
METHOD(backend_t, create_ike_cfg_enumerator, enumerator_t*,
private_backend_t *this, host_t *me, host_t *other)
{
return enumerator_create_single(this->ike_cfg, NULL);
}
METHOD(backend_t, create_peer_cfg_enumerator, enumerator_t*,
private_backend_t *this, identification_t *me, identification_t *other)
{
return enumerator_create_single(this->peer_cfg, NULL);
}
METHOD(exchange_test_helper_t, process_message, status_t,
private_exchange_test_helper_t *this, ike_sa_t *ike_sa, message_t *message)
{
status_t status = FAILED;
ike_sa_id_t *id;
if (!message)
{
message = this->public.sender->dequeue(this->public.sender);
}
id = message->get_ike_sa_id(message);
id = id->clone(id);
id->switch_initiator(id);
if (!id->get_responder_spi(id) || id->equals(id, ike_sa->get_id(ike_sa)))
{
charon->bus->set_sa(charon->bus, ike_sa);
status = ike_sa->process_message(ike_sa, message);
charon->bus->set_sa(charon->bus, NULL);
}
message->destroy(message);
id->destroy(id);
return status;
}
METHOD(exchange_test_helper_t, establish_sa, void,
private_exchange_test_helper_t *this, ike_sa_t **init, ike_sa_t **resp,
exchange_test_sa_conf_t *conf)
{
private_backend_t backend = {
.public = {
.create_ike_cfg_enumerator = _create_ike_cfg_enumerator,
.create_peer_cfg_enumerator = _create_peer_cfg_enumerator,
.get_peer_cfg_by_name = (void*)return_null,
},
};
ike_sa_id_t *id_i, *id_r;
ike_sa_t *sa_i, *sa_r;
peer_cfg_t *peer_cfg;
child_cfg_t *child_cfg;
sa_i = *init = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager,
IKEV2, TRUE);
id_i = sa_i->get_id(sa_i);
sa_r = *resp = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager,
IKEV2, FALSE);
id_r = sa_r->get_id(sa_r);
peer_cfg = create_peer_cfg(TRUE, conf);
child_cfg = create_child_cfg(TRUE, conf);
peer_cfg->add_child_cfg(peer_cfg, child_cfg->get_ref(child_cfg));
sa_i->set_peer_cfg(sa_i, peer_cfg);
peer_cfg->destroy(peer_cfg);
call_ikesa(sa_i, initiate, child_cfg, 0, NULL, NULL);
backend.ike_cfg = create_ike_cfg(FALSE, conf);
peer_cfg = backend.peer_cfg = create_peer_cfg(FALSE, conf);
child_cfg = create_child_cfg(FALSE, conf);
peer_cfg->add_child_cfg(peer_cfg, child_cfg->get_ref(child_cfg));
child_cfg->destroy(child_cfg);
charon->backends->add_backend(charon->backends, &backend.public);
/* IKE_SA_INIT --> */
id_r->set_initiator_spi(id_r, id_i->get_initiator_spi(id_i));
process_message(this, sa_r, NULL);
/* <-- IKE_SA_INIT */
id_i->set_responder_spi(id_i, id_r->get_responder_spi(id_r));
process_message(this, sa_i, NULL);
/* IKE_AUTH --> */
process_message(this, sa_r, NULL);
/* <-- IKE_AUTH */
process_message(this, sa_i, NULL);
charon->backends->remove_backend(charon->backends, &backend.public);
DESTROY_IF(backend.peer_cfg);
DESTROY_IF(backend.ike_cfg);
}
METHOD(exchange_test_helper_t, add_listener, void,
private_exchange_test_helper_t *this, listener_t *listener)
{
array_insert_create(&this->listeners, ARRAY_TAIL, listener);
charon->bus->add_listener(charon->bus, listener);
}
/**
* Enable logging in charon as requested
*/
static void initialize_logging()
{
int level = LEVEL_SILENT;
char *verbosity;
verbosity = getenv("TESTS_VERBOSITY");
if (verbosity)
{
level = atoi(verbosity);
}
lib->settings->set_int(lib->settings, "%s.filelog.stderr.default",
lib->settings->get_int(lib->settings, "%s.filelog.stderr.default",
level, lib->ns), lib->ns);
lib->settings->set_bool(lib->settings, "%s.filelog.stderr.ike_name", TRUE,
lib->ns);
charon->load_loggers(charon, NULL, TRUE);
}
/**
* Create a nonce generator with the first byte
*/
static nonce_gen_t *create_nonce_gen()
{
return mock_nonce_gen_create(exchange_test_helper->nonce_first_byte);
}
/*
* Described in header
*/
void exchange_test_helper_init(char *plugins)
{
private_exchange_test_helper_t *this;
plugin_feature_t features[] = {
PLUGIN_REGISTER(DH, mock_dh_create),
/* we only need to support a limited number of DH groups */
PLUGIN_PROVIDE(DH, MODP_2048_BIT),
PLUGIN_PROVIDE(DH, MODP_3072_BIT),
PLUGIN_PROVIDE(DH, ECP_256_BIT),
PLUGIN_REGISTER(NONCE_GEN, create_nonce_gen),
PLUGIN_PROVIDE(NONCE_GEN),
PLUGIN_DEPENDS(RNG, RNG_WEAK),
};
INIT(this,
.public = {
.sender = mock_sender_create(),
.establish_sa = _establish_sa,
.process_message = _process_message,
.add_listener = _add_listener,
},
.creds = mem_cred_create(),
);
initialize_logging();
lib->plugins->add_static_features(lib->plugins, "exchange-test-helper",
features, countof(features), TRUE, NULL, NULL);
/* the libcharon unit tests only load the libstrongswan plugins, unless
* TESTS_PLUGINS is defined */
charon->initialize(charon, plugins);
lib->plugins->status(lib->plugins, LEVEL_CTRL);
/* the original sender is not initialized because there is no socket */
charon->sender = (sender_t*)this->public.sender;
/* and there is no kernel plugin loaded
* TODO: we'd have more control if we'd implement kernel_interface_t */
charon->kernel->add_ipsec_interface(charon->kernel, mock_ipsec_create);
/* like SPIs for IPsec SAs, make IKE SPIs predictable */
charon->ike_sa_manager->set_spi_cb(charon->ike_sa_manager, get_ike_spi,
this);
lib->credmgr->add_set(lib->credmgr, &this->creds->set);
this->creds->add_shared(this->creds,
shared_key_create(SHARED_IKE, chunk_clone(chunk_from_str("test"))),
identification_create_from_string("%any"), NULL);
exchange_test_helper = &this->public;
}
/*
* Described in header
*/
void exchange_test_helper_deinit()
{
private_exchange_test_helper_t *this;
listener_t *listener;
this = (private_exchange_test_helper_t*)exchange_test_helper;
while (array_remove(this->listeners, ARRAY_HEAD, &listener))
{
charon->bus->remove_listener(charon->bus, listener);
}
lib->credmgr->remove_set(lib->credmgr, &this->creds->set);
this->creds->destroy(this->creds);
/* flush SAs before destroying the sender (in case of test failures) */
charon->ike_sa_manager->flush(charon->ike_sa_manager);
/* charon won't destroy this as it didn't initialize the original sender */
charon->sender->destroy(charon->sender);
charon->sender = NULL;
array_destroy(this->listeners);
free(this);
}
@@ -0,0 +1,128 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* This class and singleton object initializes charon and provides helper
* methods to create unit tests for IKEv2 exchanges.
*
* It also registers special implementations for the kernel_ipsec_t interface,
* the sender and provides dummy configs and credentials.
*
* @defgroup exchange_test_helper exchange_test_helper
* @{ @ingroup test_utils_c
*/
#ifndef EXCHANGE_TEST_HELPER_H_
#define EXCHANGE_TEST_HELPER_H_
#include <daemon.h>
#include "mock_sender.h"
typedef struct exchange_test_helper_t exchange_test_helper_t;
typedef struct exchange_test_sa_conf_t exchange_test_sa_conf_t;
struct exchange_test_helper_t {
/**
* Sender instance used during tests
*/
mock_sender_t *sender;
/**
* Set the initial byte of all nonces generated by future nonce
* generators (already instatiated nonce generators are not affected).
*/
u_char nonce_first_byte;
/**
* Creates an established IKE_SA/CHILD_SA
*
* @param[out] init IKE_SA of the initiator
* @param[out] resp IKE_SA of the responder
* @param conf configuration for SAs
*/
void (*establish_sa)(exchange_test_helper_t *this, ike_sa_t **init,
ike_sa_t **resp, exchange_test_sa_conf_t *conf);
/**
* Pass a message to the given IKE_SA for processing, setting the IKE_SA on
* the bus while processing the message.
*
* @param ike_sa the IKE_SA receiving the message
* @param message the message, or NULL to pass the next message in the
* send queue (adopted)
* @return return value from ike_sa_t::process_message()
*/
status_t (*process_message)(exchange_test_helper_t *this, ike_sa_t *sa,
message_t *message);
/**
* Register a listener with the bus.
*
* Don't use bus_t::add_listener() directly for listeners on the stack
* as that could lead to invalid listeners registered when hooks are
* triggered during cleanup if a test case fails. All of the listeners
* added this way are unregistered with the bus before cleaning up.
*
* @param listener listener to add to the bus
*/
void (*add_listener)(exchange_test_helper_t *this, listener_t *listener);
};
struct exchange_test_sa_conf_t {
/**
* Configuration for initiator and responder
*/
struct {
/** IKE proposal */
char *ike;
/** ESP proposal */
char *esp;
} initiator, responder;
};
/**
* Since we don't use the IKE_SA manager to checkout SAs use this to call a
* method on the given IKE_SA in its context.
*/
#define call_ikesa(sa, method, ...) ({ \
charon->bus->set_sa(charon->bus, sa); \
sa->method(sa, ##__VA_ARGS__); \
charon->bus->set_sa(charon->bus, NULL); \
})
/**
* The one and only instance of the helper object.
*
* Set between exchange_test_helper_setup() and exchange_test_helper_teardown()
* calls.
*/
extern exchange_test_helper_t *exchange_test_helper;
/**
* Initialize charon and the helper object.
*
* @param plugins plugins to load
*/
void exchange_test_helper_init(char *plugins);
/**
* Deinitialize the helper object.
*/
void exchange_test_helper_deinit();
#endif /** EXCHANGE_TEST_HELPER_H_ @} */
+59
View File
@@ -0,0 +1,59 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* Special assertions against job handling.
*
* @defgroup job_asserts job_asserts
* @{ @ingroup test_utils_c
*/
#ifndef JOB_ASSERTS_H_
#define JOB_ASSERTS_H_
/**
* Initialize an assertion that enforces that no jobs were scheduled.
* Must be matched by a call to assert_scheduler().
*/
#define assert_no_jobs_scheduled() _assert_jobs_scheduled(0)
/**
* Initialize an assertion that enforces that a specific number of jobs was
* scheduled.
* Must be matched by a call to assert_scheduler().
*
* @param count expected number of jobs getting scheduled
*/
#define assert_jobs_scheduled(count) _assert_jobs_scheduled(count)
/**
* Initialize assertions against job scheduling.
* Must be matched by a call to assert_scheduler().
*/
#define _assert_jobs_scheduled(count) \
do { \
u_int _initial = lib->scheduler->get_job_load(lib->scheduler); \
u_int _expected = count
/**
* Enforce scheduler asserts.
*/
#define assert_scheduler() \
u_int _actual = lib->scheduler->get_job_load(lib->scheduler) - _initial; \
test_assert_msg(_expected == _actual, "unexpected number of jobs " \
"scheduled (%u != %u)", _expected, _actual); \
} while(FALSE)
#endif /** JOB_ASSERTS_H_ @}*/
+87
View File
@@ -0,0 +1,87 @@
/*
* Copyright (C) 2016 Tobias Brunner
* Copyright (C) 2008 Martin Willi
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "mock_dh.h"
typedef struct private_diffie_hellman_t private_diffie_hellman_t;
/**
* Private data
*/
struct private_diffie_hellman_t {
/**
* Public interface
*/
diffie_hellman_t public;
/**
* Instantiated DH group
*/
diffie_hellman_group_t group;
};
METHOD(diffie_hellman_t, get_my_public_value, bool,
private_diffie_hellman_t *this, chunk_t *value)
{
*value = chunk_empty;
return TRUE;
}
METHOD(diffie_hellman_t, set_other_public_value, bool,
private_diffie_hellman_t *this, chunk_t value)
{
return TRUE;
}
METHOD(diffie_hellman_t, get_shared_secret, bool,
private_diffie_hellman_t *this, chunk_t *secret)
{
*secret = chunk_empty;
return TRUE;
}
METHOD(diffie_hellman_t, get_dh_group, diffie_hellman_group_t,
private_diffie_hellman_t *this)
{
return this->group;
}
METHOD(diffie_hellman_t, destroy, void,
private_diffie_hellman_t *this)
{
free(this);
}
/**
* See header
*/
diffie_hellman_t *mock_dh_create(diffie_hellman_group_t group)
{
private_diffie_hellman_t *this;
INIT(this,
.public = {
.get_shared_secret = _get_shared_secret,
.set_other_public_value = _set_other_public_value,
.get_my_public_value = _get_my_public_value,
.get_dh_group = _get_dh_group,
.destroy = _destroy,
},
.group = group,
);
return &this->public;
}
+37
View File
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* Provides a DH implementation that does no real work to make the tests run
* faster.
*
* @defgroup mock_dh mock_dh
* @{ @ingroup test_utils_c
*/
#ifndef MOCK_DH_H_
#define MOCK_DH_H_
#include <crypto/diffie_hellman.h>
/**
* Creates a diffie_hellman_t object.
*
* @param group Diffie Hellman group, supports MODP_NULL only
* @return created object
*/
diffie_hellman_t *mock_dh_create(diffie_hellman_group_t group);
#endif /** MOCK_DH_H_ @}*/
+128
View File
@@ -0,0 +1,128 @@
/*
* Copyright (C) 2016 Tobias Brunner
* Copyright (C) 2008 Martin Willi
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "mock_ipsec.h"
typedef struct private_kernel_ipsec_t private_kernel_ipsec_t;
/**
* Private data
*/
struct private_kernel_ipsec_t {
/**
* Public interface
*/
kernel_ipsec_t public;
/**
* Allocated SPI
*/
refcount_t spi;
};
METHOD(kernel_ipsec_t, get_spi, status_t,
private_kernel_ipsec_t *this, host_t *src, host_t *dst, uint8_t protocol,
uint32_t *spi)
{
*spi = (uint32_t)ref_get(&this->spi);
return SUCCESS;
}
METHOD(kernel_ipsec_t, get_cpi, status_t,
private_kernel_ipsec_t *this, host_t *src, host_t *dst, uint16_t *cpi)
{
return FAILED;
}
METHOD(kernel_ipsec_t, add_sa, status_t,
private_kernel_ipsec_t *this, kernel_ipsec_sa_id_t *id,
kernel_ipsec_add_sa_t *data)
{
return SUCCESS;
}
METHOD(kernel_ipsec_t, update_sa, status_t,
private_kernel_ipsec_t *this, kernel_ipsec_sa_id_t *id,
kernel_ipsec_update_sa_t *data)
{
return SUCCESS;
}
METHOD(kernel_ipsec_t, query_sa, status_t,
private_kernel_ipsec_t *this, kernel_ipsec_sa_id_t *id,
kernel_ipsec_query_sa_t *data, uint64_t *bytes, uint64_t *packets,
time_t *time)
{
return NOT_SUPPORTED;
}
METHOD(kernel_ipsec_t, del_sa, status_t,
private_kernel_ipsec_t *this, kernel_ipsec_sa_id_t *id,
kernel_ipsec_del_sa_t *data)
{
return SUCCESS;
}
METHOD(kernel_ipsec_t, add_policy, status_t,
private_kernel_ipsec_t *this, kernel_ipsec_policy_id_t *id,
kernel_ipsec_manage_policy_t *data)
{
return SUCCESS;
}
METHOD(kernel_ipsec_t, query_policy, status_t,
private_kernel_ipsec_t *this, kernel_ipsec_policy_id_t *id,
kernel_ipsec_query_policy_t *data, time_t *use_time)
{
*use_time = 1;
return SUCCESS;
}
METHOD(kernel_ipsec_t, del_policy, status_t,
private_kernel_ipsec_t *this, kernel_ipsec_policy_id_t *id,
kernel_ipsec_manage_policy_t *data)
{
return SUCCESS;
}
/*
* Described in header
*/
kernel_ipsec_t *mock_ipsec_create()
{
private_kernel_ipsec_t *this;
INIT(this,
.public = {
.get_spi = _get_spi,
.get_cpi = _get_cpi,
.add_sa = _add_sa,
.update_sa = _update_sa,
.query_sa = _query_sa,
.del_sa = _del_sa,
.flush_sas = (void*)return_failed,
.add_policy = _add_policy,
.query_policy = _query_policy,
.del_policy = _del_policy,
.flush_policies = (void*)return_failed,
.bypass_socket = (void*)return_true,
.enable_udp_decap = (void*)return_true,
.destroy = (void*)free,
},
);
return &this->public;
}
+36
View File
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* kernel_ipsec_t implementation used for exchange unit tests. Currently
* returns sequential SPIs, all other methods are noops.
*
* @defgroup mock_ipsec mock_ipsec
* @{ @ingroup test_utils_c
*/
#ifndef MOCK_IPSEC_H_
#define MOCK_IPSEC_H_
#include <kernel/kernel_ipsec.h>
/**
* Create an instance of kernel_ipsec_t
*
* @return created object
*/
kernel_ipsec_t *mock_ipsec_create();
#endif /** MOCK_IPSEC_H_ @}*/
@@ -0,0 +1,91 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "mock_nonce_gen.h"
typedef struct private_nonce_gen_t private_nonce_gen_t;
struct private_nonce_gen_t {
/**
* Public interface
*/
nonce_gen_t public;
/**
* Random number generator
*/
rng_t* rng;
/**
* First byte to set to the nonces
*/
u_char first;
};
METHOD(nonce_gen_t, get_nonce, bool,
private_nonce_gen_t *this, size_t size, uint8_t *buffer)
{
if (size > 0)
{
buffer[0] = this->first;
buffer++;
size--;
}
return this->rng->get_bytes(this->rng, size, buffer);
}
METHOD(nonce_gen_t, allocate_nonce, bool,
private_nonce_gen_t *this, size_t size, chunk_t *chunk)
{
*chunk = chunk_alloc(size);
if (!get_nonce(this, chunk->len, chunk->ptr))
{
chunk_free(chunk);
return FALSE;
}
return TRUE;
}
METHOD(nonce_gen_t, destroy, void,
private_nonce_gen_t *this)
{
DESTROY_IF(this->rng);
free(this);
}
/*
* Described in header
*/
nonce_gen_t *mock_nonce_gen_create(u_char first)
{
private_nonce_gen_t *this;
INIT(this,
.public = {
.get_nonce = _get_nonce,
.allocate_nonce = _allocate_nonce,
.destroy = _destroy,
},
.rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK),
.first = first,
);
if (!this->rng)
{
destroy(this);
return NULL;
}
return &this->public;
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* Special nonce generator that sets the first byte of the generated nonces to
* a fixed specified value.
*
* @defgroup mock_nonce_gen mock_nonce_gen
* @{ @ingroup test_utils_c
*/
#ifndef MOCK_NONCE_GEN_H_
#define MOCK_NONCE_GEN_H_
#include <crypto/nonce_gen.h>
/**
* Creates a nonce_gen_t instance.
*
* @param first first byte to set in generated nonces
* @return created object
*/
nonce_gen_t *mock_nonce_gen_create(u_char first);
#endif /** MOCK_NONCE_GEN_H_ @} */
+85
View File
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "mock_sender.h"
#include <collections/linked_list.h>
typedef struct private_mock_sender_t private_mock_sender_t;
/**
* Private data
*/
struct private_mock_sender_t {
/**
* Public interface
*/
mock_sender_t public;
/**
* Packet queue, as message_t*
*/
linked_list_t *queue;
};
METHOD(sender_t, send_, void,
private_mock_sender_t *this, packet_t *packet)
{
message_t *message;
message = message_create_from_packet(packet);
message->parse_header(message);
this->queue->insert_last(this->queue, message);
}
METHOD(mock_sender_t, dequeue, message_t*,
private_mock_sender_t *this)
{
message_t *message = NULL;
this->queue->remove_first(this->queue, (void**)&message);
return message;
}
METHOD(sender_t, destroy, void,
private_mock_sender_t *this)
{
this->queue->destroy_offset(this->queue, offsetof(message_t, destroy));
free(this);
}
/*
* Described in header
*/
mock_sender_t *mock_sender_create()
{
private_mock_sender_t *this;
INIT(this,
.public = {
.interface = {
.send = _send_,
.send_no_marker = (void*)nop,
.flush = (void*)nop,
.destroy = _destroy,
},
.dequeue = _dequeue,
},
.queue = linked_list_create(),
);
return &this->public;
}
+56
View File
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* sender_t implementation that does not pass the sent packet to a socket but
* instead provides it for immediate delivery to an ike_sa_t object.
*
* @defgroup mock_sender mock_sender
* @{ @ingroup test_utils_c
*/
#ifndef MOCK_SENDER_H_
#define MOCK_SENDER_H_
#include <encoding/message.h>
#include <network/sender.h>
typedef struct mock_sender_t mock_sender_t;
struct mock_sender_t {
/**
* Implemented interface
*/
sender_t interface;
/**
* Remove the next packet in the send queue as message_t object. The IKE
* header is already parsed (which is assumed does not fail) so it can
* directly be passed to ike_sa_t::process_message().
*
* @return message or NULL if none is queued
*/
message_t *(*dequeue)(mock_sender_t *this);
};
/**
* Creates a mock_sender_t instance.
*
* @return created object
*/
mock_sender_t *mock_sender_create();
#endif /** MOCK_SENDER_H_ @} */
+145
View File
@@ -0,0 +1,145 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* Special assertions against IKE_SAs and CHILD_SAs (e.g. regarding their
* state).
*
* @defgroup sa_asserts sa_asserts
* @{ @ingroup test_utils_c
*/
#ifndef SA_ASSERTS_H_
#define SA_ASSERTS_H_
#include <inttypes.h>
/**
* Check that there exists a specific number of IKE_SAs in the manager.
*/
#define assert_ike_sa_count(count) \
({ \
typeof(count) _count = count; \
u_int _actual = charon->ike_sa_manager->get_count(charon->ike_sa_manager); \
test_assert_msg(_count == _actual, "unexpected number of IKE_SAs in " \
"manager (%d != %d)", _count, _actual); \
})
/**
* Check that the IKE_SA with the given SPIs and initiator flag is in the
* manager and return it. Does not actually keep the SA checked out as
* that would block cleaning up if asserts against it fail (since we control
* access to SAs it's also not really necessary).
*/
#define assert_ike_sa_checkout(spi_i, spi_r, initiator) \
({ \
typeof(spi_i) _spi_i = spi_i; \
typeof(spi_r) _spi_r = spi_r; \
typeof(initiator) _init = initiator; \
ike_sa_id_t *_id = ike_sa_id_create(IKEV2, _spi_i, _spi_r, _init); \
ike_sa_t *_ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, _id); \
test_assert_msg(_ike_sa, "IKE_SA with SPIs %.16"PRIx64"_i %.16"PRIx64"_r " \
"(%d) does not exist", be64toh(_spi_i), be64toh(_spi_r), _init); \
_id->destroy(_id); \
charon->ike_sa_manager->checkin(charon->ike_sa_manager, _ike_sa); \
_ike_sa; \
})
/**
* Check if the given IKE_SA is in the expected state.
*/
#define assert_ike_sa_state(ike_sa, state) \
({ \
typeof(ike_sa) _sa = ike_sa; \
typeof(state) _state = state; \
test_assert_msg(_state == _sa->get_state(_sa), "%N != %N", \
ike_sa_state_names, _state, \
ike_sa_state_names, _sa->get_state(_sa)); \
})
/**
* Check that there exists a specific number of CHILD_SAs.
*/
#define assert_child_sa_count(ike_sa, count) \
({ \
typeof(ike_sa) _sa = ike_sa; \
typeof(count) _count = count; \
test_assert_msg(_count == _sa->get_child_count(_sa), "unexpected number " \
"of CHILD_SAs in IKE_SA %s (%d != %d)", #ike_sa, _count, \
_sa->get_child_count(_sa)); \
})
/**
* Check if the CHILD_SA with the given SPI is in the expected state.
*/
#define assert_child_sa_state(ike_sa, spi, state) \
({ \
typeof(ike_sa) _sa = ike_sa; \
typeof(spi) _spi = spi; \
typeof(state) _state = state; \
child_sa_t *_child = _sa->get_child_sa(_sa, PROTO_ESP, _spi, TRUE) ?: \
_sa->get_child_sa(_sa, PROTO_ESP, _spi, FALSE); \
test_assert_msg(_child, "CHILD_SA with SPI %.8x does not exist", \
ntohl(_spi)); \
test_assert_msg(_state == _child->get_state(_child), "%N != %N", \
child_sa_state_names, _state, \
child_sa_state_names, _child->get_state(_child)); \
})
/**
* Assert that the CHILD_SA with the given inbound SPI does not exist.
*/
#define assert_child_sa_not_exists(ike_sa, spi) \
({ \
typeof(ike_sa) _sa = ike_sa; \
typeof(spi) _spi = spi; \
child_sa_t *_child = _sa->get_child_sa(_sa, PROTO_ESP, _spi, TRUE) ?: \
_sa->get_child_sa(_sa, PROTO_ESP, _spi, FALSE); \
test_assert_msg(!_child, "CHILD_SA with SPI %.8x exists", ntohl(_spi)); \
})
/**
* Assert that there is a specific number of tasks in a given queue
*
* @param ike_sa IKE_SA to check
* @param count number of expected tasks
* @param queue queue to check (task_queue_t)
*/
#define assert_num_tasks(ike_sa, count, queue) \
({ \
typeof(ike_sa) _sa = ike_sa; \
typeof(count) _count = count; \
int _c = 0; task_t *_task; \
enumerator_t *_enumerator = _sa->create_task_enumerator(_sa, queue); \
while (_enumerator->enumerate(_enumerator, &_task)) { _c++; } \
_enumerator->destroy(_enumerator); \
test_assert_msg(_count == _c, "unexpected number of tasks in " #queue " " \
"of IKE_SA %s (%d != %d)", #ike_sa, _count, _c); \
})
/**
* Assert that all task queues of the given IKE_SA are empty
*
* @param ike_sa IKE_SA to check
*/
#define assert_sa_idle(ike_sa) \
({ \
typeof(ike_sa) _ike_sa = ike_sa; \
assert_num_tasks(_ike_sa, 0, TASK_QUEUE_QUEUED); \
assert_num_tasks(_ike_sa, 0, TASK_QUEUE_ACTIVE); \
assert_num_tasks(_ike_sa, 0, TASK_QUEUE_PASSIVE); \
})
#endif /** SA_ASSERTS_H_ @}*/
@@ -347,6 +347,10 @@ METHOD(crypto_factory_t, create_nonce_gen, nonce_gen_t*,
while (enumerator->enumerate(enumerator, &entry))
{
nonce_gen = entry->create_nonce_gen();
if (nonce_gen)
{
break;
}
}
enumerator->destroy(enumerator);
this->lock->unlock(this->lock);
@@ -141,6 +141,7 @@ prfmd5, PSEUDO_RANDOM_FUNCTION, PRF_HMAC_MD5, 0
prfaesxcbc, PSEUDO_RANDOM_FUNCTION, PRF_AES128_XCBC, 0
prfcamelliaxcbc, PSEUDO_RANDOM_FUNCTION, PRF_CAMELLIA128_XCBC, 0
prfaescmac, PSEUDO_RANDOM_FUNCTION, PRF_AES128_CMAC, 0
modpnone, DIFFIE_HELLMAN_GROUP, MODP_NONE, 0
modpnull, DIFFIE_HELLMAN_GROUP, MODP_NULL, 0
modp768, DIFFIE_HELLMAN_GROUP, MODP_768_BIT, 0
modp1024, DIFFIE_HELLMAN_GROUP, MODP_1024_BIT, 0
+29 -1
View File
@@ -89,6 +89,28 @@ static void apply_filter(array_t *loaded, char *filter, bool exclude)
names->destroy(names);
}
/**
* Check if the given string is contained in the filter string.
*/
static bool is_in_filter(const char *find, char *filter)
{
enumerator_t *names;
bool found = FALSE;
char *name;
names = enumerator_create_token(filter, ",", " ");
while (names->enumerate(names, &name))
{
if (streq(name, find))
{
found = TRUE;
break;
}
}
names->destroy(names);
return found;
}
/**
* Removes and destroys test suites that are not selected or
* explicitly excluded.
@@ -524,11 +546,17 @@ int test_runner_run(const char *name, test_configuration_t configs[],
enumerator_t *enumerator;
int passed = 0, result;
level_t level = LEVEL_SILENT;
char *cfg, *verbosity;
char *cfg, *runners, *verbosity;
/* redirect all output to stderr (to redirect make's stdout to /dev/null) */
dup2(2, 1);
runners = getenv("TESTS_RUNNERS");
if (runners && !is_in_filter(name, runners))
{
return EXIT_SUCCESS;
}
cfg = getenv("TESTS_STRONGSWAN_CONF");
suites = load_suites(configs, init, cfg);
+4
View File
@@ -70,6 +70,7 @@ struct test_configuration_t {
* - TESTS_VERBOSITY: Numerical loglevel for debug log
* - TESTS_STRONGSWAN_CONF: Specify a path to a custom strongswan.conf
* - TESTS_PLUGINS: Specify an explicit list of plugins to load
* - TESTS_RUNNERS: Run specific test runners only
* - TESTS_SUITES: Run specific test suites only
* - TESTS_SUITES_EXCLUDE: Don't run specific test suites
* - TESTS_REDUCED_KEYLENGTHS: Test minimal keylengths for public key tests only
@@ -77,6 +78,9 @@ struct test_configuration_t {
* Please note that TESTS_PLUGINS actually must be implemented by the init
* callback function, as plugin loading is delegated.
*
* EXIT_SUCCESS is returned right away if TESTS_RUNNERS is defined but the name
* passed to this function is not contained in it.
*
* @param name name of test runner
* @param config test suite constructors with dependencies
* @param init_cb init/deinit callback
+3
View File
@@ -28,6 +28,9 @@ AM_CPPFLAGS = \
-DPLUGINS=\""${starter_plugins}\"" \
-DDEBUG
AM_CFLAGS = \
@COVERAGE_CFLAGS@
AM_YFLAGS = -v -d
starter_LDADD = \