]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
child-create: Maintain traffic selectors during rekeying/reauthentication
authorTobias Brunner <tobias@strongswan.org>
Tue, 1 Apr 2025 15:33:35 +0000 (17:33 +0200)
committerTobias Brunner <tobias@strongswan.org>
Wed, 28 May 2025 14:01:00 +0000 (16:01 +0200)
If we don't do this, narrowed SAs would default to the wide configured
traffic selectors and the peer won't know if/how to narrow.

src/libcharon/sa/ikev2/tasks/child_create.c

index 3020967fd6dc9c58d40f0775c89fa20b62b018ec..aa0070a01e690965f3422f2e72ea32a680c348f2 100644 (file)
@@ -117,6 +117,16 @@ struct private_child_create_t {
         */
        traffic_selector_t *packet_tsr;
 
+       /**
+        * Local traffic selectors as configured or previously negotiated
+        */
+       traffic_selector_list_t *my_ts;
+
+       /**
+        * Remote traffic selectors as configured or previously negotiated
+        */
+       traffic_selector_list_t *other_ts;
+
        /**
         * Key exchanges to perform
         */
@@ -256,6 +266,15 @@ static void schedule_delayed_retry(private_child_create_t *this)
        task->use_if_ids(task, this->child.if_id_in, this->child.if_id_out);
        task->use_label(task, this->child.label);
 
+       /* clone these directly as we don't have a public method */
+       if (this->my_ts && this->other_ts)
+       {
+               private_child_create_t *priv = (private_child_create_t*)task;
+
+               priv->my_ts = this->my_ts->clone(this->my_ts);
+               priv->other_ts = this->other_ts->clone(this->other_ts);
+       }
+
        DBG1(DBG_IKE, "creating CHILD_SA failed, trying again in %d seconds",
                 retry);
        this->ike_sa->queue_task_delayed(this->ike_sa, (task_t*)task, retry);
@@ -452,15 +471,37 @@ static linked_list_t* get_transport_nat_ts(private_child_create_t *this,
        return out;
 }
 
+/**
+ * Ensure we have traffic selector lists when not recreating an SA.
+ */
+static void ensure_ts_lists(private_child_create_t *this)
+{
+       linked_list_t *ts;
+
+       if (!this->my_ts)
+       {
+               ts = this->config->get_traffic_selectors(this->config, TRUE, NULL);
+               this->my_ts = traffic_selector_list_create_from_list(ts);
+       }
+       if (!this->other_ts)
+       {
+               ts = this->config->get_traffic_selectors(this->config, FALSE, NULL);
+               this->other_ts = traffic_selector_list_create_from_list(ts);
+       }
+}
+
 /**
  * Narrow received traffic selectors with configuration
  */
 static linked_list_t* narrow_ts(private_child_create_t *this, bool local,
                                                                linked_list_t *in)
 {
-       linked_list_t *hosts, *nat, *ts;
+       traffic_selector_list_t *ts;
+       linked_list_t *hosts, *nat, *result;
        ike_condition_t cond;
 
+       ensure_ts_lists(this);
+       ts = local ? this->my_ts : this->other_ts;
        cond = local ? COND_NAT_HERE : COND_NAT_THERE;
        hosts = ike_sa_get_dynamic_hosts(this->ike_sa, local);
 
@@ -468,19 +509,16 @@ static linked_list_t* narrow_ts(private_child_create_t *this, bool local,
                this->ike_sa->has_condition(this->ike_sa, cond))
        {
                nat = get_transport_nat_ts(this, local, in);
-               ts = this->config->select_traffic_selectors(this->config, local, nat,
-                                                                                                       hosts);
+               result = child_cfg_select_ts(this->config, local, ts, nat, hosts);
                nat->destroy_offset(nat, offsetof(traffic_selector_t, destroy));
        }
        else
        {
-               ts = this->config->select_traffic_selectors(this->config, local, in,
-                                                                                                       hosts);
+               result = child_cfg_select_ts(this->config, local, ts, in, hosts);
        }
 
        hosts->destroy(hosts);
-
-       return ts;
+       return result;
 }
 
 /**
@@ -1435,57 +1473,27 @@ METHOD(task_t, build_i_multi_ke, status_t,
        return NEED_MORE;
 }
 
-METHOD(task_t, build_i, status_t,
-       private_child_create_t *this, message_t *message)
+/**
+ * Prepare proposed traffic selectors as initiator.
+ */
+static void prepare_proposed_ts(private_child_create_t *this)
 {
        enumerator_t *enumerator;
-       host_t *vip;
        peer_cfg_t *peer_cfg;
        linked_list_t *list;
-       bool no_ke = TRUE;
+       host_t *vip;
 
-       switch (message->get_exchange_type(message))
-       {
-               case IKE_SA_INIT:
-                       return get_nonce(message, &this->my_nonce);
-               case CREATE_CHILD_SA:
-                       if (!generate_nonce(this))
-                       {
-                               message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
-                               return SUCCESS;
-                       }
-                       no_ke = FALSE;
-                       break;
-               case IKE_AUTH:
-                       switch (defer_child_sa(this))
-                       {
-                               case DESTROY_ME:
-                                       /* config mismatch */
-                                       return DESTROY_ME;
-                               case NEED_MORE:
-                                       /* defer until after IKE_SA has been established */
-                                       chunk_free(&this->my_nonce);
-                                       return NEED_MORE;
-                               default:
-                                       /* just continue to establish the CHILD_SA */
-                                       break;
-                       }
-                       /* send only in the first request, not in subsequent rounds */
-                       this->public.task.build = (void*)return_need_more;
-                       break;
-               default:
-                       return NEED_MORE;
-       }
+       ensure_ts_lists(this);
 
-       /* check if we want a virtual IP, but don't have one */
        list = linked_list_create();
-       peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa);
        if (!this->rekey)
        {
+               /* check if we want a virtual IP */
+               peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa);
                enumerator = peer_cfg->create_virtual_ip_enumerator(peer_cfg);
                while (enumerator->enumerate(enumerator, &vip))
                {
-                       /* propose a 0.0.0.0/0 or ::/0 subnet when we use virtual ip */
+                       /* propose a 0.0.0.0/0 or ::/0 subnet when we use a virtual IP */
                        vip = host_create_any(vip->get_family(vip));
                        list->insert_last(list, vip);
                }
@@ -1493,21 +1501,21 @@ METHOD(task_t, build_i, status_t,
        }
        if (list->get_count(list))
        {
-               this->tsi = this->config->select_traffic_selectors(this->config, TRUE,
-                                                                                                                  NULL, list);
+               this->tsi = child_cfg_select_ts(this->config, TRUE, this->my_ts, NULL,
+                                                                               list);
                list->destroy_offset(list, offsetof(host_t, destroy));
        }
        else
-       {       /* no virtual IPs configured */
+       {
                list->destroy(list);
                list = ike_sa_get_dynamic_hosts(this->ike_sa, TRUE);
-               this->tsi = this->config->select_traffic_selectors(this->config, TRUE,
-                                                                                                                  NULL, list);
+               this->tsi = child_cfg_select_ts(this->config, TRUE, this->my_ts, NULL,
+                                                                               list);
                list->destroy(list);
        }
        list = ike_sa_get_dynamic_hosts(this->ike_sa, FALSE);
-       this->tsr = this->config->select_traffic_selectors(this->config, FALSE,
-                                                                                                          NULL, list);
+       this->tsr = child_cfg_select_ts(this->config, FALSE, this->other_ts, NULL,
+                                                                       list);
        list->destroy(list);
 
        if (this->packet_tsi)
@@ -1520,6 +1528,47 @@ METHOD(task_t, build_i, status_t,
                this->tsr->insert_first(this->tsr,
                                                                this->packet_tsr->clone(this->packet_tsr));
        }
+}
+
+METHOD(task_t, build_i, status_t,
+       private_child_create_t *this, message_t *message)
+{
+       bool no_ke = TRUE;
+
+       switch (message->get_exchange_type(message))
+       {
+               case IKE_SA_INIT:
+                       return get_nonce(message, &this->my_nonce);
+               case CREATE_CHILD_SA:
+                       if (!generate_nonce(this))
+                       {
+                               message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
+                               return SUCCESS;
+                       }
+                       no_ke = FALSE;
+                       break;
+               case IKE_AUTH:
+                       switch (defer_child_sa(this))
+                       {
+                               case DESTROY_ME:
+                                       /* config mismatch */
+                                       return DESTROY_ME;
+                               case NEED_MORE:
+                                       /* defer until after IKE_SA has been established */
+                                       chunk_free(&this->my_nonce);
+                                       return NEED_MORE;
+                               default:
+                                       /* just continue to establish the CHILD_SA */
+                                       break;
+                       }
+                       /* send only in the first request, not in subsequent rounds */
+                       this->public.task.build = (void*)return_need_more;
+                       break;
+               default:
+                       return NEED_MORE;
+       }
+
+       prepare_proposed_ts(this);
 
        if (!generic_label_only(this) && !this->child.label)
        {       /* in the simple label mode we propose the configured label as we
@@ -2544,6 +2593,61 @@ METHOD(child_create_t, use_label, void,
        this->child.label = label ? label->clone(label) : NULL;
 }
 
+/**
+ * Prepare traffic selectors for reuse when recreating a CHILD_SA.
+ */
+static void reuse_ts(private_child_create_t *this, bool local, child_sa_t *old,
+                                        traffic_selector_list_t **target)
+{
+       enumerator_t *old_ts, *hosts_enum;
+       linked_list_t *hosts, *list;
+       traffic_selector_t *ts, *new_ts;
+       host_t *host;
+
+       old_ts = old->create_ts_enumerator(old, local);
+       if (this->rekey)
+       {
+               /* when rekeying, we just reuse the previous TS. this is also the only
+                * way a responder reuses TS */
+               *target = traffic_selector_list_create_from_enumerator(old_ts);
+               return;
+       }
+
+       /* when recreating/reauthenticating, we check whether the dynamic IPs of
+        * the IKE_SA (as copied from the old SA) match the TS and replace
+        * them with dynamic TS (reusing protocol/ports in case of narrowing) so
+        * they get updated to possibly new IPs when the TS are prepared later */
+       list = linked_list_create();
+       hosts = ike_sa_get_dynamic_hosts(this->ike_sa, local);
+       hosts_enum = hosts->create_enumerator(hosts);
+       while (old_ts->enumerate(old_ts, &ts))
+       {
+               new_ts = NULL;
+               while (hosts_enum->enumerate(hosts_enum, &host))
+               {
+                       if (ts->is_host(ts, host))
+                       {
+                               new_ts = traffic_selector_create_dynamic(ts->get_protocol(ts),
+                                                                                                                ts->get_from_port(ts),
+                                                                                                                ts->get_to_port(ts));
+                               break;
+                       }
+               }
+               hosts->reset_enumerator(hosts, hosts_enum);
+
+               if (!new_ts)
+               {
+                       new_ts = ts->clone(ts);
+               }
+               list->insert_last(list, new_ts);
+       }
+       hosts_enum->destroy(hosts_enum);
+       hosts->destroy(hosts);
+       old_ts->destroy(old_ts);
+
+       *target = traffic_selector_list_create_from_list(list);
+}
+
 METHOD(child_create_t, recreate_sa, void,
        private_child_create_t *this, child_sa_t *old)
 {
@@ -2560,6 +2664,10 @@ METHOD(child_create_t, recreate_sa, void,
                        this->ke_method = ke_method;
                }
        }
+
+       /* use previously negotiated traffic selectors */
+       reuse_ts(this, TRUE, old, &this->my_ts);
+       reuse_ts(this, FALSE, old, &this->other_ts);
 }
 
 METHOD(child_create_t, get_child, child_sa_t*,
@@ -2619,32 +2727,17 @@ METHOD(task_t, migrate, void,
        chunk_free(&this->my_nonce);
        chunk_free(&this->other_nonce);
        chunk_free(&this->link);
-       if (this->tsr)
-       {
-               this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy));
-       }
-       if (this->tsi)
-       {
-               this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy));
-       }
-       if (this->labels_i)
-       {
-               this->labels_i->destroy_offset(this->labels_i, offsetof(sec_label_t, destroy));
-       }
-       if (this->labels_r)
-       {
-               this->labels_r->destroy_offset(this->labels_r, offsetof(sec_label_t, destroy));
-       }
+       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));
+       DESTROY_OFFSET_IF(this->labels_r, offsetof(sec_label_t, destroy));
        DESTROY_IF(this->child_sa);
        DESTROY_IF(this->proposal);
        DESTROY_IF(this->nonceg);
        DESTROY_IF(this->ke);
        this->ke_failed = FALSE;
        clear_key_exchanges(this);
-       if (this->proposals)
-       {
-               this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy));
-       }
+       DESTROY_OFFSET_IF(this->proposals, offsetof(proposal_t, destroy));
        if (!this->rekey && !this->retry)
        {
                this->ke_method = KE_NONE;
@@ -2675,22 +2768,10 @@ METHOD(task_t, destroy, void,
        chunk_free(&this->my_nonce);
        chunk_free(&this->other_nonce);
        chunk_free(&this->link);
-       if (this->tsr)
-       {
-               this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy));
-       }
-       if (this->tsi)
-       {
-               this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy));
-       }
-       if (this->labels_i)
-       {
-               this->labels_i->destroy_offset(this->labels_i, offsetof(sec_label_t, destroy));
-       }
-       if (this->labels_r)
-       {
-               this->labels_r->destroy_offset(this->labels_r, offsetof(sec_label_t, destroy));
-       }
+       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));
+       DESTROY_OFFSET_IF(this->labels_r, offsetof(sec_label_t, destroy));
        if (!this->established)
        {
                DESTROY_IF(this->child_sa);
@@ -2701,13 +2782,12 @@ METHOD(task_t, destroy, void,
        }
        DESTROY_IF(this->packet_tsi);
        DESTROY_IF(this->packet_tsr);
+       DESTROY_IF(this->my_ts);
+       DESTROY_IF(this->other_ts);
        DESTROY_IF(this->proposal);
        DESTROY_IF(this->ke);
        clear_key_exchanges(this);
-       if (this->proposals)
-       {
-               this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy));
-       }
+       DESTROY_OFFSET_IF(this->proposals, offsetof(proposal_t, destroy));
        DESTROY_IF(this->config);
        DESTROY_IF(this->nonceg);
        DESTROY_IF(this->child.label);