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