]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
child-create: Add support to negotiate per-CPU SAs
authorTobias Brunner <tobias@strongswan.org>
Wed, 19 May 2021 08:59:03 +0000 (10:59 +0200)
committerTobias Brunner <tobias@strongswan.org>
Wed, 28 May 2025 14:35:26 +0000 (16:35 +0200)
src/libcharon/sa/ikev2/tasks/child_create.c
src/libcharon/sa/ikev2/tasks/child_create.h

index 4c5a6fe0992c015cc764335ccd1d3d407d904136..2bcf3baad07c61bf8c2523df29d95f7c10d67cf6 100644 (file)
  * for more details.
  */
 
+#include <unistd.h>
+
 #include "child_create.h"
 
 #include <daemon.h>
 #include <sa/ikev2/keymat_v2.h>
 #include <crypto/key_exchange.h>
 #include <credentials/certificates/x509.h>
+#include <collections/hashtable.h>
 #include <encoding/payloads/sa_payload.h>
 #include <encoding/payloads/ke_payload.h>
 #include <encoding/payloads/ts_payload.h>
@@ -37,6 +40,9 @@
 
 typedef struct private_child_create_t private_child_create_t;
 
+/** Assumed minimum CPU ID, used when searching for a CPU without SA */
+#define CPU_ID_MIN 0
+
 /**
  * Private members of a child_create_t task.
  */
@@ -127,6 +133,16 @@ struct private_child_create_t {
         */
        traffic_selector_list_t *other_ts;
 
+       /**
+        * Resource info sent by the peer
+        */
+       chunk_t resource_info;
+
+       /**
+        * Whether we received resource info (might be empty)
+        */
+       bool resource_info_seen;
+
        /**
         * Key exchanges to perform
         */
@@ -265,6 +281,7 @@ static void schedule_delayed_retry(private_child_create_t *this)
        task->use_marks(task, this->child.mark_in, this->child.mark_out);
        task->use_if_ids(task, this->child.if_id_in, this->child.if_id_out);
        task->use_label(task, this->child.label);
+       task->use_per_cpu(task, this->child.per_cpu, this->child.cpu);
 
        /* clone these directly as we don't have a public method */
        if (this->my_ts && this->other_ts)
@@ -873,6 +890,83 @@ static bool select_proposal(private_child_create_t *this, bool no_ke)
        return TRUE;
 }
 
+/**
+ * Returns the number of CPUs that are configured on the system or only those
+ * that are currently online (if we can distinguish this).
+ */
+static uint32_t get_system_cpus(bool online)
+{
+       long cpus;
+
+#ifdef WIN32
+       SYSTEM_INFO sysinfo;
+       GetSystemInfo(&sysinfo);
+       cpus = sysinfo.dwNumberOfProcessors;
+#else /* WIN32 */
+       cpus = sysconf(online ? _SC_NPROCESSORS_ONLN : _SC_NPROCESSORS_CONF);
+
+       if (cpus < 0)
+       {
+               DBG1(DBG_IKE, "failed to determine number of CPUs for this system");
+               /* assume we have at least one CPU */
+               cpus = 1;
+       }
+#endif /* WIN32 */
+       return cpus;
+}
+
+/**
+ * Finds an unused CPU ID to be assigned to a responder's CHILD_SA, or return
+ * CPU_ID_MAX if we don't have a fallback yet.
+ */
+static uint32_t get_cpu(private_child_create_t *this)
+{
+       enumerator_t *enumerator;
+       hashtable_t *used;
+       child_sa_t *child_sa;
+       bool found_fallback = FALSE;
+       uint32_t cpu;
+
+       used = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 32);
+
+       enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa);
+       while (enumerator->enumerate(enumerator, (void**)&child_sa))
+       {
+               if (child_sa->get_state(child_sa) == CHILD_INSTALLED &&
+                       this->config->equals(this->config, child_sa->get_config(child_sa)))
+               {
+                       cpu = child_sa->get_cpu(child_sa);
+                       if (cpu != CPU_ID_MAX)
+                       {
+                               used->put(used, (void*)(uintptr_t)cpu, child_sa);
+                       }
+                       else
+                       {
+                               found_fallback = TRUE;
+                       }
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       if (!found_fallback)
+       {
+               cpu = CPU_ID_MAX;
+       }
+       else
+       {
+               cpu = get_system_cpus(TRUE) + CPU_ID_MIN - 1;
+               for (; cpu > CPU_ID_MIN; cpu--)
+               {
+                       if (!used->get(used, (void*)(uintptr_t)cpu))
+                       {
+                               break;
+                       }
+               }
+       }
+       used->destroy(used);
+       return cpu;
+}
+
 /**
  * Add a KE payload if a key exchange is used.  As responder we might already
  * have stored the object in the list of completed exchanges.
@@ -989,6 +1083,21 @@ static bool build_payloads(private_child_create_t *this, message_t *message)
                message->add_notify(message, FALSE, ESP_TFC_PADDING_NOT_SUPPORTED,
                                                        chunk_empty);
        }
+
+       if (!this->rekey && this->child.per_cpu)
+       {
+               chunk_t cpu_id = chunk_empty;
+               uint32_t cpu;
+
+               /* always send the notify, but possibly without CPU ID */
+               if (this->child.cpu != CPU_ID_MAX)
+               {
+                       cpu = htonl(this->child.cpu);
+                       cpu_id = chunk_from_thing(cpu);
+               }
+               message->add_notify(message, FALSE, SA_RESOURCE_INFO,
+                                                       cpu_id);
+       }
        return TRUE;
 }
 
@@ -1063,6 +1172,11 @@ static void handle_notify(private_child_create_t *this, notify_payload_t *notify
                                 notify_type_names, notify->get_notify_type(notify));
                        this->tfcv3 = FALSE;
                        break;
+               case SA_RESOURCE_INFO:
+                       chunk_free(&this->resource_info);
+                       this->resource_info = chunk_clone(notify->get_notification_data(notify));
+                       this->resource_info_seen = TRUE;
+                       break;
                default:
                        break;
        }
@@ -1384,6 +1498,7 @@ static bool child_sa_equals(private_child_create_t *this, child_sa_t *a,
                a->get_mark(a, FALSE).value == b->get_mark(b, FALSE).value &&
                a->get_if_id(a, TRUE) == b->get_if_id(b, TRUE) &&
                a->get_if_id(a, FALSE) == b->get_if_id(b, FALSE) &&
+               a->get_cpu(a) == b->get_cpu(b) &&
                sec_labels_equal(a->get_label(a), b->get_label(b)) &&
                reqid_and_ts_equals(this, a, b);
 }
@@ -1593,6 +1708,11 @@ METHOD(task_t, build_i, status_t,
                DBG2(DBG_CFG, "proposing security label '%s'",
                         this->child.label->get_string(this->child.label));
        }
+       if (!this->rekey)
+       {
+               this->child.per_cpu = this->config->has_option(this->config,
+                                                                                                          OPT_PER_CPU_SAS);
+       }
 
        this->proposals = this->config->get_proposals(this->config, no_ke);
        this->mode = this->config->get_mode(this->config);
@@ -1956,6 +2076,48 @@ static bool select_label(private_child_create_t *this)
        return TRUE;
 }
 
+/**
+ * Handle per-resource information.
+ */
+static void handle_per_resource(private_child_create_t *this)
+{
+       if (!this->resource_info_seen)
+       {
+               DBG1(DBG_IKE, "peer didn't send %N notify, creating regular SA",
+                        notify_type_names, SA_RESOURCE_INFO);
+               this->child.per_cpu = FALSE;
+               this->child.cpu = CPU_ID_MAX;
+               return;
+       }
+
+       /* as responder, assign a CPU ID (or CPU_ID_MAX if we don't have a
+        * fallback SA yet) */
+       if (!this->initiator)
+       {
+               this->child.per_cpu = TRUE;
+               this->child.cpu = get_cpu(this);
+       }
+       if (this->child.cpu != CPU_ID_MAX && this->resource_info.len)
+       {
+               DBG1(DBG_IKE, "creating per-resource SA as one of several (our ID %u, "
+                        "peer's ID %+B)", this->child.cpu, &this->resource_info);
+       }
+       else if (this->child.cpu != CPU_ID_MAX)
+       {
+               DBG1(DBG_IKE, "creating per-resource SA as one of several (our ID %u)",
+                        this->child.cpu);
+       }
+       else if (this->resource_info.len)
+       {
+               DBG1(DBG_IKE, "creating SA as one of several per-resource SAs (peer's "
+                        "ID %+B)", &this->resource_info);
+       }
+       else
+       {
+               DBG1(DBG_IKE, "creating SA as one of several per-resource SAs");
+       }
+}
+
 /**
  * Called when a key exchange is done, returns TRUE once all are done.
  */
@@ -2201,6 +2363,15 @@ METHOD(task_t, build_r, status_t,
        this->child.if_id_in_def = this->ike_sa->get_if_id(this->ike_sa, TRUE);
        this->child.if_id_out_def = this->ike_sa->get_if_id(this->ike_sa, FALSE);
        this->child.encap = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY);
+
+       if (!this->rekey && this->config->has_option(this->config, OPT_PER_CPU_SAS))
+       {
+               handle_per_resource(this);
+               /* if it's a per-resource SA, we could check if we already have
+                * "too many" and reject this one with TS_MAX_QUEUE, but for now we just
+                * keep it */
+       }
+
        this->child_sa = child_sa_create(this->ike_sa->get_my_host(this->ike_sa),
                                                                         this->ike_sa->get_other_host(this->ike_sa),
                                                                         this->config, &this->child);
@@ -2550,6 +2721,13 @@ METHOD(task_t, process_i, status_t,
                return delete_failed_sa(this);
        }
 
+       if (!this->rekey && this->config->has_option(this->config, OPT_PER_CPU_SAS))
+       {
+               handle_per_resource(this);
+               /* the peer might not have returned the notify, update the flag */
+               this->child_sa->set_per_cpu(this->child_sa, this->child.per_cpu);
+       }
+
        if (key_exchange_done_and_install_i(this, message, ike_auth) == NEED_MORE)
        {
                /* if the installation failed, we delete the failed SA, i.e. build() was
@@ -2602,6 +2780,13 @@ METHOD(child_create_t, use_label, void,
        this->child.label = label ? label->clone(label) : NULL;
 }
 
+METHOD(child_create_t, use_per_cpu, void,
+       private_child_create_t *this, bool per_cpu, uint32_t cpu)
+{
+       this->child.per_cpu = per_cpu ? per_cpu : cpu != CPU_ID_MAX;
+       this->child.cpu = cpu;
+}
+
 /**
  * Prepare traffic selectors for reuse when recreating a CHILD_SA.
  */
@@ -2736,6 +2921,7 @@ METHOD(task_t, migrate, void,
        chunk_free(&this->my_nonce);
        chunk_free(&this->other_nonce);
        chunk_free(&this->link);
+       chunk_free(&this->resource_info);
        DESTROY_OFFSET_IF(this->tsr, offsetof(traffic_selector_t, destroy));
        DESTROY_OFFSET_IF(this->tsi, offsetof(traffic_selector_t, destroy));
        DESTROY_OFFSET_IF(this->labels_i, offsetof(sec_label_t, destroy));
@@ -2767,6 +2953,7 @@ METHOD(task_t, migrate, void,
        this->ipcomp_received = IPCOMP_NONE;
        this->other_cpi = 0;
        this->established = FALSE;
+       this->resource_info_seen = FALSE;
        this->public.task.build = _build_i;
        this->public.task.process = _process_i;
 }
@@ -2777,6 +2964,7 @@ METHOD(task_t, destroy, void,
        chunk_free(&this->my_nonce);
        chunk_free(&this->other_nonce);
        chunk_free(&this->link);
+       chunk_free(&this->resource_info);
        DESTROY_OFFSET_IF(this->tsr, offsetof(traffic_selector_t, destroy));
        DESTROY_OFFSET_IF(this->tsi, offsetof(traffic_selector_t, destroy));
        DESTROY_OFFSET_IF(this->labels_i, offsetof(sec_label_t, destroy));
@@ -2824,6 +3012,7 @@ child_create_t *child_create_create(ike_sa_t *ike_sa,
                        .use_marks = _use_marks,
                        .use_if_ids = _use_if_ids,
                        .use_label = _use_label,
+                       .use_per_cpu = _use_per_cpu,
                        .recreate_sa = _recreate_sa,
                        .abort = _abort_,
                        .task = {
index 0c0e6137e7ae609107b4ea52d3d45d7f29e5ae4e..c72407f6448e2e877381e2a90b60ff57eb4a849f 100644 (file)
@@ -80,6 +80,16 @@ struct child_create_t {
         */
        void (*use_label)(child_create_t *this, sec_label_t *label);
 
+       /**
+        * Enable per-CPU feature, optionally with a specific CPU ID for the
+        * negotiated CHILD_SA.
+        *
+        * @param per_cpu       TRUE to enable per-CPU feature (automatically set if
+        *                                      cpu is not CPU_ID_MAX)
+        * @param cpu           CPU ID
+        */
+       void (*use_per_cpu)(child_create_t *this, bool per_cpu, uint32_t cpu);
+
        /**
         * Use data from the given old SA (e.g. KE method and traffic selectors)
         * when rekeying/recreating it.