Merge branch 'avoid-rekey-loss'
authorTobias Brunner <tobias@strongswan.org>
Tue, 23 May 2017 16:49:13 +0000 (18:49 +0200)
committerTobias Brunner <tobias@strongswan.org>
Tue, 23 May 2017 16:49:13 +0000 (18:49 +0200)
This changes the behavior during IKEv2 CHILD_SA rekeyings to avoid
traffic loss.  When responding to a CREATE_CHILD_SA request to rekey a
CHILD_SA the responder already has everything available to install and
use the new CHILD_SA.  However, this could lead to lost traffic as the
initiator won't be able to process inbound packets until it processed the
CREATE_CHILD_SA response and updated the inbound SA.  To avoid this the
responder now only installs the new inbound SA and delays installing the
outbound SA until it receives the DELETE for the replaced CHILD_SA.  The
messages transporting these DELETEs could reach the peer before packets
sent with the deleted outbound SAs reach the respective peer.  To reduce
the chance of traffic loss due to this the inbound SA of the replaced
CHILD_SA is not removed for a configurable amount of seconds after
the DELETE has been processed.

Fixes #1291.

17 files changed:
conf/options/charon.opt
src/libcharon/plugins/ha/ha_dispatcher.c
src/libcharon/processing/jobs/delete_child_sa_job.c
src/libcharon/processing/jobs/delete_child_sa_job.h
src/libcharon/sa/child_sa.c
src/libcharon/sa/child_sa.h
src/libcharon/sa/ikev1/tasks/quick_mode.c
src/libcharon/sa/ikev2/tasks/child_create.c
src/libcharon/sa/ikev2/tasks/child_delete.c
src/libcharon/sa/ikev2/tasks/child_rekey.c
src/libcharon/sa/trap_manager.c
src/libcharon/tests/suites/test_child_rekey.c
src/libcharon/tests/utils/exchange_test_asserts.c
src/libcharon/tests/utils/exchange_test_asserts.h
src/libcharon/tests/utils/mock_ipsec.c
src/libcharon/tests/utils/mock_ipsec.h
src/libcharon/tests/utils/sa_asserts.h

index a5f03f2..3593c6a 100644 (file)
@@ -75,6 +75,16 @@ charon.delete_rekeyed = no
        However, this might cause problems with implementations that continue to
        use rekeyed SAs until they expire.
 
+charon.delete_rekeyed_delay = 5
+       Delay in seconds until inbound IPsec SAs are deleted after rekeyings (IKEv2
+       only).
+
+       Delay in seconds until inbound IPsec SAs are deleted after rekeyings (IKEv2
+       only). To process delayed packets the inbound part of a CHILD_SA is kept
+       installed up to the configured number of seconds after it got replaced
+       during a rekeying. If set to 0 the CHILD_SA will be kept installed until it
+       expires (if no lifetime is set it will be destroyed immediately).
+
 charon.dh_exponent_ansi_x9_42 = yes
        Use ANSI X9.42 DH exponent size or optimum size matched to cryptographic
        strength.
index ee66b84..7d22257 100644 (file)
@@ -818,14 +818,14 @@ static void process_child_add(private_ha_dispatcher_t *this,
        }
        enumerator->destroy(enumerator);
 
+       child_sa->set_policies(child_sa, local_ts, remote_ts);
+
        if (initiator)
        {
                if (child_sa->install(child_sa, encr_r, integ_r, inbound_spi,
-                                                         inbound_cpi, initiator, TRUE, TRUE,
-                                                         local_ts, remote_ts) != SUCCESS ||
+                                                         inbound_cpi, initiator, TRUE, TRUE) != SUCCESS ||
                        child_sa->install(child_sa, encr_i, integ_i, outbound_spi,
-                                                         outbound_cpi, initiator, FALSE, TRUE,
-                                                         local_ts, remote_ts) != SUCCESS)
+                                                         outbound_cpi, initiator, FALSE, TRUE) != SUCCESS)
                {
                        failed = TRUE;
                }
@@ -833,11 +833,9 @@ static void process_child_add(private_ha_dispatcher_t *this,
        else
        {
                if (child_sa->install(child_sa, encr_i, integ_i, inbound_spi,
-                                                         inbound_cpi, initiator, TRUE, TRUE,
-                                                         local_ts, remote_ts) != SUCCESS ||
+                                                         inbound_cpi, initiator, TRUE, TRUE) != SUCCESS ||
                        child_sa->install(child_sa, encr_r, integ_r, outbound_spi,
-                                                         outbound_cpi, initiator, FALSE, TRUE,
-                                                         local_ts, remote_ts) != SUCCESS)
+                                                         outbound_cpi, initiator, FALSE, TRUE) != SUCCESS)
                {
                        failed = TRUE;
                }
@@ -868,7 +866,7 @@ static void process_child_add(private_ha_dispatcher_t *this,
                child_sa->get_unique_id(child_sa), local_ts, remote_ts,
                seg_i, this->segments->is_active(this->segments, seg_i) ? "*" : "",
                seg_o, this->segments->is_active(this->segments, seg_o) ? "*" : "");
-       child_sa->add_policies(child_sa, local_ts, remote_ts);
+       child_sa->install_policies(child_sa);
        local_ts->destroy_offset(local_ts, offsetof(traffic_selector_t, destroy));
        remote_ts->destroy_offset(remote_ts, offsetof(traffic_selector_t, destroy));
 
index 70dbc1b..048b879 100644 (file)
@@ -1,6 +1,7 @@
 /*
+ * Copyright (C) 2017 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
@@ -24,19 +25,19 @@ typedef struct private_delete_child_sa_job_t private_delete_child_sa_job_t;
  * Private data of an delete_child_sa_job_t object.
  */
 struct private_delete_child_sa_job_t {
-       /**
 
+       /**
         * Public delete_child_sa_job_t interface.
         */
        delete_child_sa_job_t public;
 
        /**
-        * protocol of the CHILD_SA (ESP/AH)
+        * Protocol of the CHILD_SA (ESP/AH)
         */
        protocol_id_t protocol;
 
        /**
-        * inbound SPI of the CHILD_SA
+        * Inbound SPI of the CHILD_SA
         */
        uint32_t spi;
 
@@ -49,12 +50,17 @@ struct private_delete_child_sa_job_t {
         * Delete for an expired CHILD_SA
         */
        bool expired;
+
+       /**
+        * Unique ID of the CHILD_SA
+        */
+       uint32_t id;
 };
 
 METHOD(job_t, destroy, void,
        private_delete_child_sa_job_t *this)
 {
-       this->dst->destroy(this->dst);
+       DESTROY_IF(this->dst);
        free(this);
 }
 
@@ -63,17 +69,37 @@ METHOD(job_t, execute, job_requeue_t,
 {
        ike_sa_t *ike_sa;
 
-       ike_sa = charon->child_sa_manager->checkout(charon->child_sa_manager,
-                                                                       this->protocol, this->spi, this->dst, NULL);
-       if (ike_sa == NULL)
+       if (this->id)
        {
-               DBG1(DBG_JOB, "CHILD_SA %N/0x%08x/%H not found for delete",
-                        protocol_id_names, this->protocol, htonl(this->spi), this->dst);
+               child_sa_t *child_sa;
+
+               ike_sa = charon->child_sa_manager->checkout_by_id(
+                                                               charon->child_sa_manager, this->id, &child_sa);
+               if (!ike_sa)
+               {
+                       DBG1(DBG_JOB, "CHILD_SA {%d} not found for delete", this->id);
+               }
+               else
+               {
+                       this->spi = child_sa->get_spi(child_sa, TRUE);
+                       this->protocol = child_sa->get_protocol(child_sa);
+               }
        }
        else
        {
-               ike_sa->delete_child_sa(ike_sa, this->protocol, this->spi, this->expired);
+               ike_sa = charon->child_sa_manager->checkout(charon->child_sa_manager,
+                                                                       this->protocol, this->spi, this->dst, NULL);
+               if (!ike_sa)
+               {
+                       DBG1(DBG_JOB, "CHILD_SA %N/0x%08x/%H not found for delete",
+                                protocol_id_names, this->protocol, htonl(this->spi), this->dst);
+               }
+       }
 
+       if (ike_sa)
+       {
+               ike_sa->delete_child_sa(ike_sa, this->protocol, this->spi,
+                                                               this->expired);
                charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
        }
        return JOB_REQUEUE_NONE;
@@ -109,3 +135,24 @@ delete_child_sa_job_t *delete_child_sa_job_create(protocol_id_t protocol,
 
        return &this->public;
 }
+
+/*
+ * Described in header
+ */
+delete_child_sa_job_t *delete_child_sa_job_create_id(uint32_t id)
+{
+       private_delete_child_sa_job_t *this;
+
+       INIT(this,
+               .public = {
+                       .job_interface = {
+                               .execute = _execute,
+                               .get_priority = _get_priority,
+                               .destroy = _destroy,
+                       },
+               },
+               .id = id,
+       );
+
+       return &this->public;
+}
index 349f5de..b2d5a11 100644 (file)
@@ -1,6 +1,7 @@
 /*
+ * Copyright (C) 2017 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
@@ -42,7 +43,7 @@ struct delete_child_sa_job_t {
 };
 
 /**
- * Creates a job of type DELETE_CHILD_SA.
+ * Creates a job that deletes a CHILD_SA.
  *
  * @param protocol     protocol of the CHILD_SA
  * @param spi          security parameter index of the CHILD_SA
@@ -53,4 +54,12 @@ struct delete_child_sa_job_t {
 delete_child_sa_job_t *delete_child_sa_job_create(protocol_id_t protocol,
                                                                        uint32_t spi, host_t *dst, bool expired);
 
+/**
+ * Creates a job that deletes a CHILD_SA identified by its unique ID.
+ *
+ * @param id           unique ID of the CHILD_SA
+ * @return                     delete_child_sa_job_t object
+ */
+delete_child_sa_job_t *delete_child_sa_job_create_id(uint32_t id);
+
 #endif /** DELETE_CHILD_SA_JOB_H_ @}*/
index 9dd175b..1d61591 100644 (file)
@@ -1,6 +1,6 @@
 /*
+ * Copyright (C) 2006-2017 Tobias Brunner
  * Copyright (C) 2016 Andreas Steffen
- * Copyright (C) 2006-2016 Tobias Brunner
  * Copyright (C) 2005-2008 Martin Willi
  * Copyright (C) 2006 Daniel Roethlisberger
  * Copyright (C) 2005 Jan Hutter
@@ -40,6 +40,12 @@ ENUM(child_sa_state_names, CHILD_CREATED, CHILD_DESTROYING,
        "DESTROYING",
 );
 
+ENUM(child_sa_outbound_state_names, CHILD_OUTBOUND_NONE, CHILD_OUTBOUND_INSTALLED,
+       "NONE",
+       "REGISTERED",
+       "INSTALLED",
+);
+
 typedef struct private_child_sa_t private_child_sa_t;
 
 /**
@@ -92,6 +98,31 @@ struct private_child_sa_t {
        array_t *other_ts;
 
        /**
+        * Outbound encryption key cached during a rekeying
+        */
+       chunk_t encr_r;
+
+       /**
+        * Outbound integrity key cached during a rekeying
+        */
+       chunk_t integ_r;
+
+       /**
+        * Whether the outbound SA has only been registered yet during a rekeying
+        */
+       child_sa_outbound_state_t outbound_state;
+
+       /**
+        * Whether the peer supports TFCv3
+        */
+       bool tfcv3;
+
+       /**
+        * The outbound SPI of the CHILD_SA that replaced this one during a rekeying
+        */
+       uint32_t rekey_spi;
+
+       /**
         * Protocol used to protect this SA, ESP|AH
         */
        protocol_id_t protocol;
@@ -265,6 +296,10 @@ METHOD(child_sa_t, get_config, child_cfg_t*,
 METHOD(child_sa_t, set_state, void,
           private_child_sa_t *this, child_sa_state_t state)
 {
+       DBG2(DBG_CHD, "CHILD_SA %s{%d} state change: %N => %N",
+                get_name(this), this->unique_id,
+                child_sa_state_names, this->state,
+                child_sa_state_names, state);
        charon->bus->child_state_change(charon->bus, &this->public, state);
        this->state = state;
 }
@@ -275,6 +310,12 @@ METHOD(child_sa_t, get_state, child_sa_state_t,
        return this->state;
 }
 
+METHOD(child_sa_t, get_outbound_state, child_sa_outbound_state_t,
+          private_child_sa_t *this)
+{
+       return this->outbound_state;
+}
+
 METHOD(child_sa_t, get_spi, uint32_t,
           private_child_sa_t *this, bool inbound)
 {
@@ -504,7 +545,7 @@ static status_t update_usebytes(private_child_sa_t *this, bool inbound)
        }
        else
        {
-               if (this->other_spi)
+               if (this->other_spi && this->outbound_state == CHILD_OUTBOUND_INSTALLED)
                {
                        kernel_ipsec_sa_id_t id = {
                                .src = this->my_addr,
@@ -691,14 +732,16 @@ METHOD(child_sa_t, alloc_cpi, uint16_t,
        return 0;
 }
 
-METHOD(child_sa_t, install, status_t,
-       private_child_sa_t *this, chunk_t encr, chunk_t integ, uint32_t spi,
-       uint16_t cpi, bool initiator, bool inbound, bool tfcv3,
-       linked_list_t *my_ts, linked_list_t *other_ts)
+/**
+ * Install the given SA in the kernel
+ */
+static status_t install_internal(private_child_sa_t *this, chunk_t encr,
+       chunk_t integ, uint32_t spi, uint16_t cpi, bool initiator, bool inbound,
+       bool tfcv3)
 {
        uint16_t enc_alg = ENCR_UNDEFINED, int_alg = AUTH_UNDEFINED, size;
        uint16_t esn = NO_EXT_SEQ_NUMBERS;
-       linked_list_t *src_ts = NULL, *dst_ts = NULL;
+       linked_list_t *my_ts, *other_ts, *src_ts, *dst_ts;
        time_t now;
        kernel_ipsec_sa_id_t id;
        kernel_ipsec_add_sa_t sa;
@@ -708,6 +751,12 @@ METHOD(child_sa_t, install, status_t,
        status_t status;
        bool update = FALSE;
 
+       /* BEET requires the bound address from the traffic selectors */
+       my_ts = linked_list_create_from_enumerator(
+                                                                       array_create_enumerator(this->my_ts));
+       other_ts = linked_list_create_from_enumerator(
+                                                                       array_create_enumerator(this->other_ts));
+
        /* now we have to decide which spi to use. Use self allocated, if "in",
         * or the one in the proposal, if not "in" (others). Additionally,
         * source and dest host switch depending on the role */
@@ -721,6 +770,8 @@ METHOD(child_sa_t, install, status_t,
                }
                this->my_spi = spi;
                this->my_cpi = cpi;
+               dst_ts = my_ts;
+               src_ts = other_ts;
        }
        else
        {
@@ -728,11 +779,14 @@ METHOD(child_sa_t, install, status_t,
                dst = this->other_addr;
                this->other_spi = spi;
                this->other_cpi = cpi;
+               src_ts = my_ts;
+               dst_ts = other_ts;
 
                if (tfcv3)
                {
                        tfc = this->config->get_tfc(this->config);
                }
+               this->outbound_state = CHILD_OUTBOUND_INSTALLED;
        }
 
        DBG2(DBG_CHD, "adding %s %N SA", inbound ? "inbound" : "outbound",
@@ -754,6 +808,8 @@ METHOD(child_sa_t, install, status_t,
                                                                this->mark_in, this->mark_out, &this->reqid);
                if (status != SUCCESS)
                {
+                       my_ts->destroy(my_ts);
+                       other_ts->destroy(other_ts);
                        return status;
                }
                this->reqid_allocated = TRUE;
@@ -783,18 +839,6 @@ METHOD(child_sa_t, install, status_t,
                lifetime->time.rekey = 0;
        }
 
-       /* BEET requires the bound address from the traffic selectors */
-       if (inbound)
-       {
-               dst_ts = my_ts;
-               src_ts = other_ts;
-       }
-       else
-       {
-               src_ts = my_ts;
-               dst_ts = other_ts;
-       }
-
        id = (kernel_ipsec_sa_id_t){
                .src = src,
                .dst = dst,
@@ -827,11 +871,21 @@ METHOD(child_sa_t, install, status_t,
 
        status = charon->kernel->add_sa(charon->kernel, &id, &sa);
 
+       my_ts->destroy(my_ts);
+       other_ts->destroy(other_ts);
        free(lifetime);
 
        return status;
 }
 
+METHOD(child_sa_t, install, status_t,
+       private_child_sa_t *this, chunk_t encr, chunk_t integ, uint32_t spi,
+       uint16_t cpi, bool initiator, bool inbound, bool tfcv3)
+{
+       return install_internal(this, encr, integ, spi, cpi, initiator, inbound,
+                                                       tfcv3);
+}
+
 /**
  * Check kernel interface if policy updates are required
  */
@@ -888,34 +942,21 @@ static void prepare_sa_cfg(private_child_sa_t *this, ipsec_sa_cfg_t *my_sa,
 }
 
 /**
- * Install 3 policies: out, in and forward
+ * Install inbound policie(s): in, fwd
  */
-static status_t install_policies_internal(private_child_sa_t *this,
+static status_t install_policies_inbound(private_child_sa_t *this,
        host_t *my_addr, host_t *other_addr, traffic_selector_t *my_ts,
        traffic_selector_t *other_ts, ipsec_sa_cfg_t *my_sa,
        ipsec_sa_cfg_t *other_sa, policy_type_t type,
        policy_priority_t priority,     uint32_t manual_prio)
 {
-       kernel_ipsec_policy_id_t out_id = {
-               .dir = POLICY_OUT,
-               .src_ts = my_ts,
-               .dst_ts = other_ts,
-               .mark = this->mark_out,
-               .interface = this->config->get_interface(this->config),
-       }, in_id = {
+       kernel_ipsec_policy_id_t in_id = {
                .dir = POLICY_IN,
                .src_ts = other_ts,
                .dst_ts = my_ts,
                .mark = this->mark_in,
        };
-       kernel_ipsec_manage_policy_t out_policy = {
-               .type = type,
-               .prio = priority,
-               .manual_prio = manual_prio,
-               .src = my_addr,
-               .dst = other_addr,
-               .sa = other_sa,
-       }, in_policy = {
+       kernel_ipsec_manage_policy_t in_policy = {
                .type = type,
                .prio = priority,
                .manual_prio = manual_prio,
@@ -925,13 +966,45 @@ static status_t install_policies_internal(private_child_sa_t *this,
        };
        status_t status = SUCCESS;
 
-       status |= charon->kernel->add_policy(charon->kernel, &out_id, &out_policy);
        status |= charon->kernel->add_policy(charon->kernel, &in_id, &in_policy);
        if (this->mode != MODE_TRANSPORT)
        {
                in_id.dir = POLICY_FWD;
                status |= charon->kernel->add_policy(charon->kernel, &in_id, &in_policy);
+       }
+       return status;
+}
 
+/**
+ * Install outbound policie(s): out, [fwd]
+ */
+static status_t install_policies_outbound(private_child_sa_t *this,
+       host_t *my_addr, host_t *other_addr, traffic_selector_t *my_ts,
+       traffic_selector_t *other_ts, ipsec_sa_cfg_t *my_sa,
+       ipsec_sa_cfg_t *other_sa, policy_type_t type,
+       policy_priority_t priority,     uint32_t manual_prio)
+{
+       kernel_ipsec_policy_id_t out_id = {
+               .dir = POLICY_OUT,
+               .src_ts = my_ts,
+               .dst_ts = other_ts,
+               .mark = this->mark_out,
+               .interface = this->config->get_interface(this->config),
+       };
+       kernel_ipsec_manage_policy_t out_policy = {
+               .type = type,
+               .prio = priority,
+               .manual_prio = manual_prio,
+               .src = my_addr,
+               .dst = other_addr,
+               .sa = other_sa,
+       };
+       status_t status = SUCCESS;
+
+       status |= charon->kernel->add_policy(charon->kernel, &out_id, &out_policy);
+
+       if (this->mode != MODE_TRANSPORT && this->policies_fwd_out)
+       {
                /* install an "outbound" FWD policy in case there is a drop policy
                 * matching outbound forwarded traffic, to allow another tunnel to use
                 * the reversed subnets and do the same we don't set a reqid (this also
@@ -940,52 +1013,56 @@ static status_t install_policies_internal(private_child_sa_t *this,
                 * policies of two SAs we install them with reduced priority.  As they
                 * basically act as bypass policies for drop policies we use a higher
                 * priority than is used for them. */
-               if (this->policies_fwd_out)
+               out_id.dir = POLICY_FWD;
+               other_sa->reqid = 0;
+               if (priority == POLICY_PRIORITY_DEFAULT)
                {
-                       out_id.dir = POLICY_FWD;
-                       other_sa->reqid = 0;
-                       if (priority == POLICY_PRIORITY_DEFAULT)
-                       {
-                               out_policy.prio = POLICY_PRIORITY_ROUTED;
-                       }
-                       status |= charon->kernel->add_policy(charon->kernel, &out_id,
-                                                                                                &out_policy);
-                       /* reset the reqid for any other further policies */
-                       other_sa->reqid = this->reqid;
+                       out_policy.prio = POLICY_PRIORITY_ROUTED;
                }
+               status |= charon->kernel->add_policy(charon->kernel, &out_id,
+                                                                                        &out_policy);
+               /* reset the reqid for any other further policies */
+               other_sa->reqid = this->reqid;
        }
        return status;
 }
 
 /**
- * Delete 3 policies: out, in and forward
+ * Install all policies
  */
-static void del_policies_internal(private_child_sa_t *this,
+static status_t install_policies_internal(private_child_sa_t *this,
+       host_t *my_addr, host_t *other_addr, traffic_selector_t *my_ts,
+       traffic_selector_t *other_ts, ipsec_sa_cfg_t *my_sa,
+       ipsec_sa_cfg_t *other_sa, policy_type_t type,
+       policy_priority_t priority,     uint32_t manual_prio)
+{
+       status_t status = SUCCESS;
+
+       status |= install_policies_inbound(this, my_addr, other_addr, my_ts,
+                                                                          other_ts, my_sa, other_sa, type,
+                                                                          priority, manual_prio);
+       status |= install_policies_outbound(this, my_addr, other_addr, my_ts,
+                                                                               other_ts, my_sa, other_sa, type,
+                                                                               priority, manual_prio);
+       return status;
+}
+
+/**
+ * Delete inbound policies: in, fwd
+ */
+static void del_policies_inbound(private_child_sa_t *this,
        host_t *my_addr, host_t *other_addr, traffic_selector_t *my_ts,
        traffic_selector_t *other_ts, ipsec_sa_cfg_t *my_sa,
        ipsec_sa_cfg_t *other_sa, policy_type_t type,
        policy_priority_t priority, uint32_t manual_prio)
 {
-       kernel_ipsec_policy_id_t out_id = {
-               .dir = POLICY_OUT,
-               .src_ts = my_ts,
-               .dst_ts = other_ts,
-               .mark = this->mark_out,
-               .interface = this->config->get_interface(this->config),
-       }, in_id = {
+       kernel_ipsec_policy_id_t in_id = {
                .dir = POLICY_IN,
                .src_ts = other_ts,
                .dst_ts = my_ts,
                .mark = this->mark_in,
        };
-       kernel_ipsec_manage_policy_t out_policy = {
-               .type = type,
-               .prio = priority,
-               .manual_prio = manual_prio,
-               .src = my_addr,
-               .dst = other_addr,
-               .sa = other_sa,
-       }, in_policy = {
+       kernel_ipsec_manage_policy_t in_policy = {
                .type = type,
                .prio = priority,
                .manual_prio = manual_prio,
@@ -994,49 +1071,83 @@ static void del_policies_internal(private_child_sa_t *this,
                .sa = my_sa,
        };
 
-       charon->kernel->del_policy(charon->kernel, &out_id, &out_policy);
        charon->kernel->del_policy(charon->kernel, &in_id, &in_policy);
+
        if (this->mode != MODE_TRANSPORT)
        {
                in_id.dir = POLICY_FWD;
                charon->kernel->del_policy(charon->kernel, &in_id, &in_policy);
+       }
+}
 
-               if (this->policies_fwd_out)
+/**
+ * Delete outbound policies: out, [fwd]
+ */
+static void del_policies_outbound(private_child_sa_t *this,
+       host_t *my_addr, host_t *other_addr, traffic_selector_t *my_ts,
+       traffic_selector_t *other_ts, ipsec_sa_cfg_t *my_sa,
+       ipsec_sa_cfg_t *other_sa, policy_type_t type,
+       policy_priority_t priority, uint32_t manual_prio)
+{
+       kernel_ipsec_policy_id_t out_id = {
+               .dir = POLICY_OUT,
+               .src_ts = my_ts,
+               .dst_ts = other_ts,
+               .mark = this->mark_out,
+               .interface = this->config->get_interface(this->config),
+       };
+       kernel_ipsec_manage_policy_t out_policy = {
+               .type = type,
+               .prio = priority,
+               .manual_prio = manual_prio,
+               .src = my_addr,
+               .dst = other_addr,
+               .sa = other_sa,
+       };
+
+       charon->kernel->del_policy(charon->kernel, &out_id, &out_policy);
+
+       if (this->mode != MODE_TRANSPORT && this->policies_fwd_out)
+       {
+               out_id.dir = POLICY_FWD;
+               other_sa->reqid = 0;
+               if (priority == POLICY_PRIORITY_DEFAULT)
                {
-                       out_id.dir = POLICY_FWD;
-                       other_sa->reqid = 0;
-                       if (priority == POLICY_PRIORITY_DEFAULT)
-                       {
-                               out_policy.prio = POLICY_PRIORITY_ROUTED;
-                       }
-                       charon->kernel->del_policy(charon->kernel, &out_id, &out_policy);
-                       other_sa->reqid = this->reqid;
+                       out_policy.prio = POLICY_PRIORITY_ROUTED;
                }
+               charon->kernel->del_policy(charon->kernel, &out_id, &out_policy);
+               other_sa->reqid = this->reqid;
        }
 }
 
-METHOD(child_sa_t, add_policies, status_t,
+/**
+ * Delete in- and outbound policies
+ */
+static void del_policies_internal(private_child_sa_t *this,
+       host_t *my_addr, host_t *other_addr, traffic_selector_t *my_ts,
+       traffic_selector_t *other_ts, ipsec_sa_cfg_t *my_sa,
+       ipsec_sa_cfg_t *other_sa, policy_type_t type,
+       policy_priority_t priority, uint32_t manual_prio)
+{
+       del_policies_outbound(this, my_addr, other_addr, my_ts, other_ts, my_sa,
+                                                other_sa, type, priority, manual_prio);
+       del_policies_inbound(this, my_addr, other_addr, my_ts, other_ts, my_sa,
+                                                other_sa, type, priority, manual_prio);
+}
+
+METHOD(child_sa_t, set_policies, void,
           private_child_sa_t *this, linked_list_t *my_ts_list,
           linked_list_t *other_ts_list)
 {
        enumerator_t *enumerator;
        traffic_selector_t *my_ts, *other_ts;
-       status_t status = SUCCESS;
 
-       if (!this->reqid_allocated && !this->static_reqid)
+       if (array_count(this->my_ts))
        {
-               /* trap policy, get or confirm reqid */
-               status = charon->kernel->alloc_reqid(
-                                                       charon->kernel, my_ts_list, other_ts_list,
-                                                       this->mark_in, this->mark_out, &this->reqid);
-               if (status != SUCCESS)
-               {
-                       return status;
-               }
-               this->reqid_allocated = TRUE;
+               array_destroy_offset(this->my_ts,
+                                                        offsetof(traffic_selector_t, destroy));
+               this->my_ts = array_create(0, 0);
        }
-
-       /* apply traffic selectors */
        enumerator = my_ts_list->create_enumerator(my_ts_list);
        while (enumerator->enumerate(enumerator, &my_ts))
        {
@@ -1045,6 +1156,12 @@ METHOD(child_sa_t, add_policies, status_t,
        enumerator->destroy(enumerator);
        array_sort(this->my_ts, (void*)traffic_selector_cmp, NULL);
 
+       if (array_count(this->other_ts))
+       {
+               array_destroy_offset(this->other_ts,
+                                                        offsetof(traffic_selector_t, destroy));
+               this->other_ts = array_create(0, 0);
+       }
        enumerator = other_ts_list->create_enumerator(other_ts_list);
        while (enumerator->enumerate(enumerator, &other_ts))
        {
@@ -1052,12 +1169,40 @@ METHOD(child_sa_t, add_policies, status_t,
        }
        enumerator->destroy(enumerator);
        array_sort(this->other_ts, (void*)traffic_selector_cmp, NULL);
+}
+
+METHOD(child_sa_t, install_policies, status_t,
+          private_child_sa_t *this)
+{
+       enumerator_t *enumerator;
+       linked_list_t *my_ts_list, *other_ts_list;
+       traffic_selector_t *my_ts, *other_ts;
+       status_t status = SUCCESS;
+
+       if (!this->reqid_allocated && !this->static_reqid)
+       {
+               my_ts_list = linked_list_create_from_enumerator(
+                                                                       array_create_enumerator(this->my_ts));
+               other_ts_list = linked_list_create_from_enumerator(
+                                                                       array_create_enumerator(this->other_ts));
+               status = charon->kernel->alloc_reqid(
+                                                       charon->kernel, my_ts_list, other_ts_list,
+                                                       this->mark_in, this->mark_out, &this->reqid);
+               my_ts_list->destroy(my_ts_list);
+               other_ts_list->destroy(other_ts_list);
+               if (status != SUCCESS)
+               {
+                       return status;
+               }
+               this->reqid_allocated = TRUE;
+       }
 
        if (!this->config->has_option(this->config, OPT_NO_POLICIES))
        {
                policy_priority_t priority;
                ipsec_sa_cfg_t my_sa, other_sa;
                uint32_t manual_prio;
+               bool install_outbound;
 
                prepare_sa_cfg(this, &my_sa, &other_sa);
                manual_prio = this->config->get_manual_prio(this->config);
@@ -1067,6 +1212,7 @@ METHOD(child_sa_t, add_policies, status_t,
                this->trap = this->state == CHILD_CREATED;
                priority = this->trap ? POLICY_PRIORITY_ROUTED
                                                          : POLICY_PRIORITY_DEFAULT;
+               install_outbound = this->outbound_state != CHILD_OUTBOUND_REGISTERED;
 
                /* enumerate pairs of traffic selectors */
                enumerator = create_policy_enumerator(this);
@@ -1075,20 +1221,27 @@ METHOD(child_sa_t, add_policies, status_t,
                        /* install outbound drop policy to avoid packets leaving unencrypted
                         * when updating policies */
                        if (priority == POLICY_PRIORITY_DEFAULT && manual_prio == 0 &&
-                               require_policy_update())
+                               require_policy_update() && install_outbound)
                        {
-                               status |= install_policies_internal(this, this->my_addr,
+                               status |= install_policies_outbound(this, this->my_addr,
                                                                        this->other_addr, my_ts, other_ts,
                                                                        &my_sa, &other_sa, POLICY_DROP,
                                                                        POLICY_PRIORITY_FALLBACK, 0);
                        }
 
-                       /* install policies */
-                       status |= install_policies_internal(this, this->my_addr,
+                       status |= install_policies_inbound(this, this->my_addr,
+                                                                       this->other_addr, my_ts, other_ts,
+                                                                       &my_sa, &other_sa, POLICY_IPSEC,
+                                                                       priority, manual_prio);
+
+                       if (install_outbound)
+                       {
+                               status |= install_policies_outbound(this, this->my_addr,
                                                                        this->other_addr, my_ts, other_ts,
                                                                        &my_sa, &other_sa, POLICY_IPSEC,
                                                                        priority, manual_prio);
 
+                       }
                        if (status != SUCCESS)
                        {
                                break;
@@ -1104,6 +1257,143 @@ METHOD(child_sa_t, add_policies, status_t,
        return status;
 }
 
+METHOD(child_sa_t, register_outbound, void,
+       private_child_sa_t *this, chunk_t encr, chunk_t integ, uint32_t spi,
+       uint16_t cpi, bool tfcv3)
+{
+       DBG2(DBG_CHD, "registering outbound %N SA", protocol_id_names,
+                this->protocol);
+       DBG2(DBG_CHD, "  SPI 0x%.8x, src %H dst %H", ntohl(spi), this->my_addr,
+                this->other_addr);
+
+       this->other_spi = spi;
+       this->other_cpi = cpi;
+       this->encr_r = chunk_clone(encr);
+       this->integ_r = chunk_clone(integ);
+       this->tfcv3 = tfcv3;
+       this->outbound_state = CHILD_OUTBOUND_REGISTERED;
+}
+
+METHOD(child_sa_t, install_outbound, status_t,
+       private_child_sa_t *this)
+{
+       enumerator_t *enumerator;
+       traffic_selector_t *my_ts, *other_ts;
+       status_t status;
+
+       status = install_internal(this, this->encr_r, this->integ_r,
+                                                         this->other_spi, this->other_cpi, FALSE, FALSE,
+                                                         this->tfcv3);
+       chunk_clear(&this->encr_r);
+       chunk_clear(&this->integ_r);
+       if (status != SUCCESS)
+       {
+               return status;
+       }
+       if (!this->config->has_option(this->config, OPT_NO_POLICIES))
+       {
+               ipsec_sa_cfg_t my_sa, other_sa;
+               uint32_t manual_prio;
+
+               prepare_sa_cfg(this, &my_sa, &other_sa);
+               manual_prio = this->config->get_manual_prio(this->config);
+
+               enumerator = create_policy_enumerator(this);
+               while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
+               {
+                       /* install outbound drop policy to avoid packets leaving unencrypted
+                        * when updating policies */
+                       if (manual_prio == 0 && require_policy_update())
+                       {
+                               status |= install_policies_outbound(this, this->my_addr,
+                                                                       this->other_addr, my_ts, other_ts,
+                                                                       &my_sa, &other_sa, POLICY_DROP,
+                                                                       POLICY_PRIORITY_FALLBACK, 0);
+                       }
+                       status |= install_policies_outbound(this, this->my_addr,
+                                                                       this->other_addr, my_ts, other_ts,
+                                                                       &my_sa, &other_sa, POLICY_IPSEC,
+                                                                       POLICY_PRIORITY_DEFAULT, manual_prio);
+                       if (status != SUCCESS)
+                       {
+                               break;
+                       }
+               }
+               enumerator->destroy(enumerator);
+       }
+       return status;
+}
+
+METHOD(child_sa_t, remove_outbound, void,
+       private_child_sa_t *this)
+{
+       enumerator_t *enumerator;
+       traffic_selector_t *my_ts, *other_ts;
+
+       switch (this->outbound_state)
+       {
+               case CHILD_OUTBOUND_INSTALLED:
+                       break;
+               case CHILD_OUTBOUND_REGISTERED:
+                       chunk_clear(&this->encr_r);
+                       chunk_clear(&this->integ_r);
+                       this->outbound_state = CHILD_OUTBOUND_NONE;
+                       /* fall-through */
+               case CHILD_OUTBOUND_NONE:
+                       return;
+       }
+
+       if (!this->config->has_option(this->config, OPT_NO_POLICIES))
+       {
+               ipsec_sa_cfg_t my_sa, other_sa;
+               uint32_t manual_prio;
+
+               prepare_sa_cfg(this, &my_sa, &other_sa);
+               manual_prio = this->config->get_manual_prio(this->config);
+
+               enumerator = create_policy_enumerator(this);
+               while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
+               {
+                       del_policies_outbound(this, this->my_addr, this->other_addr,
+                                                                 my_ts, other_ts, &my_sa, &other_sa,
+                                                                 POLICY_IPSEC, POLICY_PRIORITY_DEFAULT,
+                                                                 manual_prio);
+                       if (manual_prio == 0 && require_policy_update())
+                       {
+                               del_policies_outbound(this, this->my_addr, this->other_addr,
+                                                                         my_ts, other_ts, &my_sa, &other_sa,
+                                                                         POLICY_DROP, POLICY_PRIORITY_FALLBACK, 0);
+                       }
+               }
+               enumerator->destroy(enumerator);
+       }
+
+       kernel_ipsec_sa_id_t id = {
+               .src = this->my_addr,
+               .dst = this->other_addr,
+               .spi = this->other_spi,
+               .proto = proto_ike2ip(this->protocol),
+               .mark = this->mark_out,
+       };
+       kernel_ipsec_del_sa_t sa = {
+               .cpi = this->other_cpi,
+       };
+       charon->kernel->del_sa(charon->kernel, &id, &sa);
+       this->outbound_state = CHILD_OUTBOUND_NONE;
+}
+
+METHOD(child_sa_t, set_rekey_spi, void,
+       private_child_sa_t *this, uint32_t spi)
+{
+       this->rekey_spi = spi;
+}
+
+METHOD(child_sa_t, get_rekey_spi, uint32_t,
+       private_child_sa_t *this)
+{
+       return this->rekey_spi;
+}
+
 /**
  * Callback to reinstall a virtual IP
  */
@@ -1242,12 +1532,12 @@ METHOD(child_sa_t, update, status_t,
                                /* update fallback policies after the new policy is in place */
                                if (manual_prio == 0)
                                {
-                                       del_policies_internal(this, this->my_addr, this->other_addr,
+                                       del_policies_outbound(this, this->my_addr, this->other_addr,
                                                                                  old_my_ts ?: my_ts,
                                                                                  old_other_ts ?: other_ts,
                                                                                  &my_sa, &other_sa, POLICY_DROP,
                                                                                  POLICY_PRIORITY_FALLBACK, 0);
-                                       install_policies_internal(this, me, other, my_ts, other_ts,
+                                       install_policies_outbound(this, me, other, my_ts, other_ts,
                                                                                  &my_sa, &other_sa, POLICY_DROP,
                                                                                  POLICY_PRIORITY_FALLBACK, 0);
                                }
@@ -1294,21 +1584,31 @@ METHOD(child_sa_t, destroy, void,
        {
                ipsec_sa_cfg_t my_sa, other_sa;
                uint32_t manual_prio;
+               bool del_outbound;
 
                prepare_sa_cfg(this, &my_sa, &other_sa);
                manual_prio = this->config->get_manual_prio(this->config);
+               del_outbound = this->trap ||
+                                          this->outbound_state == CHILD_OUTBOUND_INSTALLED;
 
                /* delete all policies in the kernel */
                enumerator = create_policy_enumerator(this);
                while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
                {
-                       del_policies_internal(this, this->my_addr, this->other_addr,
-                                                                 my_ts, other_ts, &my_sa, &other_sa,
-                                                                 POLICY_IPSEC, priority, manual_prio);
-                       if (priority == POLICY_PRIORITY_DEFAULT && manual_prio == 0 &&
-                               require_policy_update())
+                       if (del_outbound)
                        {
-                               del_policies_internal(this, this->my_addr, this->other_addr,
+                               del_policies_outbound(this, this->my_addr,
+                                                                         this->other_addr, my_ts, other_ts,
+                                                                         &my_sa, &other_sa, POLICY_IPSEC,
+                                                                         priority, manual_prio);
+                       }
+                       del_policies_inbound(this, this->my_addr, this->other_addr,
+                                                                my_ts, other_ts, &my_sa, &other_sa,
+                                                                POLICY_IPSEC, priority, manual_prio);
+                       if (!this->trap && manual_prio == 0 && require_policy_update() &&
+                               del_outbound)
+                       {
+                               del_policies_outbound(this, this->my_addr, this->other_addr,
                                                                          my_ts, other_ts, &my_sa, &other_sa,
                                                                          POLICY_DROP, POLICY_PRIORITY_FALLBACK, 0);
                        }
@@ -1330,7 +1630,7 @@ METHOD(child_sa_t, destroy, void,
                };
                charon->kernel->del_sa(charon->kernel, &id, &sa);
        }
-       if (this->other_spi)
+       if (this->other_spi && this->outbound_state == CHILD_OUTBOUND_INSTALLED)
        {
                kernel_ipsec_sa_id_t id = {
                        .src = this->my_addr,
@@ -1360,6 +1660,8 @@ METHOD(child_sa_t, destroy, void,
        this->other_addr->destroy(this->other_addr);
        DESTROY_IF(this->proposal);
        this->config->destroy(this->config);
+       chunk_clear(&this->encr_r);
+       chunk_clear(&this->integ_r);
        free(this);
 }
 
@@ -1417,6 +1719,7 @@ child_sa_t * child_sa_create(host_t *me, host_t* other,
                        .get_config = _get_config,
                        .get_state = _get_state,
                        .set_state = _set_state,
+                       .get_outbound_state = _get_outbound_state,
                        .get_spi = _get_spi,
                        .get_cpi = _get_cpi,
                        .get_protocol = _get_protocol,
@@ -1439,8 +1742,14 @@ child_sa_t * child_sa_create(host_t *me, host_t* other,
                        .alloc_spi = _alloc_spi,
                        .alloc_cpi = _alloc_cpi,
                        .install = _install,
+                       .register_outbound = _register_outbound,
+                       .install_outbound = _install_outbound,
+                       .remove_outbound = _remove_outbound,
+                       .set_rekey_spi = _set_rekey_spi,
+                       .get_rekey_spi = _get_rekey_spi,
                        .update = _update,
-                       .add_policies = _add_policies,
+                       .set_policies = _set_policies,
+                       .install_policies = _install_policies,
                        .create_ts_enumerator = _create_ts_enumerator,
                        .create_policy_enumerator = _create_policy_enumerator,
                        .destroy = _destroy,
index bc7df99..b9a913d 100644 (file)
@@ -1,8 +1,8 @@
 /*
- * Copyright (C) 2006-2008 Tobias Brunner
+ * Copyright (C) 2006-2017 Tobias Brunner
  * Copyright (C) 2006-2008 Martin Willi
  * Copyright (C) 2006 Daniel Roethlisberger
- * 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
@@ -24,6 +24,7 @@
 #define CHILD_SA_H_
 
 typedef enum child_sa_state_t child_sa_state_t;
+typedef enum child_sa_outbound_state_t child_sa_outbound_state_t;
 typedef struct child_sa_t child_sa_t;
 
 #include <library.h>
@@ -53,7 +54,7 @@ enum child_sa_state_t {
        CHILD_INSTALLING,
 
        /**
-        * Installed an in-use CHILD_SA
+        * Installed both SAs of a CHILD_SA
         */
        CHILD_INSTALLED,
 
@@ -94,6 +95,32 @@ enum child_sa_state_t {
 extern enum_name_t *child_sa_state_names;
 
 /**
+ * States of the outbound SA of a CHILD_SA
+ */
+enum child_sa_outbound_state_t {
+
+       /**
+        * Outbound SA is not installed
+        */
+       CHILD_OUTBOUND_NONE,
+
+       /**
+        * Data for the outbound SA has been registered, but not installed yet
+        */
+       CHILD_OUTBOUND_REGISTERED,
+
+       /**
+        * The outbound SA is currently installed
+        */
+       CHILD_OUTBOUND_INSTALLED,
+};
+
+/**
+ * enum strings for child_sa_outbound_state_t.
+ */
+extern enum_name_t *child_sa_outbound_state_names;
+
+/**
  * Represents an IPsec SAs between two hosts.
  *
  * A child_sa_t contains two SAs. SAs for both
@@ -152,7 +179,14 @@ struct child_sa_t {
         *
         * @return                      CHILD_SA state
         */
-       child_sa_state_t (*get_state) (child_sa_t *this);
+       child_sa_state_t (*get_state)(child_sa_t *this);
+
+       /**
+        * Get the state of the outbound SA.
+        *
+        * @return                      outbound SA state
+        */
+       child_sa_outbound_state_t (*get_outbound_state)(child_sa_t *this);
 
        /**
         * Set the state of the CHILD_SA.
@@ -347,6 +381,8 @@ struct child_sa_t {
        /**
         * Install an IPsec SA for one direction.
         *
+        * set_policies() should be called before calling this.
+        *
         * @param encr          encryption key, if any
         * @param integ         integrity key
         * @param spi           SPI to use, allocated for inbound
@@ -354,26 +390,84 @@ struct child_sa_t {
         * @param initiator     TRUE if initiator of exchange resulting in this SA
         * @param inbound       TRUE to install an inbound SA, FALSE for outbound
         * @param tfcv3         TRUE if peer supports ESPv3 TFC
-        * @param my_ts         negotiated local traffic selector list
-        * @param other_ts      negotiated remote traffic selector list
         * @return                      SUCCESS or FAILED
         */
        status_t (*install)(child_sa_t *this, chunk_t encr, chunk_t integ,
                                                uint32_t spi, uint16_t cpi,
-                                               bool initiator, bool inbound, bool tfcv3,
-                                               linked_list_t *my_ts, linked_list_t *other_ts);
+                                               bool initiator, bool inbound, bool tfcv3);
+
+       /**
+        * Register data for the installation of an outbound SA as responder during
+        * a rekeying.
+        *
+        * The SA is not installed until install_outbound() is called.
+        *
+        * @param encr          encryption key, if any (cloned)
+        * @param integ         integrity key (cloned)
+        * @param spi           SPI to use, allocated for inbound
+        * @param cpi           CPI to use, allocated for outbound
+        * @param tfcv3         TRUE if peer supports ESPv3 TFC
+        */
+       void (*register_outbound)(child_sa_t *this, chunk_t encr, chunk_t integ,
+                                                         uint32_t spi, uint16_t cpi, bool tfcv3);
+
+       /**
+        * Install the outbound SA and the outbound policies as responder during a
+        * rekeying.
+        *
+        * @return                      SUCCESS or FAILED
+        */
+       status_t (*install_outbound)(child_sa_t *this);
+
+       /**
+        * Remove the outbound SA and the outbound policies after a rekeying.
+        */
+       void (*remove_outbound)(child_sa_t *this);
+
        /**
-        * Install the policies using some traffic selectors.
+        * Configure the policies using some traffic selectors.
         *
         * Supplied lists of traffic_selector_t's specify the policies
         * to use for this child sa.
         *
-        * @param my_ts         traffic selectors for local site
-        * @param other_ts      traffic selectors for remote site
+        * Install the policies by calling install_policies().
+        *
+        * This should be called before calling install() so the traffic selectors
+        * may be passed to the kernel interface when installing the SAs.
+        *
+        * @param my_ts         traffic selectors for local site (cloned)
+        * @param other_ts      traffic selectors for remote site (cloned)
+        */
+       void (*set_policies)(child_sa_t *this, linked_list_t *my_ts_list,
+                                                linked_list_t *other_ts_list);
+
+       /**
+        * Install the configured policies.
+        *
+        * If register_outbound() was called previously this only installs the
+        * inbound and forward policies, the outbound policies are installed when
+        * install_outbound() is called.
+        *
         * @return                      SUCCESS or FAILED
         */
-       status_t (*add_policies)(child_sa_t *this, linked_list_t *my_ts_list,
-                                                        linked_list_t *other_ts_list);
+       status_t (*install_policies)(child_sa_t *this);
+
+       /**
+        * Set the outbound SPI of the CHILD_SA that replaced this CHILD_SA during
+        * a rekeying.
+        *
+        * @param spi           outbound SPI of the CHILD_SA that replaced this CHILD_SA
+        */
+       void (*set_rekey_spi)(child_sa_t *this, uint32_t spi);
+
+       /**
+        * Get the outbound SPI of the CHILD_SA that replaced this CHILD_SA during
+        * a rekeying.
+        *
+        * @return                      outbound SPI of the CHILD_SA that replaced this CHILD_SA
+        */
+       uint32_t (*get_rekey_spi)(child_sa_t *this);
+
        /**
         * Update hosts and ecapulation mode in the kernel SAs and policies.
         *
index d65db28..8be82eb 100644 (file)
@@ -325,6 +325,17 @@ static bool install(private_quick_mode_t *this)
                return FALSE;
        }
 
+       if (this->initiator)
+       {
+               this->child_sa->set_policies(this->child_sa, tsi, tsr);
+       }
+       else
+       {
+               this->child_sa->set_policies(this->child_sa, tsr, tsi);
+       }
+       tsi->destroy_offset(tsi, offsetof(traffic_selector_t, destroy));
+       tsr->destroy_offset(tsr, offsetof(traffic_selector_t, destroy));
+
        if (this->keymat->derive_child_keys(this->keymat, this->proposal, this->dh,
                                                this->spi_i, this->spi_r, this->nonce_i, this->nonce_r,
                                                &encr_i, &integ_i, &encr_r, &integ_r))
@@ -333,19 +344,19 @@ static bool install(private_quick_mode_t *this)
                {
                        status_i = this->child_sa->install(this->child_sa,
                                                                        encr_r, integ_r, this->spi_i, this->cpi_i,
-                                                                       this->initiator, TRUE, FALSE, tsi, tsr);
+                                                                       this->initiator, TRUE, FALSE);
                        status_o = this->child_sa->install(this->child_sa,
                                                                        encr_i, integ_i, this->spi_r, this->cpi_r,
-                                                                       this->initiator, FALSE, FALSE, tsi, tsr);
+                                                                       this->initiator, FALSE, FALSE);
                }
                else
                {
                        status_i = this->child_sa->install(this->child_sa,
                                                                        encr_i, integ_i, this->spi_r, this->cpi_r,
-                                                                       this->initiator, TRUE, FALSE, tsr, tsi);
+                                                                       this->initiator, TRUE, FALSE);
                        status_o = this->child_sa->install(this->child_sa,
                                                                        encr_r, integ_r, this->spi_i, this->cpi_i,
-                                                                       this->initiator, FALSE, FALSE, tsr, tsi);
+                                                                       this->initiator, FALSE, FALSE);
                }
        }
 
@@ -355,22 +366,12 @@ static bool install(private_quick_mode_t *this)
                        (status_i != SUCCESS) ? "inbound " : "",
                        (status_i != SUCCESS && status_o != SUCCESS) ? "and ": "",
                        (status_o != SUCCESS) ? "outbound " : "");
-               tsi->destroy_offset(tsi, offsetof(traffic_selector_t, destroy));
-               tsr->destroy_offset(tsr, offsetof(traffic_selector_t, destroy));
                status = FAILED;
        }
        else
        {
-               if (this->initiator)
-               {
-                       status = this->child_sa->add_policies(this->child_sa, tsi, tsr);
-               }
-               else
-               {
-                       status = this->child_sa->add_policies(this->child_sa, tsr, tsi);
-               }
-               tsi->destroy_offset(tsi, offsetof(traffic_selector_t, destroy));
-               tsr->destroy_offset(tsr, offsetof(traffic_selector_t, destroy));
+               status = this->child_sa->install_policies(this->child_sa);
+
                if (status != SUCCESS)
                {
                        DBG1(DBG_IKE, "unable to install IPsec policies (SPD) in kernel");
index 7180bfd..896cabb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2016 Tobias Brunner
+ * Copyright (C) 2008-2017 Tobias Brunner
  * Copyright (C) 2005-2008 Martin Willi
  * Copyright (C) 2005 Jan Hutter
  * HSR Hochschule fuer Technik Rapperswil
@@ -630,6 +630,32 @@ static status_t select_and_install(private_child_create_t *this,
                        default:
                                break;
                }
+               /* use a copy of the traffic selectors, as the POST hook should not
+                * change payloads */
+               my_ts = this->tsr->clone_offset(this->tsr,
+                                                                               offsetof(traffic_selector_t, clone));
+               other_ts = this->tsi->clone_offset(this->tsi,
+                                                                               offsetof(traffic_selector_t, clone));
+               charon->bus->narrow(charon->bus, this->child_sa,
+                                                       NARROW_RESPONDER_POST, my_ts, other_ts);
+
+               if (my_ts->get_count(my_ts) == 0 ||     other_ts->get_count(other_ts) == 0)
+               {
+                       my_ts->destroy_offset(my_ts,
+                                                                 offsetof(traffic_selector_t, destroy));
+                       other_ts->destroy_offset(other_ts,
+                                                                 offsetof(traffic_selector_t, destroy));
+                       return NOT_FOUND;
+               }
+       }
+
+       this->child_sa->set_policies(this->child_sa, my_ts, other_ts);
+       if (!this->initiator)
+       {
+               my_ts->destroy_offset(my_ts,
+                                                         offsetof(traffic_selector_t, destroy));
+               other_ts->destroy_offset(other_ts,
+                                                         offsetof(traffic_selector_t, destroy));
        }
 
        this->child_sa->set_state(this->child_sa, CHILD_INSTALLING);
@@ -651,19 +677,30 @@ static status_t select_and_install(private_child_create_t *this,
                {
                        status_i = this->child_sa->install(this->child_sa, encr_r, integ_r,
                                                        this->my_spi, this->my_cpi, this->initiator,
-                                                       TRUE, this->tfcv3, my_ts, other_ts);
+                                                       TRUE, this->tfcv3);
                        status_o = this->child_sa->install(this->child_sa, encr_i, integ_i,
                                                        this->other_spi, this->other_cpi, this->initiator,
-                                                       FALSE, this->tfcv3, my_ts, other_ts);
+                                                       FALSE, this->tfcv3);
                }
-               else
+               else if (!this->rekey)
                {
                        status_i = this->child_sa->install(this->child_sa, encr_i, integ_i,
                                                        this->my_spi, this->my_cpi, this->initiator,
-                                                       TRUE, this->tfcv3, my_ts, other_ts);
+                                                       TRUE, this->tfcv3);
                        status_o = this->child_sa->install(this->child_sa, encr_r, integ_r,
                                                        this->other_spi, this->other_cpi, this->initiator,
-                                                       FALSE, this->tfcv3, my_ts, other_ts);
+                                                       FALSE, this->tfcv3);
+               }
+               else
+               {       /* as responder during a rekeying we only install the inbound
+                        * SA now, the outbound SA and policies are installed when we
+                        * receive the delete for the old SA */
+                       status_i = this->child_sa->install(this->child_sa, encr_i, integ_i,
+                                                       this->my_spi, this->my_cpi, this->initiator,
+                                                       TRUE, this->tfcv3);
+                       this->child_sa->register_outbound(this->child_sa, encr_r, integ_r,
+                                                       this->other_spi, this->other_cpi, this->tfcv3);
+                       status_o = SUCCESS;
                }
        }
 
@@ -679,36 +716,8 @@ static status_t select_and_install(private_child_create_t *this,
        }
        else
        {
-               if (this->initiator)
-               {
-                       status = this->child_sa->add_policies(this->child_sa,
-                                                                                                 my_ts, other_ts);
-               }
-               else
-               {
-                       /* use a copy of the traffic selectors, as the POST hook should not
-                        * change payloads */
-                       my_ts = this->tsr->clone_offset(this->tsr,
-                                                                               offsetof(traffic_selector_t, clone));
-                       other_ts = this->tsi->clone_offset(this->tsi,
-                                                                               offsetof(traffic_selector_t, clone));
-                       charon->bus->narrow(charon->bus, this->child_sa,
-                                                               NARROW_RESPONDER_POST, my_ts, other_ts);
-                       if (my_ts->get_count(my_ts) == 0 ||
-                               other_ts->get_count(other_ts) == 0)
-                       {
-                               status = FAILED;
-                       }
-                       else
-                       {
-                               status = this->child_sa->add_policies(this->child_sa,
-                                                                                                         my_ts, other_ts);
-                       }
-                       my_ts->destroy_offset(my_ts,
-                                                                 offsetof(traffic_selector_t, destroy));
-                       other_ts->destroy_offset(other_ts,
-                                                                 offsetof(traffic_selector_t, destroy));
-               }
+               status = this->child_sa->install_policies(this->child_sa);
+
                if (status != SUCCESS)
                {
                        DBG1(DBG_IKE, "unable to install IPsec policies (SPD) in kernel");
@@ -736,7 +745,6 @@ static status_t select_and_install(private_child_create_t *this,
        charon->bus->child_keys(charon->bus, this->child_sa, this->initiator,
                                                        this->dh, nonce_i, nonce_r);
 
-       /* add to IKE_SA, and remove from task */
        this->child_sa->set_state(this->child_sa, CHILD_INSTALLED);
        this->ike_sa->add_child_sa(this->ike_sa, this->child_sa);
        this->established = TRUE;
@@ -748,16 +756,17 @@ static status_t select_and_install(private_child_create_t *this,
        other_ts = linked_list_create_from_enumerator(
                                this->child_sa->create_ts_enumerator(this->child_sa, FALSE));
 
-       DBG0(DBG_IKE, "CHILD_SA %s{%d} established "
+       DBG0(DBG_IKE, "%sCHILD_SA %s{%d} established "
                 "with SPIs %.8x_i %.8x_o and TS %#R === %#R",
+                this->rekey && !this->initiator ? "inbound " : "",
                 this->child_sa->get_name(this->child_sa),
                 this->child_sa->get_unique_id(this->child_sa),
                 ntohl(this->child_sa->get_spi(this->child_sa, TRUE)),
-                ntohl(this->child_sa->get_spi(this->child_sa, FALSE)), my_ts, other_ts);
+                ntohl(this->child_sa->get_spi(this->child_sa, FALSE)),
+                my_ts, other_ts);
 
        my_ts->destroy(my_ts);
        other_ts->destroy(other_ts);
-
        return SUCCESS;
 }
 
@@ -1690,7 +1699,6 @@ METHOD(task_t, destroy, void,
        {
                this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy));
        }
-
        DESTROY_IF(this->config);
        DESTROY_IF(this->nonceg);
        free(this);
index 6fa8836..1608656 100644 (file)
 
 #include <daemon.h>
 #include <encoding/payloads/delete_payload.h>
+#include <processing/jobs/delete_child_sa_job.h>
 #include <sa/ikev2/tasks/child_create.h>
 #include <sa/ikev2/tasks/child_rekey.h>
 
+#ifndef DELETE_REKEYED_DELAY
+#define DELETE_REKEYED_DELAY 5
+#endif
+
 typedef struct private_child_delete_t private_child_delete_t;
 
 /**
@@ -39,67 +44,79 @@ struct private_child_delete_t {
        ike_sa_t *ike_sa;
 
        /**
-        * Are we the initiator?
+        * Whether we are the initiator of the exchange
         */
        bool initiator;
 
        /**
-        * Protocol of CHILD_SA to delete
+        * Protocol of CHILD_SA to delete (as initiator)
         */
        protocol_id_t protocol;
 
        /**
-        * Inbound SPI of CHILD_SA to delete
+        * Inbound SPI of CHILD_SA to delete (as initiator)
         */
        uint32_t spi;
 
        /**
-        * whether to enforce delete action policy
-        */
-       bool check_delete_action;
-
-       /**
-        * is this delete exchange following a rekey?
-        */
-       bool rekeyed;
-
-       /**
-        * CHILD_SA already expired?
+        * CHILD_SA already expired (as initiator)
         */
        bool expired;
 
        /**
-        * CHILD_SAs which get deleted
+        * CHILD_SAs which get deleted, entry_t*
         */
        linked_list_t *child_sas;
 };
 
 /**
+ * Information about a deleted CHILD_SA
+ */
+typedef struct {
+       /** Deleted CHILD_SA */
+       child_sa_t *child_sa;
+       /** Whether the CHILD_SA was rekeyed */
+       bool rekeyed;
+       /** Whether to enforce any delete action policy */
+       bool check_delete_action;
+} entry_t;
+
+/**
+ * Check if the given entry is for the same CHILD_SA
+ */
+static bool match_child(entry_t *entry, child_sa_t *child_sa)
+{
+       return entry->child_sa == child_sa;
+}
+
+/**
  * build the delete payloads from the listed child_sas
  */
 static void build_payloads(private_child_delete_t *this, message_t *message)
 {
        delete_payload_t *ah = NULL, *esp = NULL;
        enumerator_t *enumerator;
-       child_sa_t *child_sa;
+       entry_t *entry;
+       protocol_id_t protocol;
+       uint32_t spi;
 
        enumerator = this->child_sas->create_enumerator(this->child_sas);
-       while (enumerator->enumerate(enumerator, (void**)&child_sa))
+       while (enumerator->enumerate(enumerator, (void**)&entry))
        {
-               protocol_id_t protocol = child_sa->get_protocol(child_sa);
-               uint32_t spi = child_sa->get_spi(child_sa, TRUE);
+               protocol = entry->child_sa->get_protocol(entry->child_sa);
+               spi = entry->child_sa->get_spi(entry->child_sa, TRUE);
 
                switch (protocol)
                {
                        case PROTO_ESP:
-                               if (esp == NULL)
+                               if (!esp)
                                {
                                        esp = delete_payload_create(PLV2_DELETE, PROTO_ESP);
                                        message->add_payload(message, (payload_t*)esp);
                                }
                                esp->add_spi(esp, spi);
                                DBG1(DBG_IKE, "sending DELETE for %N CHILD_SA with SPI %.8x",
-                                                          protocol_id_names, protocol, ntohl(spi));
+                                        protocol_id_names, protocol, ntohl(spi));
                                break;
                        case PROTO_AH:
                                if (ah == NULL)
@@ -109,12 +126,12 @@ static void build_payloads(private_child_delete_t *this, message_t *message)
                                }
                                ah->add_spi(ah, spi);
                                DBG1(DBG_IKE, "sending DELETE for %N CHILD_SA with SPI %.8x",
-                                                          protocol_id_names, protocol, ntohl(spi));
+                                        protocol_id_names, protocol, ntohl(spi));
                                break;
                        default:
                                break;
                }
-               child_sa->set_state(child_sa, CHILD_DELETING);
+               entry->child_sa->set_state(entry->child_sa, CHILD_DELETING);
        }
        enumerator->destroy(enumerator);
 }
@@ -147,6 +164,57 @@ static bool is_redundant(private_child_delete_t *this, child_sa_t *child)
 }
 
 /**
+ * Install the outbound CHILD_SA with the given SPI
+ */
+static void install_outbound(private_child_delete_t *this,
+                                                        protocol_id_t protocol, uint32_t spi)
+{
+       child_sa_t *child_sa;
+       linked_list_t *my_ts, *other_ts;
+       status_t status;
+
+       child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol,
+                                                                                 spi, FALSE);
+       if (!child_sa)
+       {
+               DBG1(DBG_IKE, "CHILD_SA not found after rekeying");
+               return;
+       }
+       if (this->initiator && is_redundant(this, child_sa))
+       {       /* if we won the rekey collision we don't want to install the
+                * redundant SA created by the peer */
+               return;
+       }
+
+       status = child_sa->install_outbound(child_sa);
+       if (status != SUCCESS)
+       {
+               DBG1(DBG_IKE, "unable to install outbound IPsec SA (SAD) in kernel");
+               charon->bus->alert(charon->bus, ALERT_INSTALL_CHILD_SA_FAILED,
+                                                  child_sa);
+               /* FIXME: delete the new child_sa? */
+               return;
+       }
+       child_sa->set_state(child_sa, CHILD_INSTALLED);
+
+       my_ts = linked_list_create_from_enumerator(
+                                                       child_sa->create_ts_enumerator(child_sa, TRUE));
+       other_ts = linked_list_create_from_enumerator(
+                                                       child_sa->create_ts_enumerator(child_sa, FALSE));
+
+       DBG0(DBG_IKE, "outbound CHILD_SA %s{%d} established "
+                "with SPIs %.8x_i %.8x_o and TS %#R === %#R",
+                child_sa->get_name(child_sa),
+                child_sa->get_unique_id(child_sa),
+                ntohl(child_sa->get_spi(child_sa, TRUE)),
+                ntohl(child_sa->get_spi(child_sa, FALSE)),
+                my_ts, other_ts);
+
+       my_ts->destroy(my_ts);
+       other_ts->destroy(other_ts);
+}
+
+/**
  * read in payloads and find the children to delete
  */
 static void process_payloads(private_child_delete_t *this, message_t *message)
@@ -157,6 +225,7 @@ static void process_payloads(private_child_delete_t *this, message_t *message)
        uint32_t spi;
        protocol_id_t protocol;
        child_sa_t *child_sa;
+       entry_t *entry;
 
        payloads = message->create_payload_enumerator(message);
        while (payloads->enumerate(payloads, &payload))
@@ -174,27 +243,37 @@ static void process_payloads(private_child_delete_t *this, message_t *message)
                        {
                                child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol,
                                                                                                          spi, FALSE);
-                               if (child_sa == NULL)
+                               if (!child_sa)
                                {
-                                       DBG1(DBG_IKE, "received DELETE for %N CHILD_SA with SPI %.8x, "
-                                                "but no such SA", protocol_id_names, protocol, ntohl(spi));
+                                       DBG1(DBG_IKE, "received DELETE for unknown %N CHILD_SA with"
+                                                " SPI %.8x", protocol_id_names, protocol, ntohl(spi));
                                        continue;
                                }
                                DBG1(DBG_IKE, "received DELETE for %N CHILD_SA with SPI %.8x",
                                         protocol_id_names, protocol, ntohl(spi));
 
+                               if (this->child_sas->find_first(this->child_sas,
+                                                               (void*)match_child, NULL, child_sa) == SUCCESS)
+                               {
+                                       continue;
+                               }
+                               INIT(entry,
+                                       .child_sa = child_sa
+                               );
                                switch (child_sa->get_state(child_sa))
                                {
                                        case CHILD_REKEYED:
-                                               this->rekeyed = TRUE;
+                                               entry->rekeyed = TRUE;
                                                break;
                                        case CHILD_DELETING:
-                                               /* we don't send back a delete if we initiated ourself */
+                                               /* we don't send back a delete if we already initiated
+                                                * a delete ourself */
                                                if (!this->initiator)
                                                {
+                                                       free(entry);
                                                        continue;
                                                }
-                                               /* fall through */
+                                               break;
                                        case CHILD_REKEYING:
                                                /* we reply as usual, rekeying will fail */
                                        case CHILD_INSTALLED:
@@ -202,22 +281,18 @@ static void process_payloads(private_child_delete_t *this, message_t *message)
                                                {
                                                        if (is_redundant(this, child_sa))
                                                        {
-                                                               this->rekeyed = TRUE;
+                                                               entry->rekeyed = TRUE;
                                                        }
                                                        else
                                                        {
-                                                               this->check_delete_action = TRUE;
+                                                               entry->check_delete_action = TRUE;
                                                        }
                                                }
                                                break;
                                        default:
                                                break;
                                }
-                               if (this->child_sas->find_first(this->child_sas, NULL,
-                                                                                               (void**)&child_sa) != SUCCESS)
-                               {
-                                       this->child_sas->insert_last(this->child_sas, child_sa);
-                               }
+                               this->child_sas->insert_last(this->child_sas, entry);
                        }
                        spis->destroy(spis);
                }
@@ -231,29 +306,64 @@ static void process_payloads(private_child_delete_t *this, message_t *message)
 static status_t destroy_and_reestablish(private_child_delete_t *this)
 {
        enumerator_t *enumerator;
+       entry_t *entry;
        child_sa_t *child_sa;
        child_cfg_t *child_cfg;
        protocol_id_t protocol;
-       uint32_t spi, reqid;
+       uint32_t spi, reqid, rekey_spi;
        action_t action;
        status_t status = SUCCESS;
+       time_t now, expire;
+       u_int delay;
+
+       now = time_monotonic(NULL);
+       delay = lib->settings->get_int(lib->settings, "%s.delete_rekeyed_delay",
+                                                                  DELETE_REKEYED_DELAY, lib->ns);
 
        enumerator = this->child_sas->create_enumerator(this->child_sas);
-       while (enumerator->enumerate(enumerator, (void**)&child_sa))
+       while (enumerator->enumerate(enumerator, (void**)&entry))
        {
+               child_sa = entry->child_sa;
                /* signal child down event if we weren't rekeying */
-               if (!this->rekeyed)
+               protocol = child_sa->get_protocol(child_sa);
+               if (!entry->rekeyed)
                {
                        charon->bus->child_updown(charon->bus, child_sa, FALSE);
                }
+               else
+               {
+                       rekey_spi = child_sa->get_rekey_spi(child_sa);
+                       if (rekey_spi)
+                       {
+                               install_outbound(this, protocol, rekey_spi);
+                       }
+                       /* for rekeyed CHILD_SAs we uninstall the outbound SA but don't
+                        * immediately destroy it, by default, so we can process delayed
+                        * packets */
+                       child_sa->remove_outbound(child_sa);
+                       expire = child_sa->get_lifetime(child_sa, TRUE);
+                       if (delay && (!expire || ((now + delay) < expire)))
+                       {
+                               lib->scheduler->schedule_job(lib->scheduler,
+                                       (job_t*)delete_child_sa_job_create_id(
+                                                                       child_sa->get_unique_id(child_sa)), delay);
+                               continue;
+                       }
+                       else if (expire)
+                       {       /* let it expire naturally */
+                               continue;
+                       }
+                       /* no delay and no lifetime, destroy it immediately */
+               }
                spi = child_sa->get_spi(child_sa, TRUE);
                reqid = child_sa->get_reqid(child_sa);
-               protocol = child_sa->get_protocol(child_sa);
                child_cfg = child_sa->get_config(child_sa);
                child_cfg->get_ref(child_cfg);
                action = child_sa->get_close_action(child_sa);
+
                this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi);
-               if (this->check_delete_action)
+
+               if (entry->check_delete_action)
                {       /* enforce child_cfg policy if deleted passively */
                        switch (action)
                        {
@@ -288,12 +398,14 @@ static void log_children(private_child_delete_t *this)
 {
        linked_list_t *my_ts, *other_ts;
        enumerator_t *enumerator;
+       entry_t *entry;
        child_sa_t *child_sa;
        uint64_t bytes_in, bytes_out;
 
        enumerator = this->child_sas->create_enumerator(this->child_sas);
-       while (enumerator->enumerate(enumerator, (void**)&child_sa))
+       while (enumerator->enumerate(enumerator, (void**)&entry))
        {
+               child_sa = entry->child_sa;
                my_ts = linked_list_create_from_enumerator(
                                                        child_sa->create_ts_enumerator(child_sa, TRUE));
                other_ts = linked_list_create_from_enumerator(
@@ -328,6 +440,7 @@ METHOD(task_t, build_i, status_t,
        private_child_delete_t *this, message_t *message)
 {
        child_sa_t *child_sa;
+       entry_t *entry;
 
        child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol,
                                                                                  this->spi, TRUE);
@@ -342,15 +455,24 @@ METHOD(task_t, build_i, status_t,
                /* we work only with the inbound SPI */
                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_REKEYED)
-       {
-               this->rekeyed = TRUE;
+
+       if (child_sa->get_state(child_sa) == CHILD_DELETING)
+       {       /* DELETEs for this CHILD_SA were already exchanged, but it was not yet
+                * destroyed to allow delayed packets to get processed */
+               this->ike_sa->destroy_child_sa(this->ike_sa, this->protocol, this->spi);
+               message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
+               return SUCCESS;
        }
+
+       INIT(entry,
+               .child_sa = child_sa,
+               .rekeyed = child_sa->get_state(child_sa) == CHILD_REKEYED,
+       );
+       this->child_sas->insert_last(this->child_sas, entry);
        log_children(this);
        build_payloads(this, message);
 
-       if (!this->rekeyed && this->expired)
+       if (!entry->rekeyed && this->expired)
        {
                child_cfg_t *child_cfg;
 
@@ -397,24 +519,28 @@ METHOD(child_delete_t , get_child, child_sa_t*,
        private_child_delete_t *this)
 {
        child_sa_t *child_sa = NULL;
-       this->child_sas->get_first(this->child_sas, (void**)&child_sa);
+       entry_t *entry;
+
+       if (this->child_sas->get_first(this->child_sas, (void**)&entry) == SUCCESS)
+       {
+               child_sa = entry->child_sa;
+       }
        return child_sa;
 }
 
 METHOD(task_t, migrate, void,
        private_child_delete_t *this, ike_sa_t *ike_sa)
 {
-       this->check_delete_action = FALSE;
        this->ike_sa = ike_sa;
 
-       this->child_sas->destroy(this->child_sas);
+       this->child_sas->destroy_function(this->child_sas, free);
        this->child_sas = linked_list_create();
 }
 
 METHOD(task_t, destroy, void,
        private_child_delete_t *this)
 {
-       this->child_sas->destroy(this->child_sas);
+       this->child_sas->destroy_function(this->child_sas, free);
        free(this);
 }
 
index c04ec14..761c860 100644 (file)
@@ -132,6 +132,7 @@ static void find_child(private_child_rekey_t *this, message_t *message)
        notify_payload_t *notify;
        protocol_id_t protocol;
        uint32_t spi;
+       child_sa_t *child_sa;
 
        notify = message->get_notify(message, REKEY_SA);
        if (notify)
@@ -141,8 +142,15 @@ static void find_child(private_child_rekey_t *this, message_t *message)
 
                if (protocol == PROTO_ESP || protocol == PROTO_AH)
                {
-                       this->child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol,
-                                                                                                               spi, FALSE);
+                       child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol,
+                                                                                                 spi, FALSE);
+                       if (child_sa &&
+                               child_sa->get_state(child_sa) == CHILD_DELETING &&
+                               child_sa->get_outbound_state(child_sa) == CHILD_OUTBOUND_NONE)
+                       {       /* ignore rekeyed CHILD_SAs we keep around */
+                               return;
+                       }
+                       this->child_sa = child_sa;
                }
        }
 }
@@ -227,6 +235,7 @@ METHOD(task_t, build_r, status_t,
        child_cfg_t *config;
        uint32_t reqid;
        child_sa_state_t state;
+       child_sa_t *child_sa;
 
        if (!this->child_sa)
        {
@@ -260,7 +269,10 @@ METHOD(task_t, build_r, status_t,
                return SUCCESS;
        }
 
+       child_sa = this->child_create->get_child(this->child_create);
        this->child_sa->set_state(this->child_sa, CHILD_REKEYED);
+       this->child_sa->set_rekey_spi(this->child_sa,
+                                                                 child_sa->get_spi(child_sa, FALSE));
 
        /* invoke rekey hook */
        charon->bus->child_rekey(charon->bus, this->child_sa,
index 40a0682..51df7a0 100644 (file)
@@ -272,7 +272,8 @@ METHOD(trap_manager_t, install, uint32_t,
        proposals->destroy_offset(proposals, offsetof(proposal_t, destroy));
        child_sa->set_protocol(child_sa, proto);
        child_sa->set_mode(child_sa, child->get_mode(child));
-       status = child_sa->add_policies(child_sa, my_ts, other_ts);
+       child_sa->set_policies(child_sa, my_ts, other_ts);
+       status = child_sa->install_policies(child_sa);
        my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy));
        other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy));
        if (status != SUCCESS)
index fcac493..76b23f5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Tobias Brunner
+ * Copyright (C) 2016-2017 Tobias Brunner
  * HSR Hochschule fuer Technik Rapperswil
  *
  * This program is free software; you can redistribute it and/or modify it
        assert_hook_not_called(child_updown); \
        assert_hook_not_called(child_rekey); \
        call_ikesa(sa, rekey_child_sa, PROTO_ESP, spi); \
-       assert_child_sa_state(sa, spi, CHILD_REKEYING); \
+       assert_child_sa_state(sa, spi, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED); \
+       assert_hook(); \
+       assert_hook(); \
+})
+
+/**
+ * Destroy a rekeyed CHILD_SA that was kept around to accept inbound traffic.
+ * Simulates the job that's scheduled to do this.
+ */
+#define destroy_rekeyed(sa, spi) ({ \
+       assert_hook_not_called(child_updown); \
+       assert_hook_not_called(child_rekey); \
+       assert_no_jobs_scheduled(); \
+       assert_child_sa_state(sa, spi, CHILD_DELETING, CHILD_OUTBOUND_NONE); \
+       call_ikesa(sa, delete_child_sa, PROTO_ESP, spi, FALSE); \
+       assert_child_sa_not_exists(sa, spi); \
+       assert_scheduler(); \
        assert_hook(); \
        assert_hook(); \
 })
@@ -53,6 +69,7 @@ START_TEST(test_regular)
                                                                                   &a, &b, NULL);
        }
        initiate_rekey(a, spi_a);
+       assert_ipsec_sas_installed(a, spi_a, spi_b);
 
        /* this should never get called as this results in a successful rekeying */
        assert_hook_not_called(child_updown);
@@ -61,33 +78,51 @@ START_TEST(test_regular)
        assert_hook_called(child_rekey);
        assert_notify(IN, REKEY_SA);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, spi_b, CHILD_REKEYED);
-       assert_child_sa_state(b, 4, CHILD_INSTALLED);
+       assert_child_sa_state(b, spi_b, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+       assert_ipsec_sas_installed(b, spi_a, spi_b, 4);
        assert_hook();
 
        /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
        assert_hook_called(child_rekey);
        assert_no_notify(IN, REKEY_SA);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, spi_a, CHILD_DELETING);
-       assert_child_sa_state(a, 3, CHILD_INSTALLED);
+       assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_ipsec_sas_installed(a, spi_a, spi_b, 3, 4);
        assert_hook();
 
        /* INFORMATIONAL { D } --> */
        assert_hook_not_called(child_rekey);
+       assert_jobs_scheduled(1);
        assert_single_payload(IN, PLV2_DELETE);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 4, CHILD_INSTALLED);
-       assert_child_sa_count(b, 1);
+       assert_child_sa_state(b, spi_b, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(b, 2);
+       assert_ipsec_sas_installed(b, spi_b, 3, 4);
+       assert_scheduler();
        assert_hook();
        /* <-- INFORMATIONAL { D } */
        assert_hook_not_called(child_rekey);
+       assert_jobs_scheduled(1);
        assert_single_payload(IN, PLV2_DELETE);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 3, CHILD_INSTALLED);
-       assert_child_sa_count(a, 1);
+       assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(a, 2);
+       assert_ipsec_sas_installed(a, spi_a, 3, 4);
+       assert_scheduler();
        assert_hook();
 
+       /* simulate the execution of the scheduled jobs */
+       destroy_rekeyed(a, spi_a);
+       assert_child_sa_count(a, 1);
+       assert_ipsec_sas_installed(a, 3, 4);
+       destroy_rekeyed(b, spi_b);
+       assert_child_sa_count(b, 1);
+       assert_ipsec_sas_installed(a, 3, 4);
+
        /* child_updown */
        assert_hook();
 
@@ -125,6 +160,7 @@ START_TEST(test_regular_ke_invalid)
                                                                                   &a, &b, &conf);
        }
        initiate_rekey(a, spi_a);
+       assert_ipsec_sas_installed(a, spi_a, spi_b);
 
        /* this should never get called as this results in a successful rekeying */
        assert_hook_not_called(child_updown);
@@ -135,6 +171,7 @@ START_TEST(test_regular_ke_invalid)
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
        assert_child_sa_state(b, spi_b, CHILD_INSTALLED);
        assert_child_sa_count(b, 1);
+       assert_ipsec_sas_installed(b, spi_a, spi_b);
        assert_hook();
 
        /* <-- CREATE_CHILD_SA { N(INVAL_KE) } */
@@ -143,6 +180,7 @@ START_TEST(test_regular_ke_invalid)
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
        assert_child_sa_state(a, spi_a, CHILD_REKEYING);
        assert_child_sa_count(a, 1);
+       assert_ipsec_sas_installed(a, spi_a, spi_b);
        assert_hook();
 
        /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
@@ -150,7 +188,8 @@ START_TEST(test_regular_ke_invalid)
        assert_notify(IN, REKEY_SA);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
        assert_child_sa_state(b, spi_b, CHILD_REKEYED);
-       assert_child_sa_state(b, 6, CHILD_INSTALLED);
+       assert_child_sa_state(b, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+       assert_ipsec_sas_installed(b, spi_a, spi_b, 6);
        assert_hook();
 
        /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
@@ -158,24 +197,37 @@ START_TEST(test_regular_ke_invalid)
        assert_no_notify(IN, REKEY_SA);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
        assert_child_sa_state(a, spi_a, CHILD_DELETING);
-       assert_child_sa_state(a, 5, CHILD_INSTALLED);
+       assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_ipsec_sas_installed(a, spi_a, spi_b, 5, 6);
        assert_hook();
 
        /* INFORMATIONAL { D } --> */
        assert_hook_not_called(child_rekey);
        assert_single_payload(IN, PLV2_DELETE);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 6, CHILD_INSTALLED);
-       assert_child_sa_count(b, 1);
+       assert_child_sa_state(b, spi_b, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, 6, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(b, 2);
+       assert_ipsec_sas_installed(b, spi_b, 5, 6);
        assert_hook();
        /* <-- INFORMATIONAL { D } */
        assert_hook_not_called(child_rekey);
        assert_single_payload(IN, PLV2_DELETE);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+       assert_child_sa_state(a, spi_a, CHILD_DELETING, CHILD_OUTBOUND_NONE);
        assert_child_sa_state(a, 5, CHILD_INSTALLED);
-       assert_child_sa_count(a, 1);
+       assert_child_sa_count(a, 2);
+       assert_ipsec_sas_installed(a, spi_a, 5, 6);
        assert_hook();
 
+       /* simulate the execution of the scheduled jobs */
+       destroy_rekeyed(a, spi_a);
+       assert_child_sa_count(a, 1);
+       assert_ipsec_sas_installed(a, 5, 6);
+       destroy_rekeyed(b, spi_b);
+       assert_child_sa_count(b, 1);
+       assert_ipsec_sas_installed(b, 5, 6);
+
        /* child_updown */
        assert_hook();
 
@@ -195,6 +247,7 @@ START_TEST(test_regular_responder_ignore_soft_expire)
        exchange_test_helper->establish_sa(exchange_test_helper,
                                                                           &a, &b, NULL);
        initiate_rekey(a, 1);
+       assert_ipsec_sas_installed(a, 1, 2);
 
        /* this should never get called as this results in a successful rekeying */
        assert_hook_not_called(child_updown);
@@ -204,7 +257,8 @@ START_TEST(test_regular_responder_ignore_soft_expire)
        assert_notify(IN, REKEY_SA);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
        assert_child_sa_state(b, 2, CHILD_REKEYED);
-       assert_child_sa_state(b, 4, CHILD_INSTALLED);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+       assert_ipsec_sas_installed(b, 1, 2, 4);
        assert_hook();
 
        /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
@@ -212,7 +266,8 @@ START_TEST(test_regular_responder_ignore_soft_expire)
        assert_no_notify(IN, REKEY_SA);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
        assert_child_sa_state(a, 1, CHILD_DELETING);
-       assert_child_sa_state(a, 3, CHILD_INSTALLED);
+       assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_ipsec_sas_installed(a, 1, 2, 3, 4);
        assert_hook();
 
        /* we don't expect this to get called anymore */
@@ -223,15 +278,31 @@ START_TEST(test_regular_responder_ignore_soft_expire)
        assert_child_sa_state(b, 2, CHILD_REKEYED);
 
        /* INFORMATIONAL { D } --> */
+       assert_jobs_scheduled(1);
        assert_single_payload(IN, PLV2_DELETE);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 4, CHILD_INSTALLED);
-       assert_child_sa_count(b, 1);
+       assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(b, 2);
+       assert_ipsec_sas_installed(b, 2, 3, 4);
+       assert_scheduler();
        /* <-- INFORMATIONAL { D } */
+       assert_jobs_scheduled(1);
        assert_single_payload(IN, PLV2_DELETE);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+       assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
        assert_child_sa_state(a, 3, CHILD_INSTALLED);
+       assert_child_sa_count(a, 2);
+       assert_ipsec_sas_installed(a, 1, 3, 4);
+       assert_scheduler();
+
+       /* simulate the execution of the scheduled jobs */
+       destroy_rekeyed(a, 1);
        assert_child_sa_count(a, 1);
+       assert_ipsec_sas_installed(a, 3, 4);
+       destroy_rekeyed(b, 2);
+       assert_child_sa_count(b, 1);
+       assert_ipsec_sas_installed(b, 3, 4);
 
        /* child_rekey/child_updown */
        assert_hook();
@@ -254,6 +325,7 @@ START_TEST(test_regular_responder_handle_hard_expire)
        exchange_test_helper->establish_sa(exchange_test_helper,
                                                                           &a, &b, NULL);
        initiate_rekey(a, 1);
+       assert_ipsec_sas_installed(a, 1, 2);
 
        /* this should never get called as this results in a successful rekeying */
        assert_hook_not_called(child_updown);
@@ -263,7 +335,8 @@ START_TEST(test_regular_responder_handle_hard_expire)
        assert_notify(IN, REKEY_SA);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
        assert_child_sa_state(b, 2, CHILD_REKEYED);
-       assert_child_sa_state(b, 4, CHILD_INSTALLED);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+       assert_ipsec_sas_installed(b, 1, 2, 4);
        assert_hook();
 
        /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
@@ -271,7 +344,8 @@ START_TEST(test_regular_responder_handle_hard_expire)
        assert_no_notify(IN, REKEY_SA);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
        assert_child_sa_state(a, 1, CHILD_DELETING);
-       assert_child_sa_state(a, 3, CHILD_INSTALLED);
+       assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_ipsec_sas_installed(a, 1, 2, 3, 4);
        assert_hook();
 
        /* we don't expect this to get called anymore */
@@ -279,28 +353,51 @@ START_TEST(test_regular_responder_handle_hard_expire)
        /* this is similar to a regular delete collision */
        assert_single_payload(OUT, PLV2_DELETE);
        call_ikesa(b, delete_child_sa, PROTO_ESP, 2, TRUE);
-       assert_child_sa_state(b, 2, CHILD_DELETING);
+       assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+       /* since the SAs expired they would not actually be installed in the kernel
+        * anymore and since we have not yet installed a new outbound SA this
+        * will result in dropped packets and possibly acquires */
+       assert_ipsec_sas_installed(b, 1, 2, 4);
 
        /* INFORMATIONAL { D } --> */
        assert_single_payload(IN, PLV2_DELETE);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 4, CHILD_INSTALLED);
-       assert_child_sa_state(a, 2, CHILD_DELETING);
+       assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+       assert_ipsec_sas_installed(b, 1, 2, 4);
        /* <-- INFORMATIONAL { D } */
        assert_single_payload(IN, PLV2_DELETE);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 3, CHILD_INSTALLED);
-       assert_child_sa_state(a, 1, CHILD_DELETING);
+       assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_ipsec_sas_installed(a, 1, 2, 3, 4);
        /* <-- INFORMATIONAL { } */
+       assert_jobs_scheduled(1);
        assert_message_empty(IN);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 3, CHILD_INSTALLED);
-       assert_child_sa_count(a, 1);
+       assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(a, 2);
+       assert_ipsec_sas_installed(a, 1, 3, 4);
+       assert_scheduler();
        /* INFORMATIONAL { } --> */
+       assert_jobs_scheduled(1);
        assert_message_empty(IN);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 4, CHILD_INSTALLED);
+       assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(b, 2);
+       assert_ipsec_sas_installed(b, 2, 3, 4);
+       assert_scheduler();
+
+       /* simulate the execution of the scheduled jobs */
+       destroy_rekeyed(a, 1);
+       assert_child_sa_count(a, 1);
+       assert_ipsec_sas_installed(a, 3, 4);
+       destroy_rekeyed(b, 2);
        assert_child_sa_count(b, 1);
+       assert_ipsec_sas_installed(b, 3, 4);
 
        /* child_rekey/child_updown */
        assert_hook();
@@ -350,8 +447,10 @@ START_TEST(test_collision)
 
        exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
        initiate_rekey(a, 1);
+       assert_ipsec_sas_installed(a, 1, 2);
        exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
        initiate_rekey(b, 2);
+       assert_ipsec_sas_installed(b, 1, 2);
 
        /* this should never get called as this results in a successful rekeying */
        assert_hook_not_called(child_updown);
@@ -360,15 +459,17 @@ START_TEST(test_collision)
        exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
        assert_hook_rekey(child_rekey, 2, 5);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 2, CHILD_REKEYED);
-       assert_child_sa_state(b, 5, CHILD_INSTALLED);
+       assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+       assert_ipsec_sas_installed(b, 1, 2, 5);
        assert_hook();
        /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
        exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
        assert_hook_rekey(child_rekey, 1, 6);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 1, CHILD_REKEYED);
-       assert_child_sa_state(a, 6, CHILD_INSTALLED);
+       assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+       assert_ipsec_sas_installed(a, 1, 2, 6);
        assert_hook();
 
        /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
@@ -378,53 +479,113 @@ START_TEST(test_collision)
                assert_hook_rekey(child_rekey, 1, data[_i].spi_a);
                exchange_test_helper->process_message(exchange_test_helper, a, NULL);
                assert_hook();
+               assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED,
+                                                         CHILD_OUTBOUND_REGISTERED);
+               assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+                                                         CHILD_OUTBOUND_INSTALLED);
        }
        else
        {
                assert_hook_not_called(child_rekey);
                exchange_test_helper->process_message(exchange_test_helper, a, NULL);
                assert_hook();
+               assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED,
+                                                         CHILD_OUTBOUND_INSTALLED);
+               assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+                                                         CHILD_OUTBOUND_REGISTERED);
        }
-       assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING);
-       assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED);
-       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
+       assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_ipsec_sas_installed(a, 1, 2, 3, 5, 6);
        /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
        if (data[_i].spi_del_b == 2)
        {
                assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
                exchange_test_helper->process_message(exchange_test_helper, b, NULL);
                assert_hook();
+               assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+                                                         CHILD_OUTBOUND_REGISTERED);
+               assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+                                                         CHILD_OUTBOUND_INSTALLED);
        }
        else
        {
                assert_hook_not_called(child_rekey);
                exchange_test_helper->process_message(exchange_test_helper, b, NULL);
                assert_hook();
+               assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+                                                         CHILD_OUTBOUND_INSTALLED);
+               assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+                                                         CHILD_OUTBOUND_REGISTERED);
        }
-       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING);
-       assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED);
-       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_ipsec_sas_installed(b, 1, 2, 4, 5, 6);
 
        /* we don't expect this hook to get called anymore */
        assert_hook_not_called(child_rekey);
        /* INFORMATIONAL { D } --> */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING);
-       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
-       assert_child_sa_count(b, 2);
+       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(b, data[_i].spi_del_a, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(b, 3);
+       assert_ipsec_sas_installed(b, 2, 4, 5, 6,
+                                                          data[_i].spi_del_b == 2 ? 1 : 3);
+       assert_scheduler();
        /* <-- INFORMATIONAL { D } */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING);
-       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
-       assert_child_sa_count(a, 2);
+       assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(a, 3);
+       assert_ipsec_sas_installed(a, 1, 3, 5, 6,
+                                                          data[_i].spi_del_a == 1 ? 2 : 4);
+       assert_scheduler();
        /* <-- INFORMATIONAL { D } */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
-       assert_child_sa_count(a, 1);
+       assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(a, 3);
+       assert_ipsec_sas_installed(a, 1, 3, 6,
+                                                          data[_i].spi_del_a == 1 ? 5 : 4);
+       assert_scheduler();
        /* INFORMATIONAL { D } --> */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, data[_i].spi_del_a, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(b, 3);
+       assert_ipsec_sas_installed(b, 2, 4, 5,
+                                                          data[_i].spi_del_b == 2 ? 6 : 3);
+       assert_scheduler();
+
+       /* simulate the execution of the scheduled jobs */
+       destroy_rekeyed(a, data[_i].spi_del_a);
+       destroy_rekeyed(a, data[_i].spi_del_b);
+       assert_child_sa_count(a, 1);
+       assert_ipsec_sas_installed(a, data[_i].spi_a, data[_i].spi_b);
+       destroy_rekeyed(b, data[_i].spi_del_a);
+       destroy_rekeyed(b, data[_i].spi_del_b);
        assert_child_sa_count(b, 1);
+       assert_ipsec_sas_installed(b, data[_i].spi_a, data[_i].spi_b);
 
        /* child_rekey/child_updown */
        assert_hook();
@@ -483,8 +644,10 @@ START_TEST(test_collision_delayed_response)
 
        exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
        initiate_rekey(a, 1);
+       assert_ipsec_sas_installed(a, 1, 2);
        exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
        initiate_rekey(b, 2);
+       assert_ipsec_sas_installed(b, 1, 2);
 
        /* this should never get called as this results in a successful rekeying */
        assert_hook_not_called(child_updown);
@@ -493,15 +656,17 @@ START_TEST(test_collision_delayed_response)
        exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
        assert_hook_rekey(child_rekey, 2, 5);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 2, CHILD_REKEYED);
-       assert_child_sa_state(b, 5, CHILD_INSTALLED);
+       assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(b, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+       assert_ipsec_sas_installed(b, 1, 2, 5);
        assert_hook();
        /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
        exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
        assert_hook_rekey(child_rekey, 1, 6);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 1, CHILD_REKEYED);
-       assert_child_sa_state(a, 6, CHILD_INSTALLED);
+       assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(a, 6, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+       assert_ipsec_sas_installed(a, 1, 2, 6);
        assert_hook();
 
        /* delay the CREATE_CHILD_SA response from b to a */
@@ -513,35 +678,68 @@ START_TEST(test_collision_delayed_response)
                assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
                exchange_test_helper->process_message(exchange_test_helper, b, NULL);
                assert_hook();
+               assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+                                                         CHILD_OUTBOUND_REGISTERED);
+               assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+                                                         CHILD_OUTBOUND_INSTALLED);
        }
        else
        {
                assert_hook_not_called(child_rekey);
                exchange_test_helper->process_message(exchange_test_helper, b, NULL);
                assert_hook();
+               assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+                                                         CHILD_OUTBOUND_INSTALLED);
+               assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+                                                         CHILD_OUTBOUND_REGISTERED);
        }
-       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING);
-       assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED);
-       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_ipsec_sas_installed(b, 1, 2, 4, 5, 6);
 
        /* <-- INFORMATIONAL { D } */
        assert_hook_not_called(child_rekey);
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
        if (data[_i].spi_del_b == 2)
        {
-               assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
-               assert_child_sa_count(a, 1);
+               assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+               assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+                                                         CHILD_OUTBOUND_INSTALLED);
+               assert_ipsec_sas_installed(a, 1, 4, 6);
        }
        else
        {
-               assert_child_sa_state(a, 1, CHILD_REKEYED);
-               assert_child_sa_count(a, 1);
+               assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+               assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETING,
+                                                         CHILD_OUTBOUND_NONE);
+               assert_ipsec_sas_installed(a, 1, 2, 6);
        }
+       assert_child_sa_count(a, 2);
+       assert_scheduler();
        /* INFORMATIONAL { D } --> */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED);
-       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
-       assert_child_sa_count(b, 2);
+       if (data[_i].spi_del_b == 2)
+       {
+               assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+                                                         CHILD_OUTBOUND_REGISTERED);
+               assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+                                                         CHILD_OUTBOUND_INSTALLED);
+               assert_ipsec_sas_installed(b, 2, 4, 5, 6);
+       }
+       else
+       {
+               assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+                                                         CHILD_OUTBOUND_INSTALLED);
+               assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+                                                         CHILD_OUTBOUND_REGISTERED);
+               assert_ipsec_sas_installed(b, 1, 2, 4, 5);
+       }
+       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_count(b, 3);
+       assert_scheduler();
        assert_hook();
 
        /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } (delayed) */
@@ -557,20 +755,54 @@ START_TEST(test_collision_delayed_response)
                exchange_test_helper->process_message(exchange_test_helper, a, msg);
                assert_hook();
        }
-       assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING);
-       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
-       assert_child_sa_count(a, 2);
+       assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_ipsec_sas_installed(a, 1, 3, 5, 6,
+                                                          data[_i].spi_del_a == 1 ? 2 : 4);
+       assert_child_sa_count(a, 3);
 
        /* we don't expect this hook to get called anymore */
        assert_hook_not_called(child_rekey);
        /* INFORMATIONAL { D } --> */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
-       assert_child_sa_count(b, 1);
+       assert_child_sa_state(b, data[_i].spi_del_a, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_ipsec_sas_installed(b, 2, 4, 5,
+                                                          data[_i].spi_del_b == 2 ? 6 : 3);
+       assert_child_sa_count(b, 3);
+       assert_scheduler();
        /* <-- INFORMATIONAL { D } */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
+       assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(a, 3);
+       assert_ipsec_sas_installed(a, 1, 3, 6,
+                                                          data[_i].spi_del_a == 1 ? 5 : 4);
+       assert_scheduler();
+
+       /* simulate the execution of the scheduled jobs */
+       destroy_rekeyed(a, data[_i].spi_del_a);
+       destroy_rekeyed(a, data[_i].spi_del_b);
        assert_child_sa_count(a, 1);
+       assert_ipsec_sas_installed(a, data[_i].spi_a, data[_i].spi_b);
+       destroy_rekeyed(b, data[_i].spi_del_a);
+       destroy_rekeyed(b, data[_i].spi_del_b);
+       assert_child_sa_count(b, 1);
+       assert_ipsec_sas_installed(b, data[_i].spi_a, data[_i].spi_b);
 
        /* child_rekey/child_updown */
        assert_hook();
@@ -621,8 +853,10 @@ START_TEST(test_collision_delayed_request)
 
        exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
        initiate_rekey(a, 1);
+       assert_ipsec_sas_installed(a, 1, 2);
        exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
        initiate_rekey(b, 2);
+       assert_ipsec_sas_installed(b, 1, 2);
 
        /* delay the CREATE_CHILD_SA request from a to b */
        msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
@@ -634,14 +868,16 @@ START_TEST(test_collision_delayed_request)
        exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
        assert_hook_rekey(child_rekey, 1, 5);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 1, CHILD_REKEYED);
-       assert_child_sa_state(a, 5, CHILD_INSTALLED);
+       assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+       assert_ipsec_sas_installed(a, 1, 2, 5);
        assert_hook();
        /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
        assert_hook_rekey(child_rekey, 2, 4);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 2, CHILD_DELETING);
-       assert_child_sa_state(b, 4, CHILD_INSTALLED);
+       assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_ipsec_sas_installed(b, 1, 2, 4, 5);
        assert_hook();
 
        /* we don't expect this hook to get called anymore */
@@ -650,25 +886,43 @@ START_TEST(test_collision_delayed_request)
        /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> (delayed) */
        assert_single_notify(OUT, TEMPORARY_FAILURE);
        exchange_test_helper->process_message(exchange_test_helper, b, msg);
-       assert_child_sa_state(b, 2, CHILD_DELETING);
-       assert_child_sa_state(b, 4, CHILD_INSTALLED);
+       assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
 
        /* <-- INFORMATIONAL { D } */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 5, CHILD_INSTALLED);
-       assert_child_sa_count(a, 1);
+       assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(a, 2);
+       assert_ipsec_sas_installed(a, 1, 4, 5);
+       assert_scheduler();
 
        /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
        assert_no_jobs_scheduled();
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 5, CHILD_INSTALLED);
-       assert_child_sa_count(a, 1);
+       assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(a, 2);
+       assert_ipsec_sas_installed(a, 1, 4, 5);
        assert_scheduler();
 
        /* INFORMATIONAL { D } --> */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 4, CHILD_INSTALLED);
+       assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(b, 2);
+       assert_ipsec_sas_installed(b, 2, 4, 5);
+       assert_scheduler();
+
+       /* simulate the execution of the scheduled jobs */
+       destroy_rekeyed(a, 1);
+       assert_child_sa_count(a, 1);
+       assert_ipsec_sas_installed(a, 4, 5);
+       destroy_rekeyed(b, 2);
        assert_child_sa_count(b, 1);
+       assert_ipsec_sas_installed(b, 4, 5);
 
        /* child_rekey/child_updown */
        assert_hook();
@@ -722,8 +976,10 @@ START_TEST(test_collision_delayed_request_more)
 
        exchange_test_helper->nonce_first_byte = data[_i].nonces[0];
        initiate_rekey(a, 1);
+       assert_ipsec_sas_installed(a, 1, 2);
        exchange_test_helper->nonce_first_byte = data[_i].nonces[1];
        initiate_rekey(b, 2);
+       assert_ipsec_sas_installed(b, 1, 2);
 
        /* delay the CREATE_CHILD_SA request from a to b */
        msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
@@ -735,40 +991,62 @@ START_TEST(test_collision_delayed_request_more)
        exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
        assert_hook_rekey(child_rekey, 1, 5);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 1, CHILD_REKEYED);
-       assert_child_sa_state(a, 5, CHILD_INSTALLED);
+       assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
+       assert_ipsec_sas_installed(a, 1, 2, 5);
        assert_hook();
        /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
        assert_hook_rekey(child_rekey, 2, 4);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 2, CHILD_DELETING);
-       assert_child_sa_state(b, 4, CHILD_INSTALLED);
+       assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_ipsec_sas_installed(b, 1, 2, 4, 5);
        assert_hook();
 
        /* we don't expect this hook to get called anymore */
        assert_hook_not_called(child_rekey);
 
        /* <-- INFORMATIONAL { D } */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 5, CHILD_INSTALLED);
-       assert_child_sa_count(a, 1);
+       assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(a, 2);
+       assert_ipsec_sas_installed(a, 1, 4, 5);
+       assert_scheduler();
        /* INFORMATIONAL { D } --> */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 4, CHILD_INSTALLED);
-       assert_child_sa_count(b, 1);
+       assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(b, 2);
+       assert_ipsec_sas_installed(b, 2, 4, 5);
+       assert_scheduler();
 
        /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
        assert_single_notify(OUT, CHILD_SA_NOT_FOUND);
        exchange_test_helper->process_message(exchange_test_helper, b, msg);
-       assert_child_sa_state(b, 4, CHILD_INSTALLED);
-       assert_child_sa_count(b, 1);
+       assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(b, 2);
+       assert_ipsec_sas_installed(b, 2, 4, 5);
        /* <-- CREATE_CHILD_SA { N(NO_CHILD_SA) } */
        assert_no_jobs_scheduled();
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 5, CHILD_INSTALLED);
-       assert_child_sa_count(a, 1);
+       assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, 5, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(a, 2);
+       assert_ipsec_sas_installed(a, 1, 4, 5);
        assert_scheduler();
 
+       /* simulate the execution of the scheduled jobs */
+       destroy_rekeyed(a, 1);
+       assert_child_sa_count(a, 1);
+       assert_ipsec_sas_installed(a, 4, 5);
+       destroy_rekeyed(b, 2);
+       assert_child_sa_count(b, 1);
+       assert_ipsec_sas_installed(b, 4, 5);
+
        /* child_rekey/child_updown */
        assert_hook();
        assert_hook();
@@ -842,13 +1120,13 @@ START_TEST(test_collision_ke_invalid)
        /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
        assert_hook_not_called(child_rekey);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 2, CHILD_REKEYING);
+       assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
        assert_child_sa_count(b, 1);
        assert_hook();
        /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
        assert_hook_not_called(child_rekey);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 1, CHILD_REKEYING);
+       assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
        assert_child_sa_count(a, 1);
        assert_hook();
 
@@ -857,7 +1135,7 @@ START_TEST(test_collision_ke_invalid)
        assert_hook_not_called(child_rekey);
        assert_single_notify(IN, INVALID_KE_PAYLOAD);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 1, CHILD_REKEYING);
+       assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
        assert_child_sa_count(a, 1);
        assert_hook();
        /* CREATE_CHILD_SA { N(INVAL_KE) } --> */
@@ -865,7 +1143,7 @@ START_TEST(test_collision_ke_invalid)
        assert_hook_not_called(child_rekey);
        assert_single_notify(IN, INVALID_KE_PAYLOAD);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 2, CHILD_REKEYING);
+       assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
        assert_child_sa_count(b, 1);
        assert_hook();
 
@@ -873,15 +1151,15 @@ START_TEST(test_collision_ke_invalid)
        exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
        assert_hook_rekey(child_rekey, 2, 9);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 2, CHILD_REKEYED);
-       assert_child_sa_state(b, 9, CHILD_INSTALLED);
+       assert_child_sa_state(b, 2, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(b, 9, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
        assert_hook();
        /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
        exchange_test_helper->nonce_first_byte = data[_i].nonces[3];
        assert_hook_rekey(child_rekey, 1, 10);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 1, CHILD_REKEYED);
-       assert_child_sa_state(a,10, CHILD_INSTALLED);
+       assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(a,10, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
        assert_hook();
 
        /* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } */
@@ -891,49 +1169,99 @@ START_TEST(test_collision_ke_invalid)
                assert_hook_rekey(child_rekey, 1, data[_i].spi_a);
                exchange_test_helper->process_message(exchange_test_helper, a, NULL);
                assert_hook();
+               assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED,
+                                                         CHILD_OUTBOUND_REGISTERED);
+               assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+                                                         CHILD_OUTBOUND_INSTALLED);
        }
        else
        {
                exchange_test_helper->process_message(exchange_test_helper, a, NULL);
+               assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED,
+                                                         CHILD_OUTBOUND_INSTALLED);
+               assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+                                                         CHILD_OUTBOUND_REGISTERED);
        }
-       assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING);
-       assert_child_sa_state(a, data[_i].spi_del_b, CHILD_REKEYED);
-       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
+       assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_INSTALLED);
        /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
        if (data[_i].spi_del_b == 2)
        {
                assert_hook_rekey(child_rekey, 2, data[_i].spi_b);
                exchange_test_helper->process_message(exchange_test_helper, b, NULL);
                assert_hook();
+               assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+                                                         CHILD_OUTBOUND_REGISTERED);
+               assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+                                                         CHILD_OUTBOUND_INSTALLED);
        }
        else
        {
                exchange_test_helper->process_message(exchange_test_helper, b, NULL);
+               assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED,
+                                                         CHILD_OUTBOUND_INSTALLED);
+               assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+                                                         CHILD_OUTBOUND_REGISTERED);
        }
-       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING);
-       assert_child_sa_state(b, data[_i].spi_del_a, CHILD_REKEYED);
-       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_INSTALLED);
 
        /* we don't expect this hook to get called anymore */
        assert_hook_not_called(child_rekey);
        /* INFORMATIONAL { D } --> */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING);
-       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
-       assert_child_sa_count(b, 2);
+       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(b, data[_i].spi_del_a, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(b, 3);
+       assert_scheduler();
        /* <-- INFORMATIONAL { D } */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING);
-       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
-       assert_child_sa_count(a, 2);
+       assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(a, 3);
+       assert_scheduler();
        /* <-- INFORMATIONAL { D } */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED);
-       assert_child_sa_count(a, 1);
+       assert_child_sa_state(a, data[_i].spi_del_a, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, data[_i].spi_a, CHILD_INSTALLED,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(a, 3);
+       assert_scheduler();
        /* INFORMATIONAL { D } --> */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED);
+       assert_child_sa_state(b, data[_i].spi_del_b, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, data[_i].spi_del_a, CHILD_DELETING,
+                                                 CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, data[_i].spi_b, CHILD_INSTALLED,
+                                                 CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(b, 3);
+       assert_scheduler();
+
+       /* simulate the execution of the scheduled jobs */
+       destroy_rekeyed(a, data[_i].spi_del_a);
+       destroy_rekeyed(a, data[_i].spi_del_b);
+       assert_child_sa_count(a, 1);
+       assert_ipsec_sas_installed(a, data[_i].spi_a, data[_i].spi_b);
+       destroy_rekeyed(b, data[_i].spi_del_a);
+       destroy_rekeyed(b, data[_i].spi_del_b);
        assert_child_sa_count(b, 1);
+       assert_ipsec_sas_installed(b, data[_i].spi_a, data[_i].spi_b);
 
        /* child_rekey/child_updown */
        assert_hook();
@@ -1004,13 +1332,13 @@ START_TEST(test_collision_ke_invalid_delayed_retry)
        /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
        assert_hook_not_called(child_rekey);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 2, CHILD_REKEYING);
+       assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
        assert_child_sa_count(b, 1);
        assert_hook();
        /* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
        assert_hook_not_called(child_rekey);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 1, CHILD_REKEYING);
+       assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
        assert_child_sa_count(a, 1);
        assert_hook();
 
@@ -1019,7 +1347,7 @@ START_TEST(test_collision_ke_invalid_delayed_retry)
        assert_hook_not_called(child_rekey);
        assert_single_notify(IN, INVALID_KE_PAYLOAD);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 1, CHILD_REKEYING);
+       assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
        assert_child_sa_count(a, 1);
        assert_hook();
        /* CREATE_CHILD_SA { N(INVAL_KE) } --> */
@@ -1027,7 +1355,7 @@ START_TEST(test_collision_ke_invalid_delayed_retry)
        assert_hook_not_called(child_rekey);
        assert_single_notify(IN, INVALID_KE_PAYLOAD);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 2, CHILD_REKEYING);
+       assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
        assert_child_sa_count(b, 1);
        assert_hook();
 
@@ -1038,14 +1366,14 @@ START_TEST(test_collision_ke_invalid_delayed_retry)
        exchange_test_helper->nonce_first_byte = data[_i].nonces[2];
        assert_hook_rekey(child_rekey, 1, 9);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 1, CHILD_REKEYED);
-       assert_child_sa_state(a, 9, CHILD_INSTALLED);
+       assert_child_sa_state(a, 1, CHILD_REKEYED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(a, 9, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
        assert_hook();
        /* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
        assert_hook_rekey(child_rekey, 2, 8);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 2, CHILD_DELETING);
-       assert_child_sa_state(b, 8, CHILD_INSTALLED);
+       assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(b, 8, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
        assert_hook();
 
        /* we don't expect this hook to get called anymore */
@@ -1054,25 +1382,40 @@ START_TEST(test_collision_ke_invalid_delayed_retry)
        /* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> (delayed) */
        assert_single_notify(OUT, TEMPORARY_FAILURE);
        exchange_test_helper->process_message(exchange_test_helper, b, msg);
-       assert_child_sa_state(b, 2, CHILD_DELETING);
-       assert_child_sa_state(b, 8, CHILD_INSTALLED);
+       assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_state(b, 8, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
 
        /* <-- INFORMATIONAL { D } */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 9, CHILD_INSTALLED);
-       assert_child_sa_count(a, 1);
+       assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, 9, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(a, 2);
+       assert_scheduler();
 
        /* <-- CREATE_CHILD_SA { N(TEMP_FAIL) } */
        assert_no_jobs_scheduled();
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, 9, CHILD_INSTALLED);
-       assert_child_sa_count(a, 1);
+       assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(a, 9, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(a, 2);
        assert_scheduler();
 
        /* INFORMATIONAL { D } --> */
+       assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, 8, CHILD_INSTALLED);
+       assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_NONE);
+       assert_child_sa_state(b, 8, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
+       assert_child_sa_count(b, 2);
+       assert_scheduler();
+
+       /* simulate the execution of the scheduled jobs */
+       destroy_rekeyed(a, 1);
+       assert_child_sa_count(a, 1);
+       assert_ipsec_sas_installed(a, 8, 9);
+       destroy_rekeyed(b, 2);
        assert_child_sa_count(b, 1);
+       assert_ipsec_sas_installed(b, 8, 9);
 
        /* child_rekey/child_updown */
        assert_hook();
@@ -1114,7 +1457,7 @@ START_TEST(test_collision_delete)
        }
        initiate_rekey(a, spi_a);
        call_ikesa(b, delete_child_sa, PROTO_ESP, spi_b, FALSE);
-       assert_child_sa_state(b, spi_b, CHILD_DELETING);
+       assert_child_sa_state(b, spi_b, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
 
        /* this should never get called as there is no successful rekeying on
         * either side */
@@ -1129,7 +1472,7 @@ START_TEST(test_collision_delete)
        assert_notify(IN, REKEY_SA);
        assert_single_notify(OUT, TEMPORARY_FAILURE);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, spi_b, CHILD_DELETING);
+       assert_child_sa_state(b, spi_b, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
        assert_hook();
 
        /* RFC 7296, 2.25.1: If a peer receives a request to delete a CHILD_SA that
@@ -1201,7 +1544,7 @@ START_TEST(test_collision_delete_drop_delete)
        }
        initiate_rekey(a, spi_a);
        call_ikesa(b, delete_child_sa, PROTO_ESP, spi_b, FALSE);
-       assert_child_sa_state(b, spi_b, CHILD_DELETING);
+       assert_child_sa_state(b, spi_b, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
 
        /* this should never get called as there is no successful rekeying on
         * either side */
@@ -1216,7 +1559,7 @@ START_TEST(test_collision_delete_drop_delete)
        assert_notify(IN, REKEY_SA);
        assert_single_notify(OUT, TEMPORARY_FAILURE);
        exchange_test_helper->process_message(exchange_test_helper, b, NULL);
-       assert_child_sa_state(b, spi_b, CHILD_DELETING);
+       assert_child_sa_state(b, spi_b, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
        assert_hook();
 
        /* delay the DELETE request */
@@ -1227,7 +1570,7 @@ START_TEST(test_collision_delete_drop_delete)
        /* we expect a job to retry the rekeying is scheduled */
        assert_jobs_scheduled(1);
        exchange_test_helper->process_message(exchange_test_helper, a, NULL);
-       assert_child_sa_state(a, spi_a, CHILD_INSTALLED);
+       assert_child_sa_state(a, spi_a, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
        assert_scheduler();
        assert_hook();
 
@@ -1286,7 +1629,7 @@ END_TEST
        }
        initiate_rekey(a, spi_a);
        call_ikesa(b, delete_child_sa, PROTO_ESP, spi_b, FALSE);
-       assert_child_sa_state(b, spi_b, CHILD_DELETING);
+       assert_child_sa_state(b, spi_b, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
 
        /* this should never get called as there is no successful rekeying on
         * either side */
@@ -1419,13 +1762,13 @@ START_TEST(test_collision_ike_rekey)
        /* <-- 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_REKEYING);
+       assert_child_sa_state(a, spi_a, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
 
        /* <-- 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, a, NULL);
-       assert_child_sa_state(a, spi_a, CHILD_INSTALLED);
+       assert_child_sa_state(a, spi_a, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
        assert_scheduler();
 
        /* CREATE_CHILD_SA { N(TEMP_FAIL) } --> */
index 2602b97..8042d0b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Tobias Brunner
+ * Copyright (C) 2016-2017 Tobias Brunner
  * HSR Hochschule fuer Technik Rapperswil
  *
  * This program is free software; you can redistribute it and/or modify it
@@ -18,6 +18,7 @@
 #include <test_suite.h>
 
 #include "exchange_test_asserts.h"
+#include "mock_ipsec.h"
 
 /*
  * Described in header
@@ -180,3 +181,57 @@ bool exchange_test_asserts_message(listener_t *listener, ike_sa_t *ike_sa,
        }
        return TRUE;
 }
+
+/**
+ * Compare two SPIs
+ */
+static int spis_cmp(const void *a, const void *b)
+{
+       return *(const uint32_t*)a - *(const uint32_t*)b;
+}
+
+/**
+ * Compare two SPIs to sort them
+ */
+static int spis_sort(const void *a, const void *b, void *data)
+{
+       return spis_cmp(a, b);
+}
+
+
+/*
+ * Described in header
+ */
+void exchange_test_asserts_ipsec_sas(ipsec_sas_assert_t *sas)
+{
+       enumerator_t *enumerator;
+       array_t *spis;
+       ike_sa_t *ike_sa;
+       uint32_t spi;
+       int i;
+
+       spis = array_create(sizeof(uint32_t), 0);
+       for (i = 0; i < sas->count; i++)
+       {
+               array_insert(spis, ARRAY_TAIL, &sas->spis[i]);
+       }
+       array_sort(spis, spis_sort, NULL);
+
+       enumerator = mock_ipsec_create_sa_enumerator();
+       while (enumerator->enumerate(enumerator, &ike_sa, &spi))
+       {
+               if (ike_sa == sas->ike_sa)
+               {
+                       i = array_bsearch(spis, &spi, spis_cmp, NULL);
+                       assert_listener_msg(i != -1, sas, "unexpected IPsec SA %.8x", spi);
+                       array_remove(spis, i, NULL);
+               }
+       }
+       enumerator->destroy(enumerator);
+       for (i = 0; i < array_count(spis); i++)
+       {
+               array_get(spis, i, &spi);
+               assert_listener_msg(!spi, sas, "expected IPsec SA %.8x not found", spi);
+       }
+       array_destroy(spis);
+}
index 32afcc2..4d363ed 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Tobias Brunner
+ * Copyright (C) 2016-2017 Tobias Brunner
  * HSR Hochschule fuer Technik Rapperswil
  *
  * This program is free software; you can redistribute it and/or modify it
@@ -14,7 +14,7 @@
  */
 
 /**
- * Special assertions using listener_t.
+ * Special assertions using listener_t etc.
  *
  * @defgroup exchange_test_asserts exchange_test_asserts
  * @{ @ingroup test_utils_c
@@ -28,6 +28,7 @@
 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;
+typedef struct ipsec_sas_assert_t ipsec_sas_assert_t;
 
 struct listener_hook_assert_t {
 
@@ -340,4 +341,60 @@ bool exchange_test_asserts_message(listener_t *this, ike_sa_t *ike_sa,
        exchange_test_helper->add_listener(exchange_test_helper, &_listener.listener); \
 })
 
+/**
+ * Data used to check IPsec SAs
+ */
+struct ipsec_sas_assert_t {
+
+       /**
+        * Original source file
+        */
+       const char *file;
+
+       /**
+        * Source line
+        */
+       int line;
+
+       /**
+        * IKE_SA that installed the IPsec SAs
+        */
+       ike_sa_t *ike_sa;
+
+       /**
+        * SPIs to check
+        */
+       uint32_t *spis;
+
+       /**
+        * Number of SPIs for IPsec SAs to check
+        */
+       int count;
+};
+
+/**
+ * Assert that all given IPsec SAs (and only these) are installed for the given
+ * IKE_SA.
+ */
+void exchange_test_asserts_ipsec_sas(ipsec_sas_assert_t *sas);
+
+/**
+ * Assert that the IPsec SAs with the given SPIs (and none other) are currently
+ * installed by the given IKE_SA.
+ *
+ * @param sa           IKE_SA
+ * @param ...          list of SPIs
+ */
+#define assert_ipsec_sas_installed(sa, ...) ({ \
+       uint32_t _spis[] = { __VA_ARGS__ }; \
+       ipsec_sas_assert_t _sas_assert = { \
+               .file = __FILE__, \
+               .line = __LINE__, \
+               .ike_sa = sa, \
+               .spis = _spis, \
+               .count = countof(_spis), \
+       }; \
+       exchange_test_asserts_ipsec_sas(&_sas_assert); \
+})
+
 #endif /** EXCHANGE_TEST_ASSERTS_H_ @}*/
index d57a26a..68daaac 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Tobias Brunner
+ * Copyright (C) 2016-2017 Tobias Brunner
  * Copyright (C) 2008 Martin Willi
  * HSR Hochschule fuer Technik Rapperswil
  *
 
 #include "mock_ipsec.h"
 
+#include <daemon.h>
+#include <collections/hashtable.h>
+#include <collections/array.h>
+
+#include <assert.h>
+
 typedef struct private_kernel_ipsec_t private_kernel_ipsec_t;
 
 /**
@@ -29,16 +35,80 @@ struct private_kernel_ipsec_t {
        kernel_ipsec_t public;
 
        /**
+        * Rekey listener
+        */
+       listener_t listener;
+
+       /**
         * Allocated SPI
         */
        refcount_t spi;
+
+       /**
+        * Installed SAs
+        */
+       hashtable_t *sas;
 };
 
+/**
+ * Global instance
+ */
+static private_kernel_ipsec_t *instance;
+
+/**
+ * Data about installed IPsec SAs
+ */
+typedef struct {
+       /**
+        * SPI of the SA
+        */
+       uint32_t spi;
+
+       /**
+        * Associated IKE_SA
+        */
+       ike_sa_t *ike_sa;
+
+       /**
+        * TRUE if this was an allocated SPI
+        */
+       bool alloc;
+
+} entry_t;
+
+/**
+ * Hash an IPsec SA entry
+ */
+static u_int entry_hash(const void *key)
+{
+       entry_t *entry = (entry_t*)key;
+       return chunk_hash_inc(chunk_from_thing(entry->spi),
+                                                 chunk_hash(chunk_from_thing(entry->ike_sa)));
+}
+
+/**
+ * Compare an IPsec SA entry
+ */
+static bool entry_equals(const void *key, const void *other_key)
+{
+       entry_t *a = (entry_t*)key, *b = (entry_t*)other_key;
+       return a->spi == b->spi && a->ike_sa == b->ike_sa;
+}
+
 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)
 {
+       entry_t *entry;
+
        *spi = (uint32_t)ref_get(&this->spi);
+       INIT(entry,
+               .spi = *spi,
+               .ike_sa = charon->bus->get_sa(charon->bus),
+               .alloc = TRUE,
+       );
+       entry = this->sas->put(this->sas, entry, entry);
+       assert(!entry);
        return SUCCESS;
 }
 
@@ -52,6 +122,23 @@ 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)
 {
+       entry_t *entry;
+
+       INIT(entry,
+               .spi = id->spi,
+               .ike_sa = charon->bus->get_sa(charon->bus),
+       );
+       if (data->inbound)
+       {
+               entry = this->sas->put(this->sas, entry, entry);
+               assert(entry && entry->alloc);
+               free(entry);
+       }
+       else
+       {
+               entry = this->sas->put(this->sas, entry, entry);
+               assert(!entry);
+       }
        return SUCCESS;
 }
 
@@ -74,9 +161,47 @@ 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)
 {
+       entry_t *entry, lookup = {
+               .spi = id->spi,
+               .ike_sa = charon->bus->get_sa(charon->bus),
+       };
+
+       entry = this->sas->remove(this->sas, &lookup);
+       assert(entry);
+       free(entry);
        return SUCCESS;
 }
 
+METHOD(listener_t, ike_rekey, bool,
+       listener_t *listener, ike_sa_t *old, ike_sa_t *new)
+{
+       enumerator_t *enumerator;
+       array_t *sas = NULL;
+       entry_t *entry;
+
+       enumerator = instance->sas->create_enumerator(instance->sas);
+       while (enumerator->enumerate(enumerator, &entry, NULL))
+       {
+               if (entry->ike_sa == old)
+               {
+                       instance->sas->remove_at(instance->sas, enumerator);
+                       array_insert_create(&sas, ARRAY_TAIL, entry);
+               }
+       }
+       enumerator->destroy(enumerator);
+       enumerator = array_create_enumerator(sas);
+       while (enumerator->enumerate(enumerator, &entry))
+       {
+               array_remove_at(sas, enumerator);
+               entry->ike_sa = new;
+               entry = instance->sas->put(instance->sas, entry, entry);
+               assert(!entry);
+       }
+       enumerator->destroy(enumerator);
+       array_destroy(sas);
+       return TRUE;
+}
+
 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)
@@ -99,6 +224,14 @@ METHOD(kernel_ipsec_t, del_policy, status_t,
        return SUCCESS;
 }
 
+METHOD(kernel_ipsec_t, destroy, void,
+       private_kernel_ipsec_t *this)
+{
+       charon->bus->remove_listener(charon->bus, &this->listener);
+       this->sas->destroy(this->sas);
+       free(this);
+}
+
 /*
  * Described in header
  */
@@ -121,8 +254,42 @@ kernel_ipsec_t *mock_ipsec_create()
                        .flush_policies = (void*)return_failed,
                        .bypass_socket = (void*)return_true,
                        .enable_udp_decap = (void*)return_true,
-                       .destroy = (void*)free,
+                       .destroy = _destroy,
                },
+               .listener = {
+                       .ike_rekey = _ike_rekey,
+               },
+               .sas = hashtable_create(entry_hash, entry_equals, 8),
        );
+
+       instance = this;
+
+       charon->bus->add_listener(charon->bus, &this->listener);
+
        return &this->public;
 }
+
+/**
+ * Filter SAs
+ */
+static bool filter_sas(void *data, entry_t **entry, ike_sa_t **ike_sa,
+                                          void *unused, uint32_t *spi)
+{
+       if ((*entry)->alloc)
+       {
+               return FALSE;
+       }
+       *ike_sa = (*entry)->ike_sa;
+       *spi = (*entry)->spi;
+       return TRUE;
+}
+
+/*
+ * Described in header
+ */
+enumerator_t *mock_ipsec_create_sa_enumerator()
+{
+       return enumerator_create_filter(
+                                                       instance->sas->create_enumerator(instance->sas),
+                                                       (void*)filter_sas, NULL, NULL);
+}
index cbf2152..95038a5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Tobias Brunner
+ * Copyright (C) 2016-2017 Tobias Brunner
  * HSR Hochschule fuer Technik Rapperswil
  *
  * This program is free software; you can redistribute it and/or modify it
@@ -15,7 +15,7 @@
 
 /**
  * kernel_ipsec_t implementation used for exchange unit tests.  Currently
- * returns sequential SPIs, all other methods are noops.
+ * returns sequential SPIs, and keeps track of installed SAs.
  *
  * @defgroup mock_ipsec mock_ipsec
  * @{ @ingroup test_utils_c
  */
 kernel_ipsec_t *mock_ipsec_create();
 
+/**
+ * Enumerate the installed SAs
+ *
+ * @return             enumerator over (ike_sa_t*, uint32_t)
+ */
+enumerator_t *mock_ipsec_create_sa_enumerator();
+
 #endif /** MOCK_IPSEC_H_ @}*/
index 7afa3b5..d23f724 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Tobias Brunner
+ * Copyright (C) 2016-2017 Tobias Brunner
  * HSR Hochschule fuer Technik Rapperswil
  *
  * This program is free software; you can redistribute it and/or modify it
 })
 
 /**
+ * Check if the CHILD_SA with the given SPI is in the expected state, optionally
+ * check the state of the outbound SA.
+ */
+#define assert_child_sa_state(...) VA_ARGS_DISPATCH(assert_child_sa_state, __VA_ARGS__)(__VA_ARGS__)
+
+/**
  * Check if the CHILD_SA with the given SPI is in the expected state.
  */
-#define assert_child_sa_state(ike_sa, spi, state) \
+#define assert_child_sa_state3(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)); \
+})
+
+/**
+ * Check if the outbound SA of a CHILD_SA with the given SPI is in the
+ * expected state.
+ */
+#define assert_child_sa_state4(ike_sa, spi, state, outbound) \
 ({ \
        typeof(ike_sa) _sa = ike_sa; \
        typeof(spi) _spi = spi; \
        typeof(state) _state = state; \
+       typeof(outbound) _outbound = outbound; \
        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", \
        test_assert_msg(_state == _child->get_state(_child), "%N != %N", \
                                        child_sa_state_names, _state, \
                                        child_sa_state_names, _child->get_state(_child)); \
+       test_assert_msg(_outbound == _child->get_outbound_state(_child), "%N != %N", \
+                                       child_sa_outbound_state_names, _outbound, \
+                                       child_sa_outbound_state_names, _child->get_outbound_state(_child)); \
 })
 
 /**