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/config/child_cfg.c b/src/libcharon/config/child_cfg.c index 837495c59..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,165 +258,66 @@ 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); } } -METHOD(child_cfg_t, get_traffic_selectors, linked_list_t*, - private_child_cfg_t *this, bool local, linked_list_t *supplied, - linked_list_t *hosts, bool log) +/** + * Check whether the config is for regular transport mode. + */ +static bool is_transport_mode(private_child_cfg_t *this) { - enumerator_t *e1, *e2; - traffic_selector_t *ts1, *ts2, *selected; - linked_list_t *result, *derived; - host_t *host; + return this->mode == MODE_TRANSPORT && !has_option(this, OPT_PROXY_MODE); +} - 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); +METHOD(child_cfg_t, get_traffic_selectors, linked_list_t*, + private_child_cfg_t *this, bool local, linked_list_t *hosts) +{ + traffic_selector_list_t *ts = local ? this->my_ts : this->other_ts; - 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) - { - if (log) - { - DBG2(DBG_CFG, " %R", ts1); - } - result->insert_last(result, ts1); - } - derived->destroy(derived); - } - else - { - e1 = derived->create_enumerator(derived); - e2 = supplied->create_enumerator(supplied); - /* enumerate all configured/derived selectors */ - while (e1->enumerate(e1, &ts1)) - { - /* enumerate all supplied traffic selectors */ - while (e2->enumerate(e2, &ts2)) - { - selected = ts1->get_subset(ts1, ts2); - if (selected) - { - if (log) - { - DBG2(DBG_CFG, " config: %R, received: %R => match: %R", - ts1, ts2, selected); - } - result->insert_last(result, selected); - } - else if (log) - { - DBG2(DBG_CFG, " config: %R, received: %R => no match", - ts1, ts2); - } - } - supplied->reset_enumerator(supplied, e2); - } - e1->destroy(e1); - e2->destroy(e2); + /* force replacing non-dynamic TS to the IPs in transport mode */ + return ts->get(ts, hosts, is_transport_mode(this)); +} - /* check if we/peer did any narrowing, raise alert */ - e1 = derived->create_enumerator(derived); - 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); +/* + * 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; - derived->destroy_offset(derived, offsetof(traffic_selector_t, destroy)); - } + DBG2(DBG_CFG, "%s traffic selectors for %s:", + supplied ? "selecting" : "proposing", local ? "us" : "other"); - /* remove any redundant traffic selectors in the list */ - e1 = result->create_enumerator(result); - e2 = result->create_enumerator(result); - while (e1->enumerate(e1, &ts1)) + /* 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) { - 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); + charon->bus->alert(charon->bus, ALERT_TS_NARROWED, + local, result, this); } - e1->destroy(e1); - e2->destroy(e2); - 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) +{ + return child_cfg_select_ts(&this->public, local, NULL, supplied, hosts); +} + METHOD(child_cfg_t, get_updown, char*, private_child_cfg_t *this) { @@ -681,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; } @@ -733,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); @@ -755,6 +651,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, @@ -809,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 4a9a699df..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 @@ -119,26 +120,53 @@ 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 + * 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 + * \p 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 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 - * 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 + * \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 - * @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. @@ -449,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_ @}*/ 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/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_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/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.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 caea63fbc..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 * @@ -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; }; /** @@ -117,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_ @}*/ 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) { 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); } } 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..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); @@ -684,7 +685,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 +696,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 +1003,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 +1014,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/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; } diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 726234207..2dfae0efb 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 @@ -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, @@ -2018,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); @@ -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), ); @@ -2142,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) { @@ -2176,3 +2198,72 @@ 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; +} + +/** + * 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 + */ +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); +} + +/* + * 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 7c3763b0a..57d345857 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 * @@ -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; }; @@ -575,4 +594,30 @@ 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); + +/** + * 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_ @}*/ 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/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..5afc66ae8 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: @@ -1691,18 +1691,31 @@ 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, args->src, args->dst); + 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); 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 +1829,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..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) { @@ -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..d46559182 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,8 @@ 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); + child_create->recreate_sa(child_create, child_sa); reqid = child_sa->get_reqid_ref(child_sa); if (reqid) { @@ -2369,19 +2370,34 @@ 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, args->src, args->dst); + 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); 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..534afb96c 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 * @@ -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 */ @@ -249,12 +259,22 @@ 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); 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); @@ -451,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); @@ -467,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->get_traffic_selectors(this->config, local, nat, - hosts, TRUE); + result = child_cfg_select_ts(this->config, local, ts, nat, hosts); nat->destroy_offset(nat, offsetof(traffic_selector_t, destroy)); } else { - ts = this->config->get_traffic_selectors(this->config, local, in, - hosts, TRUE); + result = child_cfg_select_ts(this->config, local, ts, in, hosts); } hosts->destroy(hosts); - - return ts; + return result; } /** @@ -652,7 +691,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 */ @@ -669,6 +707,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) { @@ -1291,21 +1330,62 @@ static status_t defer_child_sa(private_child_create_t *this) } /** - * Compare two CHILD_SA objects for equality + * 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 child_sa_equals(child_sa_t *a, child_sa_t *b) +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 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; +} + +/** + * 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); } /** @@ -1321,7 +1401,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; @@ -1402,13 +1482,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)) @@ -1444,49 +1577,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->get_traffic_selectors(this->config, - TRUE, NULL, list, TRUE); - 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); - 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); - 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 @@ -1541,7 +1632,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) { @@ -2047,7 +2142,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)) { @@ -2507,10 +2602,81 @@ 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) +/** + * 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) { - this->ke_method = ke_method; + 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) +{ + 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; + } + } + + /* 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*, @@ -2570,32 +2736,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; @@ -2626,22 +2777,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); @@ -2652,13 +2791,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); @@ -2669,8 +2807,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; @@ -2685,7 +2824,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, @@ -2693,6 +2832,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..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. @@ -140,10 +139,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..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)); + 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 6c73d0671..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); + config->get_ref(config), TRUE, NULL, NULL, 0); + + this->child_create->recreate_sa(this->child_create, this->child_sa); - 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); - } 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) { @@ -1240,7 +1235,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; 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/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. diff --git a/src/libcharon/sa/trap_manager.c b/src/libcharon/sa/trap_manager.c index 1b85c66a5..814c8c8c0 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; }; /** @@ -121,8 +126,10 @@ typedef struct { uint32_t reqid; /** destination address (wildcard case) */ host_t *dst; - /** security label, if any */ - sec_label_t *label; + /** 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; /** @@ -145,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); } @@ -156,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, @@ -178,7 +185,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)) { @@ -206,8 +213,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 */ @@ -519,13 +526,13 @@ 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; + uint32_t allocated_reqid, seq = 0; + bool wildcard; this->lock->read_lock(this->lock); enumerator = this->traps->create_enumerator(this->traps); @@ -550,44 +557,40 @@ 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 { - 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); - } + this->acquires->find_first(this->acquires, acquire_by_reqid, + (void**)&acquire, reqid, data->label); + } + if (!acquire) + { + INIT(acquire, + .dst = host, + .reqid = reqid, + .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 && data->seq != acquire->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 +645,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 +683,90 @@ 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 */ + 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; + } + + this->mutex->lock(this->mutex); + enumerator = this->acquires->create_enumerator(this->acquires); + while (enumerator->enumerate(enumerator, &acquire)) + { + 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->data->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->data->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 +793,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); 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; } 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")); 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