child-create: Maintain traffic selectors during rekeying/reauthentication

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.
This commit is contained in:
Tobias Brunner 2025-04-01 17:33:35 +02:00
parent 9205458355
commit e7fc7a4ecc

View 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,13 +1473,66 @@ METHOD(task_t, build_i_multi_ke, status_t,
return NEED_MORE;
}
/**
* Prepare proposed traffic selectors as initiator.
*/
static void prepare_proposed_ts(private_child_create_t *this)
{
enumerator_t *enumerator;
peer_cfg_t *peer_cfg;
linked_list_t *list;
host_t *vip;
ensure_ts_lists(this);
list = linked_list_create();
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 a virtual IP */
vip = host_create_any(vip->get_family(vip));
list->insert_last(list, vip);
}
enumerator->destroy(enumerator);
}
if (list->get_count(list))
{
this->tsi = child_cfg_select_ts(this->config, TRUE, this->my_ts, NULL,
list);
list->destroy_offset(list, offsetof(host_t, destroy));
}
else
{
list->destroy(list);
list = ike_sa_get_dynamic_hosts(this->ike_sa, TRUE);
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 = child_cfg_select_ts(this->config, FALSE, this->other_ts, NULL,
list);
list->destroy(list);
if (this->packet_tsi)
{
this->tsi->insert_first(this->tsi,
this->packet_tsi->clone(this->packet_tsi));
}
if (this->packet_tsr)
{
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)
{
enumerator_t *enumerator;
host_t *vip;
peer_cfg_t *peer_cfg;
linked_list_t *list;
bool no_ke = TRUE;
switch (message->get_exchange_type(message))
@ -1477,49 +1568,7 @@ METHOD(task_t, build_i, status_t,
return NEED_MORE;
}
/* 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)
{
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 */
vip = host_create_any(vip->get_family(vip));
list->insert_last(list, vip);
}
enumerator->destroy(enumerator);
}
if (list->get_count(list))
{
this->tsi = this->config->select_traffic_selectors(this->config, TRUE,
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);
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);
list->destroy(list);
if (this->packet_tsi)
{
this->tsi->insert_first(this->tsi,
this->packet_tsi->clone(this->packet_tsi));
}
if (this->packet_tsr)
{
this->tsr->insert_first(this->tsr,
this->packet_tsr->clone(this->packet_tsr));
}
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);