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 allocated. Has to be different from **charon.port**, otherwise a random
port will be allocated. 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 charon.prefer_temporary_addrs = no
By default public IPv6 addresses are preferred over temporary ones (RFC By default public IPv6 addresses are preferred over temporary ones (RFC
4941), to make connections more stable. Enable this option to reverse this. 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_DIR=\"${ipsecdir}\" \
-DIPSEC_PIDDIR=\"${piddir}\" -DIPSEC_PIDDIR=\"${piddir}\"
AM_CFLAGS = \
@COVERAGE_CFLAGS@
AM_LDFLAGS = \ AM_LDFLAGS = \
-no-undefined -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*, METHOD(child_cfg_t, select_proposal, proposal_t*,
private_child_cfg_t*this, linked_list_t *proposals, bool strip_dh, 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; enumerator_t *prefer_enum, *match_enum;
proposal_t *stored, *supplied, *selected = NULL; proposal_t *proposal, *match, *selected = NULL;
stored_enum = this->proposals->create_enumerator(this->proposals); if (prefer_self)
supplied_enum = proposals->create_enumerator(proposals);
/* compare all stored proposals with all supplied. Stored ones are preferred. */
while (stored_enum->enumerate(stored_enum, &stored))
{ {
stored = stored->clone(stored); prefer_enum = this->proposals->create_enumerator(this->proposals);
while (supplied_enum->enumerate(supplied_enum, &supplied)) 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) 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) if (selected)
{ {
DBG2(DBG_CFG, "received proposals: %#P", proposals); DBG2(DBG_CFG, "received proposals: %#P", proposals);
@@ -238,17 +253,15 @@ METHOD(child_cfg_t, select_proposal, proposal_t*,
break; break;
} }
} }
stored->destroy(stored); proposal->destroy(proposal);
if (selected) if (selected)
{ {
break; break;
} }
supplied_enum->destroy(supplied_enum);
supplied_enum = proposals->create_enumerator(proposals);
} }
stored_enum->destroy(stored_enum); prefer_enum->destroy(prefer_enum);
supplied_enum->destroy(supplied_enum); match_enum->destroy(match_enum);
if (selected == NULL) if (!selected)
{ {
DBG1(DBG_CFG, "received proposals: %#P", proposals); DBG1(DBG_CFG, "received proposals: %#P", proposals);
DBG1(DBG_CFG, "configured proposals: %#P", this->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 proposals list from which proposals are selected
* @param strip_dh TRUE strip out diffie hellman groups * @param strip_dh TRUE strip out diffie hellman groups
* @param private accept algorithms from a private range * @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 * @return selected proposal, or NULL if nothing matches
*/ */
proposal_t* (*select_proposal)(child_cfg_t*this, linked_list_t *proposals, 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. * 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-2007 Martin Willi
* Copyright (C) 2005 Jan Hutter * Copyright (C) 2005 Jan Hutter
* Hochschule fuer Technik Rapperswil * 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*, 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; enumerator_t *prefer_enum, *match_enum;
proposal_t *stored, *supplied, *selected; proposal_t *proposal, *match, *selected = NULL;
stored_enum = this->proposals->create_enumerator(this->proposals); if (prefer_self)
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))
{ {
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) 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, "received proposals: %#P", proposals);
DBG2(DBG_CFG, "configured proposals: %#P", this->proposals); DBG2(DBG_CFG, "configured proposals: %#P", this->proposals);
DBG2(DBG_CFG, "selected proposal: %P", selected); DBG2(DBG_CFG, "selected proposal: %P", selected);
return selected; break;
} }
} }
if (selected)
{
break;
}
} }
/* no proposal match :-(, will result in a NO_PROPOSAL_CHOSEN... */ prefer_enum->destroy(prefer_enum);
stored_enum->destroy(stored_enum); match_enum->destroy(match_enum);
supplied_enum->destroy(supplied_enum); if (!selected)
DBG1(DBG_CFG, "received proposals: %#P", proposals); {
DBG1(DBG_CFG, "configured proposals: %#P", this->proposals); DBG1(DBG_CFG, "received proposals: %#P", proposals);
DBG1(DBG_CFG, "configured proposals: %#P", this->proposals);
return NULL; }
return selected;
} }
METHOD(ike_cfg_t, get_dh_group, diffie_hellman_group_t, 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-2007 Martin Willi
* Copyright (C) 2005 Jan Hutter * Copyright (C) 2005 Jan Hutter
* Hochschule fuer Technik Rapperswil * Hochschule fuer Technik Rapperswil
@@ -165,16 +165,17 @@ struct ike_cfg_t {
linked_list_t* (*get_proposals) (ike_cfg_t *this); 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. * Returned proposal must be destroyed after use.
* *
* @param proposals list of proposals to select from * @param proposals list of proposals to select from
* @param private accept algorithms from a private range * @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. * @return selected proposal, or NULL if none matches.
*/ */
proposal_t *(*select_proposal) (ike_cfg_t *this, linked_list_t *proposals, 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? * 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) 2006-2010 Martin Willi
* Copyright (C) 2013-2015 Andreas Steffen * Copyright (C) 2013-2015 Andreas Steffen
* Hochschule fuer Technik Rapperswil * Hochschule fuer Technik Rapperswil
@@ -210,7 +210,7 @@ static bool select_algo(private_proposal_t *this, proposal_t *other,
e1 = create_enumerator(this, type); e1 = create_enumerator(this, type);
e2 = other->create_enumerator(other, 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)) if (!e2->enumerate(e2, &alg2, NULL))
{ {
@@ -219,12 +219,23 @@ static bool select_algo(private_proposal_t *this, proposal_t *other,
else if (optional) else if (optional)
{ {
do do
{ /* if the other peer proposes NONE, we accept the proposal */ { /* if NONE is proposed, we accept the proposal */
found = !alg2; found = !alg2;
} }
while (!found && e2->enumerate(e2, &alg2, NULL)); 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->destroy(e1);
e1 = create_enumerator(this, type); 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"); "but peer implementation is unknown, skipped");
continue; continue;
} }
/* ok, we have an algorithm */
selected->add_algorithm(selected, type, alg1, ks1); selected->add_algorithm(selected, type, alg1, ks1);
found = TRUE; found = TRUE;
break; break;
@@ -288,9 +298,7 @@ METHOD(proposal_t, select_proposal, proposal_t*,
} }
DBG2(DBG_CFG, " proposal matches"); DBG2(DBG_CFG, " proposal matches");
selected->set_spi(selected, other->get_spi(other)); selected->set_spi(selected, other->get_spi(other));
return selected; return selected;
} }
@@ -445,6 +453,16 @@ static void check_proposal(private_proposal_t *this)
} }
} }
e->destroy(e); 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) 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) 2006 Daniel Roethlisberger
* Copyright (C) 2005-2009 Martin Willi * Copyright (C) 2005-2009 Martin Willi
* Copyright (C) 2005 Jan Hutter * 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 * 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 * 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", "ESTABLISHED",
"PASSIVE", "PASSIVE",
"REKEYING", "REKEYING",
"REKEYED",
"DELETING", "DELETING",
"DESTROYING", "DESTROYING",
); );
@@ -2356,7 +2357,8 @@ METHOD(ike_sa_t, retransmit, status_t,
reestablish(this); reestablish(this);
break; break;
} }
if (this->state != IKE_CONNECTING) if (this->state != IKE_CONNECTING &&
this->state != IKE_REKEYED)
{ {
charon->bus->ike_updown(charon->bus, &this->public, FALSE); 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_DELETING:
case IKE_DESTROYING: case IKE_DESTROYING:
case IKE_PASSIVE: case IKE_PASSIVE:
case IKE_REKEYED:
return SUCCESS; return SUCCESS;
default: default:
break; break;
@@ -2617,6 +2620,12 @@ METHOD(ike_sa_t, queue_task, void,
this->task_manager->queue_task(this->task_manager, task); 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, METHOD(ike_sa_t, inherit_pre, void,
private_ike_sa_t *this, ike_sa_t *other_public) 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, .create_task_enumerator = _create_task_enumerator,
.flush_queue = _flush_queue, .flush_queue = _flush_queue,
.queue_task = _queue_task, .queue_task = _queue_task,
.queue_task_delayed = _queue_task_delayed,
#ifdef ME #ifdef ME
.act_as_mediation_server = _act_as_mediation_server, .act_as_mediation_server = _act_as_mediation_server,
.get_server_reflexive_host = _get_server_reflexive_host, .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) 2006 Daniel Roethlisberger
* Copyright (C) 2005-2009 Martin Willi * Copyright (C) 2005-2009 Martin Willi
* Copyright (C) 2005 Jan Hutter * 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 * 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 * 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. * 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. * 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. * Number of redirects allowed within REDIRECT_LOOP_DETECT_PERIOD.
@@ -308,6 +308,11 @@ enum ike_sa_state_t {
*/ */
IKE_REKEYING, IKE_REKEYING,
/**
* IKE_SA has been rekeyed (or is redundant)
*/
IKE_REKEYED,
/** /**
* IKE_SA is in progress of deletion * 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); 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. * 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; 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 */ { /* skip IKE_SAs which are not usable, wake other waiting threads */
entry->condvar->signal(entry->condvar); entry->condvar->signal(entry->condvar);
continue; continue;
@@ -2188,7 +2189,7 @@ METHOD(ike_sa_manager_t, flush, void,
unlock_all_segments(this); unlock_all_segments(this);
this->spi_lock->write_lock(this->spi_lock); this->spi_lock->write_lock(this->spi_lock);
this->rng->destroy(this->rng); DESTROY_IF(this->rng);
this->rng = NULL; this->rng = NULL;
this->spi_cb.cb = NULL; this->spi_cb.cb = NULL;
this->spi_cb.data = NULL; this->spi_cb.data = NULL;
+10 -3
View File
@@ -1,7 +1,7 @@
/* /*
* Copyright (C) 2007-2016 Tobias Brunner * Copyright (C) 2007-2016 Tobias Brunner
* Copyright (C) 2007-2011 Martin Willi * 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 * 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 * 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; return found;
} }
METHOD(task_manager_t, queue_task, void, METHOD(task_manager_t, queue_task_delayed, void,
private_task_manager_t *this, task_t *task) private_task_manager_t *this, task_t *task, uint32_t delay)
{ {
task_type_t type = task->get_type(task); 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); 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, METHOD(task_manager_t, queue_ike, void,
private_task_manager_t *this) private_task_manager_t *this)
{ {
@@ -1975,6 +1981,7 @@ task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa)
.task_manager = { .task_manager = {
.process_message = _process_message, .process_message = _process_message,
.queue_task = _queue_task, .queue_task = _queue_task,
.queue_task_delayed = _queue_task_delayed,
.queue_ike = _queue_ike, .queue_ike = _queue_ike,
.queue_ike_rekey = _queue_ike_rekey, .queue_ike_rekey = _queue_ike_rekey,
.queue_ike_reauth = _queue_ike_reauth, .queue_ike_reauth = _queue_ike_reauth,
@@ -378,6 +378,7 @@ METHOD(task_t, process_r, status_t,
identification_t *id; identification_t *id;
linked_list_t *list; linked_list_t *list;
uint16_t group; uint16_t group;
bool prefer_configured;
this->ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); this->ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
DBG0(DBG_IKE, "%H is initiating a Aggressive Mode 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); 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, this->proposal = this->ike_cfg->select_proposal(this->ike_cfg,
list, FALSE); list, FALSE, prefer_configured);
list->destroy_offset(list, offsetof(proposal_t, destroy)); list->destroy_offset(list, offsetof(proposal_t, destroy));
if (!this->proposal) if (!this->proposal)
{ {
@@ -640,7 +643,7 @@ METHOD(task_t, process_i, status_t,
} }
list = sa_payload->get_proposals(sa_payload); list = sa_payload->get_proposals(sa_payload);
this->proposal = this->ike_cfg->select_proposal(this->ike_cfg, this->proposal = this->ike_cfg->select_proposal(this->ike_cfg,
list, FALSE); list, FALSE, TRUE);
list->destroy_offset(list, offsetof(proposal_t, destroy)); list->destroy_offset(list, offsetof(proposal_t, destroy));
if (!this->proposal) if (!this->proposal)
{ {
+6 -4
View File
@@ -367,7 +367,7 @@ METHOD(task_t, process_r, status_t,
{ {
linked_list_t *list; linked_list_t *list;
sa_payload_t *sa_payload; sa_payload_t *sa_payload;
bool private; bool private, prefer_configured;
this->ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); this->ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
DBG0(DBG_IKE, "%H is initiating a Main Mode 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); list = sa_payload->get_proposals(sa_payload);
private = this->ike_sa->supports_extension(this->ike_sa, 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, this->proposal = this->ike_cfg->select_proposal(this->ike_cfg,
list, private); list, private, prefer_configured);
list->destroy_offset(list, offsetof(proposal_t, destroy)); list->destroy_offset(list, offsetof(proposal_t, destroy));
if (!this->proposal) if (!this->proposal)
{ {
@@ -641,7 +643,7 @@ METHOD(task_t, process_i, status_t,
private = this->ike_sa->supports_extension(this->ike_sa, private = this->ike_sa->supports_extension(this->ike_sa,
EXT_STRONGSWAN); EXT_STRONGSWAN);
this->proposal = this->ike_cfg->select_proposal(this->ike_cfg, this->proposal = this->ike_cfg->select_proposal(this->ike_cfg,
list, private); list, private, TRUE);
list->destroy_offset(list, offsetof(proposal_t, destroy)); list->destroy_offset(list, offsetof(proposal_t, destroy));
if (!this->proposal) 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; linked_list_t *tsi, *tsr, *hostsi, *hostsr, *list = NULL;
peer_cfg_t *peer_cfg; peer_cfg_t *peer_cfg;
uint16_t group; uint16_t group;
bool private; bool private, prefer_configured;
sa_payload = (sa_payload_t*)message->get_payload(message, sa_payload = (sa_payload_t*)message->get_payload(message,
PLV1_SECURITY_ASSOCIATION); PLV1_SECURITY_ASSOCIATION);
@@ -1109,8 +1109,10 @@ METHOD(task_t, process_r, status_t,
} }
private = this->ike_sa->supports_extension(this->ike_sa, private = this->ike_sa->supports_extension(this->ike_sa,
EXT_STRONGSWAN); EXT_STRONGSWAN);
this->proposal = this->config->select_proposal(this->config, prefer_configured = lib->settings->get_bool(lib->settings,
list, FALSE, private); "%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)); list->destroy_offset(list, offsetof(proposal_t, destroy));
get_lifetimes(this); get_lifetimes(this);
@@ -1323,8 +1325,8 @@ METHOD(task_t, process_i, status_t,
} }
private = this->ike_sa->supports_extension(this->ike_sa, private = this->ike_sa->supports_extension(this->ike_sa,
EXT_STRONGSWAN); EXT_STRONGSWAN);
this->proposal = this->config->select_proposal(this->config, this->proposal = this->config->select_proposal(this->config, list,
list, FALSE, private); FALSE, private, TRUE);
list->destroy_offset(list, offsetof(proposal_t, destroy)); list->destroy_offset(list, offsetof(proposal_t, destroy));
if (!this->proposal) 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 * 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 * 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 * under the terms of the GNU General Public License as published by the
@@ -43,30 +43,14 @@
#include <encoding/payloads/unknown_payload.h> #include <encoding/payloads/unknown_payload.h>
#include <processing/jobs/retransmit_job.h> #include <processing/jobs/retransmit_job.h>
#include <processing/jobs/delete_ike_sa_job.h> #include <processing/jobs/delete_ike_sa_job.h>
#include <processing/jobs/initiate_tasks_job.h>
#ifdef ME #ifdef ME
#include <sa/ikev2/tasks/ike_me.h> #include <sa/ikev2/tasks/ike_me.h>
#endif #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 private_task_manager_t private_task_manager_t;
typedef struct queued_task_t queued_task_t;
/** /**
* private data of the task manager * private data of the task manager
@@ -181,6 +165,22 @@ struct private_task_manager_t {
bool make_before_break; 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 * Reset retransmission packet list
*/ */
@@ -216,6 +216,12 @@ METHOD(task_manager_t, flush_queue, void,
} }
while (array_remove(array, ARRAY_TAIL, &task)) 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); 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) static bool activate_task(private_task_manager_t *this, task_type_t type)
{ {
enumerator_t *enumerator; enumerator_t *enumerator;
task_t *task; queued_task_t *queued;
timeval_t now;
bool found = FALSE; bool found = FALSE;
time_monotonic(&now);
enumerator = array_create_enumerator(this->queued_tasks); 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); DBG2(DBG_IKE, " activating %N task", task_type_names, type);
array_remove_at(this->queued_tasks, enumerator); 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; found = TRUE;
break; break;
} }
@@ -535,6 +547,7 @@ METHOD(task_manager_t, initiate, status_t,
break; break;
} }
case IKE_REKEYING: case IKE_REKEYING:
case IKE_REKEYED:
if (activate_task(this, TASK_IKE_DELETE)) if (activate_task(this, TASK_IKE_DELETE))
{ {
exchange = INFORMATIONAL; exchange = INFORMATIONAL;
@@ -611,7 +624,8 @@ METHOD(task_manager_t, initiate, status_t,
case FAILED: case FAILED:
default: default:
this->initiating.type = EXCHANGE_TYPE_UNDEFINED; 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); 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 */ /* do we have to check */
if (type == TASK_IKE_REKEY || type == TASK_CHILD_REKEY || if (type == TASK_IKE_REKEY || type == TASK_CHILD_REKEY ||
type == TASK_CHILD_DELETE || type == TASK_IKE_DELETE || type == TASK_CHILD_DELETE || type == TASK_IKE_DELETE)
type == TASK_IKE_REAUTH)
{ {
/* find an exchange collision, and notify these tasks */ /* find an exchange collision, and notify these tasks */
enumerator = array_create_enumerator(this->active_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)) switch (active->get_type(active))
{ {
case TASK_IKE_REKEY: case TASK_IKE_REKEY:
if (type == TASK_IKE_REKEY || type == TASK_IKE_DELETE || if (type == TASK_IKE_REKEY || type == TASK_IKE_DELETE)
type == TASK_IKE_REAUTH)
{ {
ike_rekey_t *rekey = (ike_rekey_t*)active; ike_rekey_t *rekey = (ike_rekey_t*)active;
rekey->collide(rekey, task); rekey->collide(rekey, task);
@@ -847,6 +859,10 @@ static status_t build_response(private_task_manager_t *this, message_t *request)
/* FALL */ /* FALL */
case DESTROY_ME: case DESTROY_ME:
/* destroy IKE_SA, but SEND response first */ /* destroy IKE_SA, but SEND response first */
if (handle_collisions(this, task))
{
array_remove_at(this->passive_tasks, enumerator);
}
delete = TRUE; delete = TRUE;
break; break;
} }
@@ -909,9 +925,11 @@ static status_t process_request(private_task_manager_t *this,
payload_t *payload; payload_t *payload;
notify_payload_t *notify; notify_payload_t *notify;
delete_payload_t *delete; delete_payload_t *delete;
ike_sa_state_t state;
if (array_count(this->passive_tasks) == 0) if (array_count(this->passive_tasks) == 0)
{ /* create tasks depending on request type, if not already some queued */ { /* 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)) switch (message->get_exchange_type(message))
{ {
case IKE_SA_INIT: 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 */ { /* FIXME: we should prevent this on mediation connections */
bool notify_found = FALSE, ts_found = FALSE; bool notify_found = FALSE, ts_found = FALSE;
if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED || if (state == IKE_CREATED ||
this->ike_sa->get_state(this->ike_sa) == IKE_CONNECTING) state == IKE_CONNECTING)
{ {
DBG1(DBG_IKE, "received CREATE_CHILD_SA request for " DBG1(DBG_IKE, "received CREATE_CHILD_SA request for "
"unestablished IKE_SA, rejected"); "unestablished IKE_SA, rejected");
@@ -1013,6 +1031,14 @@ static status_t process_request(private_task_manager_t *this,
case PLV2_NOTIFY: case PLV2_NOTIFY:
{ {
notify = (notify_payload_t*)payload; 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)) switch (notify->get_notify_type(notify))
{ {
case ADDITIONAL_IP4_ADDRESS: case ADDITIONAL_IP4_ADDRESS:
@@ -1355,6 +1381,8 @@ METHOD(task_manager_t, process_message, status_t,
status_t status; status_t status;
uint32_t mid; uint32_t mid;
bool schedule_delete_job = FALSE; bool schedule_delete_job = FALSE;
ike_sa_state_t state;
exchange_type_t type;
charon->bus->message(charon->bus, msg, TRUE, FALSE); charon->bus->message(charon->bus, msg, TRUE, FALSE);
status = parse_message(this, msg); status = parse_message(this, msg);
@@ -1395,15 +1423,16 @@ METHOD(task_manager_t, process_message, status_t,
{ {
if (mid == this->responding.mid) if (mid == this->responding.mid)
{ {
/* reject initial messages if not received in specific states */ /* reject initial messages if not received in specific states,
if ((msg->get_exchange_type(msg) == IKE_SA_INIT && * after rekeying we only expect a DELETE in an INFORMATIONAL */
this->ike_sa->get_state(this->ike_sa) != IKE_CREATED) || type = msg->get_exchange_type(msg);
(msg->get_exchange_type(msg) == IKE_AUTH && state = this->ike_sa->get_state(this->ike_sa);
this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING)) 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", DBG1(DBG_IKE, "ignoring %N in IKE_SA state %N",
exchange_type_names, msg->get_exchange_type(msg), exchange_type_names, type, ike_sa_state_names, state);
ike_sa_state_names, this->ike_sa->get_state(this->ike_sa));
return FAILED; return FAILED;
} }
if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE)) 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; return SUCCESS;
} }
METHOD(task_manager_t, queue_task, void, METHOD(task_manager_t, queue_task_delayed, void,
private_task_manager_t *this, task_t *task) 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) if (task->get_type(task) == TASK_IKE_MOBIKE)
{ /* there is no need to queue more than one mobike task */ { /* there is no need to queue more than one mobike task */
enumerator_t *enumerator;
task_t *current;
enumerator = array_create_enumerator(this->queued_tasks); 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); enumerator->destroy(enumerator);
task->destroy(task); task->destroy(task);
@@ -1527,8 +1557,35 @@ METHOD(task_manager_t, queue_task, void,
} }
enumerator->destroy(enumerator); enumerator->destroy(enumerator);
} }
DBG2(DBG_IKE, "queueing %N task", task_type_names, task->get_type(task)); time_monotonic(&time);
array_insert(this->queued_tasks, ARRAY_TAIL, task); 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; enumerator_t *enumerator;
bool found = FALSE; bool found = FALSE;
task_t *task; queued_task_t *queued;
enumerator = array_create_enumerator(this->queued_tasks); 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; found = TRUE;
break; break;
@@ -1622,7 +1679,7 @@ static void trigger_mbb_reauth(private_task_manager_t *this)
child_cfg_t *cfg; child_cfg_t *cfg;
ike_sa_t *new; ike_sa_t *new;
host_t *host; host_t *host;
task_t *task; queued_task_t *queued;
new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager,
this->ike_sa->get_version(this->ike_sa), TRUE); 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->destroy(enumerator);
enumerator = array_create_enumerator(this->queued_tasks); 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); queued->task->migrate(queued->task, new);
new->queue_task(new, task); new->queue_task(new, queued->task);
array_remove_at(this->queued_tasks, enumerator); array_remove_at(this->queued_tasks, enumerator);
free(queued);
} }
} }
enumerator->destroy(enumerator); 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 *this, task_manager_t *other_public)
{ {
private_task_manager_t *other = (private_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 */ /* 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)); DBG2(DBG_IKE, "migrating %N task", task_type_names,
task->migrate(task, this->ike_sa); queued->task->get_type(queued->task));
array_insert(this->queued_tasks, ARRAY_HEAD, 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, 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; enumerator_t *enumerator;
array_t *array;
task_t *task; 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)) 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) if (task->get_type(task) == TASK_CHILD_CREATE)
{ {
array_remove_at(src, enumerator); array_remove_at(array, enumerator);
task->migrate(task, this->ike_sa); task->migrate(task, this->ike_sa);
array_insert(dst, ARRAY_TAIL, task); queue_task(this, task);
free(queued);
} }
} }
enumerator->destroy(enumerator); 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; private_task_manager_t *other = (private_task_manager_t*)other_public;
/* move active child tasks from other to this */ /* 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 */ /* 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, 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) private_task_manager_t *this, uint32_t initiate, uint32_t respond)
{ {
enumerator_t *enumerator; enumerator_t *enumerator;
queued_task_t *queued;
task_t *task; task_t *task;
timeval_t now;
/* reset message counters and retransmit packets */ /* reset message counters and retransmit packets */
clear_packets(this->responding.packets); clear_packets(this->responding.packets);
@@ -1857,11 +1945,13 @@ METHOD(task_manager_t, reset, void,
} }
this->initiating.type = EXCHANGE_TYPE_UNDEFINED; this->initiating.type = EXCHANGE_TYPE_UNDEFINED;
time_monotonic(&now);
/* reset queued tasks */ /* reset queued tasks */
enumerator = array_create_enumerator(this->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); enumerator->destroy(enumerator);
@@ -1869,12 +1959,25 @@ METHOD(task_manager_t, reset, void,
while (array_remove(this->active_tasks, ARRAY_TAIL, &task)) while (array_remove(this->active_tasks, ARRAY_TAIL, &task))
{ {
task->migrate(task, this->ike_sa); 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; 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*, METHOD(task_manager_t, create_task_enumerator, enumerator_t*,
private_task_manager_t *this, task_queue_t queue) 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: case TASK_QUEUE_PASSIVE:
return array_create_enumerator(this->passive_tasks); return array_create_enumerator(this->passive_tasks);
case TASK_QUEUE_QUEUED: 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: default:
return enumerator_create_empty(); return enumerator_create_empty();
} }
@@ -1921,6 +2026,7 @@ task_manager_v2_t *task_manager_v2_create(ike_sa_t *ike_sa)
.task_manager = { .task_manager = {
.process_message = _process_message, .process_message = _process_message,
.queue_task = _queue_task, .queue_task = _queue_task,
.queue_task_delayed = _queue_task_delayed,
.queue_ike = _queue_ike, .queue_ike = _queue_ike,
.queue_ike_rekey = _queue_ike_rekey, .queue_ike_rekey = _queue_ike_rekey,
.queue_ike_reauth = _queue_ike_reauth, .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-2008 Martin Willi
* Copyright (C) 2005 Jan Hutter * 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 * 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 * under the terms of the GNU General Public License as published by the
@@ -29,7 +29,7 @@
#include <encoding/payloads/delete_payload.h> #include <encoding/payloads/delete_payload.h>
#include <processing/jobs/delete_ike_sa_job.h> #include <processing/jobs/delete_ike_sa_job.h>
#include <processing/jobs/inactivity_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; typedef struct private_child_create_t private_child_create_t;
@@ -204,6 +204,25 @@ struct private_child_create_t {
bool retry; 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 * 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; chunk_t integ_i = chunk_empty, integ_r = chunk_empty;
linked_list_t *my_ts, *other_ts; linked_list_t *my_ts, *other_ts;
host_t *me, *other; host_t *me, *other;
bool private; bool private, prefer_configured;
if (this->proposals == NULL) 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); other = this->ike_sa->get_other_host(this->ike_sa);
private = this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN); 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->proposal = this->config->select_proposal(this->config,
this->proposals, no_dh, private); this->proposals, no_dh, private, prefer_configured);
if (this->proposal == NULL) if (this->proposal == NULL)
{ {
DBG1(DBG_IKE, "no acceptable proposal found"); 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) if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING)
{ {
DBG1(DBG_IKE, "unable to create CHILD_SA while rekeying IKE_SA"); 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; return SUCCESS;
} }
if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING) if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
{ {
DBG1(DBG_IKE, "unable to create CHILD_SA while deleting IKE_SA"); 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; return SUCCESS;
} }
@@ -1441,6 +1462,17 @@ METHOD(task_t, process_i, status_t,
/* an error in CHILD_SA creation is not critical */ /* an error in CHILD_SA creation is not critical */
return SUCCESS; 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: case INVALID_KE_PAYLOAD:
{ {
chunk_t data; chunk_t data;
+46 -15
View File
@@ -1,6 +1,7 @@
/* /*
* Copyright (C) 2009-2016 Tobias Brunner
* Copyright (C) 2006-2007 Martin Willi * 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 * 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 * under the terms of the GNU General Public License as published by the
@@ -18,7 +19,7 @@
#include <daemon.h> #include <daemon.h>
#include <encoding/payloads/delete_payload.h> #include <encoding/payloads/delete_payload.h>
#include <sa/ikev2/tasks/child_create.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; 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); 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 * 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)) switch (child_sa->get_state(child_sa))
{ {
case CHILD_REKEYING: case CHILD_REKEYED:
this->rekeyed = TRUE; this->rekeyed = TRUE;
/* we reply as usual, rekeying will fail */
break; break;
case CHILD_DELETING: case CHILD_DELETING:
/* we don't send back a delete if we initiated ourself */ /* we don't send back a delete if we initiated ourself */
if (!this->initiator) if (!this->initiator)
{ {
this->ike_sa->destroy_child_sa(this->ike_sa,
protocol, spi);
continue; continue;
} }
/* fall through */ /* fall through */
case CHILD_REKEYING:
/* we reply as usual, rekeying will fail */
case CHILD_INSTALLED: case CHILD_INSTALLED:
if (!this->initiator) 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: default:
break; 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); enumerator = this->child_sas->create_enumerator(this->child_sas);
while (enumerator->enumerate(enumerator, (void**)&child_sa)) 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) if (!this->rekeyed)
{ {
charon->bus->child_updown(charon->bus, child_sa, FALSE); 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->spi = child_sa->get_spi(child_sa, TRUE);
} }
this->child_sas->insert_last(this->child_sas, child_sa); 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; this->rekeyed = TRUE;
} }
@@ -347,11 +382,7 @@ METHOD(task_t, process_r, status_t,
METHOD(task_t, build_r, status_t, METHOD(task_t, build_r, status_t,
private_child_delete_t *this, message_t *message) private_child_delete_t *this, message_t *message)
{ {
/* if we are rekeying, we send an empty informational */ build_payloads(this, message);
if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING)
{
build_payloads(this, message);
}
DBG1(DBG_IKE, "CHILD_SA closed"); DBG1(DBG_IKE, "CHILD_SA closed");
return destroy_and_reestablish(this); 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-2007 Martin Willi
* Copyright (C) 2005 Jan Hutter * 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 * 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 * 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 */ { /* check if it is an outbound CHILD_SA */
this->child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol, this->child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol,
this->spi, FALSE); this->spi, FALSE);
if (!this->child_sa) 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.*/ /* we work only with the inbound SPI */
message->set_exchange_type(message, INFORMATIONAL); this->spi = this->child_sa->get_spi(this->child_sa, TRUE);
return SUCCESS;
} }
/* 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); config = this->child_sa->get_config(this->child_sa);
@@ -218,12 +226,18 @@ METHOD(task_t, build_r, status_t,
{ {
child_cfg_t *config; child_cfg_t *config;
uint32_t reqid; uint32_t reqid;
child_sa_state_t state;
if (this->child_sa == NULL || if (!this->child_sa)
this->child_sa->get_state(this->child_sa) == CHILD_DELETING)
{ {
DBG1(DBG_IKE, "unable to rekey, CHILD_SA not found"); 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; 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->set_config(this->child_create, config->get_ref(config));
this->child_create->task.build(&this->child_create->task, message); 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) if (message->get_payload(message, PLV2_SECURITY_ASSOCIATION) == NULL)
{ { /* rekeying failed, reuse old child */
/* rekeying failed, reuse old child */ this->child_sa->set_state(this->child_sa, state);
this->child_sa->set_state(this->child_sa, CHILD_INSTALLED);
return SUCCESS; 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 */ /* invoke rekey hook */
charon->bus->child_rekey(charon->bus, this->child_sa, 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) if (child_sa)
{ {
child_sa->set_close_action(child_sa, ACTION_NONE); 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)); this->ike_sa->get_id(this->ike_sa), TRUE));
return SUCCESS; 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, if (this->child_create->task.process(&this->child_create->task,
message) == NEED_MORE) message) == NEED_MORE)
@@ -346,10 +390,10 @@ METHOD(task_t, process_i, status_t,
} }
if (message->get_payload(message, PLV2_SECURITY_ASSOCIATION) == NULL) if (message->get_payload(message, PLV2_SECURITY_ASSOCIATION) == NULL)
{ {
/* establishing new child failed, reuse old. but not when we /* establishing new child failed, reuse old and try again. but not when
* received a delete in the meantime */ * we received a delete in the meantime */
if (!(this->collision && if (!this->collision ||
this->collision->get_type(this->collision) == TASK_CHILD_DELETE)) this->collision->get_type(this->collision) != TASK_CHILD_DELETE)
{ {
schedule_delayed_rekey(this); schedule_delayed_rekey(this);
} }
@@ -377,9 +421,9 @@ METHOD(task_t, process_i, status_t,
return SUCCESS; return SUCCESS;
} }
/* disable updown event for redundant CHILD_SA */ /* 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); spi = to_delete->get_spi(to_delete, TRUE);
protocol = to_delete->get_protocol(to_delete); protocol = to_delete->get_protocol(to_delete);
@@ -398,6 +442,18 @@ METHOD(task_t, get_type, task_type_t,
return TASK_CHILD_REKEY; 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, METHOD(child_rekey_t, collide, void,
private_child_rekey_t *this, task_t *other) 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) if (other->get_type(other) == TASK_CHILD_REKEY)
{ {
private_child_rekey_t *rekey = (private_child_rekey_t*)other; private_child_rekey_t *rekey = (private_child_rekey_t*)other;
child_sa_t *other_child;
if (rekey->child_sa != this->child_sa) 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); other->destroy(other);
return; return;
} }
@@ -416,19 +481,11 @@ METHOD(child_rekey_t, collide, void,
else if (other->get_type(other) == TASK_CHILD_DELETE) else if (other->get_type(other) == TASK_CHILD_DELETE)
{ {
child_delete_t *del = (child_delete_t*)other; child_delete_t *del = (child_delete_t*)other;
if (this->collision && if (is_redundant(this, del->get_child(del)))
this->collision->get_type(this->collision) == TASK_CHILD_REKEY)
{ {
private_child_rekey_t *rekey; this->other_child_destroyed = TRUE;
other->destroy(other);
rekey = (private_child_rekey_t*)this->collision; return;
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;
}
} }
if (del->get_child(del) != this->child_sa) if (del->get_child(del) != this->child_sa)
{ {
@@ -439,7 +496,7 @@ METHOD(child_rekey_t, collide, void,
} }
else else
{ {
/* any other task is not critical for collisisions, ignore */ /* any other task is not critical for collisions, ignore */
other->destroy(other); other->destroy(other);
return; return;
} }
@@ -496,6 +553,7 @@ child_rekey_t *child_rekey_create(ike_sa_t *ike_sa, protocol_id_t protocol,
.migrate = _migrate, .migrate = _migrate,
.destroy = _destroy, .destroy = _destroy,
}, },
.is_redundant = _is_redundant,
.collide = _collide, .collide = _collide,
}, },
.ike_sa = ike_sa, .ike_sa = ike_sa,
+17 -4
View File
@@ -1,6 +1,7 @@
/* /*
* Copyright (C) 2016 Tobias Brunner
* Copyright (C) 2007 Martin Willi * 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 * 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 * under the terms of the GNU General Public License as published by the
@@ -39,13 +40,25 @@ struct child_rekey_t {
task_t task; 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 * If two peers initiate rekeying at the same time, the collision must
* be handled gracefully. The task manager is aware of what exchanges * 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); 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 * 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 * 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 * under the terms of the GNU General Public License as published by the
@@ -17,7 +18,7 @@
#include <daemon.h> #include <daemon.h>
#include <encoding/payloads/delete_payload.h> #include <encoding/payloads/delete_payload.h>
#include <sa/ikev2/tasks/ike_rekey.h>
typedef struct private_ike_delete_t private_ike_delete_t; 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? * are we deleting a rekeyed SA?
*/ */
bool rekeyed; bool rekeyed;
/**
* are we responding to a delete, but have initated our own?
*/
bool simultaneous;
}; };
METHOD(task_t, build_i, status_t, 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); delete_payload = delete_payload_create(PLV2_DELETE, PROTO_IKE);
message->add_payload(message, (payload_t*)delete_payload); 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; this->rekeyed = TRUE;
} }
@@ -93,6 +90,33 @@ METHOD(task_t, process_i, status_t,
return DESTROY_ME; 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, METHOD(task_t, process_r, status_t,
private_ike_delete_t *this, message_t *message) 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)) 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: case IKE_ESTABLISHED:
this->ike_sa->set_state(this->ike_sa, IKE_DELETING); this->ike_sa->set_state(this->ike_sa, IKE_DELETING);
this->ike_sa->reestablish(this->ike_sa); this->ike_sa->reestablish(this->ike_sa);
return NEED_MORE; return NEED_MORE;
case IKE_REKEYING: case IKE_REKEYED:
this->rekeyed = TRUE; this->rekeyed = TRUE;
break; break;
case IKE_DELETING:
this->simultaneous = TRUE;
break;
default: default:
break; break;
} }
@@ -141,11 +173,6 @@ METHOD(task_t, build_r, status_t,
{ {
DBG0(DBG_IKE, "IKE_SA deleted"); DBG0(DBG_IKE, "IKE_SA deleted");
if (this->simultaneous)
{
/* wait for peer's response for our delete request */
return SUCCESS;
}
if (!this->rekeyed) if (!this->rekeyed)
{ /* invoke ike_down() hook if SA has not been rekeyed */ { /* invoke ike_down() hook if SA has not been rekeyed */
charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); 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) private_ike_delete_t *this, ike_sa_t *ike_sa)
{ {
this->ike_sa = ike_sa; this->ike_sa = ike_sa;
this->simultaneous = FALSE;
} }
METHOD(task_t, destroy, void, 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; sa_payload_t *sa_payload = (sa_payload_t*)payload;
linked_list_t *proposal_list; linked_list_t *proposal_list;
bool private; bool private, prefer_configured;
proposal_list = sa_payload->get_proposals(sa_payload); proposal_list = sa_payload->get_proposals(sa_payload);
private = this->ike_sa->supports_extension(this->ike_sa, 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->config->select_proposal(this->config, this->proposal = this->config->select_proposal(this->config,
proposal_list, private); proposal_list, private, prefer_configured);
if (!this->proposal) if (!this->proposal)
{ {
charon->bus->alert(charon->bus, ALERT_PROPOSAL_MISMATCH_IKE, 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-2008 Martin Willi
* Copyright (C) 2005 Jan Hutter * 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 * 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 * 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 * colliding task detected by the task manager
*/ */
task_t *collision; 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 * 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)); job = (job_t*)initiate_tasks_job_create(ike_sa->get_id(ike_sa));
} }
enumerator->destroy(enumerator); enumerator->destroy(enumerator);
return job; return job;
} }
@@ -117,22 +138,11 @@ static void establish_new(private_ike_rekey_t *this)
} }
this->new_sa = NULL; this->new_sa = NULL;
charon->bus->set_sa(charon->bus, this->ike_sa); 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, METHOD(task_t, build_i_delete, status_t,
private_ike_rekey_t *this, message_t *message) private_ike_rekey_t *this, message_t *message)
{ {
@@ -172,36 +182,59 @@ METHOD(task_t, build_i, status_t,
return NEED_MORE; 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; enumerator_t *enumerator;
child_sa_t *child_sa; child_sa_t *child_sa;
task_t *task;
if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
{
DBG1(DBG_IKE, "peer initiated rekeying, but we are deleting");
return NEED_MORE;
}
enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa); enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa);
while (enumerator->enumerate(enumerator, (void**)&child_sa)) while (enumerator->enumerate(enumerator, (void**)&child_sa))
{ {
switch (child_sa->get_state(child_sa)) switch (child_sa->get_state(child_sa))
{ {
case CHILD_CREATED:
case CHILD_REKEYING: case CHILD_REKEYING:
case CHILD_RETRYING: case CHILD_RETRYING:
case CHILD_DELETING: 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); enumerator->destroy(enumerator);
return NEED_MORE; return TRUE;
default: default:
break; break;
} }
} }
enumerator->destroy(enumerator); 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->new_sa = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager,
this->ike_sa->get_version(this->ike_sa), FALSE); 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, METHOD(task_t, build_r, status_t,
private_ike_rekey_t *this, message_t *message) 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) if (this->new_sa == NULL)
{ {
/* IKE_SA/a CHILD_SA is in an inacceptable state, deny rekeying */ /* IKE_SA/a CHILD_SA is in an inacceptable state, deny rekeying */
message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty); message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
return SUCCESS; return SUCCESS;
} }
if (this->ike_init->task.build(&this->ike_init->task, message) == FAILED) 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); charon->bus->set_sa(charon->bus, this->ike_sa);
return SUCCESS; return SUCCESS;
} }
charon->bus->set_sa(charon->bus, this->ike_sa); 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 */ if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING)
this->ike_delete = ike_delete_create(this->ike_sa, FALSE); { /* in case of a collision we let the initiating task handle this */
this->public.task.build = _build_r_delete; establish_new(this);
this->public.task.process = _process_r_delete; /* 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 * Conclude any undetected rekey collision.
* this a timeout condition by the peer, and trigger a delete actively. */ *
lib->scheduler->schedule_job(lib->scheduler, (job_t*) * If the peer does not detect the collision it will delete this IKE_SA.
delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE), 90); * Depending on when our request reaches the peer and we receive the delete
* this may get called at different times.
return NEED_MORE; *
* 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, METHOD(task_t, process_i, status_t,
@@ -266,18 +323,9 @@ METHOD(task_t, process_i, status_t,
{ {
case FAILED: case FAILED:
/* rekeying failed, fallback to old SA */ /* rekeying failed, fallback to old SA */
if (!(this->collision && ( if (!conclude_undetected_collision(this))
this->collision->get_type(this->collision) == TASK_IKE_DELETE ||
this->collision->get_type(this->collision) == TASK_IKE_REAUTH)))
{ {
job_t *job; schedule_delayed_rekey(this);
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);
} }
return SUCCESS; return SUCCESS;
case NEED_MORE: case NEED_MORE:
@@ -293,55 +341,53 @@ METHOD(task_t, process_i, status_t,
this->collision->get_type(this->collision) == TASK_IKE_REKEY) this->collision->get_type(this->collision) == TASK_IKE_REKEY)
{ {
private_ike_rekey_t *other = (private_ike_rekey_t*)this->collision; 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 */ this_nonce = this->ike_init->get_lower_nonce(this->ike_init);
if (other->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; DBG1(DBG_IKE, "IKE_SA rekey collision lost, deleting redundant "
chunk_t this_nonce, other_nonce; "IKE_SA %s[%d]", this->new_sa->get_name(this->new_sa),
this->new_sa->get_unique_id(this->new_sa));
this_nonce = this->ike_init->get_lower_nonce(this->ike_init); /* apply host for a proper delete */
other_nonce = other->ike_init->get_lower_nonce(other->ike_init); host = this->ike_sa->get_my_host(this->ike_sa);
this->new_sa->set_my_host(this->new_sa, host->clone(host));
/* if we have the lower nonce, delete rekeyed SA. If not, delete host = this->ike_sa->get_other_host(this->ike_sa);
* the redundant. */ this->new_sa->set_other_host(this->new_sa, host->clone(host));
if (memcmp(this_nonce.ptr, other_nonce.ptr, /* IKE_SAs in state IKE_REKEYED are silently deleted, so we use
min(this_nonce.len, other_nonce.len)) > 0) * 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. */ this->new_sa->destroy(this->new_sa);
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;
} }
else else
{ {
DBG1(DBG_IKE, "IKE_SA rekey collision lost, " charon->ike_sa_manager->checkin(charon->ike_sa_manager,
"deleting redundant IKE_SA"); 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));
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->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); 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; 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, METHOD(ike_rekey_t, collide, void,
private_ike_rekey_t* this, task_t *other) private_ike_rekey_t* this, task_t *other)
{ {
DBG1(DBG_IKE, "detected %N collision with %N", task_type_names, DBG1(DBG_IKE, "detected %N collision with %N", task_type_names,
TASK_IKE_REKEY, task_type_names, other->get_type(other)); 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); DESTROY_IF(this->collision);
this->collision = other; this->collision = other;
} }
@@ -425,6 +501,7 @@ ike_rekey_t *ike_rekey_create(ike_sa_t *ike_sa, bool initiator)
.migrate = _migrate, .migrate = _migrate,
.destroy = _destroy, .destroy = _destroy,
}, },
.did_collide = _did_collide,
.collide = _collide, .collide = _collide,
}, },
.ike_sa = ike_sa, .ike_sa = ike_sa,
+9 -1
View File
@@ -1,6 +1,7 @@
/* /*
* Copyright (C) 2016 Tobias Brunner
* Copyright (C) 2007 Martin Willi * 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 * 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 * under the terms of the GNU General Public License as published by the
@@ -37,6 +38,13 @@ struct ike_rekey_t {
*/ */
task_t task; 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. * 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 * 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 * 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 * 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 * @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. * Queue IKE_SA establishing tasks.
+33 -1
View File
@@ -1,8 +1,9 @@
TESTS = libcharon_tests TESTS = libcharon_tests exchange_tests
check_PROGRAMS = $(TESTS) check_PROGRAMS = $(TESTS)
libcharon_tests_SOURCES = \ libcharon_tests_SOURCES = \
suites/test_proposal.c \
suites/test_ike_cfg.c \ suites/test_ike_cfg.c \
suites/test_mem_pool.c \ suites/test_mem_pool.c \
suites/test_message_chapoly.c \ suites/test_message_chapoly.c \
@@ -21,3 +22,34 @@ libcharon_tests_LDADD = \
$(top_builddir)/src/libcharon/libcharon.la \ $(top_builddir)/src/libcharon/libcharon.la \
$(top_builddir)/src/libstrongswan/libstrongswan.la \ $(top_builddir)/src/libstrongswan/libstrongswan.la \
$(top_builddir)/src/libstrongswan/tests/libtest.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 else
{ {
lib->processor->set_threads(lib->processor, 0);
lib->processor->cancel(lib->processor);
lib->plugins->unload(lib->plugins);
libcharon_deinit(); libcharon_deinit();
} }
return TRUE; 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 Martin Willi
* Copyright (C) 2014 revosec AG * Copyright (C) 2014 revosec AG
* *
@@ -13,6 +16,15 @@
* for more details. * 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(ike_cfg_suite_create)
TEST_SUITE(mem_pool_suite_create) TEST_SUITE(mem_pool_suite_create)
TEST_SUITE_DEPEND(message_chapoly_suite_create, AEAD, ENCR_CHACHA20_POLY1305, 32) 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)) while (enumerator->enumerate(enumerator, &entry))
{ {
nonce_gen = entry->create_nonce_gen(); nonce_gen = entry->create_nonce_gen();
if (nonce_gen)
{
break;
}
} }
enumerator->destroy(enumerator); enumerator->destroy(enumerator);
this->lock->unlock(this->lock); 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 prfaesxcbc, PSEUDO_RANDOM_FUNCTION, PRF_AES128_XCBC, 0
prfcamelliaxcbc, PSEUDO_RANDOM_FUNCTION, PRF_CAMELLIA128_XCBC, 0 prfcamelliaxcbc, PSEUDO_RANDOM_FUNCTION, PRF_CAMELLIA128_XCBC, 0
prfaescmac, PSEUDO_RANDOM_FUNCTION, PRF_AES128_CMAC, 0 prfaescmac, PSEUDO_RANDOM_FUNCTION, PRF_AES128_CMAC, 0
modpnone, DIFFIE_HELLMAN_GROUP, MODP_NONE, 0
modpnull, DIFFIE_HELLMAN_GROUP, MODP_NULL, 0 modpnull, DIFFIE_HELLMAN_GROUP, MODP_NULL, 0
modp768, DIFFIE_HELLMAN_GROUP, MODP_768_BIT, 0 modp768, DIFFIE_HELLMAN_GROUP, MODP_768_BIT, 0
modp1024, DIFFIE_HELLMAN_GROUP, MODP_1024_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); 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 * Removes and destroys test suites that are not selected or
* explicitly excluded. * explicitly excluded.
@@ -524,11 +546,17 @@ int test_runner_run(const char *name, test_configuration_t configs[],
enumerator_t *enumerator; enumerator_t *enumerator;
int passed = 0, result; int passed = 0, result;
level_t level = LEVEL_SILENT; 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) */ /* redirect all output to stderr (to redirect make's stdout to /dev/null) */
dup2(2, 1); dup2(2, 1);
runners = getenv("TESTS_RUNNERS");
if (runners && !is_in_filter(name, runners))
{
return EXIT_SUCCESS;
}
cfg = getenv("TESTS_STRONGSWAN_CONF"); cfg = getenv("TESTS_STRONGSWAN_CONF");
suites = load_suites(configs, init, cfg); 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_VERBOSITY: Numerical loglevel for debug log
* - TESTS_STRONGSWAN_CONF: Specify a path to a custom strongswan.conf * - TESTS_STRONGSWAN_CONF: Specify a path to a custom strongswan.conf
* - TESTS_PLUGINS: Specify an explicit list of plugins to load * - 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: Run specific test suites only
* - TESTS_SUITES_EXCLUDE: Don't run specific test suites * - TESTS_SUITES_EXCLUDE: Don't run specific test suites
* - TESTS_REDUCED_KEYLENGTHS: Test minimal keylengths for public key tests only * - 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 * Please note that TESTS_PLUGINS actually must be implemented by the init
* callback function, as plugin loading is delegated. * 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 name name of test runner
* @param config test suite constructors with dependencies * @param config test suite constructors with dependencies
* @param init_cb init/deinit callback * @param init_cb init/deinit callback
+3
View File
@@ -28,6 +28,9 @@ AM_CPPFLAGS = \
-DPLUGINS=\""${starter_plugins}\"" \ -DPLUGINS=\""${starter_plugins}\"" \
-DDEBUG -DDEBUG
AM_CFLAGS = \
@COVERAGE_CFLAGS@
AM_YFLAGS = -v -d AM_YFLAGS = -v -d
starter_LDADD = \ starter_LDADD = \