From 3e6d7db5e3c20855924d18fb1a893741220740e4 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 19 Mar 2025 13:27:20 +0100 Subject: [PATCH 01/23] kernel-interface: Add members for optional acquire sequence number --- src/libcharon/kernel/kernel_handler.c | 12 ++++++++---- src/libcharon/kernel/kernel_ipsec.h | 2 ++ src/libcharon/kernel/kernel_listener.h | 2 ++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/libcharon/kernel/kernel_handler.c b/src/libcharon/kernel/kernel_handler.c index ba7c3b821..8bbb44917 100644 --- a/src/libcharon/kernel/kernel_handler.c +++ b/src/libcharon/kernel/kernel_handler.c @@ -56,22 +56,26 @@ static inline protocol_id_t proto_ip2ike(uint8_t protocol) METHOD(kernel_listener_t, acquire, bool, private_kernel_handler_t *this, uint32_t reqid, kernel_acquire_data_t *data) { - char buf[BUF_LEN] = ""; + char buf[BUF_LEN] = "", sbuf[BUF_LEN] = ""; if (data->label) { snprintf(buf, sizeof(buf), ", label {%s}", data->label->get_string(data->label)); } + if (data->seq) + { + snprintf(sbuf, sizeof(sbuf), ", seq {%u}", data->seq); + } if (data->src && data->dst) { DBG1(DBG_KNL, "creating acquire job for policy %R === %R with " - "reqid {%u}%s", data->src, data->dst, reqid, buf); + "reqid {%u}%s%s", data->src, data->dst, reqid, buf, sbuf); } else { - DBG1(DBG_KNL, "creating acquire job for policy with reqid {%u}%s", - reqid, buf); + DBG1(DBG_KNL, "creating acquire job for policy with reqid {%u}%s%s", + reqid, buf, sbuf); } lib->processor->queue_job(lib->processor, (job_t*)acquire_job_create(reqid, data)); diff --git a/src/libcharon/kernel/kernel_ipsec.h b/src/libcharon/kernel/kernel_ipsec.h index 0c7fca0a7..be78a9904 100644 --- a/src/libcharon/kernel/kernel_ipsec.h +++ b/src/libcharon/kernel/kernel_ipsec.h @@ -67,6 +67,8 @@ struct kernel_ipsec_sa_id_t { struct kernel_ipsec_add_sa_t { /** Reqid */ uint32_t reqid; + /** Optional sequence number associated with the acquire triggering the SA */ + uint32_t seq; /** Mode (tunnel, transport...) */ ipsec_mode_t mode; /** List of source traffic selectors */ diff --git a/src/libcharon/kernel/kernel_listener.h b/src/libcharon/kernel/kernel_listener.h index caea63fbc..a0dcddfc0 100644 --- a/src/libcharon/kernel/kernel_listener.h +++ b/src/libcharon/kernel/kernel_listener.h @@ -40,6 +40,8 @@ struct kernel_acquire_data_t { traffic_selector_t *dst; /** Optional security label of the triggering packet */ sec_label_t *label; + /** Optional sequence number associated with the acquire */ + uint32_t seq; }; /** From 0c9bac73d9485d3cf620c934b9c7a31f69b0d19f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 19 Mar 2025 13:28:12 +0100 Subject: [PATCH 02/23] child-sa: Store and propagate optional acquire sequence number --- src/libcharon/sa/child_sa.c | 21 +++++++++++++++++++++ src/libcharon/sa/child_sa.h | 19 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 726234207..245d87af7 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -161,6 +161,11 @@ struct private_child_sa_t { */ uint32_t unique_id; + /** + * Optional sequence number associated with triggering acquire + */ + uint32_t seq; + /** * Whether FWD policies in the outbound direction should be installed */ @@ -817,6 +822,18 @@ METHOD(child_sa_t, get_label, sec_label_t*, return this->label ?: this->config->get_label(this->config); } +METHOD(child_sa_t, get_acquire_seq, uint32_t, + private_child_sa_t *this) +{ + return this->seq; +} + +METHOD(child_sa_t, set_acquire_seq, void, + private_child_sa_t *this, uint32_t seq) +{ + this->seq = seq; +} + METHOD(child_sa_t, get_lifetime, time_t, private_child_sa_t *this, bool hard) { @@ -1022,6 +1039,7 @@ static status_t install_internal(private_child_sa_t *this, chunk_t encr, }; sa = (kernel_ipsec_add_sa_t){ .reqid = this->reqid, + .seq = this->seq, .mode = this->mode, .src_ts = src_ts, .dst_ts = dst_ts, @@ -2074,6 +2092,8 @@ child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, .get_mark = _get_mark, .get_if_id = _get_if_id, .get_label = _get_label, + .get_acquire_seq = _get_acquire_seq, + .set_acquire_seq = _set_acquire_seq, .has_encap = _has_encap, .get_ipcomp = _get_ipcomp, .set_ipcomp = _set_ipcomp, @@ -2112,6 +2132,7 @@ child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, .if_id_in = config->get_if_id(config, TRUE) ?: data->if_id_in_def, .if_id_out = config->get_if_id(config, FALSE) ?: data->if_id_out_def, .label = data->label ? data->label->clone(data->label) : NULL, + .seq = data->seq, .install_time = time_monotonic(NULL), .policies_fwd_out = config->has_option(config, OPT_FWD_OUT_POLICIES), ); diff --git a/src/libcharon/sa/child_sa.h b/src/libcharon/sa/child_sa.h index 7c3763b0a..43f306fe9 100644 --- a/src/libcharon/sa/child_sa.h +++ b/src/libcharon/sa/child_sa.h @@ -390,6 +390,22 @@ struct child_sa_t { */ sec_label_t *(*get_label)(child_sa_t *this); + /** + * Get the optional sequence number associated with the acquire that + * triggered this CHILD_SA. + * + * @return sequence number associated with the acquire or 0 + */ + uint32_t (*get_acquire_seq)(child_sa_t *this); + + /** + * Set the optional sequence number associated with the acquire that + * triggered this CHILD_SA. + * + * @param seq sequence number associated with the acquire + */ + void (*set_acquire_seq)(child_sa_t *this, uint32_t seq); + /** * Create an enumerator over traffic selectors of one side. * @@ -559,6 +575,9 @@ struct child_sa_create_t { uint32_t if_id_out_def; /** Optional security label to apply on SAs (cloned) */ sec_label_t *label; + /** Optional sequence number associated with the acquire that triggered + * this SA */ + uint32_t seq; /** TRUE to enable UDP encapsulation (NAT traversal) */ bool encap; }; From 82c82cbbd633cfaf3ecc66b022d33369a32f661c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 19 Mar 2025 13:28:44 +0100 Subject: [PATCH 03/23] ike: Support optional acquire sequence numbers when creating CHILD_SAs --- src/libcharon/sa/ike_sa.h | 2 ++ src/libcharon/sa/ikev1/task_manager_v1.c | 9 +++++---- src/libcharon/sa/ikev1/tasks/quick_mode.c | 6 +++++- src/libcharon/sa/ikev1/tasks/quick_mode.h | 4 +++- src/libcharon/sa/ikev2/task_manager_v2.c | 11 ++++++----- src/libcharon/sa/ikev2/tasks/child_create.c | 11 ++++++++--- src/libcharon/sa/ikev2/tasks/child_create.h | 6 ++++-- src/libcharon/sa/ikev2/tasks/child_delete.c | 2 +- src/libcharon/sa/ikev2/tasks/child_rekey.c | 5 +++-- 9 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/libcharon/sa/ike_sa.h b/src/libcharon/sa/ike_sa.h index 94f4fe7cc..c6fa62ff3 100644 --- a/src/libcharon/sa/ike_sa.h +++ b/src/libcharon/sa/ike_sa.h @@ -412,6 +412,8 @@ struct child_init_args_t { traffic_selector_t *dst; /** Optional security label of triggering packet */ sec_label_t *label; + /** Optional sequence number associated with the acquire triggering the SA */ + uint32_t seq; }; /** diff --git a/src/libcharon/sa/ikev1/task_manager_v1.c b/src/libcharon/sa/ikev1/task_manager_v1.c index c5a8e9a45..672a04927 100644 --- a/src/libcharon/sa/ikev1/task_manager_v1.c +++ b/src/libcharon/sa/ikev1/task_manager_v1.c @@ -1009,7 +1009,7 @@ static status_t process_request(private_task_manager_t *this, break; } task = (task_t *)quick_mode_create(this->ike_sa, NULL, - NULL, NULL); + NULL, NULL, 0); this->passive_tasks->insert_last(this->passive_tasks, task); break; case INFORMATIONAL_V1: @@ -1697,12 +1697,13 @@ METHOD(task_manager_t, queue_child, void, if (args) { - task = quick_mode_create(this->ike_sa, cfg, args->src, args->dst); + task = quick_mode_create(this->ike_sa, cfg, args->src, args->dst, + args->seq); task->use_reqid(task, args->reqid); } else { - task = quick_mode_create(this->ike_sa, cfg, NULL, NULL); + task = quick_mode_create(this->ike_sa, cfg, NULL, NULL, 0); } queue_task(this, &task->task); } @@ -1816,7 +1817,7 @@ METHOD(task_manager_t, queue_child_rekey, void, child_sa->set_state(child_sa, CHILD_REKEYING); cfg = child_sa->get_config(child_sa); task = quick_mode_create(this->ike_sa, cfg->get_ref(cfg), - get_first_ts(child_sa, TRUE), get_first_ts(child_sa, FALSE)); + get_first_ts(child_sa, TRUE), get_first_ts(child_sa, FALSE), 0); reqid = child_sa->get_reqid_ref(child_sa); if (reqid) { diff --git a/src/libcharon/sa/ikev1/tasks/quick_mode.c b/src/libcharon/sa/ikev1/tasks/quick_mode.c index 3589241cc..ea54b24d2 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_mode.c +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.c @@ -1546,7 +1546,8 @@ METHOD(task_t, destroy, void, * Described in header. */ quick_mode_t *quick_mode_create(ike_sa_t *ike_sa, child_cfg_t *config, - traffic_selector_t *tsi, traffic_selector_t *tsr) + traffic_selector_t *tsi, traffic_selector_t *tsr, + uint32_t seq) { private_quick_mode_t *this; @@ -1565,6 +1566,9 @@ quick_mode_t *quick_mode_create(ike_sa_t *ike_sa, child_cfg_t *config, .rekey = _rekey, .abort = _abort_, }, + .child = { + .seq = seq, + }, .ike_sa = ike_sa, .initiator = config != NULL, .config = config, diff --git a/src/libcharon/sa/ikev1/tasks/quick_mode.h b/src/libcharon/sa/ikev1/tasks/quick_mode.h index 85e986dc6..dbfbbaa80 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_mode.h +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.h @@ -102,9 +102,11 @@ struct quick_mode_t { * @param config child_cfg if task initiator, NULL if responder * @param tsi source of triggering packet, or NULL * @param tsr destination of triggering packet, or NULL + * @param seq optional sequence number of triggering acquire, or 0 * @return task to handle by the task_manager */ quick_mode_t *quick_mode_create(ike_sa_t *ike_sa, child_cfg_t *config, - traffic_selector_t *tsi, traffic_selector_t *tsr); + traffic_selector_t *tsi, traffic_selector_t *tsr, + uint32_t seq); #endif /** QUICK_MODE_H_ @}*/ diff --git a/src/libcharon/sa/ikev2/task_manager_v2.c b/src/libcharon/sa/ikev2/task_manager_v2.c index 1e213aaaf..2180bd909 100644 --- a/src/libcharon/sa/ikev2/task_manager_v2.c +++ b/src/libcharon/sa/ikev2/task_manager_v2.c @@ -1168,7 +1168,7 @@ static status_t process_request(private_task_manager_t *this, task = (task_t*)ike_auth_lifetime_create(this->ike_sa, FALSE); array_insert(this->passive_tasks, ARRAY_TAIL, task); task = (task_t*)child_create_create(this->ike_sa, NULL, FALSE, - NULL, NULL); + NULL, NULL, 0); array_insert(this->passive_tasks, ARRAY_TAIL, task); break; } @@ -1222,7 +1222,7 @@ static status_t process_request(private_task_manager_t *this, else { task = (task_t*)child_create_create(this->ike_sa, NULL, - FALSE, NULL, NULL); + FALSE, NULL, NULL, 0); } } else @@ -2196,7 +2196,7 @@ static void trigger_mbb_reauth(private_task_manager_t *this) } cfg = child_sa->get_config(child_sa); child_create = child_create_create(new, cfg->get_ref(cfg), - FALSE, NULL, NULL); + FALSE, NULL, NULL, 0); reqid = child_sa->get_reqid_ref(child_sa); if (reqid) { @@ -2375,13 +2375,14 @@ METHOD(task_manager_t, queue_child, void, if (args) { - task = child_create_create(this->ike_sa, cfg, FALSE, args->src, args->dst); + task = child_create_create(this->ike_sa, cfg, FALSE, args->src, + args->dst, args->seq); task->use_reqid(task, args->reqid); task->use_label(task, args->label); } else { - task = child_create_create(this->ike_sa, cfg, FALSE, NULL, NULL); + task = child_create_create(this->ike_sa, cfg, FALSE, NULL, NULL, 0); } queue_task(this, &task->task); } diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index ee76433f7..9efaed28f 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -249,7 +249,8 @@ static void schedule_delayed_retry(private_child_create_t *this) task = child_create_create(this->ike_sa, this->config->get_ref(this->config), FALSE, - this->packet_tsi, this->packet_tsr); + this->packet_tsi, this->packet_tsr, + this->child.seq); task->use_reqid(task, this->child.reqid); 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); @@ -2669,8 +2670,9 @@ METHOD(task_t, destroy, void, * Described in header. */ child_create_t *child_create_create(ike_sa_t *ike_sa, - child_cfg_t *config, bool rekey, - traffic_selector_t *tsi, traffic_selector_t *tsr) + child_cfg_t *config, bool rekey, + traffic_selector_t *tsi, + traffic_selector_t *tsr, uint32_t seq) { private_child_create_t *this; @@ -2693,6 +2695,9 @@ child_create_t *child_create_create(ike_sa_t *ike_sa, .destroy = _destroy, }, }, + .child = { + .seq = seq, + }, .ike_sa = ike_sa, .config = config, .packet_tsi = tsi ? tsi->clone(tsi) : NULL, diff --git a/src/libcharon/sa/ikev2/tasks/child_create.h b/src/libcharon/sa/ikev2/tasks/child_create.h index 0a65ba17a..f3ff0cc7d 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.h +++ b/src/libcharon/sa/ikev2/tasks/child_create.h @@ -140,10 +140,12 @@ struct child_create_t { * @param rekey whether we do a rekey or not * @param tsi source of triggering packet, or NULL * @param tsr destination of triggering packet, or NULL + * @param seq optional sequence number of triggering acquire, or 0 * @return child_create task to handle by the task_manager */ child_create_t *child_create_create(ike_sa_t *ike_sa, - child_cfg_t *config, bool rekey, - traffic_selector_t *tsi, traffic_selector_t *tsr); + child_cfg_t *config, bool rekey, + traffic_selector_t *tsi, + traffic_selector_t *tsr, uint32_t seq); #endif /** CHILD_CREATE_H_ @}*/ diff --git a/src/libcharon/sa/ikev2/tasks/child_delete.c b/src/libcharon/sa/ikev2/tasks/child_delete.c index e2e198b28..3282a21f7 100644 --- a/src/libcharon/sa/ikev2/tasks/child_delete.c +++ b/src/libcharon/sa/ikev2/tasks/child_delete.c @@ -556,7 +556,7 @@ METHOD(task_t, build_i, status_t, child_cfg = child_sa->get_config(child_sa); this->ike_sa->queue_task(this->ike_sa, (task_t*) child_create_create(this->ike_sa, child_cfg->get_ref(child_cfg), - FALSE, NULL, NULL)); + FALSE, NULL, NULL, 0)); } return NEED_MORE; } diff --git a/src/libcharon/sa/ikev2/tasks/child_rekey.c b/src/libcharon/sa/ikev2/tasks/child_rekey.c index 6c73d0671..86161423b 100644 --- a/src/libcharon/sa/ikev2/tasks/child_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/child_rekey.c @@ -275,7 +275,7 @@ METHOD(task_t, build_i, status_t, config = this->child_sa->get_config(this->child_sa); this->child_create = child_create_create(this->ike_sa, - config->get_ref(config), TRUE, NULL, NULL); + config->get_ref(config), TRUE, NULL, NULL, 0); proposal = this->child_sa->get_proposal(this->child_sa); if (proposal->get_algorithm(proposal, KEY_EXCHANGE_METHOD, @@ -1240,7 +1240,8 @@ child_rekey_t *child_rekey_create(ike_sa_t *ike_sa, protocol_id_t protocol, this->public.task.build = _build_r; this->public.task.process = _process_r; this->initiator = FALSE; - this->child_create = child_create_create(ike_sa, NULL, TRUE, NULL, NULL); + this->child_create = child_create_create(ike_sa, NULL, TRUE, + NULL, NULL, 0); } return &this->public; From 84da416082268a67be97ebb175673210ec4154da Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 19 Mar 2025 14:20:46 +0100 Subject: [PATCH 04/23] trap-manager: Use sequence numbers to identify acquires Either use the sequence number from the kernel (and potentially update it if the acquire was retriggered), or generate our own sequence numbers, which simplifies matching acquires to established/destroyed CHILD_SAs. --- src/libcharon/sa/trap_manager.c | 140 +++++++++++++++++++++----------- 1 file changed, 91 insertions(+), 49 deletions(-) diff --git a/src/libcharon/sa/trap_manager.c b/src/libcharon/sa/trap_manager.c index 1b85c66a5..5dcb49b49 100644 --- a/src/libcharon/sa/trap_manager.c +++ b/src/libcharon/sa/trap_manager.c @@ -93,6 +93,11 @@ struct private_trap_manager_t { * Whether to ignore traffic selectors from acquires */ bool ignore_acquire_ts; + + /** + * Current acquire sequence number if not generated by the kernel + */ + refcount_t acquire_seq; }; /** @@ -123,6 +128,10 @@ typedef struct { host_t *dst; /** security label, if any */ sec_label_t *label; + /** sequence number from the kernel or generated by the trap manager */ + uint32_t seq; + /** optional sequence number from the kernel if an acquire is retriggered */ + uint32_t new_seq; } acquire_t; /** @@ -519,13 +528,14 @@ METHOD(trap_manager_t, acquire, void, { enumerator_t *enumerator; entry_t *entry, *found = NULL; - acquire_t *acquire; + acquire_t *acquire = NULL; peer_cfg_t *peer; child_cfg_t *child; ike_sa_t *ike_sa; - host_t *host; - uint32_t allocated_reqid; - bool wildcard, ignore = FALSE; + host_t *host = NULL; + sec_label_t *label = NULL; + uint32_t allocated_reqid, seq = 0; + bool wildcard; this->lock->read_lock(this->lock); enumerator = this->traps->create_enumerator(this->traps); @@ -550,44 +560,42 @@ METHOD(trap_manager_t, acquire, void, this->mutex->lock(this->mutex); if (wildcard) - { /* for wildcard acquires we check that we don't have a pending acquire + { + /* for wildcard acquires we check that we don't have a pending acquire * with the same peer */ uint8_t mask; data->dst->to_subnet(data->dst, &host, &mask); if (this->acquires->find_first(this->acquires, acquire_by_dst, - (void**)&acquire, host)) + (void**)&acquire, host)) { host->destroy(host); - ignore = TRUE; - } - else - { - INIT(acquire, - .dst = host, - .reqid = reqid, - ); - this->acquires->insert_last(this->acquires, acquire); } } - else + else if (!this->acquires->find_first(this->acquires, acquire_by_reqid, + (void**)&acquire, reqid, data->label)) { - if (this->acquires->find_first(this->acquires, acquire_by_reqid, - (void**)&acquire, reqid, data->label)) - { - ignore = TRUE; - } - else - { - INIT(acquire, - .reqid = reqid, - .label = data->label ? data->label->clone(data->label) : NULL, - ); - this->acquires->insert_last(this->acquires, acquire); - } + label = data->label ? data->label->clone(data->label) : NULL; + } + if (!acquire) + { + seq = data->seq ?: ref_get_nonzero(&this->acquire_seq); + + INIT(acquire, + .dst = host, + .reqid = reqid, + .label = label, + .seq = seq, + ); + this->acquires->insert_last(this->acquires, acquire); + } + else if (data->seq && acquire->seq != data->seq) + { + /* acquire got retriggered, update to latest sequence number */ + acquire->new_seq = data->seq; } this->mutex->unlock(this->mutex); - if (ignore) + if (!seq) { DBG1(DBG_CFG, "ignoring acquire for reqid %u, connection attempt " "pending", reqid); @@ -642,6 +650,7 @@ METHOD(trap_manager_t, acquire, void, .src = data->src, .dst = data->dst, .label = data->label, + .seq = seq, }; if (this->ignore_acquire_ts || ike_sa->get_version(ike_sa) == IKEV1) @@ -679,39 +688,69 @@ METHOD(trap_manager_t, acquire, void, } /** - * Complete the acquire, if successful or failed + * Update the sequence number of the CHILD_SA before installing it in case + * the acquire got retriggered. + */ +static void update_acquire_seq(private_trap_manager_t *this, ike_sa_t *ike_sa, + child_sa_t *child_sa) +{ + enumerator_t *enumerator; + acquire_t *acquire; + uint32_t seq; + + /* ignore trap policies and CHILD_SAs not triggered by an acquire */ + if (!ike_sa || !(seq = child_sa->get_acquire_seq(child_sa))) + { + return; + } + + this->mutex->lock(this->mutex); + enumerator = this->acquires->create_enumerator(this->acquires); + while (enumerator->enumerate(enumerator, &acquire)) + { + if (!acquire->ike_sa || seq != acquire->seq) + { + continue; + } + if (acquire->new_seq) + { + child_sa->set_acquire_seq(child_sa, acquire->new_seq); + acquire->seq = acquire->new_seq; + acquire->new_seq = 0; + } + break; + } + enumerator->destroy(enumerator); + this->mutex->unlock(this->mutex); +} + +/** + * Complete the acquire, if successful or failed. */ static void complete(private_trap_manager_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa) { enumerator_t *enumerator; acquire_t *acquire; + uint32_t seq = 0; + + /* ignore trap policies and CHILD_SAs not triggered by an acquire */ + if (!ike_sa || (child_sa && !(seq = child_sa->get_acquire_seq(child_sa)))) + { + return; + } this->mutex->lock(this->mutex); enumerator = this->acquires->create_enumerator(this->acquires); while (enumerator->enumerate(enumerator, &acquire)) { - if (!acquire->ike_sa || acquire->ike_sa != ike_sa) + /* just look at the sequence number when handling a CHILD_SA event, + * otherwise, compare the IKE_SA */ + if (!acquire->ike_sa || (seq && seq != acquire->seq) || + (!seq && acquire->ike_sa != ike_sa)) { continue; } - if (child_sa) - { - if (acquire->dst) - { - /* since every wildcard acquire results in a separate IKE_SA - * there is no need to compare the destination address */ - } - else if (child_sa->get_reqid(child_sa) != acquire->reqid) - { - continue; - } - else if (!sec_labels_equal(acquire->label, - child_sa->get_label(child_sa))) - { - continue; - } - } this->acquires->remove_at(this->acquires, enumerator); destroy_acquire(acquire); } @@ -738,6 +777,9 @@ METHOD(listener_t, child_state_change, bool, { switch (state) { + case CHILD_INSTALLING: + update_acquire_seq(listener->traps, ike_sa, child_sa); + return TRUE; case CHILD_INSTALLED: case CHILD_DESTROYING: complete(listener->traps, ike_sa, child_sa); From c5b2a8eaa37544369079862d1f2419eea1610993 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 19 Mar 2025 15:34:58 +0100 Subject: [PATCH 05/23] child-cfg: Use separate method to get configured traffic selectors Optionally with "dynamic" traffic selectors resolved. A new method is added for those cases where we actually want to select potentially narrowed traffic selectors using a supplied list. The latter now also always logs details, while the former does not. --- src/libcharon/config/child_cfg.c | 218 +++++++++++-------- src/libcharon/config/child_cfg.h | 44 +++- src/libcharon/config/peer_cfg.c | 2 +- src/libcharon/plugins/smp/smp.c | 6 +- src/libcharon/plugins/stroke/stroke_list.c | 11 +- src/libcharon/plugins/unity/unity_narrow.c | 5 +- src/libcharon/plugins/unity/unity_provider.c | 3 +- src/libcharon/plugins/vici/vici_query.c | 10 +- src/libcharon/sa/child_sa.c | 2 +- src/libcharon/sa/ikev1/tasks/quick_mode.c | 4 +- src/libcharon/sa/ikev2/tasks/child_create.c | 20 +- src/libcharon/sa/shunt_manager.c | 12 +- src/libcharon/sa/trap_manager.c | 6 +- 13 files changed, 190 insertions(+), 153 deletions(-) diff --git a/src/libcharon/config/child_cfg.c b/src/libcharon/config/child_cfg.c index 837495c59..23c96646e 100644 --- a/src/libcharon/config/child_cfg.c +++ b/src/libcharon/config/child_cfg.c @@ -266,81 +266,138 @@ METHOD(child_cfg_t, add_traffic_selector, void, } } +/** + * Create a copy of the traffic selectors in the given list, while resolving + * "dynamic" traffic selectors using the given hosts, if any. When not narrowing + * as initiator, we also replace TS in transport mode. + */ +static linked_list_t *resolve_dynamic_ts(private_child_cfg_t *this, + linked_list_t *list, + linked_list_t *hosts, + bool narrowing) +{ + enumerator_t *e1, *e2; + traffic_selector_t *ts1, *ts2; + linked_list_t *result; + host_t *host; + bool transport_mode; + + if (!hosts || !hosts->get_count(hosts)) + { + return list->clone_offset(list, offsetof(traffic_selector_t, clone)); + } + transport_mode = !narrowing && this->mode == MODE_TRANSPORT && + !has_option(this, OPT_PROXY_MODE); + + result = linked_list_create(); + e1 = list->create_enumerator(list); + while (e1->enumerate(e1, &ts1)) + { + /* set hosts if TS is dynamic or as initiator in transport mode */ + bool dynamic = ts1->is_dynamic(ts1); + if (!dynamic && !transport_mode) + { + result->insert_last(result, ts1->clone(ts1)); + continue; + } + e2 = hosts->create_enumerator(hosts); + while (e2->enumerate(e2, &host)) + { + if (!dynamic && !host->is_anyaddr(host) && + !ts1->includes(ts1, host)) + { /* for transport mode, we skip TS that don't match + * specific IPs */ + continue; + } + ts2 = ts1->clone(ts1); + if (dynamic || !host->is_anyaddr(host)) + { /* don't make regular TS larger than they were */ + ts2->set_address(ts2, host); + } + result->insert_last(result, ts2); + } + e2->destroy(e2); + } + e1->destroy(e1); + return result; +} + +/** + * Remove duplicate traffic selectors in the given list. + */ +static void remove_duplicate_ts(linked_list_t *list) +{ + enumerator_t *e1, *e2; + traffic_selector_t *ts1, *ts2; + + e1 = list->create_enumerator(list); + e2 = list->create_enumerator(list); + while (e1->enumerate(e1, &ts1)) + { + while (e2->enumerate(e2, &ts2)) + { + if (ts1 != ts2) + { + if (ts2->is_contained_in(ts2, ts1)) + { + list->remove_at(list, e2); + ts2->destroy(ts2); + list->reset_enumerator(list, e1); + break; + } + if (ts1->is_contained_in(ts1, ts2)) + { + list->remove_at(list, e1); + ts1->destroy(ts1); + break; + } + } + } + list->reset_enumerator(list, e2); + } + e1->destroy(e1); + e2->destroy(e2); +} + METHOD(child_cfg_t, get_traffic_selectors, linked_list_t*, + private_child_cfg_t *this, bool local, linked_list_t *hosts) +{ + linked_list_t *result; + + result = resolve_dynamic_ts(this, local ? this->my_ts : this->other_ts, + hosts, FALSE); + remove_duplicate_ts(result); + return result; +} + +METHOD(child_cfg_t, select_traffic_selectors, linked_list_t*, private_child_cfg_t *this, bool local, linked_list_t *supplied, - linked_list_t *hosts, bool log) + linked_list_t *hosts) { enumerator_t *e1, *e2; traffic_selector_t *ts1, *ts2, *selected; - linked_list_t *result, *derived; - host_t *host; + linked_list_t *resolved, *result; result = linked_list_create(); - derived = linked_list_create(); - if (local) - { - e1 = this->my_ts->create_enumerator(this->my_ts); - } - else - { - e1 = this->other_ts->create_enumerator(this->other_ts); - } - /* in a first step, replace "dynamic" TS with the host list */ - while (e1->enumerate(e1, &ts1)) - { - if (hosts && hosts->get_count(hosts)) - { /* set hosts if TS is dynamic or as initiator in transport mode */ - bool dynamic = ts1->is_dynamic(ts1), - proxy_mode = has_option(this, OPT_PROXY_MODE); - if (dynamic || (this->mode == MODE_TRANSPORT && !proxy_mode && - !supplied)) - { - e2 = hosts->create_enumerator(hosts); - while (e2->enumerate(e2, &host)) - { - if (!dynamic && !host->is_anyaddr(host) && - !ts1->includes(ts1, host)) - { /* for transport mode, we skip TS that don't match - * specific IPs */ - continue; - } - ts2 = ts1->clone(ts1); - if (dynamic || !host->is_anyaddr(host)) - { /* don't make regular TS larger than they were */ - ts2->set_address(ts2, host); - } - derived->insert_last(derived, ts2); - } - e2->destroy(e2); - continue; - } - } - derived->insert_last(derived, ts1->clone(ts1)); - } - e1->destroy(e1); + resolved = resolve_dynamic_ts(this, local ? this->my_ts : this->other_ts, + hosts, supplied); + + DBG2(DBG_CFG, "%s traffic selectors for %s:", + supplied ? "selecting" : "proposing", local ? "us" : "other"); - if (log) - { - DBG2(DBG_CFG, "%s traffic selectors for %s:", - supplied ? "selecting" : "proposing", local ? "us" : "other"); - } if (!supplied) { - while (derived->remove_first(derived, (void**)&ts1) == SUCCESS) + while (resolved->remove_first(resolved, (void**)&ts1) == SUCCESS) { - if (log) - { - DBG2(DBG_CFG, " %R", ts1); - } + DBG2(DBG_CFG, " %R", ts1); result->insert_last(result, ts1); } - derived->destroy(derived); } else { - e1 = derived->create_enumerator(derived); + e1 = resolved->create_enumerator(resolved); e2 = supplied->create_enumerator(supplied); - /* enumerate all configured/derived selectors */ + /* enumerate all configured/resolved selectors */ while (e1->enumerate(e1, &ts1)) { /* enumerate all supplied traffic selectors */ @@ -349,14 +406,11 @@ METHOD(child_cfg_t, get_traffic_selectors, linked_list_t*, selected = ts1->get_subset(ts1, ts2); if (selected) { - if (log) - { - DBG2(DBG_CFG, " config: %R, received: %R => match: %R", - ts1, ts2, selected); - } + DBG2(DBG_CFG, " config: %R, received: %R => match: %R", + ts1, ts2, selected); result->insert_last(result, selected); } - else if (log) + else { DBG2(DBG_CFG, " config: %R, received: %R => no match", ts1, ts2); @@ -368,7 +422,7 @@ METHOD(child_cfg_t, get_traffic_selectors, linked_list_t*, e2->destroy(e2); /* check if we/peer did any narrowing, raise alert */ - e1 = derived->create_enumerator(derived); + e1 = resolved->create_enumerator(resolved); e2 = result->create_enumerator(result); while (e1->enumerate(e1, &ts1)) { @@ -382,38 +436,9 @@ METHOD(child_cfg_t, get_traffic_selectors, linked_list_t*, e1->destroy(e1); e2->destroy(e2); - derived->destroy_offset(derived, offsetof(traffic_selector_t, destroy)); } - - /* remove any redundant traffic selectors in the list */ - e1 = result->create_enumerator(result); - e2 = result->create_enumerator(result); - while (e1->enumerate(e1, &ts1)) - { - while (e2->enumerate(e2, &ts2)) - { - if (ts1 != ts2) - { - if (ts2->is_contained_in(ts2, ts1)) - { - result->remove_at(result, e2); - ts2->destroy(ts2); - result->reset_enumerator(result, e1); - break; - } - if (ts1->is_contained_in(ts1, ts2)) - { - result->remove_at(result, e1); - ts1->destroy(ts1); - break; - } - } - } - result->reset_enumerator(result, e2); - } - e1->destroy(e1); - e2->destroy(e2); - + resolved->destroy_offset(resolved, offsetof(traffic_selector_t, destroy)); + remove_duplicate_ts(result); return result; } @@ -755,6 +780,7 @@ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data) .get_name = _get_name, .add_traffic_selector = _add_traffic_selector, .get_traffic_selectors = _get_traffic_selectors, + .select_traffic_selectors = _select_traffic_selectors, .add_proposal = _add_proposal, .get_proposals = _get_proposals, .select_proposal = _select_proposal, diff --git a/src/libcharon/config/child_cfg.h b/src/libcharon/config/child_cfg.h index 4a9a699df..eb78b3d32 100644 --- a/src/libcharon/config/child_cfg.h +++ b/src/libcharon/config/child_cfg.h @@ -119,26 +119,50 @@ struct child_cfg_t { traffic_selector_t *ts); /** - * Get a list of traffic selectors to use for the CHILD_SA. + * Get a list of configured traffic selectors to use for the CHILD_SA. * * The config contains two set of traffic selectors, one for the local * side, one for the remote side. + * + * Some traffic selectors may be "dynamic", meaning they are narrowed down + * to a specific address (host-to-host or virtual-IP setups). Use the + * "hosts" parameter to narrow such traffic selectors to an address. + * + * Returned list and its traffic selectors must be destroyed after use. + * + * Note that this method does not log anything. If logging is required, use + * select_traffic_selectors() without passing supplied traffic selectors. + * + * @param local TRUE for TS on local side, FALSE for remote + * @param hosts addresses to use for narrowing "dynamic" TS', host_t + * @return list containing the traffic selectors + */ + linked_list_t *(*get_traffic_selectors)(child_cfg_t *this, bool local, + linked_list_t *hosts); + + /** + * Select a list of traffic selectors to use for the CHILD_SA. + * + * The config contains two set of traffic selectors, one for the local + * side, one for the remote side. + * * If a list with traffic selectors is supplied, these are used to narrow - * down the traffic selector list to the greatest common divisor. - * Some traffic selector may be "dynamic", meaning they are narrowed down - * to a specific address (host-to-host or virtual-IP setups). Use - * the "host" parameter to narrow such traffic selectors to that address. - * Resulted list and its traffic selectors must be destroyed after use. + * down the traffic selector list to the greatest common subset. + * + * Some traffic selectors may be "dynamic", meaning they are narrowed down + * to a specific address (host-to-host or virtual-IP setups). Use the + * "hosts" parameter to narrow such traffic selectors to an address. + * + * Returned list and its traffic selectors must be destroyed after use. * * @param local TRUE for TS on local side, FALSE for remote * @param supplied list with TS to select from, or NULL * @param hosts addresses to use for narrowing "dynamic" TS', host_t - * @param log FALSE to avoid logging details about the selection * @return list containing the traffic selectors */ - linked_list_t *(*get_traffic_selectors)(child_cfg_t *this, bool local, - linked_list_t *supplied, - linked_list_t *hosts, bool log); + linked_list_t *(*select_traffic_selectors)(child_cfg_t *this, bool local, + linked_list_t *supplied, + linked_list_t *hosts); /** * Get the updown script to run for the CHILD_SA. diff --git a/src/libcharon/config/peer_cfg.c b/src/libcharon/config/peer_cfg.c index c44f87025..a0da01724 100644 --- a/src/libcharon/config/peer_cfg.c +++ b/src/libcharon/config/peer_cfg.c @@ -414,7 +414,7 @@ static u_int get_ts_match(child_cfg_t *cfg, bool local, } /* fetch configured TS list, narrowing dynamic TS */ - cfg_list = cfg->get_traffic_selectors(cfg, local, NULL, hosts, TRUE); + cfg_list = cfg->select_traffic_selectors(cfg, local, NULL, hosts); /* use a round counter to rate leading TS with higher priority */ round = sup_list->get_count(sup_list); diff --git a/src/libcharon/plugins/smp/smp.c b/src/libcharon/plugins/smp/smp.c index 85ff5830b..d2afea708 100644 --- a/src/libcharon/plugins/smp/smp.c +++ b/src/libcharon/plugins/smp/smp.c @@ -330,12 +330,10 @@ static void request_query_config(xmlTextReaderPtr reader, xmlTextWriterPtr write xmlTextWriterStartElement(writer, "childconfig"); xmlTextWriterWriteElement(writer, "name", child_cfg->get_name(child_cfg)); - list = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL, - NULL, FALSE); + list = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL); write_networks(writer, "local", list); list->destroy_offset(list, offsetof(traffic_selector_t, destroy)); - list = child_cfg->get_traffic_selectors(child_cfg, FALSE, NULL, - NULL, FALSE); + list = child_cfg->get_traffic_selectors(child_cfg, FALSE, NULL); write_networks(writer, "remote", list); list->destroy_offset(list, offsetof(traffic_selector_t, destroy)); xmlTextWriterEndElement(writer); diff --git a/src/libcharon/plugins/stroke/stroke_list.c b/src/libcharon/plugins/stroke/stroke_list.c index 34d840f75..49e5eba07 100644 --- a/src/libcharon/plugins/stroke/stroke_list.c +++ b/src/libcharon/plugins/stroke/stroke_list.c @@ -585,10 +585,9 @@ METHOD(stroke_list_t, status, void, children = peer_cfg->create_child_cfg_enumerator(peer_cfg); while (children->enumerate(children, &child_cfg)) { - my_ts = child_cfg->get_traffic_selectors(child_cfg, TRUE, - NULL, NULL, FALSE); + my_ts = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL); other_ts = child_cfg->get_traffic_selectors(child_cfg, FALSE, - NULL, NULL, FALSE); + NULL); fprintf(out, "%12s: child: %#R === %#R %N", child_cfg->get_name(child_cfg), my_ts, other_ts, ipsec_mode_names, child_cfg->get_mode(child_cfg)); @@ -621,10 +620,8 @@ METHOD(stroke_list_t, status, void, fprintf(out, "Shunted Connections:\n"); first = FALSE; } - my_ts = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL, - NULL, FALSE); - other_ts = child_cfg->get_traffic_selectors(child_cfg, FALSE, NULL, - NULL, FALSE); + my_ts = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL); + other_ts = child_cfg->get_traffic_selectors(child_cfg, FALSE, NULL); fprintf(out, "%12s: %#R === %#R %N\n", child_cfg->get_name(child_cfg), my_ts, other_ts, ipsec_mode_names, child_cfg->get_mode(child_cfg)); diff --git a/src/libcharon/plugins/unity/unity_narrow.c b/src/libcharon/plugins/unity/unity_narrow.c index 1a3c74935..a0337a25c 100644 --- a/src/libcharon/plugins/unity/unity_narrow.c +++ b/src/libcharon/plugins/unity/unity_narrow.c @@ -55,7 +55,7 @@ static void narrow_ts(child_cfg_t *cfg, traffic_selector_t *ts, received = linked_list_create(); received->insert_last(received, ts); - selected = cfg->get_traffic_selectors(cfg, FALSE, received, NULL, FALSE); + selected = cfg->select_traffic_selectors(cfg, FALSE, received, NULL); while (selected->remove_first(selected, (void**)&ts) == SUCCESS) { list->insert_last(list, ts); @@ -139,8 +139,7 @@ static void narrow_responder_post(child_cfg_t *child_cfg, linked_list_t *local) { ts->destroy(ts); } - configured = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL, NULL, - FALSE); + configured = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL); while (configured->remove_first(configured, (void**)&ts) == SUCCESS) { diff --git a/src/libcharon/plugins/unity/unity_provider.c b/src/libcharon/plugins/unity/unity_provider.c index ffdc723b1..133907ebf 100644 --- a/src/libcharon/plugins/unity/unity_provider.c +++ b/src/libcharon/plugins/unity/unity_provider.c @@ -159,8 +159,7 @@ METHOD(attribute_provider_t, create_attribute_enumerator, enumerator_t*, enumerator = peer_cfg->create_child_cfg_enumerator(peer_cfg); while (enumerator->enumerate(enumerator, &child_cfg)) { - current = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL, NULL, - FALSE); + current = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL); while (current->remove_first(current, (void**)&ts) == SUCCESS) { if (use_ts(ts)) diff --git a/src/libcharon/plugins/vici/vici_query.c b/src/libcharon/plugins/vici/vici_query.c index 628763e19..7bea7579b 100644 --- a/src/libcharon/plugins/vici/vici_query.c +++ b/src/libcharon/plugins/vici/vici_query.c @@ -684,7 +684,7 @@ static void raise_policy_cfg(private_vici_query_t *this, u_int id, char *ike, list_label(b, NULL, cfg); b->begin_list(b, "local-ts"); - list = cfg->get_traffic_selectors(cfg, TRUE, NULL, NULL, FALSE); + list = cfg->get_traffic_selectors(cfg, TRUE, NULL); enumerator = list->create_enumerator(list); while (enumerator->enumerate(enumerator, &ts)) { @@ -695,7 +695,7 @@ static void raise_policy_cfg(private_vici_query_t *this, u_int id, char *ike, b->end_list(b /* local-ts */); b->begin_list(b, "remote-ts"); - list = cfg->get_traffic_selectors(cfg, FALSE, NULL, NULL, FALSE); + list = cfg->get_traffic_selectors(cfg, FALSE, NULL); enumerator = list->create_enumerator(list); while (enumerator->enumerate(enumerator, &ts)) { @@ -1002,8 +1002,7 @@ CALLBACK(list_conns, vici_message_t*, child_cfg->get_close_action(child_cfg)); b->begin_list(b, "local-ts"); - list = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL, - NULL, FALSE); + list = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL); selectors = list->create_enumerator(list); while (selectors->enumerate(selectors, &ts)) { @@ -1014,8 +1013,7 @@ CALLBACK(list_conns, vici_message_t*, b->end_list(b /* local-ts */); b->begin_list(b, "remote-ts"); - list = child_cfg->get_traffic_selectors(child_cfg, FALSE, NULL, - NULL, FALSE); + list = child_cfg->get_traffic_selectors(child_cfg, FALSE, NULL); selectors = list->create_enumerator(list); while (selectors->enumerate(selectors, &ts)) { diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 245d87af7..3c4f721ca 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -2036,7 +2036,7 @@ static host_t* get_proxy_addr(child_cfg_t *config, host_t *ike, bool local) traffic_selector_t *ts; list = linked_list_create_with_items(ike, NULL); - ts_list = config->get_traffic_selectors(config, local, NULL, list, FALSE); + ts_list = config->get_traffic_selectors(config, local, list); list->destroy(list); enumerator = ts_list->create_enumerator(ts_list); diff --git a/src/libcharon/sa/ikev1/tasks/quick_mode.c b/src/libcharon/sa/ikev1/tasks/quick_mode.c index ea54b24d2..686bc9ea9 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_mode.c +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.c @@ -540,8 +540,8 @@ static traffic_selector_t* select_ts(private_quick_mode_t *this, bool local, linked_list_t *list, *hosts; hosts = get_dynamic_hosts(this->ike_sa, local); - list = this->config->get_traffic_selectors(this->config, - local, supplied, hosts, TRUE); + list = this->config->select_traffic_selectors(this->config, local, + supplied, hosts); hosts->destroy(hosts); if (list->get_first(list, (void**)&ts) == SUCCESS) { diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index 9efaed28f..791ca4b36 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -468,14 +468,14 @@ 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->get_traffic_selectors(this->config, local, nat, - hosts, TRUE); + ts = this->config->select_traffic_selectors(this->config, local, nat, + hosts); nat->destroy_offset(nat, offsetof(traffic_selector_t, destroy)); } else { - ts = this->config->get_traffic_selectors(this->config, local, in, - hosts, TRUE); + ts = this->config->select_traffic_selectors(this->config, local, in, + hosts); } hosts->destroy(hosts); @@ -1461,21 +1461,21 @@ METHOD(task_t, build_i, status_t, } if (list->get_count(list)) { - this->tsi = this->config->get_traffic_selectors(this->config, - TRUE, NULL, list, TRUE); + 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->get_traffic_selectors(this->config, - TRUE, NULL, list, 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->get_traffic_selectors(this->config, - FALSE, NULL, list, TRUE); + this->tsr = this->config->select_traffic_selectors(this->config, FALSE, + NULL, list); list->destroy(list); if (this->packet_tsi) diff --git a/src/libcharon/sa/shunt_manager.c b/src/libcharon/sa/shunt_manager.c index c19b1fa39..b0c674c64 100644 --- a/src/libcharon/sa/shunt_manager.c +++ b/src/libcharon/sa/shunt_manager.c @@ -119,10 +119,8 @@ static bool install_shunt_policy(child_cfg_t *child) host_any6 = host_create_any(AF_INET6); hosts = linked_list_create_with_items(host_any, host_any6, NULL); - my_ts_list = child->get_traffic_selectors(child, TRUE, NULL, hosts, - FALSE); - other_ts_list = child->get_traffic_selectors(child, FALSE, NULL, hosts, - FALSE); + my_ts_list = child->get_traffic_selectors(child, TRUE, hosts); + other_ts_list = child->get_traffic_selectors(child, FALSE, hosts); hosts->destroy(hosts); manual_prio = child->get_manual_prio(child); @@ -293,10 +291,8 @@ static void uninstall_shunt_policy(child_cfg_t *child) host_any6 = host_create_any(AF_INET6); hosts = linked_list_create_with_items(host_any, host_any6, NULL); - my_ts_list = child->get_traffic_selectors(child, TRUE, NULL, hosts, - FALSE); - other_ts_list = child->get_traffic_selectors(child, FALSE, NULL, hosts, - FALSE); + my_ts_list = child->get_traffic_selectors(child, TRUE, hosts); + other_ts_list = child->get_traffic_selectors(child, FALSE, hosts); hosts->destroy(hosts); manual_prio = child->get_manual_prio(child); diff --git a/src/libcharon/sa/trap_manager.c b/src/libcharon/sa/trap_manager.c index 5dcb49b49..7bd2c698f 100644 --- a/src/libcharon/sa/trap_manager.c +++ b/src/libcharon/sa/trap_manager.c @@ -187,7 +187,7 @@ static bool dynamic_remote_ts(child_cfg_t *child) traffic_selector_t *ts; bool found = FALSE; - other_ts = child->get_traffic_selectors(child, FALSE, NULL, NULL, FALSE); + other_ts = child->get_traffic_selectors(child, FALSE, NULL); enumerator = other_ts->create_enumerator(other_ts); while (enumerator->enumerate(enumerator, &ts)) { @@ -215,8 +215,8 @@ static status_t install_trap(child_sa_t *child_sa, linked_list_t *local, child = child_sa->get_config(child_sa); - my_ts = child->get_traffic_selectors(child, TRUE, NULL, local, FALSE); - other_ts = child->get_traffic_selectors(child, FALSE, NULL, remote, FALSE); + my_ts = child->get_traffic_selectors(child, TRUE, local); + other_ts = child->get_traffic_selectors(child, FALSE, remote); /* we don't know the finally negotiated protocol (ESP|AH), we install * the SA with the protocol of the first proposal */ From 46c338a78fef5793f4846cd18a8fb538da471794 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 20 Mar 2025 14:52:32 +0100 Subject: [PATCH 06/23] kernel-interface: Change reqid if seq. nos. are supported and narrowing occurred With the sequence numbers we don't have to maintain the reqid to delete the temporary state. One exception is with labels. There we currently only install trap policies with the generic label. SAs created from those don't have policies installed, so we have to reuse the reqid of the trap even if narrowing occurs. And as before, we reuse the reqid without checking traffic selectors if sequence numbers are not supported. Note that if a CHILD_SA is manually initiated (i.e. has no sequence number assigned) right before an acquire is triggered, there are several possible outcomes depending on whether narrowing occurs. If there is no narrowing, the same reqid is assigned and the kernel will remove the temporary SA when the SA is installed (no seq => reqid match). Afterwards, the queued duplicate CHILD_SA is destroyed and the acquire state in the trap manager gets removed. If there is narrowing, a new reqid is allocated, so the installation of the SA will not remove the temporary state. However, due to the narrowing, the duplicate check fails and when the duplicate is installed (with sequence number), the temporary state is deleted (as is the state in the trap manager). --- src/libcharon/kernel/kernel_interface.c | 17 ++++++++++++----- src/libcharon/kernel/kernel_interface.h | 2 ++ src/libcharon/sa/child_sa.c | 15 ++++++++------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/libcharon/kernel/kernel_interface.c b/src/libcharon/kernel/kernel_interface.c index ba5c03b9b..b752ad744 100644 --- a/src/libcharon/kernel/kernel_interface.c +++ b/src/libcharon/kernel/kernel_interface.c @@ -392,12 +392,19 @@ METHOD(kernel_interface_t, alloc_reqid, status_t, /* search by reqid if given */ entry = this->reqids->get(this->reqids, tmpl); } - if (entry && entry_equals_selectors(entry, tmpl)) + + /* if the IPsec stack supports sequence numbers for acquires, we can + * allocate a new reqid if narrowing occurred (otherwise, we get the same + * reqid back anyway). if not, we want to reuse the existing reqid of + * the trap polices and explicitly don't want to match the traffic + * selectors. another case where we want to reuse an existing reqid is + * when labels are used. because we currently only install policies once + * with the generic label, the reqid has to stay the same even if narrowing + * occurs. however, in either case we don't want to reuse the reqid if the + * additional selectors (e.g. marks) are different */ + if (entry && (label || !(get_features(this) & KERNEL_ACQUIRE_SEQ)) && + entry_equals_selectors(entry, tmpl)) { - /* we don't require a traffic selector match for existing reqids, - * as we want to reuse a reqid for trap-triggered policies that - * got narrowed during negotiation, but we don't want to reuse the - * reqid if the additional selectors (e.g. marks) are different */ reqid_entry_destroy(tmpl); } else diff --git a/src/libcharon/kernel/kernel_interface.h b/src/libcharon/kernel/kernel_interface.h index a70e7f860..21cabe7f0 100644 --- a/src/libcharon/kernel/kernel_interface.h +++ b/src/libcharon/kernel/kernel_interface.h @@ -81,6 +81,8 @@ enum kernel_feature_t { KERNEL_POLICY_SPI = (1<<4), /** IPsec backend reports use time per SA via query_sa() */ KERNEL_SA_USE_TIME = (1<<5), + /** IPsec backend associates acquires and SAs with a sequence number */ + KERNEL_ACQUIRE_SEQ = (1<<6), }; /** diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 3c4f721ca..abb98a2f4 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -2163,13 +2163,14 @@ child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, if (!this->reqid) { /* reuse old reqid if we are rekeying an existing CHILD_SA and when - * initiating a trap policy. While the reqid cache would find the same - * reqid for our selectors, this does not work in a special case: If an - * SA is triggered by a trap policy, but the negotiated TS get - * narrowed, we still must reuse the same reqid to successfully - * replace the temporary SA on the kernel level. Rekeying such an SA - * requires an explicit reqid, as the cache currently knows the original - * selectors only for that reqid. */ + * initiating a trap policy. the reqid cache will generally find the + * same reqid for our selectors. but this does not work in a special + * case: if the IPsec stack does not use sequence numbers for acquires, + * an SA is triggered by a trap policy and the negotiated TS get + * narrowed, we still must reuse the same reqid to successfully replace + * the temporary SA on the kernel level. however, if sequence numbers + * are supported, the reqid will later get updated in case of narrowing + * when alloc_reqid() is called */ if (data->reqid && charon->kernel->ref_reqid(charon->kernel, data->reqid) == SUCCESS) { From b024b7e9a652f10f45fcbffb2c95edeef36be056 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 19 Mar 2025 16:27:39 +0100 Subject: [PATCH 07/23] kernel-netlink: Add support for acquire sequence numbers Note that while PF_KEYv2 also uses sequence numbers to identify acquires, which we currently don't use correctly by the way, it does not include information about the packet that triggered an acquire. What we receive in src and dst, and currently forward as traffic selectors, are actually the designated endpoints of the SA. So especially in tunnel mode this is useless to do narrowing on the responder (these addresses might not even match the configured TS). --- src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index df2822885..138e42075 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -1010,12 +1010,12 @@ static void process_acquire(private_kernel_netlink_ipsec_t *this, break; default: /* acquire for AH/ESP only, not for IPCOMP */ - return; } data.src = selector2ts(&acquire->sel, TRUE); data.dst = selector2ts(&acquire->sel, FALSE); data.label = label.len ? sec_label_from_encoding(label) : NULL; + data.seq = acquire->seq; charon->kernel->acquire(charon->kernel, reqid, &data); @@ -1188,7 +1188,7 @@ CALLBACK(receive_events, void, METHOD(kernel_ipsec_t, get_features, kernel_feature_t, private_kernel_netlink_ipsec_t *this) { - return KERNEL_ESP_V3_TFC | KERNEL_POLICY_SPI | + return KERNEL_ESP_V3_TFC | KERNEL_POLICY_SPI | KERNEL_ACQUIRE_SEQ | (this->sa_lastused ? KERNEL_SA_USE_TIME : 0); } @@ -1751,6 +1751,7 @@ METHOD(kernel_ipsec_t, add_sa, status_t, sa->id.proto = id->proto; sa->family = id->src->get_family(id->src); sa->mode = mode2kernel(mode); + sa->seq = data->seq; if (!data->copy_ecn) { From 5624f7ffaa52f24b8909a1561d52988b5aec7744 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 8 Apr 2025 09:02:12 +0200 Subject: [PATCH 08/23] kernel-pfkey: Don't use TS from acquire in tunnel mode The addresses are actually the endpoints of the SA, not information on the matched packet (except that the RFC says to set the ports and protocol of the packet in the source address, which the Linux kernel doesn't do). So these are useless, unless transport mode is used, where the addresses are needed for the wildcard trap policy use case. The RFC mentions a PROXY address (a single one, not two), that could apparently be something like the source address in tunnel mode. However, the description of how this is used in the RFC is quite weird and neither Linux nor FreeBSD send such an attribute in SADB_ACQUIRE. --- .../plugins/kernel_pfkey/kernel_pfkey_ipsec.c | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c b/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c index 41e13a832..f9ac579ad 100644 --- a/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c +++ b/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c @@ -1326,6 +1326,7 @@ static void process_acquire(private_kernel_pfkey_ipsec_t *this, pfkey_msg_t response; kernel_acquire_data_t data = {}; uint32_t index, reqid = 0; + uint8_t mode = 0; policy_entry_t *policy; policy_sa_t *sa; @@ -1350,6 +1351,7 @@ static void process_acquire(private_kernel_pfkey_ipsec_t *this, if (response.x_sa2) { reqid = response.x_sa2->sadb_x_sa2_reqid; + mode = response.x_sa2->sadb_x_sa2_mode; } else { @@ -1360,6 +1362,7 @@ static void process_acquire(private_kernel_pfkey_ipsec_t *this, policy->used_by->get_first(policy->used_by, (void**)&sa) == SUCCESS) { reqid = sa->sa->cfg.reqid; + mode = sa->sa->cfg.mode; } else { @@ -1371,13 +1374,24 @@ static void process_acquire(private_kernel_pfkey_ipsec_t *this, if (reqid) { - data.src = sadb_address2ts(response.src); - data.dst = sadb_address2ts(response.dst); + /* while we could pass the sequence number from the acquire in order + * to use it in the SA install, we currently don't do that. the reason + * is that the addresses we get here are the endpoints of the SA, not + * information about the matched packet (except for the ports, according + * to the RFC, although the Linux kernel doesn't do that). so these are + * only useful in transport mode with wildcard policies. in tunnel mode, + * where narrowing could occur and the sequence number would be + * relevant, these TS are useless and might not even match the policy */ + if (mode == IPSEC_MODE_TRANSPORT) + { + data.src = sadb_address2ts(response.src); + data.dst = sadb_address2ts(response.dst); + } charon->kernel->acquire(charon->kernel, reqid, &data); - data.src->destroy(data.src); - data.dst->destroy(data.dst); + DESTROY_IF(data.src); + DESTROY_IF(data.dst); } } From 6e274271af11e78459e9597a68fc4ed997e275fd Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 21 Mar 2025 14:51:08 +0100 Subject: [PATCH 09/23] vici: Include reqid when listing trap policies --- src/libcharon/plugins/vici/vici_query.c | 1 + src/swanctl/commands/list_pols.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/vici/vici_query.c b/src/libcharon/plugins/vici/vici_query.c index 7bea7579b..bb0212107 100644 --- a/src/libcharon/plugins/vici/vici_query.c +++ b/src/libcharon/plugins/vici/vici_query.c @@ -629,6 +629,7 @@ static void raise_policy(private_vici_query_t *this, u_int id, char *ike, snprintf(buf, sizeof(buf), "%s/%s", ike, child->get_name(child)); b->begin_section(b, buf); b->add_kv(b, "child", "%s", child->get_name(child)); + b->add_kv(b, "reqid", "%u", child->get_reqid(child)); b->add_kv(b, "ike", "%s", ike); list_mode(b, child, NULL); diff --git a/src/swanctl/commands/list_pols.c b/src/swanctl/commands/list_pols.c index 5fe3f2597..3db435bff 100644 --- a/src/swanctl/commands/list_pols.c +++ b/src/swanctl/commands/list_pols.c @@ -86,7 +86,8 @@ CALLBACK(policies, int, pol = hashtable_create(hashtable_hash_str, hashtable_equals_str, 1); ret = vici_parse_cb(res, NULL, policy_values, policy_list, pol); - printf("%s, %s\n", name, pol->get(pol, "mode")); + printf("%s, reqid %s, %s\n", name, pol->get(pol, "reqid"), + pol->get(pol, "mode")); print_label(" label: ", pol->get(pol, "label")); printf(" local: %s\n", pol->get(pol, "local-ts")); printf(" remote: %s\n", pol->get(pol, "remote-ts")); From 6ae29af18b73dded48cd76e7bcbdb11a10d629b4 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 24 Mar 2025 15:32:57 +0100 Subject: [PATCH 10/23] child-create: Consider triggering TS when checking for duplicate --- src/libcharon/sa/ikev2/tasks/child_create.c | 71 ++++++++++++++++++--- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index 791ca4b36..6111776c8 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -1292,21 +1292,76 @@ static status_t defer_child_sa(private_child_create_t *this) } /** - * Compare two CHILD_SA objects for equality + * Check if the given TS is contained in any of the ones of the given CHILD_SA. */ -static bool child_sa_equals(child_sa_t *a, child_sa_t *b) +static bool ts_match_existing(traffic_selector_t *ts, child_sa_t *child_sa, + bool local) +{ + enumerator_t *enumerator; + traffic_selector_t *negotiated; + + enumerator = child_sa->create_ts_enumerator(child_sa, local); + while (enumerator->enumerate(enumerator, &negotiated)) + { + if (ts->is_contained_in(ts, negotiated)) + { + enumerator->destroy(enumerator); + return TRUE; + } + } + enumerator->destroy(enumerator); + return FALSE; +} + +/** + * Compare the reqids and possibly traffic selectors of two CHILD_SAs for + * equality. + * + * The second CHILD_SA is assumed to be the one newly created. + */ +static bool reqid_and_ts_equals(private_child_create_t *this, child_sa_t *a, + child_sa_t *b) +{ + /* reqids are allocated based on the traffic selectors. if all the other + * selectors are the same, they can only differ if narrowing occurred. + * + * if the new SA has no reqid assigned, it was initiated manually or due to + * a start action and we assume the peer will do the same narrowing it + * possibly did before, so we treat the SAs as equal. + * + * if the new SA has a reqid, it was either triggered by an acquire or + * during a reestablishment. if they are equal, we are done */ + if (!b->get_reqid(b) || a->get_reqid(a) == b->get_reqid(b)) + { + return TRUE; + } + /* if the reqids differ, the one of the established SA was changed due to + * narrowing. in this case we check if we have triggering TS. if not, we + * assume the peer will do the same narrowing and treat the SAs equal. + * otherwise, we check whether they match the TS of the existing SA. if + * they do, there is no point to negotiate another SA. if not, the peer + * will potentially narrow the TS to a different set for the new SA */ + return !this->packet_tsi || !this->packet_tsr || + (ts_match_existing(this->packet_tsi, a, TRUE) && + ts_match_existing(this->packet_tsr, a, FALSE)); +} + +/** + * Compare two CHILD_SA objects for equality. + * + * The second CHILD_SA is assumed to be the one newly created. + */ +static bool child_sa_equals(private_child_create_t *this, child_sa_t *a, + child_sa_t *b) { child_cfg_t *cfg = a->get_config(a); return cfg->equals(cfg, b->get_config(b)) && - /* reqids are allocated based on the final TS, so we can only compare - * them if they are static (i.e. both have them) */ - (!a->get_reqid(a) || !b->get_reqid(b) || - a->get_reqid(a) == b->get_reqid(b)) && a->get_mark(a, TRUE).value == b->get_mark(b, TRUE).value && 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) && - sec_labels_equal(a->get_label(a), b->get_label(b)); + sec_labels_equal(a->get_label(a), b->get_label(b)) && + reqid_and_ts_equals(this, a, b); } /** @@ -1322,7 +1377,7 @@ static bool check_for_duplicate(private_child_create_t *this) while (enumerator->enumerate(enumerator, (void**)&child_sa)) { if (child_sa->get_state(child_sa) == CHILD_INSTALLED && - child_sa_equals(child_sa, this->child_sa)) + child_sa_equals(this, child_sa, this->child_sa)) { found = child_sa; break; From c563b0d9303301970ab8a9bf89ea4c220f5a6dfb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 24 Mar 2025 15:53:00 +0100 Subject: [PATCH 11/23] kernel-listener: Add helpers to clone/destroy acquire data --- src/libcharon/Android.mk | 3 +- src/libcharon/Makefile.am | 3 +- src/libcharon/kernel/kernel_listener.c | 57 ++++++++++++++++++++++++++ src/libcharon/kernel/kernel_listener.h | 19 ++++++++- 4 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 src/libcharon/kernel/kernel_listener.c diff --git a/src/libcharon/Android.mk b/src/libcharon/Android.mk index 11ea96049..c6a794ebc 100644 --- a/src/libcharon/Android.mk +++ b/src/libcharon/Android.mk @@ -50,7 +50,8 @@ encoding/payloads/fragment_payload.c encoding/payloads/fragment_payload.h \ kernel/kernel_interface.c kernel/kernel_interface.h \ kernel/kernel_ipsec.c kernel/kernel_ipsec.h \ kernel/kernel_net.c kernel/kernel_net.h \ -kernel/kernel_listener.h kernel/kernel_handler.c kernel/kernel_handler.h \ +kernel/kernel_listener.c kernel/kernel_listener.h \ +kernel/kernel_handler.c kernel/kernel_handler.h \ network/receiver.c network/receiver.h network/sender.c network/sender.h \ network/socket.c network/socket.h \ network/socket_manager.c network/socket_manager.h \ diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index f53bb4c47..86011c5b0 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -48,7 +48,8 @@ encoding/payloads/fragment_payload.c encoding/payloads/fragment_payload.h \ kernel/kernel_interface.c kernel/kernel_interface.h \ kernel/kernel_ipsec.c kernel/kernel_ipsec.h \ kernel/kernel_net.c kernel/kernel_net.h \ -kernel/kernel_listener.h kernel/kernel_handler.c kernel/kernel_handler.h \ +kernel/kernel_listener.c kernel/kernel_listener.h \ +kernel/kernel_handler.c kernel/kernel_handler.h \ network/receiver.c network/receiver.h network/sender.c network/sender.h \ network/socket.c network/socket.h \ network/socket_manager.c network/socket_manager.h \ diff --git a/src/libcharon/kernel/kernel_listener.c b/src/libcharon/kernel/kernel_listener.c new file mode 100644 index 000000000..66052530e --- /dev/null +++ b/src/libcharon/kernel/kernel_listener.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2025 Tobias Brunner + * + * Copyright (C) secunet Security Networks AG + * + * 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "kernel_listener.h" + +/* + * Described in header + */ +kernel_acquire_data_t *kernel_acquire_data_clone(kernel_acquire_data_t *data) +{ + kernel_acquire_data_t *clone; + + INIT(clone); + + *clone = *data; + + if (clone->src) + { + clone->src = clone->src->clone(clone->src); + } + if (clone->dst) + { + clone->dst = clone->dst->clone(clone->dst); + } + if (clone->label) + { + clone->label = clone->label->clone(clone->label); + } + return clone; +} + +/* + * Described in header + */ +void kernel_acquire_data_destroy(kernel_acquire_data_t *data) +{ + if (data) + { + DESTROY_IF(data->src); + DESTROY_IF(data->dst); + DESTROY_IF(data->label); + free(data); + } +} diff --git a/src/libcharon/kernel/kernel_listener.h b/src/libcharon/kernel/kernel_listener.h index a0dcddfc0..55f952a17 100644 --- a/src/libcharon/kernel/kernel_listener.h +++ b/src/libcharon/kernel/kernel_listener.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2013 Tobias Brunner + * Copyright (C) 2010-2025 Tobias Brunner * * Copyright (C) secunet Security Networks AG * @@ -119,4 +119,21 @@ struct kernel_listener_t { bool (*tun)(kernel_listener_t *this, tun_device_t *tun, bool created); }; +/** + * Clone the given acquire data received from the kernel. + * + * @param data data to clone + * @return clone of the original object and all associated data + */ +kernel_acquire_data_t *kernel_acquire_data_clone(kernel_acquire_data_t *data); + +/** + * Destroy the given previously cloned acquire data. + * + * @note This must not be used on the original data from the kernel interface. + * + * @param data cloned data to destroy + */ +void kernel_acquire_data_destroy(kernel_acquire_data_t *data); + #endif /** KERNEL_LISTENER_H_ @}*/ From 65b810e9b0edaebd290c0b119392071cd81b4d12 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 24 Mar 2025 15:53:19 +0100 Subject: [PATCH 12/23] acquire-job: Use helpers to manage acquire data --- src/libcharon/processing/jobs/acquire_job.c | 24 +++++---------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/libcharon/processing/jobs/acquire_job.c b/src/libcharon/processing/jobs/acquire_job.c index 8b04f11a4..33f6ae213 100644 --- a/src/libcharon/processing/jobs/acquire_job.c +++ b/src/libcharon/processing/jobs/acquire_job.c @@ -1,4 +1,5 @@ /* + * Copyright (C) 2022-2025 Tobias Brunner * Copyright (C) 2006-2009 Martin Willi * * Copyright (C) secunet Security Networks AG @@ -38,22 +39,20 @@ struct private_acquire_job_t { /** * Data from the acquire */ - kernel_acquire_data_t data; + kernel_acquire_data_t *data; }; METHOD(job_t, destroy, void, private_acquire_job_t *this) { - DESTROY_IF(this->data.src); - DESTROY_IF(this->data.dst); - DESTROY_IF(this->data.label); + kernel_acquire_data_destroy(this->data); free(this); } METHOD(job_t, execute, job_requeue_t, private_acquire_job_t *this) { - charon->traps->acquire(charon->traps, this->reqid, &this->data); + charon->traps->acquire(charon->traps, this->reqid, this->data); return JOB_REQUEUE_NONE; } @@ -79,21 +78,8 @@ acquire_job_t *acquire_job_create(uint32_t reqid, kernel_acquire_data_t *data) }, }, .reqid = reqid, - .data = *data, + .data = kernel_acquire_data_clone(data), ); - if (this->data.src) - { - this->data.src = this->data.src->clone(this->data.src); - } - if (this->data.dst) - { - this->data.dst = this->data.dst->clone(this->data.dst); - } - if (this->data.label) - { - this->data.label = this->data.label->clone(this->data.label); - } - return &this->public; } From 769d9a12aaecc4b6f8ef9b5d1682a26e722e33fa Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 24 Mar 2025 16:47:54 +0100 Subject: [PATCH 13/23] child-sa: Add helper to check if two TS match negotiated TS --- src/libcharon/sa/child_sa.c | 35 ++++++++++++++++++++++++++++++++++- src/libcharon/sa/child_sa.h | 15 ++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index abb98a2f4..8dbdd6055 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2023 Tobias Brunner + * Copyright (C) 2006-2025 Tobias Brunner * Copyright (C) 2016 Andreas Steffen * Copyright (C) 2005-2008 Martin Willi * Copyright (C) 2006 Daniel Roethlisberger @@ -2198,3 +2198,36 @@ child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, } return &this->public; } + +/** + * Check if the given traffic selector is contained in any of the traffic + * selectors in the given list. + */ +static bool is_ts_match(traffic_selector_t *to_check, array_t *list) +{ + traffic_selector_t *ts; + int i; + + for (i = 0; i < array_count(list); i++) + { + array_get(list, i, &ts); + if (to_check->is_contained_in(to_check, ts)) + { + return TRUE; + } + } + return FALSE; +} + +/* + * Described in header + */ +bool child_sa_ts_match(child_sa_t *child, traffic_selector_t *src, + traffic_selector_t *dst) +{ + private_child_sa_t *this = (private_child_sa_t*)child; + + return src && dst && + is_ts_match(src, this->my_ts) && + is_ts_match(dst, this->other_ts); +} diff --git a/src/libcharon/sa/child_sa.h b/src/libcharon/sa/child_sa.h index 43f306fe9..da2055b03 100644 --- a/src/libcharon/sa/child_sa.h +++ b/src/libcharon/sa/child_sa.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2023 Tobias Brunner + * Copyright (C) 2006-2025 Tobias Brunner * Copyright (C) 2006-2008 Martin Willi * Copyright (C) 2006 Daniel Roethlisberger * @@ -594,4 +594,17 @@ struct child_sa_create_t { child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, child_sa_create_t *data); +/** + * Check if the given source and destination traffic selectors (e.g. from a + * packet triggering an acquire) match the negotiated local and remote traffic + * selectors of this child SA. + * + * @param this CHILD_SA to check traffic selectors against + * @param src source traffic selector + * @param dst destination traffic selector + * @return TRUE if both traffic selectors match + */ +bool child_sa_ts_match(child_sa_t *this, traffic_selector_t *src, + traffic_selector_t *dst); + #endif /** CHILD_SA_H_ @}*/ From 5bb6f636adec5b60bd0229cb7e8037f8cfd94a4c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 24 Mar 2025 16:48:19 +0100 Subject: [PATCH 14/23] child-create: Use helper to compare packet TS against CHILD_SAs --- src/libcharon/sa/ikev2/tasks/child_create.c | 25 +-------------------- 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index 6111776c8..5cf738f53 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -1291,28 +1291,6 @@ static status_t defer_child_sa(private_child_create_t *this) return NOT_SUPPORTED; } -/** - * Check if the given TS is contained in any of the ones of the given CHILD_SA. - */ -static bool ts_match_existing(traffic_selector_t *ts, child_sa_t *child_sa, - bool local) -{ - enumerator_t *enumerator; - traffic_selector_t *negotiated; - - enumerator = child_sa->create_ts_enumerator(child_sa, local); - while (enumerator->enumerate(enumerator, &negotiated)) - { - if (ts->is_contained_in(ts, negotiated)) - { - enumerator->destroy(enumerator); - return TRUE; - } - } - enumerator->destroy(enumerator); - return FALSE; -} - /** * Compare the reqids and possibly traffic selectors of two CHILD_SAs for * equality. @@ -1342,8 +1320,7 @@ static bool reqid_and_ts_equals(private_child_create_t *this, child_sa_t *a, * they do, there is no point to negotiate another SA. if not, the peer * will potentially narrow the TS to a different set for the new SA */ return !this->packet_tsi || !this->packet_tsr || - (ts_match_existing(this->packet_tsi, a, TRUE) && - ts_match_existing(this->packet_tsr, a, FALSE)); + child_sa_ts_match(a, this->packet_tsi, this->packet_tsr); } /** From ac0c73a412c03a5a0084626d126b2e26d151fb6e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 24 Mar 2025 17:56:46 +0100 Subject: [PATCH 15/23] child-create: Trigger CHILD_INSTALLING event after setting traffic selectors This was the case before bce0c5fd74a0 ("child-create: Update CHILD_SA IP addresses before installation") and allows listeners to consider the traffic selectors of the SA that's about to get installed. --- src/libcharon/sa/ikev2/tasks/child_create.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index 5cf738f53..be4662f1c 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -653,7 +653,6 @@ static status_t install_child_sa(private_child_create_t *this) this->child_sa->set_mode(this->child_sa, this->mode); this->child_sa->set_protocol(this->child_sa, this->proposal->get_protocol(this->proposal)); - this->child_sa->set_state(this->child_sa, CHILD_INSTALLING); /* addresses might have changed since we originally sent the request, update * them before we configure any policies and install the SAs */ @@ -670,6 +669,7 @@ static status_t install_child_sa(private_child_create_t *this) other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy)); } + this->child_sa->set_state(this->child_sa, CHILD_INSTALLING); if (this->my_cpi == 0 || this->other_cpi == 0 || this->ipcomp == IPCOMP_NONE) { From 79815b4e675a352fb2b9d480dea618e5293942f6 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 24 Mar 2025 18:03:42 +0100 Subject: [PATCH 16/23] trap-manager: Set seq. no. for CHILD_SAs not initiated by an acquire This fixes cases where `start_action = trap|start` is used and an acquire is triggered while the SA is initiated (granted if narrowing is expected, that's not a recommended configuration as the responder can only use the first config when there is no packet TS). The resulting second create-child task will potentially get dropped by the duplicate check, so the temporary state won't get removed and traffic is blocked until that expires, neither can acquires get triggered for traffic that doesn't match the initial SA's policies. --- src/libcharon/sa/trap_manager.c | 56 +++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/src/libcharon/sa/trap_manager.c b/src/libcharon/sa/trap_manager.c index 7bd2c698f..814c8c8c0 100644 --- a/src/libcharon/sa/trap_manager.c +++ b/src/libcharon/sa/trap_manager.c @@ -126,10 +126,8 @@ typedef struct { uint32_t reqid; /** destination address (wildcard case) */ host_t *dst; - /** security label, if any */ - sec_label_t *label; - /** sequence number from the kernel or generated by the trap manager */ - uint32_t seq; + /** data from the kernel */ + kernel_acquire_data_t *data; /** optional sequence number from the kernel if an acquire is retriggered */ uint32_t new_seq; } acquire_t; @@ -154,7 +152,7 @@ static void destroy_entry(entry_t *this) static void destroy_acquire(acquire_t *this) { DESTROY_IF(this->dst); - DESTROY_IF(this->label); + kernel_acquire_data_destroy(this->data); free(this); } @@ -165,7 +163,7 @@ CALLBACK(acquire_by_reqid, bool, sec_label_t *label; VA_ARGS_VGET(args, reqid, label); - return this->reqid == reqid && sec_labels_equal(this->label, label); + return this->reqid == reqid && sec_labels_equal(this->data->label, label); } CALLBACK(acquire_by_dst, bool, @@ -533,7 +531,6 @@ METHOD(trap_manager_t, acquire, void, child_cfg_t *child; ike_sa_t *ike_sa; host_t *host = NULL; - sec_label_t *label = NULL; uint32_t allocated_reqid, seq = 0; bool wildcard; @@ -572,24 +569,22 @@ METHOD(trap_manager_t, acquire, void, host->destroy(host); } } - else if (!this->acquires->find_first(this->acquires, acquire_by_reqid, - (void**)&acquire, reqid, data->label)) + else { - label = data->label ? data->label->clone(data->label) : NULL; + this->acquires->find_first(this->acquires, acquire_by_reqid, + (void**)&acquire, reqid, data->label); } if (!acquire) { - seq = data->seq ?: ref_get_nonzero(&this->acquire_seq); - INIT(acquire, .dst = host, .reqid = reqid, - .label = label, - .seq = seq, + .data = kernel_acquire_data_clone(data), ); + seq = data->seq = data->seq ?: ref_get_nonzero(&this->acquire_seq); this->acquires->insert_last(this->acquires, acquire); } - else if (data->seq && acquire->seq != data->seq) + else if (data->seq && data->seq != acquire->data->seq) { /* acquire got retriggered, update to latest sequence number */ acquire->new_seq = data->seq; @@ -698,8 +693,18 @@ static void update_acquire_seq(private_trap_manager_t *this, ike_sa_t *ike_sa, acquire_t *acquire; uint32_t seq; - /* ignore trap policies and CHILD_SAs not triggered by an acquire */ - if (!ike_sa || !(seq = child_sa->get_acquire_seq(child_sa))) + /* ignore trap policies */ + if (!ike_sa) + { + return; + } + /* if a CHILD_SA was not triggered by an acquire (e.g. manually or via + * start action) and the kernel supports sequence numbers, we check if the + * negotiated TS match the triggering packet and set the sequence number + * to remove the temporary state in the kernel */ + seq = child_sa->get_acquire_seq(child_sa); + if (!seq && + !(charon->kernel->get_features(charon->kernel) & KERNEL_ACQUIRE_SEQ)) { return; } @@ -708,14 +713,25 @@ static void update_acquire_seq(private_trap_manager_t *this, ike_sa_t *ike_sa, enumerator = this->acquires->create_enumerator(this->acquires); while (enumerator->enumerate(enumerator, &acquire)) { - if (!acquire->ike_sa || seq != acquire->seq) + if (!seq) + { + /* if not triggered by an acquire, compare the TS from the packet + * that triggered the current acquire, set the seq if they match */ + if (!child_sa_ts_match(child_sa, acquire->data->src, + acquire->data->dst)) + { + continue; + } + child_sa->set_acquire_seq(child_sa, acquire->data->seq); + } + else if (!acquire->ike_sa || seq != acquire->data->seq) { continue; } if (acquire->new_seq) { child_sa->set_acquire_seq(child_sa, acquire->new_seq); - acquire->seq = acquire->new_seq; + acquire->data->seq = acquire->new_seq; acquire->new_seq = 0; } break; @@ -746,7 +762,7 @@ static void complete(private_trap_manager_t *this, ike_sa_t *ike_sa, { /* just look at the sequence number when handling a CHILD_SA event, * otherwise, compare the IKE_SA */ - if (!acquire->ike_sa || (seq && seq != acquire->seq) || + if (!acquire->ike_sa || (seq && seq != acquire->data->seq) || (!seq && acquire->ike_sa != ike_sa)) { continue; From 4b468126ca17b26bc2ed5271c9c0fda1599729c2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 25 Mar 2025 12:43:26 +0100 Subject: [PATCH 17/23] traffic-selector-list: Add helper class to manage a collection of TS Provides functions to optionally resolve dynamic TS and to narrow them based on a list of supplied TS. --- src/libstrongswan/Android.mk | 2 +- src/libstrongswan/Makefile.am | 4 +- .../selectors/traffic_selector_list.c | 292 ++++++++++++++++++ .../selectors/traffic_selector_list.h | 155 ++++++++++ .../tests/suites/test_traffic_selector.c | 203 +++++++++++- 5 files changed, 652 insertions(+), 4 deletions(-) create mode 100644 src/libstrongswan/selectors/traffic_selector_list.c create mode 100644 src/libstrongswan/selectors/traffic_selector_list.h diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk index 1f2d0af52..ce78233d5 100644 --- a/src/libstrongswan/Android.mk +++ b/src/libstrongswan/Android.mk @@ -42,7 +42,7 @@ networking/streams/stream_tcp.c networking/streams/stream_service_tcp.c \ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \ processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ processing/watcher.c resolver/resolver_manager.c resolver/rr_set.c \ -selectors/sec_label.c selectors/traffic_selector.c \ +selectors/sec_label.c selectors/traffic_selector.c selectors/traffic_selector_list.c \ settings/settings.c settings/settings_types.c \ settings/settings_parser.c settings/settings_lexer.c utils/cpu_feature.c \ utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \ diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index 8d3c869bd..79d94be9b 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -40,7 +40,7 @@ networking/streams/stream_tcp.c networking/streams/stream_service_tcp.c \ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \ processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ processing/watcher.c resolver/resolver_manager.c resolver/rr_set.c \ -selectors/sec_label.c selectors/traffic_selector.c \ +selectors/sec_label.c selectors/traffic_selector.c selectors/traffic_selector_list.c \ settings/settings.c settings/settings_types.c \ settings/settings_parser.y settings/settings_lexer.l utils/cpu_feature.c \ utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \ @@ -125,7 +125,7 @@ resolver/rr.h resolver/resolver_manager.h \ plugins/plugin_loader.h plugins/plugin.h plugins/plugin_feature.h \ processing/jobs/job.h processing/jobs/callback_job.h processing/processor.h \ processing/scheduler.h processing/watcher.h \ -selectors/sec_label.h selectors/traffic_selector.h \ +selectors/sec_label.h selectors/traffic_selector.h selectors/traffic_selector_list.h \ settings/settings.h settings/settings_parser.h threading/thread_value.h \ threading/thread.h threading/windows/thread.h \ threading/mutex.h threading/condvar.h threading/spinlock.h threading/semaphore.h \ diff --git a/src/libstrongswan/selectors/traffic_selector_list.c b/src/libstrongswan/selectors/traffic_selector_list.c new file mode 100644 index 000000000..71c83fb67 --- /dev/null +++ b/src/libstrongswan/selectors/traffic_selector_list.c @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2008-2025 Tobias Brunner + * Copyright (C) 2005-2007 Martin Willi + * + * Copyright (C) secunet Security Networks AG + * + * 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "traffic_selector_list.h" + +#include + +typedef struct private_traffic_selector_list_t private_traffic_selector_list_t; + +/** + * Private data. + */ +struct private_traffic_selector_list_t { + + /** + * Public interface. + */ + traffic_selector_list_t public; + + /** + * List of managed traffic selectors. + */ + linked_list_t *ts; +}; + +METHOD(traffic_selector_list_t, add, void, + private_traffic_selector_list_t *this, traffic_selector_t *ts) +{ + this->ts->insert_last(this->ts, ts); +} + +METHOD(traffic_selector_list_t, create_enumerator, enumerator_t*, + private_traffic_selector_list_t *this) +{ + return this->ts->create_enumerator(this->ts); +} + +/** + * Create a copy of the traffic selectors in the given list, while resolving + * "dynamic" traffic selectors using the given hosts, if any. When not narrowing + * as initiator, we also replace TS in transport mode. + */ +static linked_list_t *resolve_dynamic_ts(private_traffic_selector_list_t *this, + linked_list_t *hosts, bool narrowing, + bool force_dynamic) +{ + enumerator_t *e1, *e2; + traffic_selector_t *ts1, *ts2; + linked_list_t *result; + host_t *host; + + if (!hosts || !hosts->get_count(hosts)) + { + return this->ts->clone_offset(this->ts, + offsetof(traffic_selector_t, clone)); + } + + result = linked_list_create(); + e1 = this->ts->create_enumerator(this->ts); + while (e1->enumerate(e1, &ts1)) + { + /* set hosts if TS is dynamic or if forced as initiator in + * transport mode */ + bool dynamic = ts1->is_dynamic(ts1); + if (!dynamic && !force_dynamic) + { + result->insert_last(result, ts1->clone(ts1)); + continue; + } + e2 = hosts->create_enumerator(hosts); + while (e2->enumerate(e2, &host)) + { + if (!dynamic && !host->is_anyaddr(host) && + !ts1->includes(ts1, host)) + { /* for transport mode, we skip TS that don't match + * specific IPs */ + continue; + } + ts2 = ts1->clone(ts1); + if (dynamic || !host->is_anyaddr(host)) + { /* don't make regular TS larger than they were */ + ts2->set_address(ts2, host); + } + result->insert_last(result, ts2); + } + e2->destroy(e2); + } + e1->destroy(e1); + return result; +} + +/** + * Remove duplicate traffic selectors in the given list. + */ +static void remove_duplicate_ts(linked_list_t *list) +{ + enumerator_t *e1, *e2; + traffic_selector_t *ts1, *ts2; + + e1 = list->create_enumerator(list); + e2 = list->create_enumerator(list); + while (e1->enumerate(e1, &ts1)) + { + while (e2->enumerate(e2, &ts2)) + { + if (ts1 != ts2) + { + if (ts2->is_contained_in(ts2, ts1)) + { + list->remove_at(list, e2); + ts2->destroy(ts2); + list->reset_enumerator(list, e1); + break; + } + if (ts1->is_contained_in(ts1, ts2)) + { + list->remove_at(list, e1); + ts1->destroy(ts1); + break; + } + } + } + list->reset_enumerator(list, e2); + } + e1->destroy(e1); + e2->destroy(e2); +} + +METHOD(traffic_selector_list_t, get, linked_list_t*, + private_traffic_selector_list_t *this, linked_list_t *hosts, + bool force_dynamic) +{ + linked_list_t *result; + + result = resolve_dynamic_ts(this, hosts, FALSE, force_dynamic); + remove_duplicate_ts(result); + return result; +} + +METHOD(traffic_selector_list_t, select_, linked_list_t*, + private_traffic_selector_list_t *this, linked_list_t *supplied, + linked_list_t *hosts, bool force_dynamic, bool *narrowed) +{ + enumerator_t *e1, *e2; + traffic_selector_t *ts1, *ts2, *selected; + linked_list_t *resolved, *result; + + result = linked_list_create(); + resolved = resolve_dynamic_ts(this, hosts, supplied != NULL, force_dynamic); + + if (!supplied) + { + while (resolved->remove_first(resolved, (void**)&ts1) == SUCCESS) + { + DBG2(DBG_CFG, " %R", ts1); + result->insert_last(result, ts1); + } + } + else + { + e1 = resolved->create_enumerator(resolved); + e2 = supplied->create_enumerator(supplied); + /* enumerate all configured/resolved selectors */ + while (e1->enumerate(e1, &ts1)) + { + /* enumerate all supplied traffic selectors */ + while (e2->enumerate(e2, &ts2)) + { + selected = ts1->get_subset(ts1, ts2); + if (selected) + { + DBG2(DBG_CFG, " config: %R, received: %R => match: %R", + ts1, ts2, selected); + result->insert_last(result, selected); + } + else + { + DBG2(DBG_CFG, " config: %R, received: %R => no match", + ts1, ts2); + } + } + supplied->reset_enumerator(supplied, e2); + } + e1->destroy(e1); + e2->destroy(e2); + + if (narrowed) + { + *narrowed = FALSE; + + e1 = resolved->create_enumerator(resolved); + e2 = result->create_enumerator(result); + while (e1->enumerate(e1, &ts1)) + { + if (!e2->enumerate(e2, &ts2) || !ts1->equals(ts1, ts2)) + { + *narrowed = TRUE; + break; + } + } + e1->destroy(e1); + e2->destroy(e2); + } + } + resolved->destroy_offset(resolved, offsetof(traffic_selector_t, destroy)); + remove_duplicate_ts(result); + return result; +} + +METHOD(traffic_selector_list_t, equals, bool, + private_traffic_selector_list_t *this, traffic_selector_list_t *other_pub) +{ + private_traffic_selector_list_t *other = (private_traffic_selector_list_t*)other_pub; + return this->ts->equals_offset(this->ts, other->ts, + offsetof(traffic_selector_t, equals)); +} + +METHOD(traffic_selector_list_t, destroy, void, + private_traffic_selector_list_t *this) +{ + this->ts->destroy_offset(this->ts, offsetof(traffic_selector_t, destroy)); + free(this); +} + +METHOD(traffic_selector_list_t, clone_, traffic_selector_list_t*, + private_traffic_selector_list_t *this) +{ + return traffic_selector_list_create_from_list( + this->ts->clone_offset(this->ts, offsetof(traffic_selector_t, clone))); +} + +/* + * Described in header + */ +traffic_selector_list_t *traffic_selector_list_create_from_list(linked_list_t *list) +{ + private_traffic_selector_list_t *this; + + INIT(this, + .public = { + .add = _add, + .create_enumerator = _create_enumerator, + .get = _get, + .select = _select_, + .equals = _equals, + .clone = _clone_, + .destroy = _destroy, + }, + .ts = list, + ); + return &this->public; +} + +/* + * Described in header + */ +traffic_selector_list_t *traffic_selector_list_create() +{ + return traffic_selector_list_create_from_list(linked_list_create()); +} + +/* + * Described in header + */ +traffic_selector_list_t *traffic_selector_list_create_from_enumerator( + enumerator_t *enumerator) +{ + traffic_selector_list_t *this = traffic_selector_list_create(); + traffic_selector_t *ts; + + while (enumerator->enumerate(enumerator, &ts)) + { + add((private_traffic_selector_list_t*)this, ts->clone(ts)); + } + enumerator->destroy(enumerator); + + return this; +} diff --git a/src/libstrongswan/selectors/traffic_selector_list.h b/src/libstrongswan/selectors/traffic_selector_list.h new file mode 100644 index 000000000..30fd60704 --- /dev/null +++ b/src/libstrongswan/selectors/traffic_selector_list.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2025 Tobias Brunner + * + * Copyright (C) secunet Security Networks AG + * + * 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup traffic_selector_list traffic_selector_list + * @{ @ingroup selectors + */ + +#ifndef TRAFFIC_SELECTOR_LIST_H_ +#define TRAFFIC_SELECTOR_LIST_H_ + +typedef struct traffic_selector_list_t traffic_selector_list_t; + +#include "traffic_selector.h" + +/** + * Collection of traffic selectors that can be narrowed to a new set of + * traffic selectors. + */ +struct traffic_selector_list_t { + + /** + * Add a traffic selector to the collection. + * + * @param ts traffic_selector to add (adopted) + */ + void (*add)(traffic_selector_list_t *this, traffic_selector_t *ts); + + /** + * Enumerate all traffic selectors in the collection. + * + * Similar to calling get() without \p hosts, but does not clone the traffic + * selectors and duplicates are not removed. + * + * @return enumerator over traffic_selector_t* + */ + enumerator_t *(*create_enumerator)(traffic_selector_list_t *this); + + /** + * Get a list of traffic selectors contained in the collection. + * + * Some traffic selectors may be "dynamic", meaning they are narrowed down + * to a specific address (host-to-host or virtual-IP setups). Use the + * \p hosts parameter to narrow such traffic selectors to an address. If + * \p force_dynamic is also passed, even non-dynamic traffic selectors that + * match are replaced using the IPs in \p hosts (useful as initiator with + * transport mode). + * + * If \p hosts is not passed, the list of traffic selectors is returned as + * configured, except that exact duplicates are removed. However, note that + * "dynamic" traffic selectors are not considered duplicates. + * + * Returned list and its traffic selectors must be destroyed after use. + * + * Note that this method does not log anything. If logging is required, use + * select() without passing supplied traffic selectors. + * + * @param hosts addresses to use for narrowing "dynamic" TS, host_t + * @param force_dynamic TRUE to replace non-"dynamic" TS with \p hosts as + * initiator in transport mode + * @return list containing the traffic selectors + */ + linked_list_t *(*get)(traffic_selector_list_t *this, linked_list_t *hosts, + bool force_dynamic); + + /** + * Select a list of traffic selectors contained in the collection. + * + * If a list with traffic selectors is supplied, these are used to narrow + * down the traffic selectors to the greatest common subset. + * + * Some traffic selectors may be "dynamic", meaning they are narrowed down + * to a specific address (host-to-host or virtual-IP setups). Use the + * \p hosts parameter to narrow such traffic selectors to an address. If + * \p force_dynamic is also passed, even non-dynamic traffic selectors that + * match are replaced using the IPs in \p hosts (useful as initiator with + * transport mode). + * + * Details about the selection of each individual traffic selector are + * logged. + * + * Returned list and its traffic selectors must be destroyed after use. + * + * @param supplied list with TS to select from, or NULL + * @param hosts addresses to use for narrowing "dynamic" TS', host_t + * @param force_dynamic TRUE to replace non-"dynamic" TS with \p hosts as + * initiator in transport mode + * @param narrowed[out] optional flag that indicates if any TS were narrowed + * @return list containing the traffic selectors + */ + linked_list_t *(*select)(traffic_selector_list_t *this, + linked_list_t *supplied, linked_list_t *hosts, + bool force_dynamic, bool *narrowed); + + /** + * Compare two collections of traffic selectors. + * + * @param other collection to compare with this + * @return TRUE if equal, FALSE otherwise + */ + bool (*equals)(traffic_selector_list_t *this, traffic_selector_list_t *other); + + /** + * Clone this collection of traffic selectors. + * + * @return cloned collection + */ + traffic_selector_list_t *(*clone)(traffic_selector_list_t *this); + + /** + * Destroys this collection. + */ + void (*destroy)(traffic_selector_list_t *this); +}; + +/** + * Create an empty traffic selector collection. + * + * @return created object + */ +traffic_selector_list_t *traffic_selector_list_create(); + +/** + * Create a collection with traffic selectors from the given list (adopted). + * + * @param list list of traffic_selector_t (adopted) + * @return created object + */ +traffic_selector_list_t *traffic_selector_list_create_from_list( + linked_list_t *list); + +/** + * Create a collection with traffic selectors from the given enumerator (objects + * are cloned, the enumerator is destroyed). + * + * @param enumerator enumerator over traffic_selector_t (cloned/destroyed) + * @return created object + */ +traffic_selector_list_t *traffic_selector_list_create_from_enumerator( + enumerator_t *enumerator); + +#endif /** TRAFFIC_SELECTOR_LIST_H_ @}*/ diff --git a/src/libstrongswan/tests/suites/test_traffic_selector.c b/src/libstrongswan/tests/suites/test_traffic_selector.c index cbcc0c08d..45daa9082 100644 --- a/src/libstrongswan/tests/suites/test_traffic_selector.c +++ b/src/libstrongswan/tests/suites/test_traffic_selector.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Tobias Brunner + * Copyright (C) 2015-2025 Tobias Brunner * Copyright (C) 2015 Martin Willi * * Copyright (C) secunet Security Networks AG @@ -18,6 +18,7 @@ #include "test_suite.h" #include +#include static void verify(const char *str, const char *alt, traffic_selector_t *ts) @@ -814,6 +815,199 @@ START_TEST(test_printf_hook_hash) } END_TEST +/** + * Create a linked list of either traffic selectors or hosts from a + * comma-separated list. + */ +static linked_list_t *create_list(char *str, bool hosts) +{ + linked_list_t *list = linked_list_create(); + enumerator_t *enumerator; + char *item; + void *obj; + + enumerator = enumerator_create_token(str, " ", ""); + while (enumerator->enumerate(enumerator, &item)) + { + if (hosts) + { + obj = host_create_from_string(item, 0); + } + else if (streq(item, "dynamic")) + { + obj = traffic_selector_create_dynamic(0, 0, 65535); + } + else + { + obj = traffic_selector_create_from_cidr(item, 0, 0, 65535); + } + list->insert_last(list, obj); + } + enumerator->destroy(enumerator); + return list; +} + +struct { + char *ts; + char *hosts; + char *get; + char *get_force; +} list_get_tests[] = { + { "dynamic", NULL, "dynamic", "dynamic" }, + { "dynamic", "10.0.1.1", "10.0.1.1/32", "10.0.1.1/32" }, + { "dynamic", "10.0.1.1 192.168.0.1", + "10.0.1.1/32 192.168.0.1/32", "10.0.1.1/32 192.168.0.1/32" }, + { "0.0.0.0/0", "10.0.1.1", "0.0.0.0/0", "10.0.1.1/32" }, + { "0.0.0.0/0", "10.0.1.1 192.168.0.1", + "0.0.0.0/0", "10.0.1.1/32 192.168.0.1/32" }, + { "10.0.1.0/24", "10.0.1.1", "10.0.1.0/24", "10.0.1.1/32" }, + { "10.0.2.0/24", "10.0.1.1", "10.0.2.0/24", "" }, + { "10.0.2.0/24", "10.0.1.1 10.0.2.1", "10.0.2.0/24", "10.0.2.1/32" }, + /* two dynamic TS are not treated as duplicates */ + { "dynamic dynamic", NULL, "dynamic dynamic", "dynamic dynamic" }, + { "dynamic dynamic", "10.0.1.1", "10.0.1.1/32", "10.0.1.1/32" }, + { "dynamic dynamic", "10.0.1.1 192.168.0.1", + "10.0.1.1/32 192.168.0.1/32", "10.0.1.1/32 192.168.0.1/32" }, + { "0.0.0.0/0 0.0.0.0/0", "10.0.1.1", "0.0.0.0/0", "10.0.1.1/32" }, + { "0.0.0.0/0 0.0.0.0/0", "10.0.1.1 192.168.0.1", + "0.0.0.0/0", "10.0.1.1/32 192.168.0.1/32" }, + { "10.0.1.0/24 10.0.1.0/24", NULL, "10.0.1.0/24", "10.0.1.0/24" }, + { "10.0.1.1/32 10.0.1.0/24", NULL, "10.0.1.0/24", "10.0.1.0/24" }, + { "10.0.1.0/24 10.0.1.1/32", NULL, "10.0.1.0/24", "10.0.1.0/24" }, + { "10.0.1.0/24 10.0.2.0/24", NULL, + "10.0.1.0/24 10.0.2.0/24", "10.0.1.0/24 10.0.2.0/24" }, + { "10.0.1.0/24 10.0.2.0/24", "10.0.1.1", + "10.0.1.0/24 10.0.2.0/24", "10.0.1.1/32" }, + { "10.0.1.0/24 10.0.2.0/24", "10.0.1.1 10.0.2.1", + "10.0.1.0/24 10.0.2.0/24", "10.0.1.1/32 10.0.2.1/32" }, +}; + +START_TEST(test_list_get) +{ + traffic_selector_list_t *ts; + linked_list_t *list, *hosts, *result; + + list = create_list(list_get_tests[_i].ts, FALSE); + ts = traffic_selector_list_create_from_enumerator(list->create_enumerator(list)); + hosts = list_get_tests[_i].hosts ? create_list(list_get_tests[_i].hosts, TRUE) : NULL; + + result = ts->get(ts, hosts, FALSE); + verify_list(list_get_tests[_i].get, NULL, result); + + result = ts->get(ts, hosts, TRUE); + verify_list(list_get_tests[_i].get_force, NULL, result); + + DESTROY_OFFSET_IF(hosts, offsetof(host_t, destroy)); + list->destroy_offset(list, offsetof(traffic_selector_t, destroy)); + ts->destroy(ts); +} +END_TEST + +struct { + char *ts; + char *hosts; + char *supplied; + char *select; + char *select_force; + bool narrowed; +} list_select_tests[] = { + { "dynamic", NULL, NULL, "dynamic", "dynamic", FALSE }, + { "dynamic", NULL, "10.0.1.0/24", "", "", TRUE }, + { "dynamic", "10.0.1.1", "0.0.0.0/0", "10.0.1.1/32", "10.0.1.1/32", FALSE }, + { "dynamic", "10.0.1.1", "10.0.1.1/32", "10.0.1.1/32", "10.0.1.1/32", FALSE }, + { "dynamic", "10.0.1.1 192.168.0.1", "10.0.1.0/24", + "10.0.1.1/32", "10.0.1.1/32", TRUE }, + { "dynamic", "10.0.1.1 192.168.0.1", "10.0.1.0/24 192.168.0.0/24", + "10.0.1.1/32 192.168.0.1/32", "10.0.1.1/32 192.168.0.1/32", FALSE }, + { "0.0.0.0/0", NULL, "0.0.0.0/0", "0.0.0.0/0", "0.0.0.0/0", FALSE }, + { "0.0.0.0/0", "10.0.1.1", "0.0.0.0/0", "0.0.0.0/0", "10.0.1.1/32", FALSE }, + { "0.0.0.0/0", NULL, "10.0.1.0/24", "10.0.1.0/24", "10.0.1.0/24", TRUE }, + { "0.0.0.0/0", NULL, "10.0.1.0/24 10.0.2.0/24", + "10.0.1.0/24 10.0.2.0/24", "10.0.1.0/24 10.0.2.0/24", TRUE }, + { "0.0.0.0/0", NULL, "10.0.2.0/24 10.0.1.0/24", + "10.0.2.0/24 10.0.1.0/24", "10.0.2.0/24 10.0.1.0/24", TRUE }, + { "10.0.1.0/24", NULL, "0.0.0.0/0", "10.0.1.0/24", "10.0.1.0/24", FALSE }, + { "10.0.1.0/24", "10.0.1.1", "0.0.0.0/0", "10.0.1.0/24", "10.0.1.1/32", FALSE }, + { "10.0.1.0/24 10.0.2.0/24", NULL, "0.0.0.0/0", + "10.0.1.0/24 10.0.2.0/24", "10.0.1.0/24 10.0.2.0/24", FALSE }, + { "10.0.2.0/24 10.0.1.0/24", NULL, "0.0.0.0/0", + "10.0.2.0/24 10.0.1.0/24", "10.0.2.0/24 10.0.1.0/24", FALSE }, + { "10.0.1.0/24 10.0.2.0/24", NULL, "10.0.2.0/24 10.0.1.0/24", + "10.0.1.0/24 10.0.2.0/24", "10.0.1.0/24 10.0.2.0/24", FALSE }, + { "10.0.1.0/24 10.0.2.0/24", NULL, "10.0.1.1/32 10.0.1.0/24", + "10.0.1.0/24", "10.0.1.0/24", TRUE }, + { "10.0.1.0/24 10.0.2.0/24", NULL, "10.0.2.1/32 10.0.1.0/24", + "10.0.1.0/24 10.0.2.1/32", "10.0.1.0/24 10.0.2.1/32", TRUE }, +}; + +START_TEST(test_list_select) +{ + traffic_selector_list_t *ts; + linked_list_t *list, *hosts, *supplied, *result; + bool narrowed = FALSE; + + list = create_list(list_select_tests[_i].ts, FALSE); + ts = traffic_selector_list_create_from_enumerator(list->create_enumerator(list)); + hosts = list_select_tests[_i].hosts ? create_list(list_select_tests[_i].hosts, TRUE) : NULL; + supplied = list_select_tests[_i].supplied ? create_list(list_select_tests[_i].supplied, FALSE) : NULL; + + result = ts->select(ts, supplied, hosts, FALSE, &narrowed); + verify_list(list_select_tests[_i].select, NULL, result); + ck_assert(narrowed == list_select_tests[_i].narrowed); + + result = ts->select(ts, supplied, hosts, TRUE, &narrowed); + verify_list(list_select_tests[_i].select_force, NULL, result); + ck_assert(narrowed == list_select_tests[_i].narrowed); + + DESTROY_OFFSET_IF(hosts, offsetof(host_t, destroy)); + DESTROY_OFFSET_IF(supplied, offsetof(traffic_selector_t, destroy)); + list->destroy_offset(list, offsetof(traffic_selector_t, destroy)); + ts->destroy(ts); +} +END_TEST + +START_TEST(test_list_equals) +{ + traffic_selector_list_t *ts1, *ts2; + + ts1 = traffic_selector_list_create(); + ts2 = traffic_selector_list_create(); + ck_assert(ts1->equals(ts1, ts2)); + + ts1->add(ts1, traffic_selector_create_from_cidr("10.0.1.0/24", 0, 0, 65535)); + ck_assert(!ts1->equals(ts1, ts2)); + + ts2->add(ts2, traffic_selector_create_from_cidr("10.0.1.0/24", 0, 0, 65535)); + ck_assert(ts1->equals(ts1, ts2)); + + ts1->destroy(ts1); + ts2->destroy(ts2); +} +END_TEST + +START_TEST(test_list_clone) +{ + traffic_selector_list_t *ts1, *ts2; + + ts1 = traffic_selector_list_create(); + ts2 = ts1->clone(ts1); + ck_assert(ts1->equals(ts1, ts2)); + ts2->destroy(ts2); + + ts1->add(ts1, traffic_selector_create_from_cidr("10.0.1.0/24", 0, 0, 65535)); + ts2 = ts1->clone(ts1); + ck_assert(ts1->equals(ts1, ts2)); + ts2->destroy(ts2); + + ts1->add(ts1, traffic_selector_create_from_cidr("10.0.1.0/24", 0, 0, 65535)); + ts2 = ts1->clone(ts1); + ck_assert(ts1->equals(ts1, ts2)); + + ts1->destroy(ts1); + ts2->destroy(ts2); +} +END_TEST + Suite *traffic_selector_suite_create() { Suite *s; @@ -890,5 +1084,12 @@ Suite *traffic_selector_suite_create() tcase_add_test(tc, test_printf_hook_hash); suite_add_tcase(s, tc); + tc = tcase_create("list"); + tcase_add_loop_test(tc, test_list_get, 0, 0/*countof(list_get_tests)*/); + tcase_add_loop_test(tc, test_list_select, 0, countof(list_select_tests)); + tcase_add_test(tc, test_list_equals); + tcase_add_test(tc, test_list_clone); + suite_add_tcase(s, tc); + return s; } From ad1ad2159f0b52c4debc52b889e696cac1d78408 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 1 Apr 2025 10:02:31 +0200 Subject: [PATCH 18/23] child-cfg: Use traffic selector list --- src/libcharon/config/child_cfg.c | 219 +++++++------------------------ src/libcharon/config/child_cfg.h | 29 +++- 2 files changed, 70 insertions(+), 178 deletions(-) diff --git a/src/libcharon/config/child_cfg.c b/src/libcharon/config/child_cfg.c index 23c96646e..3b5d60af6 100644 --- a/src/libcharon/config/child_cfg.c +++ b/src/libcharon/config/child_cfg.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2019 Tobias Brunner + * Copyright (C) 2008-2025 Tobias Brunner * Copyright (C) 2016 Andreas Steffen * Copyright (C) 2005-2007 Martin Willi * Copyright (C) 2005 Jan Hutter @@ -67,12 +67,12 @@ struct private_child_cfg_t { /** * list for traffic selectors for my site */ - linked_list_t *my_ts; + traffic_selector_list_t *my_ts; /** * list for traffic selectors for others site */ - linked_list_t *other_ts; + traffic_selector_list_t *other_ts; /** * updown script @@ -258,115 +258,56 @@ METHOD(child_cfg_t, add_traffic_selector, void, { if (local) { - this->my_ts->insert_last(this->my_ts, ts); + this->my_ts->add(this->my_ts, ts); } else { - this->other_ts->insert_last(this->other_ts, ts); + this->other_ts->add(this->other_ts, ts); } } /** - * Create a copy of the traffic selectors in the given list, while resolving - * "dynamic" traffic selectors using the given hosts, if any. When not narrowing - * as initiator, we also replace TS in transport mode. + * Check whether the config is for regular transport mode. */ -static linked_list_t *resolve_dynamic_ts(private_child_cfg_t *this, - linked_list_t *list, - linked_list_t *hosts, - bool narrowing) +static bool is_transport_mode(private_child_cfg_t *this) { - enumerator_t *e1, *e2; - traffic_selector_t *ts1, *ts2; - linked_list_t *result; - host_t *host; - bool transport_mode; - - if (!hosts || !hosts->get_count(hosts)) - { - return list->clone_offset(list, offsetof(traffic_selector_t, clone)); - } - transport_mode = !narrowing && this->mode == MODE_TRANSPORT && - !has_option(this, OPT_PROXY_MODE); - - result = linked_list_create(); - e1 = list->create_enumerator(list); - while (e1->enumerate(e1, &ts1)) - { - /* set hosts if TS is dynamic or as initiator in transport mode */ - bool dynamic = ts1->is_dynamic(ts1); - if (!dynamic && !transport_mode) - { - result->insert_last(result, ts1->clone(ts1)); - continue; - } - e2 = hosts->create_enumerator(hosts); - while (e2->enumerate(e2, &host)) - { - if (!dynamic && !host->is_anyaddr(host) && - !ts1->includes(ts1, host)) - { /* for transport mode, we skip TS that don't match - * specific IPs */ - continue; - } - ts2 = ts1->clone(ts1); - if (dynamic || !host->is_anyaddr(host)) - { /* don't make regular TS larger than they were */ - ts2->set_address(ts2, host); - } - result->insert_last(result, ts2); - } - e2->destroy(e2); - } - e1->destroy(e1); - return result; -} - -/** - * Remove duplicate traffic selectors in the given list. - */ -static void remove_duplicate_ts(linked_list_t *list) -{ - enumerator_t *e1, *e2; - traffic_selector_t *ts1, *ts2; - - e1 = list->create_enumerator(list); - e2 = list->create_enumerator(list); - while (e1->enumerate(e1, &ts1)) - { - while (e2->enumerate(e2, &ts2)) - { - if (ts1 != ts2) - { - if (ts2->is_contained_in(ts2, ts1)) - { - list->remove_at(list, e2); - ts2->destroy(ts2); - list->reset_enumerator(list, e1); - break; - } - if (ts1->is_contained_in(ts1, ts2)) - { - list->remove_at(list, e1); - ts1->destroy(ts1); - break; - } - } - } - list->reset_enumerator(list, e2); - } - e1->destroy(e1); - e2->destroy(e2); + return this->mode == MODE_TRANSPORT && !has_option(this, OPT_PROXY_MODE); } METHOD(child_cfg_t, get_traffic_selectors, linked_list_t*, private_child_cfg_t *this, bool local, linked_list_t *hosts) { - linked_list_t *result; + traffic_selector_list_t *ts = local ? this->my_ts : this->other_ts; - result = resolve_dynamic_ts(this, local ? this->my_ts : this->other_ts, - hosts, FALSE); - remove_duplicate_ts(result); + /* force replacing non-dynamic TS to the IPs in transport mode */ + return ts->get(ts, hosts, is_transport_mode(this)); +} + +/* + * Described in header + */ +linked_list_t *child_cfg_select_ts(child_cfg_t *cfg, bool local, + traffic_selector_list_t *list, + linked_list_t *supplied, linked_list_t *hosts) +{ + private_child_cfg_t *this = (private_child_cfg_t*)cfg; + traffic_selector_list_t *ts = list ?: (local ? this->my_ts : this->other_ts); + linked_list_t *result; + bool force, narrowed = FALSE; + + DBG2(DBG_CFG, "%s traffic selectors for %s:", + supplied ? "selecting" : "proposing", local ? "us" : "other"); + + /* force replacing non-dynamic TS to the IPs in transport mode, but only + * when proposing as initiator */ + force = supplied && is_transport_mode(this); + + result = ts->select(ts, supplied, hosts, force, &narrowed); + if (narrowed) + { + charon->bus->alert(charon->bus, ALERT_TS_NARROWED, + local, result, this); + } return result; } @@ -374,72 +315,7 @@ METHOD(child_cfg_t, select_traffic_selectors, linked_list_t*, private_child_cfg_t *this, bool local, linked_list_t *supplied, linked_list_t *hosts) { - enumerator_t *e1, *e2; - traffic_selector_t *ts1, *ts2, *selected; - linked_list_t *resolved, *result; - - result = linked_list_create(); - resolved = resolve_dynamic_ts(this, local ? this->my_ts : this->other_ts, - hosts, supplied); - - DBG2(DBG_CFG, "%s traffic selectors for %s:", - supplied ? "selecting" : "proposing", local ? "us" : "other"); - - if (!supplied) - { - while (resolved->remove_first(resolved, (void**)&ts1) == SUCCESS) - { - DBG2(DBG_CFG, " %R", ts1); - result->insert_last(result, ts1); - } - } - else - { - e1 = resolved->create_enumerator(resolved); - e2 = supplied->create_enumerator(supplied); - /* enumerate all configured/resolved selectors */ - while (e1->enumerate(e1, &ts1)) - { - /* enumerate all supplied traffic selectors */ - while (e2->enumerate(e2, &ts2)) - { - selected = ts1->get_subset(ts1, ts2); - if (selected) - { - DBG2(DBG_CFG, " config: %R, received: %R => match: %R", - ts1, ts2, selected); - result->insert_last(result, selected); - } - else - { - DBG2(DBG_CFG, " config: %R, received: %R => no match", - ts1, ts2); - } - } - supplied->reset_enumerator(supplied, e2); - } - e1->destroy(e1); - e2->destroy(e2); - - /* check if we/peer did any narrowing, raise alert */ - e1 = resolved->create_enumerator(resolved); - e2 = result->create_enumerator(result); - while (e1->enumerate(e1, &ts1)) - { - if (!e2->enumerate(e2, &ts2) || !ts1->equals(ts1, ts2)) - { - charon->bus->alert(charon->bus, ALERT_TS_NARROWED, - local, result, this); - break; - } - } - e1->destroy(e1); - e2->destroy(e2); - - } - resolved->destroy_offset(resolved, offsetof(traffic_selector_t, destroy)); - remove_duplicate_ts(result); - return result; + return child_cfg_select_ts(&this->public, local, NULL, supplied, hosts); } METHOD(child_cfg_t, get_updown, char*, @@ -706,13 +582,8 @@ METHOD(child_cfg_t, equals, bool, { return FALSE; } - if (!this->my_ts->equals_offset(this->my_ts, other->my_ts, - offsetof(traffic_selector_t, equals))) - { - return FALSE; - } - if (!this->other_ts->equals_offset(this->other_ts, other->other_ts, - offsetof(traffic_selector_t, equals))) + if (!this->my_ts->equals(this->my_ts, other->my_ts) || + !this->other_ts->equals(this->other_ts, other->other_ts)) { return FALSE; } @@ -758,8 +629,8 @@ METHOD(child_cfg_t, destroy, void, if (ref_put(&this->refcount)) { this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy)); - this->my_ts->destroy_offset(this->my_ts, offsetof(traffic_selector_t, destroy)); - this->other_ts->destroy_offset(this->other_ts, offsetof(traffic_selector_t, destroy)); + this->my_ts->destroy(this->my_ts); + this->other_ts->destroy(this->other_ts); DESTROY_IF(this->label); free(this->updown); free(this->interface); @@ -835,8 +706,8 @@ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data) .interface = strdupnull(data->interface), .refcount = 1, .proposals = linked_list_create(), - .my_ts = linked_list_create(), - .other_ts = linked_list_create(), + .my_ts = traffic_selector_list_create(), + .other_ts = traffic_selector_list_create(), .replay_window = lib->settings->get_int(lib->settings, "%s.replay_window", DEFAULT_REPLAY_WINDOW, lib->ns), .hw_offload = data->hw_offload, diff --git a/src/libcharon/config/child_cfg.h b/src/libcharon/config/child_cfg.h index eb78b3d32..e6219f68c 100644 --- a/src/libcharon/config/child_cfg.h +++ b/src/libcharon/config/child_cfg.h @@ -32,6 +32,7 @@ typedef struct child_cfg_create_t child_cfg_create_t; #include #include +#include #include #include @@ -121,12 +122,12 @@ struct child_cfg_t { /** * Get a list of configured traffic selectors to use for the CHILD_SA. * - * The config contains two set of traffic selectors, one for the local + * The config contains two sets of traffic selectors, one for the local * side, one for the remote side. * * Some traffic selectors may be "dynamic", meaning they are narrowed down * to a specific address (host-to-host or virtual-IP setups). Use the - * "hosts" parameter to narrow such traffic selectors to an address. + * \p hosts parameter to narrow such traffic selectors to an address. * * Returned list and its traffic selectors must be destroyed after use. * @@ -143,7 +144,7 @@ struct child_cfg_t { /** * Select a list of traffic selectors to use for the CHILD_SA. * - * The config contains two set of traffic selectors, one for the local + * The config contains two sets of traffic selectors, one for the local * side, one for the remote side. * * If a list with traffic selectors is supplied, these are used to narrow @@ -151,10 +152,13 @@ struct child_cfg_t { * * Some traffic selectors may be "dynamic", meaning they are narrowed down * to a specific address (host-to-host or virtual-IP setups). Use the - * "hosts" parameter to narrow such traffic selectors to an address. + * \p hosts parameter to narrow such traffic selectors to an address. * * Returned list and its traffic selectors must be destroyed after use. * + * Details about the selection process are logged and an alert is triggered + * if narrowing occurred. + * * @param local TRUE for TS on local side, FALSE for remote * @param supplied list with TS to select from, or NULL * @param hosts addresses to use for narrowing "dynamic" TS', host_t @@ -473,4 +477,21 @@ struct child_cfg_create_t { */ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data); +/** + * Select and narrow traffic selectors in the given traffic selector list. + * Refer to child_cfg_t::select_traffic_selectors() for details, the difference + * is that this can work with external traffic selector lists. + * + * @param this config to use + * @param local TRUE for TS on local side, FALSE for remote (also + * used for logging) + * @param list traffic selectors to use instead of those in config + * @param supplied list with TS to select from, or NULL + * @param hosts addresses to use for narrowing "dynamic" TS', host_t + * @return list containing the traffic selectors + */ +linked_list_t *child_cfg_select_ts(child_cfg_t *this, bool local, + traffic_selector_list_t *list, + linked_list_t *supplied, linked_list_t *hosts); + #endif /** CHILD_CFG_H_ @}*/ From 920545835539e32cda3691ba12b47a6fe5634329 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 1 Apr 2025 17:28:28 +0200 Subject: [PATCH 19/23] child-create: Use more generic method to pass information from previous SA Besides the previous key exchange method, this will allow us to also reuse the previous traffic selectors. Some data is still passed in separate methods as some are set even when there is no previous SA and others are not set in all cases. The interface for queue_child() now optionally takes the previous Child SA to handle both recreations and initiations from scratch. --- src/libcharon/sa/ike_sa.c | 12 +--- src/libcharon/sa/ikev1/task_manager_v1.c | 16 ++++- src/libcharon/sa/ikev2/task_manager_v2.c | 19 +++++- src/libcharon/sa/ikev2/tasks/child_create.c | 30 ++++++--- src/libcharon/sa/ikev2/tasks/child_create.h | 11 ++-- src/libcharon/sa/ikev2/tasks/child_delete.c | 68 ++++++++++----------- src/libcharon/sa/ikev2/tasks/child_rekey.c | 11 +--- src/libcharon/sa/task_manager.h | 3 +- 8 files changed, 100 insertions(+), 70 deletions(-) diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index 77e572182..3cfcdcf33 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -1628,7 +1628,7 @@ METHOD(ike_sa_t, initiate, status_t, if (child_cfg) { /* normal IKE_SA with CHILD_SA */ - this->task_manager->queue_child(this->task_manager, child_cfg, args); + this->task_manager->queue_child(this->task_manager, child_cfg, args, NULL); #ifdef ME if (this->peer_cfg->get_mediated_by(this->peer_cfg)) { @@ -2119,20 +2119,12 @@ static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new, } if (action & ACTION_START) { - child_init_args_t args = { - .reqid = child_sa->get_reqid_ref(child_sa), - .label = child_sa->get_label(child_sa), - }; child_cfg = child_sa->get_config(child_sa); DBG1(DBG_IKE, "restarting CHILD_SA %s", child_cfg->get_name(child_cfg)); other->task_manager->queue_child(other->task_manager, child_cfg->get_ref(child_cfg), - &args); - if (args.reqid) - { - charon->kernel->release_reqid(charon->kernel, args.reqid); - } + NULL, child_sa); } } enumerator->destroy(enumerator); diff --git a/src/libcharon/sa/ikev1/task_manager_v1.c b/src/libcharon/sa/ikev1/task_manager_v1.c index 672a04927..5afc66ae8 100644 --- a/src/libcharon/sa/ikev1/task_manager_v1.c +++ b/src/libcharon/sa/ikev1/task_manager_v1.c @@ -1691,11 +1691,23 @@ METHOD(task_manager_t, queue_mobike, void, } METHOD(task_manager_t, queue_child, void, - private_task_manager_t *this, child_cfg_t *cfg, child_init_args_t *args) + private_task_manager_t *this, child_cfg_t *cfg, child_init_args_t *args, + child_sa_t *child_sa) { quick_mode_t *task; + uint32_t reqid; - if (args) + if (child_sa) + { + task = quick_mode_create(this->ike_sa, cfg, NULL, NULL, 0); + reqid = child_sa->get_reqid_ref(child_sa); + if (reqid) + { + task->use_reqid(task, reqid); + charon->kernel->release_reqid(charon->kernel, reqid); + } + } + else if (args) { task = quick_mode_create(this->ike_sa, cfg, args->src, args->dst, args->seq); diff --git a/src/libcharon/sa/ikev2/task_manager_v2.c b/src/libcharon/sa/ikev2/task_manager_v2.c index 2180bd909..d46559182 100644 --- a/src/libcharon/sa/ikev2/task_manager_v2.c +++ b/src/libcharon/sa/ikev2/task_manager_v2.c @@ -2197,6 +2197,7 @@ static void trigger_mbb_reauth(private_task_manager_t *this) cfg = child_sa->get_config(child_sa); child_create = child_create_create(new, cfg->get_ref(cfg), FALSE, NULL, NULL, 0); + child_create->recreate_sa(child_create, child_sa); reqid = child_sa->get_reqid_ref(child_sa); if (reqid) { @@ -2369,11 +2370,25 @@ METHOD(task_manager_t, queue_dpd, void, } METHOD(task_manager_t, queue_child, void, - private_task_manager_t *this, child_cfg_t *cfg, child_init_args_t *args) + private_task_manager_t *this, child_cfg_t *cfg, child_init_args_t *args, + child_sa_t *child_sa) { child_create_t *task; + uint32_t reqid; - if (args) + if (child_sa) + { + task = child_create_create(this->ike_sa, cfg, FALSE, NULL, NULL, 0); + task->recreate_sa(task, child_sa); + reqid = child_sa->get_reqid_ref(child_sa); + if (reqid) + { + task->use_reqid(task, reqid); + charon->kernel->release_reqid(charon->kernel, reqid); + } + task->use_label(task, child_sa->get_label(child_sa)); + } + else if (args) { task = child_create_create(this->ike_sa, cfg, FALSE, args->src, args->dst, args->seq); diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index be4662f1c..3020967fd 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2024 Tobias Brunner + * Copyright (C) 2008-2025 Tobias Brunner * Copyright (C) 2005-2008 Martin Willi * Copyright (C) 2005 Jan Hutter * @@ -1574,7 +1574,11 @@ METHOD(task_t, build_i, status_t, return FAILED; } - if (!no_ke && !this->retry) + if (no_ke) + { /* we might have one set if we are recreating this SA */ + this->ke_method = KE_NONE; + } + else if (!this->retry) { /* during a rekeying the method might already be set */ if (this->ke_method == KE_NONE) { @@ -2080,7 +2084,7 @@ METHOD(task_t, build_r, status_t, return SUCCESS; } - /* check if ike_config_t included non-critical error notifies */ + /* check if ike_config_t task included non-critical error notifies */ enumerator = message->create_payload_enumerator(message); while (enumerator->enumerate(enumerator, &payload)) { @@ -2540,10 +2544,22 @@ METHOD(child_create_t, use_label, void, this->child.label = label ? label->clone(label) : NULL; } -METHOD(child_create_t, use_ke_method, void, - private_child_create_t *this, key_exchange_method_t ke_method) +METHOD(child_create_t, recreate_sa, void, + private_child_create_t *this, child_sa_t *old) { - this->ke_method = ke_method; + if (this->initiator) + { + proposal_t *proposal; + uint16_t ke_method; + + proposal = old->get_proposal(old); + if (proposal->get_algorithm(proposal, KEY_EXCHANGE_METHOD, + &ke_method, NULL)) + { + /* reuse the KE method negotiated previously */ + this->ke_method = ke_method; + } + } } METHOD(child_create_t, get_child, child_sa_t*, @@ -2719,7 +2735,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_ke_method = _use_ke_method, + .recreate_sa = _recreate_sa, .abort = _abort_, .task = { .get_type = _get_type, diff --git a/src/libcharon/sa/ikev2/tasks/child_create.h b/src/libcharon/sa/ikev2/tasks/child_create.h index f3ff0cc7d..0c0e6137e 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.h +++ b/src/libcharon/sa/ikev2/tasks/child_create.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2024 Tobias Brunner + * Copyright (C) 2018-2025 Tobias Brunner * Copyright (C) 2007 Martin Willi * * Copyright (C) secunet Security Networks AG @@ -81,13 +81,12 @@ struct child_create_t { void (*use_label)(child_create_t *this, sec_label_t *label); /** - * Initially propose a specific KE method to override configuration. + * Use data from the given old SA (e.g. KE method and traffic selectors) + * when rekeying/recreating it. * - * This is used during rekeying to prefer the previously negotiated method. - * - * @param ke_method KE method to use + * @param old old CHILD_SA that is getting rekeyed/recreated */ - void (*use_ke_method)(child_create_t *this, key_exchange_method_t ke_method); + void (*recreate_sa)(child_create_t *this, child_sa_t *old); /** * Get the lower of the two nonces, used for rekey collisions. diff --git a/src/libcharon/sa/ikev2/tasks/child_delete.c b/src/libcharon/sa/ikev2/tasks/child_delete.c index 3282a21f7..7987bdd53 100644 --- a/src/libcharon/sa/ikev2/tasks/child_delete.c +++ b/src/libcharon/sa/ikev2/tasks/child_delete.c @@ -151,6 +151,29 @@ static void conclude_rekeying(private_child_delete_t *this, child_sa_t *old) child_rekey_conclude_rekeying(old, child_sa); } +/** + * Queue a task to recreate the given CHILD_SA. + */ +static void queue_child_create(ike_sa_t *ike_sa, child_sa_t *child_sa) +{ + child_create_t *child_create; + child_cfg_t *child_cfg; + uint32_t reqid; + + child_cfg = child_sa->get_config(child_sa); + child_create = child_create_create(ike_sa, child_cfg->get_ref(child_cfg), + FALSE, NULL, NULL, 0); + child_create->recreate_sa(child_create, child_sa); + reqid = child_sa->get_reqid_ref(child_sa); + if (reqid) + { + child_create->use_reqid(child_create, reqid); + charon->kernel->release_reqid(charon->kernel, reqid); + } + child_create->use_label(child_create, child_sa->get_label(child_sa)); + ike_sa->queue_task(ike_sa, (task_t*)child_create); +} + /** * Destroy and optionally reestablish the given CHILD_SA according to config. */ @@ -160,12 +183,9 @@ static status_t destroy_and_reestablish_internal(ike_sa_t *ike_sa, bool delete_action, action_t forced_action) { - child_init_args_t args = {}; child_cfg_t *child_cfg; - protocol_id_t protocol; - uint32_t spi; action_t action; - status_t status = SUCCESS; + bool initiate = FALSE; child_sa->set_state(child_sa, CHILD_DELETED); if (trigger_updown) @@ -173,44 +193,29 @@ static status_t destroy_and_reestablish_internal(ike_sa_t *ike_sa, charon->bus->child_updown(charon->bus, child_sa, FALSE); } - protocol = child_sa->get_protocol(child_sa); - spi = child_sa->get_spi(child_sa, TRUE); - child_cfg = child_sa->get_config(child_sa); - child_cfg->get_ref(child_cfg); - args.reqid = child_sa->get_reqid_ref(child_sa); - args.label = child_sa->get_label(child_sa); - if (args.label) - { - args.label = args.label->clone(args.label); - } - action = forced_action ?: child_sa->get_close_action(child_sa); - DBG1(DBG_IKE, "CHILD_SA %s{%u} closed", child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa)); - ike_sa->destroy_child_sa(ike_sa, protocol, spi); + action = forced_action ?: child_sa->get_close_action(child_sa); if (delete_action) { if (action & ACTION_TRAP) { + child_cfg = child_sa->get_config(child_sa); charon->traps->install(charon->traps, ike_sa->get_peer_cfg(ike_sa), - child_cfg); + child_cfg->get_ref(child_cfg)); } if (action & ACTION_START) { - child_cfg->get_ref(child_cfg); - status = ike_sa->initiate(ike_sa, child_cfg, &args); + queue_child_create(ike_sa, child_sa); + initiate = TRUE; } } - child_cfg->destroy(child_cfg); - if (args.reqid) - { - charon->kernel->release_reqid(charon->kernel, args.reqid); - } - DESTROY_IF(args.label); - return status; + ike_sa->destroy_child_sa(ike_sa, child_sa->get_protocol(child_sa), + child_sa->get_spi(child_sa, TRUE)); + return initiate ? ike_sa->initiate(ike_sa, NULL, NULL) : SUCCESS; } /* @@ -550,13 +555,8 @@ METHOD(task_t, build_i, status_t, if (this->expired) { - child_cfg_t *child_cfg; - - DBG1(DBG_IKE, "scheduling CHILD_SA recreate after hard expire"); - child_cfg = child_sa->get_config(child_sa); - this->ike_sa->queue_task(this->ike_sa, (task_t*) - child_create_create(this->ike_sa, child_cfg->get_ref(child_cfg), - FALSE, NULL, NULL, 0)); + DBG1(DBG_IKE, "queue CHILD_SA recreate after hard expire"); + queue_child_create(this->ike_sa, child_sa); } return NEED_MORE; } diff --git a/src/libcharon/sa/ikev2/tasks/child_rekey.c b/src/libcharon/sa/ikev2/tasks/child_rekey.c index 86161423b..fb4a724f4 100644 --- a/src/libcharon/sa/ikev2/tasks/child_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/child_rekey.c @@ -269,20 +269,14 @@ METHOD(task_t, build_i, status_t, if (!this->child_create) { child_cfg_t *config; - proposal_t *proposal; - uint16_t ke_method; uint32_t reqid; config = this->child_sa->get_config(this->child_sa); this->child_create = child_create_create(this->ike_sa, config->get_ref(config), TRUE, NULL, NULL, 0); - proposal = this->child_sa->get_proposal(this->child_sa); - if (proposal->get_algorithm(proposal, KEY_EXCHANGE_METHOD, - &ke_method, NULL)) - { /* reuse the KE method negotiated previously */ - this->child_create->use_ke_method(this->child_create, ke_method); - } + this->child_create->recreate_sa(this->child_create, this->child_sa); + reqid = this->child_sa->get_reqid_ref(this->child_sa); if (reqid) { @@ -436,6 +430,7 @@ METHOD(task_t, build_r, status_t, if (message->get_exchange_type(message) == CREATE_CHILD_SA) { + this->child_create->recreate_sa(this->child_create, this->child_sa); reqid = this->child_sa->get_reqid_ref(this->child_sa); if (reqid) { diff --git a/src/libcharon/sa/task_manager.h b/src/libcharon/sa/task_manager.h index 69e5a812e..a30fab5a9 100644 --- a/src/libcharon/sa/task_manager.h +++ b/src/libcharon/sa/task_manager.h @@ -202,9 +202,10 @@ struct task_manager_t { * * @param cfg CHILD_SA config to establish * @param args optional arguments for the initiation + * @param child_sa optional CHILD_SA when recreating (instead of args) */ void (*queue_child)(task_manager_t *this, child_cfg_t *cfg, - child_init_args_t *args); + child_init_args_t *args, child_sa_t *child_sa); /** * Queue CHILD_SA rekeying tasks. From e7fc7a4ecc0b81b03964c327eff436f2abf2d9fd Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 1 Apr 2025 17:33:35 +0200 Subject: [PATCH 20/23] 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. --- src/libcharon/sa/ikev2/tasks/child_create.c | 268 +++++++++++++------- 1 file changed, 174 insertions(+), 94 deletions(-) diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index 3020967fd..aa0070a01 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -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); From 58d6778adb2a5fd672bf4d52269b969213affbe3 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 2 Apr 2025 14:39:38 +0200 Subject: [PATCH 21/23] child-sa: Add helper to check if a list of TS match negotiated TS --- src/libcharon/sa/child_sa.c | 36 ++++++++++++++++++++++++++++++++++++ src/libcharon/sa/child_sa.h | 13 +++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 8dbdd6055..2dfae0efb 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -2219,6 +2219,29 @@ static bool is_ts_match(traffic_selector_t *to_check, array_t *list) return FALSE; } +/** + * Check if all given traffic selectors are contained in any of the traffic + * selectors in the given list. + */ +static bool is_ts_list_match(traffic_selector_list_t *to_check, array_t *list) +{ + enumerator_t *enumerator; + traffic_selector_t *ts; + bool matched = TRUE; + + enumerator = to_check->create_enumerator(to_check); + while (enumerator->enumerate(enumerator, &ts)) + { + if (!is_ts_match(ts, list)) + { + matched = FALSE; + break; + } + } + enumerator->destroy(enumerator); + return matched; +} + /* * Described in header */ @@ -2231,3 +2254,16 @@ bool child_sa_ts_match(child_sa_t *child, traffic_selector_t *src, is_ts_match(src, this->my_ts) && is_ts_match(dst, this->other_ts); } + +/* + * Described in header + */ +bool child_sa_ts_lists_match(child_sa_t *child, traffic_selector_list_t *src, + traffic_selector_list_t *dst) +{ + private_child_sa_t *this = (private_child_sa_t*)child; + + return src && dst && + is_ts_list_match(src, this->my_ts) && + is_ts_list_match(dst, this->other_ts); +} diff --git a/src/libcharon/sa/child_sa.h b/src/libcharon/sa/child_sa.h index da2055b03..57d345857 100644 --- a/src/libcharon/sa/child_sa.h +++ b/src/libcharon/sa/child_sa.h @@ -607,4 +607,17 @@ child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, bool child_sa_ts_match(child_sa_t *this, traffic_selector_t *src, traffic_selector_t *dst); +/** + * Check if the given lists of source and destination traffic selectors (e.g. + * from a previous SA) match the negotiated local and remote traffic + * selectors of this child SA. + * + * @param this CHILD_SA to check traffic selectors against + * @param src source traffic selector list + * @param dst destination traffic selector list + * @return TRUE if all traffic selectors match + */ +bool child_sa_ts_lists_match(child_sa_t *this, traffic_selector_list_t *src, + traffic_selector_list_t *dst); + #endif /** CHILD_SA_H_ @}*/ From 6c7c539eaf07c1931f1fa08a03ad503ee0b0ef8a Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 2 Apr 2025 14:49:58 +0200 Subject: [PATCH 22/23] child-create: Consider previous TS when checking for duplicates --- src/libcharon/sa/ikev2/tasks/child_create.c | 23 ++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index aa0070a01..534afb96c 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -1352,13 +1352,22 @@ static bool reqid_and_ts_equals(private_child_create_t *this, child_sa_t *a, return TRUE; } /* if the reqids differ, the one of the established SA was changed due to - * narrowing. in this case we check if we have triggering TS. if not, we - * assume the peer will do the same narrowing and treat the SAs equal. - * otherwise, we check whether they match the TS of the existing SA. if - * they do, there is no point to negotiate another SA. if not, the peer - * will potentially narrow the TS to a different set for the new SA */ - return !this->packet_tsi || !this->packet_tsr || - child_sa_ts_match(a, this->packet_tsi, this->packet_tsr); + * narrowing. in this case, we check if we have either triggering TS or + * previous TS. if so, we check whether the available TS match the TS of + * the existing SA. if they do, there is no point to negotiate another SA. + * if not, the peer will potentially narrow the TS to a different set for + * the new SA. */ + if (this->packet_tsi && this->packet_tsr) + { + return child_sa_ts_match(a, this->packet_tsi, this->packet_tsr); + } + if (this->my_ts && this->other_ts) + { + return child_sa_ts_lists_match(a, this->my_ts, this->other_ts); + } + /* if we don't have any TS to compare, we assume the peer will do the same + * narrowing and treat the SAs equal.*/ + return TRUE; } /** From 8cb5918b0c7bb0d1391720d14cac08e9d4e5a2a6 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 24 Mar 2025 14:02:25 +0100 Subject: [PATCH 23/23] testing: Add ikev2/net2net-route-narrow scenario --- .../net2net-route-narrow/description.txt | 11 ++++++ .../ikev2/net2net-route-narrow/evaltest.dat | 21 ++++++++++++ .../hosts/moon/etc/strongswan.conf | 14 ++++++++ .../hosts/moon/etc/swanctl/swanctl.conf | 34 +++++++++++++++++++ .../hosts/sun/etc/strongswan.conf | 16 +++++++++ .../hosts/sun/etc/swanctl/swanctl.conf | 30 ++++++++++++++++ .../ikev2/net2net-route-narrow/posttest.dat | 5 +++ .../ikev2/net2net-route-narrow/pretest.dat | 9 +++++ .../ikev2/net2net-route-narrow/test.conf | 25 ++++++++++++++ 9 files changed, 165 insertions(+) create mode 100755 testing/tests/ikev2/net2net-route-narrow/description.txt create mode 100755 testing/tests/ikev2/net2net-route-narrow/evaltest.dat create mode 100755 testing/tests/ikev2/net2net-route-narrow/hosts/moon/etc/strongswan.conf create mode 100755 testing/tests/ikev2/net2net-route-narrow/hosts/moon/etc/swanctl/swanctl.conf create mode 100755 testing/tests/ikev2/net2net-route-narrow/hosts/sun/etc/strongswan.conf create mode 100755 testing/tests/ikev2/net2net-route-narrow/hosts/sun/etc/swanctl/swanctl.conf create mode 100755 testing/tests/ikev2/net2net-route-narrow/posttest.dat create mode 100755 testing/tests/ikev2/net2net-route-narrow/pretest.dat create mode 100755 testing/tests/ikev2/net2net-route-narrow/test.conf diff --git a/testing/tests/ikev2/net2net-route-narrow/description.txt b/testing/tests/ikev2/net2net-route-narrow/description.txt new file mode 100755 index 000000000..03796e657 --- /dev/null +++ b/testing/tests/ikev2/net2net-route-narrow/description.txt @@ -0,0 +1,11 @@ +A trap policy on gateway sun will trigger SAs to gateway moon +that connec the subnets behind the two gateways. Based on the received traffic +selector from the triggering packet, gateway moon narrows down the +traffic selectors to one of two options. +Subsequent pings issued by client bob behind gateway sun to +alice and venus located behind gateway moon trigger the +trap policy and lead to the automatic establishment of the subnet-to-subnet +tunnels. +

+The updown script automatically inserts iptables-based firewall rules +that let pass the tunneled traffic. diff --git a/testing/tests/ikev2/net2net-route-narrow/evaltest.dat b/testing/tests/ikev2/net2net-route-narrow/evaltest.dat new file mode 100755 index 000000000..a2b1bb5e5 --- /dev/null +++ b/testing/tests/ikev2/net2net-route-narrow/evaltest.dat @@ -0,0 +1,21 @@ +sun::swanctl --list-pols --raw 2> /dev/null::net-net.*mode=TUNNEL local-ts=\[10.2.0.0/16] remote-ts=\[10.1.0.0/16]::YES +sun::cat /var/log/daemon.log::creating acquire job for policy 10.2.0.10/32\[icmp/8] === 10.1.0.10/32\[icmp/8]::YES +sun::cat /var/log/daemon.log::creating acquire job for policy 10.2.0.10/32\[icmp/8] === 10.1.0.20/32\[icmp/8]::YES +sun:: swanctl --list-sas --raw 2> /dev/null::gw-gw.*version=2 state=ESTABLISHED local-host=192.168.0.2 local-port=500 local-id=sun.strongswan.org remote-host=192.168.0.1 remote-port=500 remote-id=moon.strongswan.org initiator=yes.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-net.*state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.2.0.0/16] remote-ts=\[10.1.0.0/28]::YES +sun:: swanctl --list-sas --raw 2> /dev/null::gw-gw.*version=2 state=ESTABLISHED local-host=192.168.0.2 local-port=500 local-id=sun.strongswan.org remote-host=192.168.0.1 remote-port=500 remote-id=moon.strongswan.org initiator=yes.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-net.*state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128.*local-ts=\[10.2.0.0/16] remote-ts=\[10.1.0.16/28]::YES +moon::swanctl --list-sas --raw 2> /dev/null::gw-gw.*version=2 state=ESTABLISHED local-host=192.168.0.1 local-port=500 local-id=moon.strongswan.org remote-host=192.168.0.2 remote-port=500 remote-id=sun.strongswan.org.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-1.*state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.1.0.0/28] remote-ts=\[10.2.0.0/16]::YES +moon::swanctl --list-sas --raw 2> /dev/null::gw-gw.*version=2 state=ESTABLISHED local-host=192.168.0.1 local-port=500 local-id=moon.strongswan.org remote-host=192.168.0.2 remote-port=500 remote-id=sun.strongswan.org.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-2.*state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128.*local-ts=\[10.1.0.16/28] remote-ts=\[10.2.0.0/16]::YES +bob::ping -c 1 PH_IP_ALICE::64 bytes from PH_IP_ALICE: icmp_.eq=1::YES +bob::ping -c 1 PH_IP_VENUS::64 bytes from PH_IP_VENUS: icmp_.eq=1::YES +sun::swanctl --rekey --child net-net +sun::sleep 2 +bob::ping -c 1 PH_IP_ALICE::64 bytes from PH_IP_ALICE: icmp_.eq=1::YES +bob::ping -c 1 PH_IP_VENUS::64 bytes from PH_IP_VENUS: icmp_.eq=1::YES +sun::swanctl --rekey --reauth --ike gw-gw +sun::sleep 1 +sun::swanctl --list-sas --ike-id 2 --raw 2> /dev/null::gw-gw.*version=2 state=ESTABLISHED local-host=192.168.0.2 local-port=500 local-id=sun.strongswan.org remote-host=192.168.0.1 remote-port=500 remote-id=moon.strongswan.org initiator=yes.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-net.*state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.2.0.0/16] remote-ts=\[10.1.0.0/28]::YES +sun::swanctl --list-sas --ike-id 2 --raw 2> /dev/null::gw-gw.*version=2 state=ESTABLISHED local-host=192.168.0.2 local-port=500 local-id=sun.strongswan.org remote-host=192.168.0.1 remote-port=500 remote-id=moon.strongswan.org initiator=yes.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-net.*state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128.*local-ts=\[10.2.0.0/16] remote-ts=\[10.1.0.16/28]::YES +bob::ping -c 1 PH_IP_ALICE::64 bytes from PH_IP_ALICE: icmp_.eq=1::YES +bob::ping -c 1 PH_IP_VENUS::64 bytes from PH_IP_VENUS: icmp_.eq=1::YES +moon::tcpdump::IP moon.strongswan.org > sun.strongswan.org: ESP::YES +moon::tcpdump::IP sun.strongswan.org > moon.strongswan.org: ESP::YES diff --git a/testing/tests/ikev2/net2net-route-narrow/hosts/moon/etc/strongswan.conf b/testing/tests/ikev2/net2net-route-narrow/hosts/moon/etc/strongswan.conf new file mode 100755 index 000000000..091aea509 --- /dev/null +++ b/testing/tests/ikev2/net2net-route-narrow/hosts/moon/etc/strongswan.conf @@ -0,0 +1,14 @@ +# /etc/strongswan.conf - strongSwan configuration file + +swanctl { + load = pem pkcs1 revocation constraints pubkey openssl random +} + +charon-systemd { + load = random nonce openssl pem pkcs1 revocation curl kernel-netlink socket-default updown vici + syslog { + daemon { + cfg = 2 + } + } +} diff --git a/testing/tests/ikev2/net2net-route-narrow/hosts/moon/etc/swanctl/swanctl.conf b/testing/tests/ikev2/net2net-route-narrow/hosts/moon/etc/swanctl/swanctl.conf new file mode 100755 index 000000000..e2a766cf8 --- /dev/null +++ b/testing/tests/ikev2/net2net-route-narrow/hosts/moon/etc/swanctl/swanctl.conf @@ -0,0 +1,34 @@ +connections { + + gw-gw { + local_addrs = 192.168.0.1 + remote_addrs = 192.168.0.2 + + local { + auth = pubkey + certs = moonCert.pem + id = moon.strongswan.org + } + remote { + auth = pubkey + id = sun.strongswan.org + } + children { + net-1 { + local_ts = 10.1.0.0/28 + remote_ts = 10.2.0.0/16 + + start_action = none + updown = /usr/local/libexec/ipsec/_updown iptables + esp_proposals = aes128gcm128-x25519 + } + net-2 : connections.gw-gw.children.net-1 { + local_ts = 10.1.0.16/28 + esp_proposals = aes128-sha256-x25519 + } + } + version = 2 + mobike = no + proposals = aes128-sha256-x25519 + } +} diff --git a/testing/tests/ikev2/net2net-route-narrow/hosts/sun/etc/strongswan.conf b/testing/tests/ikev2/net2net-route-narrow/hosts/sun/etc/strongswan.conf new file mode 100755 index 000000000..5b930b77d --- /dev/null +++ b/testing/tests/ikev2/net2net-route-narrow/hosts/sun/etc/strongswan.conf @@ -0,0 +1,16 @@ +# /etc/strongswan.conf - strongSwan configuration file + +swanctl { + load = pem pkcs1 revocation constraints pubkey openssl random +} + +charon-systemd { + load = random nonce openssl pem pkcs1 revocation kernel-netlink socket-default updown vici + # delete rekeyed CHILD_SAs quickly so we can reauthenticate the IKE_SA + delete_rekeyed_delay = 1 + syslog { + daemon { + cfg = 2 + } + } +} diff --git a/testing/tests/ikev2/net2net-route-narrow/hosts/sun/etc/swanctl/swanctl.conf b/testing/tests/ikev2/net2net-route-narrow/hosts/sun/etc/swanctl/swanctl.conf new file mode 100755 index 000000000..010bc6b13 --- /dev/null +++ b/testing/tests/ikev2/net2net-route-narrow/hosts/sun/etc/swanctl/swanctl.conf @@ -0,0 +1,30 @@ +connections { + + gw-gw { + local_addrs = 192.168.0.2 + remote_addrs = 192.168.0.1 + + local { + auth = pubkey + certs = sunCert.pem + id = sun.strongswan.org + } + remote { + auth = pubkey + id = moon.strongswan.org + } + children { + net-net { + local_ts = 10.2.0.0/16 + remote_ts = 10.1.0.0/16 + + start_action = trap + updown = /usr/local/libexec/ipsec/_updown iptables + esp_proposals = aes128gcm128-x25519,aes128-sha256-x25519 + } + } + version = 2 + mobike = no + proposals = aes128-sha256-x25519 + } +} diff --git a/testing/tests/ikev2/net2net-route-narrow/posttest.dat b/testing/tests/ikev2/net2net-route-narrow/posttest.dat new file mode 100755 index 000000000..74b4f9bd7 --- /dev/null +++ b/testing/tests/ikev2/net2net-route-narrow/posttest.dat @@ -0,0 +1,5 @@ +sun::swanctl --terminate --ike gw-gw 2> /dev/null +sun::systemctl stop strongswan +moon::systemctl stop strongswan +sun::iptables-restore < /etc/iptables.flush +moon::iptables-restore < /etc/iptables.flush diff --git a/testing/tests/ikev2/net2net-route-narrow/pretest.dat b/testing/tests/ikev2/net2net-route-narrow/pretest.dat new file mode 100755 index 000000000..c699b5e7c --- /dev/null +++ b/testing/tests/ikev2/net2net-route-narrow/pretest.dat @@ -0,0 +1,9 @@ +sun::iptables-restore < /etc/iptables.rules +moon::iptables-restore < /etc/iptables.rules +sun::systemctl start strongswan +moon::systemctl start strongswan +sun::expect-connection gw-gw +moon::expect-connection gw-gw +bob::ping -c 3 -W 1 -i 0.2 PH_IP_ALICE +bob::sleep 0.5 +bob::ping -c 3 -W 1 -i 0.2 PH_IP_VENUS diff --git a/testing/tests/ikev2/net2net-route-narrow/test.conf b/testing/tests/ikev2/net2net-route-narrow/test.conf new file mode 100755 index 000000000..474ca4078 --- /dev/null +++ b/testing/tests/ikev2/net2net-route-narrow/test.conf @@ -0,0 +1,25 @@ +#!/bin/bash +# +# This configuration file provides information on the +# guest instances used for this test + +# All guest instances that are required for this test +# +VIRTHOSTS="alice venus moon winnetou sun bob" + +# Corresponding block diagram +# +DIAGRAM="a-v-m-w-s-b.png" + +# Guest instances on which tcpdump is to be started +# +TCPDUMPHOSTS="moon" + +# Guest instances on which IPsec is started +# Used for IPsec logging purposes +# +IPSECHOSTS="moon sun" + +# charon controlled by swanctl +# +SWANCTL=1