ikev2: Delay installation of outbound SAs during rekeying on the responder
[strongswan.git] / src / libcharon / sa / ikev2 / tasks / child_delete.c
index 2b16974..d69f44b 100644 (file)
@@ -1,6 +1,7 @@
 /*
+ * Copyright (C) 2009-2016 Tobias Brunner
  * Copyright (C) 2006-2007 Martin Willi
- * Hochschule fuer Technik Rapperswil
+ * HSR Hochschule fuer Technik Rapperswil
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
@@ -18,7 +19,7 @@
 #include <daemon.h>
 #include <encoding/payloads/delete_payload.h>
 #include <sa/ikev2/tasks/child_create.h>
-
+#include <sa/ikev2/tasks/child_rekey.h>
 
 typedef struct private_child_delete_t private_child_delete_t;
 
@@ -50,7 +51,7 @@ struct private_child_delete_t {
        /**
         * Inbound SPI of CHILD_SA to delete
         */
-       u_int32_t spi;
+       uint32_t spi;
 
        /**
         * whether to enforce delete action policy
@@ -86,7 +87,7 @@ static void build_payloads(private_child_delete_t *this, message_t *message)
        while (enumerator->enumerate(enumerator, (void**)&child_sa))
        {
                protocol_id_t protocol = child_sa->get_protocol(child_sa);
-               u_int32_t spi = child_sa->get_spi(child_sa, TRUE);
+               uint32_t spi = child_sa->get_spi(child_sa, TRUE);
 
                switch (protocol)
                {
@@ -119,6 +120,84 @@ static void build_payloads(private_child_delete_t *this, message_t *message)
 }
 
 /**
+ * Check if the given CHILD_SA is the redundant SA created in a rekey collision.
+ */
+static bool is_redundant(private_child_delete_t *this, child_sa_t *child)
+{
+       enumerator_t *tasks;
+       task_t *task;
+
+       tasks = this->ike_sa->create_task_enumerator(this->ike_sa,
+                                                                                                TASK_QUEUE_ACTIVE);
+       while (tasks->enumerate(tasks, &task))
+       {
+               if (task->get_type(task) == TASK_CHILD_REKEY)
+               {
+                       child_rekey_t *rekey = (child_rekey_t*)task;
+
+                       if (rekey->is_redundant(rekey, child))
+                       {
+                               tasks->destroy(tasks);
+                               return TRUE;
+                       }
+               }
+       }
+       tasks->destroy(tasks);
+       return FALSE;
+}
+
+/**
+ * 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)
@@ -126,7 +205,7 @@ static void process_payloads(private_child_delete_t *this, message_t *message)
        enumerator_t *payloads, *spis;
        payload_t *payload;
        delete_payload_t *delete_payload;
-       u_int32_t spi;
+       uint32_t spi;
        protocol_id_t protocol;
        child_sa_t *child_sa;
 
@@ -157,24 +236,32 @@ static void process_payloads(private_child_delete_t *this, message_t *message)
 
                                switch (child_sa->get_state(child_sa))
                                {
-                                       case CHILD_REKEYING:
+                                       case CHILD_REKEYED:
                                                this->rekeyed = TRUE;
-                                               /* we reply as usual, rekeying will fail */
                                                break;
                                        case CHILD_DELETING:
                                                /* we don't send back a delete if we initiated ourself */
                                                if (!this->initiator)
                                                {
-                                                       this->ike_sa->destroy_child_sa(this->ike_sa,
-                                                                                                                  protocol, spi);
                                                        continue;
                                                }
                                                /* fall through */
+                                       case CHILD_REKEYING:
+                                               /* we reply as usual, rekeying will fail */
+                                       case CHILD_INSTALLED_INBOUND:
                                        case CHILD_INSTALLED:
                                                if (!this->initiator)
-                                               {       /* reestablish installed children if required */
-                                                       this->check_delete_action = TRUE;
+                                               {
+                                                       if (is_redundant(this, child_sa))
+                                                       {
+                                                               this->rekeyed = TRUE;
+                                                       }
+                                                       else
+                                                       {
+                                                               this->check_delete_action = TRUE;
+                                                       }
                                                }
+                                               break;
                                        default:
                                                break;
                                }
@@ -199,21 +286,29 @@ static status_t destroy_and_reestablish(private_child_delete_t *this)
        child_sa_t *child_sa;
        child_cfg_t *child_cfg;
        protocol_id_t protocol;
-       u_int32_t spi, reqid;
+       uint32_t spi, reqid, rekey_spi;
        action_t action;
        status_t status = SUCCESS;
 
        enumerator = this->child_sas->create_enumerator(this->child_sas);
        while (enumerator->enumerate(enumerator, (void**)&child_sa))
        {
-               /* signal child down event if we are not rekeying */
+               /* signal child down event if we weren't rekeying */
+               protocol = child_sa->get_protocol(child_sa);
                if (!this->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);
+                       }
+               }
                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);
@@ -254,7 +349,7 @@ static void log_children(private_child_delete_t *this)
        linked_list_t *my_ts, *other_ts;
        enumerator_t *enumerator;
        child_sa_t *child_sa;
-       u_int64_t bytes_in, bytes_out;
+       uint64_t bytes_in, bytes_out;
 
        enumerator = this->child_sas->create_enumerator(this->child_sas);
        while (enumerator->enumerate(enumerator, (void**)&child_sa))
@@ -266,8 +361,8 @@ static void log_children(private_child_delete_t *this)
                if (this->expired)
                {
                        DBG0(DBG_IKE, "closing expired CHILD_SA %s{%d} "
-                                "with SPIs %.8x_i %.8x_o and TS %#R=== %#R",
-                                child_sa->get_name(child_sa), child_sa->get_reqid(child_sa),
+                                "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);
                }
@@ -277,8 +372,8 @@ static void log_children(private_child_delete_t *this)
                        child_sa->get_usestats(child_sa, FALSE, NULL, &bytes_out, NULL);
 
                        DBG0(DBG_IKE, "closing CHILD_SA %s{%d} with SPIs %.8x_i "
-                                "(%llu bytes) %.8x_o (%llu bytes) and TS %#R=== %#R",
-                                child_sa->get_name(child_sa), child_sa->get_reqid(child_sa),
+                                "(%llu bytes) %.8x_o (%llu bytes) 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)), bytes_in,
                                 ntohl(child_sa->get_spi(child_sa, FALSE)), bytes_out,
                                 my_ts, other_ts);
@@ -308,7 +403,7 @@ METHOD(task_t, build_i, status_t,
                this->spi = child_sa->get_spi(child_sa, TRUE);
        }
        this->child_sas->insert_last(this->child_sas, child_sa);
-       if (child_sa->get_state(child_sa) == CHILD_REKEYING)
+       if (child_sa->get_state(child_sa) == CHILD_REKEYED)
        {
                this->rekeyed = TRUE;
        }
@@ -347,11 +442,7 @@ METHOD(task_t, process_r, status_t,
 METHOD(task_t, build_r, status_t,
        private_child_delete_t *this, message_t *message)
 {
-       /* if we are rekeying, we send an empty informational */
-       if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING)
-       {
-               build_payloads(this, message);
-       }
+       build_payloads(this, message);
        DBG1(DBG_IKE, "CHILD_SA closed");
        return destroy_and_reestablish(this);
 }
@@ -391,7 +482,7 @@ METHOD(task_t, destroy, void,
  * Described in header.
  */
 child_delete_t *child_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol,
-                                                                       u_int32_t spi, bool expired)
+                                                                       uint32_t spi, bool expired)
 {
        private_child_delete_t *this;