diff --git a/configure.ac b/configure.ac index 799a1d170..86e50a366 100644 --- a/configure.ac +++ b/configure.ac @@ -321,6 +321,7 @@ ARG_ENABL_SET([python-eggs], [enable build of provided python eggs.]) ARG_ENABL_SET([python-eggs-install],[enable installation of provided python eggs.]) ARG_ENABL_SET([perl-cpan], [enable build of provided perl CPAN module.]) ARG_ENABL_SET([perl-cpan-install],[enable installation of provided CPAN module.]) +ARG_ENABL_SET([selinux], [enable SELinux support for labeled IPsec.]) ARG_ENABL_SET([tss-trousers], [enable the use of the TrouSerS Trusted Software Stack]) ARG_ENABL_SET([tss-tss2], [enable the use of the TSS 2.0 Trusted Software Stack]) @@ -1248,6 +1249,13 @@ if test x$capabilities = xlibcap; then AC_DEFINE([CAPABILITIES_LIBCAP], [], [have libpcap library]) fi +if test x$selinux = xtrue; then + PKG_CHECK_MODULES(selinux, [libselinux]) + AC_SUBST(selinux_CFLAGS) + AC_SUBST(selinux_LIBS) + AC_DEFINE([USE_SELINUX], [], [build with support for SELinux]) +fi + if test x$integrity_test = xtrue; then AC_MSG_CHECKING([for dladdr()]) AC_COMPILE_IFELSE( @@ -1516,6 +1524,7 @@ ADD_PLUGIN([kernel-iph], [c charon]) ADD_PLUGIN([kernel-pfkey], [c charon starter nm cmd]) ADD_PLUGIN([kernel-pfroute], [c charon starter nm cmd]) ADD_PLUGIN([kernel-netlink], [c charon starter nm cmd]) +ADD_PLUGIN([selinux], [c charon starter nm cmd]) ADD_PLUGIN([resolve], [c charon cmd]) ADD_PLUGIN([save-keys], [c]) ADD_PLUGIN([socket-default], [c charon nm cmd]) @@ -1762,6 +1771,7 @@ AM_CONDITIONAL(USE_RESOLVE, test x$resolve = xtrue) AM_CONDITIONAL(USE_ATTR, test x$attr = xtrue) AM_CONDITIONAL(USE_ATTR_SQL, test x$attr_sql = xtrue) AM_CONDITIONAL(USE_COUNTERS, test x$counters = xtrue) +AM_CONDITIONAL(USE_SELINUX, test x$selinux = xtrue) # other options # --------------- @@ -2065,6 +2075,7 @@ AC_CONFIG_FILES([ src/libcharon/plugins/resolve/Makefile src/libcharon/plugins/attr/Makefile src/libcharon/plugins/attr_sql/Makefile + src/libcharon/plugins/selinux/Makefile src/libcharon/tests/Makefile src/libtpmtss/Makefile src/libtpmtss/plugins/tpm/Makefile diff --git a/scripts/test.sh b/scripts/test.sh index c773b3ef1..15ca7e0e6 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -171,7 +171,8 @@ all|coverage|sonarcloud) DEPS="$DEPS libcurl4-gnutls-dev libsoup2.4-dev libunbound-dev libldns-dev libmysqlclient-dev libsqlite3-dev clearsilver-dev libfcgi-dev libldap2-dev libpcsclite-dev libpam0g-dev binutils-dev libnm-dev - libgcrypt20-dev libjson-c-dev python3-pip libtspi-dev libsystemd-dev" + libgcrypt20-dev libjson-c-dev python3-pip libtspi-dev libsystemd-dev + libselinux1-dev" if [ "$ID" = "ubuntu" -a "$VERSION_ID" = "20.04" ]; then DEPS="$DEPS libiptc-dev" else diff --git a/src/charon-cmd/cmd/cmd_connection.c b/src/charon-cmd/cmd/cmd_connection.c index 0481d78d4..37d951951 100644 --- a/src/charon-cmd/cmd/cmd_connection.c +++ b/src/charon-cmd/cmd/cmd_connection.c @@ -388,6 +388,8 @@ static child_cfg_t* create_child_cfg(private_cmd_connection_t *this, case TS_IPV6_ADDR_RANGE: has_v6 = TRUE; break; + default: + continue; } child_cfg->add_traffic_selector(child_cfg, FALSE, ts); } diff --git a/src/charon-nm/nm/nm_service.c b/src/charon-nm/nm/nm_service.c index 2d93b2fae..09107a76b 100644 --- a/src/charon-nm/nm/nm_service.c +++ b/src/charon-nm/nm/nm_service.c @@ -635,8 +635,8 @@ static gboolean connect_(NMVpnServicePlugin *plugin, NMConnection *connection, }, }, .mode = MODE_TUNNEL, - .dpd_action = ACTION_RESTART, - .close_action = ACTION_RESTART, + .dpd_action = ACTION_START, + .close_action = ACTION_START, }; /** @@ -903,7 +903,7 @@ static gboolean connect_(NMVpnServicePlugin *plugin, NMConnection *connection, * Initiate */ child_cfg->get_ref(child_cfg); - if (ike_sa->initiate(ike_sa, child_cfg, 0, NULL, NULL) != SUCCESS) + if (ike_sa->initiate(ike_sa, child_cfg, NULL) != SUCCESS) { charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, ike_sa); diff --git a/src/charon-tkm/src/ees/ees_callbacks.c b/src/charon-tkm/src/ees/ees_callbacks.c index 863f618bc..304f4cac3 100644 --- a/src/charon-tkm/src/ees/ees_callbacks.c +++ b/src/charon-tkm/src/ees/ees_callbacks.c @@ -24,8 +24,10 @@ void charon_esa_acquire(result_type *res, const sp_id_type sp_id) { + kernel_acquire_data_t data = {}; + DBG1(DBG_KNL, "ees: acquire received for reqid %u", sp_id); - charon->kernel->acquire(charon->kernel, sp_id, NULL, NULL); + charon->kernel->acquire(charon->kernel, sp_id, &data); *res = TKM_OK; } diff --git a/src/conftest/hooks/pretend_auth.c b/src/conftest/hooks/pretend_auth.c index 5a86c5392..94b0ae4c1 100644 --- a/src/conftest/hooks/pretend_auth.c +++ b/src/conftest/hooks/pretend_auth.c @@ -305,12 +305,12 @@ static void process_auth_response(private_pretend_auth_t *this, if (this->tsi) { message->add_payload(message, (payload_t*) - ts_payload_create_from_traffic_selectors(TRUE, this->tsi)); + ts_payload_create_from_traffic_selectors(TRUE, this->tsi, NULL)); } if (this->tsr) { message->add_payload(message, (payload_t*) - ts_payload_create_from_traffic_selectors(FALSE, this->tsr)); + ts_payload_create_from_traffic_selectors(FALSE, this->tsr, NULL)); } } diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c index 3e686ac83..77decfd80 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c @@ -801,8 +801,8 @@ static job_requeue_t initiate(private_android_service_t *this) }, }, .mode = MODE_TUNNEL, - .dpd_action = ACTION_RESTART, - .close_action = ACTION_RESTART, + .dpd_action = ACTION_START, + .close_action = ACTION_START, }; char *type, *remote_id; @@ -924,7 +924,7 @@ static job_requeue_t initiate(private_android_service_t *this) /* get an additional reference because initiate consumes one */ child_cfg->get_ref(child_cfg); - if (ike_sa->initiate(ike_sa, child_cfg, 0, NULL, NULL) != SUCCESS) + if (ike_sa->initiate(ike_sa, child_cfg, NULL) != SUCCESS) { DBG1(DBG_CFG, "failed to initiate tunnel"); charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index 46346a44d..fb6de8ebd 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -726,6 +726,13 @@ if MONOLITHIC endif endif +if USE_SELINUX + SUBDIRS += plugins/selinux +if MONOLITHIC + libcharon_la_LIBADD += plugins/selinux/libstrongswan-selinux.la +endif +endif + if USE_ATTR_SQL SUBDIRS += plugins/attr_sql if MONOLITHIC diff --git a/src/libcharon/config/child_cfg.c b/src/libcharon/config/child_cfg.c index d4fb37cdc..862ec69ea 100644 --- a/src/libcharon/config/child_cfg.c +++ b/src/libcharon/config/child_cfg.c @@ -22,10 +22,10 @@ #include -ENUM(action_names, ACTION_NONE, ACTION_RESTART, - "clear", - "hold", - "restart", +ENUM_FLAGS(action_names, ACTION_TRAP, ACTION_START, + "none", + "trap", + "start", ); /** Default replay window size, if not set using charon.replay_window */ @@ -143,6 +143,16 @@ struct private_child_cfg_t { */ mark_t set_mark_out; + /** + * Optional security label for policies + */ + sec_label_t *label; + + /** + * Optional label mode for policies + */ + sec_label_mode_t label_mode; + /** * Traffic Flow Confidentiality padding, if enabled */ @@ -522,6 +532,97 @@ METHOD(child_cfg_t, get_set_mark, mark_t, return inbound ? this->set_mark_in : this->set_mark_out; } +METHOD(child_cfg_t, get_label, sec_label_t*, + private_child_cfg_t *this) +{ + return this->label; +} + +METHOD(child_cfg_t, get_label_mode, sec_label_mode_t, + private_child_cfg_t *this) +{ + return this->label_mode; +} + +METHOD(child_cfg_t, select_label, bool, + private_child_cfg_t *this, linked_list_t *labels, bool log, + sec_label_t **label, bool *exact_out) +{ + enumerator_t *enumerator; + sec_label_t *current, *match = NULL; + bool exact = FALSE; + + if (labels && labels->get_count(labels)) + { + if (!this->label) + { + DBG2(DBG_CFG, "peer proposed a security label, but none expected"); + return FALSE; + } + if (log) + { + DBG2(DBG_CFG, "selecting security label matching '%s':", + this->label->get_string(this->label)); + } + enumerator = labels->create_enumerator(labels); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (this->label->equals(this->label, current)) + { + if (log) + { + DBG2(DBG_CFG, " %s => matches exactly", + current->get_string(current)); + } + match = current; + exact = TRUE; + break; + } + else if (this->label_mode == SEC_LABEL_MODE_SELINUX && + this->label->matches(this->label, current)) + { + if (log) + { + DBG2(DBG_CFG, " %s => matches%s", + current->get_string(current), match ? ", ignored" : ""); + } + /* return the first match if we don't find an exact one */ + if (!match) + { + match = current; + } + } + else if (log) + { + DBG2(DBG_CFG, " %s => no match", current->get_string(current)); + } + } + enumerator->destroy(enumerator); + if (!match) + { + DBG2(DBG_CFG, "none of the proposed security labels match the " + "configured label '%s'", this->label->get_string(this->label)); + return FALSE; + } + } + else if (this->label) + { + DBG2(DBG_CFG, "peer didn't propose any security labels, we expect one " + "matching '%s'", this->label->get_string(this->label)); + return FALSE; + } + + if (label) + { + *label = match; + } + if (exact_out) + { + *exact_out = exact; + } + return TRUE; +} + METHOD(child_cfg_t, get_tfc, uint32_t, private_child_cfg_t *this) { @@ -607,7 +708,9 @@ METHOD(child_cfg_t, equals, bool, this->hw_offload == other->hw_offload && this->copy_dscp == other->copy_dscp && streq(this->updown, other->updown) && - streq(this->interface, other->interface); + streq(this->interface, other->interface) && + sec_labels_equal(this->label, other->label) && + this->label_mode == other->label_mode; } METHOD(child_cfg_t, get_ref, child_cfg_t*, @@ -625,6 +728,7 @@ METHOD(child_cfg_t, destroy, void, 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)); + DESTROY_IF(this->label); free(this->updown); free(this->interface); free(this->name); @@ -659,6 +763,9 @@ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data) .get_if_id = _get_if_id, .get_mark = _get_mark, .get_set_mark = _get_set_mark, + .get_label = _get_label, + .get_label_mode = _get_label_mode, + .select_label = _select_label, .get_tfc = _get_tfc, .get_manual_prio = _get_manual_prio, .get_interface = _get_interface, @@ -685,6 +792,9 @@ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data) .mark_out = data->mark_out, .set_mark_in = data->set_mark_in, .set_mark_out = data->set_mark_out, + .label = data->label ? data->label->clone(data->label) : NULL, + .label_mode = data->label_mode != SEC_LABEL_MODE_SYSTEM ? + data->label_mode : sec_label_mode_default(), .lifetime = data->lifetime, .inactivity = data->inactivity, .tfc = data->tfc, diff --git a/src/libcharon/config/child_cfg.h b/src/libcharon/config/child_cfg.h index b176d67bd..710596d89 100644 --- a/src/libcharon/config/child_cfg.h +++ b/src/libcharon/config/child_cfg.h @@ -40,11 +40,11 @@ typedef struct child_cfg_create_t child_cfg_create_t; */ enum action_t { /** No action */ - ACTION_NONE, - /** Route config to establish or reestablish on demand */ - ACTION_ROUTE, - /** Start or restart config immediately */ - ACTION_RESTART, + ACTION_NONE = 0, + /** Install trap policy to (re-)establish on demand */ + ACTION_TRAP = (1<<0), + /** Start or restart immediately */ + ACTION_START = (1<<1), }; /** @@ -247,6 +247,41 @@ struct child_cfg_t { */ mark_t (*get_set_mark)(child_cfg_t *this, bool inbound); + /** + * Optional security label to be configured on policies. + * + * @return label or NULL + */ + sec_label_t *(*get_label)(child_cfg_t *this); + + /** + * Get the mode in which the security label is used. + * + * @return label mode (never SEC_LABEL_MODE_SYSTEM) + */ + sec_label_mode_t (*get_label_mode)(child_cfg_t *this); + + /** + * Select a security label from the given list that matches the configured + * label. + * + * This fails under the following conditions: + * - a label is configured but no labels are provided + * - no label is configured but at least one label is provided + * - the configured and provided labels don't match + * + * If no label is configured and none are provided, that's considered a + * success and label will be set to NULL. + * + * @param labels list of labels to match + * @param log FALSE to avoid logging details about the selection + * @param label[out] selected label or NULL if no label necessary + * @param exact[out] TRUE if there was an exact match + * @return FALSE on failure + */ + bool (*select_label)(child_cfg_t *this, linked_list_t *labels, bool log, + sec_label_t **label, bool *exact); + /** * Get the TFC padding value to use for CHILD_SA. * @@ -367,6 +402,10 @@ struct child_cfg_create_t { mark_t set_mark_in; /** Optional outbound mark the SA should apply to traffic */ mark_t set_mark_out; + /** Optional security label configured on policies (cloned) */ + sec_label_t *label; + /** Optional security label mode */ + sec_label_mode_t label_mode; /** Mode to propose for CHILD_SA */ ipsec_mode_t mode; /** TFC padding size, 0 to disable, -1 to pad to PMTU */ diff --git a/src/libcharon/config/peer_cfg.c b/src/libcharon/config/peer_cfg.c index 2350ad501..1f2c5f109 100644 --- a/src/libcharon/config/peer_cfg.c +++ b/src/libcharon/config/peer_cfg.c @@ -386,13 +386,28 @@ METHOD(peer_cfg_t, create_child_cfg_enumerator, enumerator_t*, /** * Check how good a list of TS matches a given child config */ -static int get_ts_match(child_cfg_t *cfg, bool local, - linked_list_t *sup_list, linked_list_t *hosts) +static u_int get_ts_match(child_cfg_t *cfg, bool local, + linked_list_t *sup_list, linked_list_t *hosts, + linked_list_t *sup_labels) { linked_list_t *cfg_list; enumerator_t *sup_enum, *cfg_enum; traffic_selector_t *sup_ts, *cfg_ts, *subset; - int match = 0, round; + sec_label_t *label; + u_int match = 0, round; + bool exact = FALSE; + + if (cfg->select_label(cfg, sup_labels, TRUE, &label, &exact)) + { + if (label) + { + match += exact ? 500 : 100; + } + } + else + { /* label config doesn't match, no need to check TS */ + return match; + } /* fetch configured TS list, narrowing dynamic TS */ cfg_list = cfg->get_traffic_selectors(cfg, local, NULL, hosts, TRUE); @@ -432,24 +447,29 @@ static int get_ts_match(child_cfg_t *cfg, bool local, METHOD(peer_cfg_t, select_child_cfg, child_cfg_t*, private_peer_cfg_t *this, linked_list_t *my_ts, linked_list_t *other_ts, - linked_list_t *my_hosts, linked_list_t *other_hosts) + linked_list_t *my_hosts, linked_list_t *other_hosts, + linked_list_t *my_labels, linked_list_t *other_labels) { child_cfg_t *current, *found = NULL; enumerator_t *enumerator; - int best = 0; + u_int best = 0; DBG2(DBG_CFG, "looking for a child config for %#R === %#R", my_ts, other_ts); enumerator = create_child_cfg_enumerator(this); while (enumerator->enumerate(enumerator, ¤t)) { - int my_prio, other_prio; + u_int my_prio, other_prio; - my_prio = get_ts_match(current, TRUE, my_ts, my_hosts); - other_prio = get_ts_match(current, FALSE, other_ts, other_hosts); - - if (my_prio && other_prio) + my_prio = get_ts_match(current, TRUE, my_ts, my_hosts, my_labels); + if (!my_prio) { - DBG2(DBG_CFG, " candidate \"%s\" with prio %d+%d", + continue; + } + other_prio = get_ts_match(current, FALSE, other_ts, other_hosts, + other_labels); + if (other_prio) + { + DBG2(DBG_CFG, " candidate \"%s\" with prio %u+%u", current->get_name(current), my_prio, other_prio); if (my_prio + other_prio > best) { diff --git a/src/libcharon/config/peer_cfg.h b/src/libcharon/config/peer_cfg.h index b80e809c2..da3d6a0cc 100644 --- a/src/libcharon/config/peer_cfg.h +++ b/src/libcharon/config/peer_cfg.h @@ -174,17 +174,20 @@ struct peer_cfg_t { enumerator_t* (*create_child_cfg_enumerator) (peer_cfg_t *this); /** - * Select a CHILD config from traffic selectors. + * Select a CHILD config from received traffic selectors. * * @param my_ts TS for local side * @param other_ts TS for remote side * @param my_hosts hosts to narrow down dynamic TS for local side * @param other_hosts hosts to narrow down dynamic TS for remote side - * @return selected CHILD config, or NULL if no match found + * @param my_labels optional local security labels + * @param other_labels optional remove security labels + * @return selected CHILD config, or NULL if no match found */ - child_cfg_t* (*select_child_cfg) (peer_cfg_t *this, + child_cfg_t* (*select_child_cfg)(peer_cfg_t *this, linked_list_t *my_ts, linked_list_t *other_ts, - linked_list_t *my_hosts, linked_list_t *other_hosts); + linked_list_t *my_hosts, linked_list_t *other_hosts, + linked_list_t *my_labels, linked_list_t *other_labels); /** * Add an authentication config to the peer configuration. diff --git a/src/libcharon/control/controller.c b/src/libcharon/control/controller.c index 46b065e3f..cd25b28fe 100644 --- a/src/libcharon/control/controller.c +++ b/src/libcharon/control/controller.c @@ -473,7 +473,7 @@ METHOD(job_t, initiate_execute, job_requeue_t, } } - if (ike_sa->initiate(ike_sa, listener->child_cfg, 0, NULL, NULL) == SUCCESS) + if (ike_sa->initiate(ike_sa, listener->child_cfg, NULL) == SUCCESS) { if (!listener->logger.callback) { diff --git a/src/libcharon/encoding/generator.c b/src/libcharon/encoding/generator.c index f898c87bd..4bf0ab0cf 100644 --- a/src/libcharon/encoding/generator.c +++ b/src/libcharon/encoding/generator.c @@ -205,7 +205,6 @@ static void generate_u_int_type(private_generator_t *this, case U_INT_4: number_of_bits = 4; break; - case TS_TYPE: case RESERVED_BYTE: case SPI_SIZE: case U_INT_8: @@ -281,7 +280,6 @@ static void generate_u_int_type(private_generator_t *this, } break; } - case TS_TYPE: case RESERVED_BYTE: case SPI_SIZE: case U_INT_8: @@ -478,7 +476,6 @@ METHOD(generator_t, generate_payload, void, case IKE_SPI: case RESERVED_BYTE: case SPI_SIZE: - case TS_TYPE: case ATTRIBUTE_TYPE: case ATTRIBUTE_LENGTH: generate_u_int_type(this, rules[i].type, rules[i].offset); @@ -491,7 +488,6 @@ METHOD(generator_t, generate_payload, void, this->header_length_offset = get_offset(this); generate_u_int_type(this, U_INT_32, rules[i].offset); break; - case ADDRESS: case SPI: case CHUNK_DATA: case ENCRYPTED_DATA: diff --git a/src/libcharon/encoding/parser.c b/src/libcharon/encoding/parser.c index de66ad467..6e2e175d8 100644 --- a/src/libcharon/encoding/parser.c +++ b/src/libcharon/encoding/parser.c @@ -368,7 +368,6 @@ METHOD(parser_t, parse_payload, status_t, payload_t *pld; void *output; int payload_length = 0, spi_size = 0, attribute_length = 0, header_length; - uint16_t ts_type = 0; bool attribute_format = FALSE; int rule_number, rule_count; encoding_rule_t *rule; @@ -588,28 +587,6 @@ METHOD(parser_t, parse_payload, status_t, } break; } - case TS_TYPE: - { - if (!parse_uint8(this, rule_number, output + rule->offset)) - { - pld->destroy(pld); - return PARSE_ERROR; - } - ts_type = *(uint8_t*)(output + rule->offset); - break; - } - case ADDRESS: - { - int address_length = (ts_type == TS_IPV4_ADDR_RANGE) ? 4 : 16; - - if (!parse_chunk(this, rule_number, output + rule->offset, - address_length)) - { - pld->destroy(pld); - return PARSE_ERROR; - } - break; - } default: { DBG1(DBG_ENC, " no rule to parse rule %d %N", diff --git a/src/libcharon/encoding/payloads/encodings.c b/src/libcharon/encoding/payloads/encodings.c index e81122e87..580925c27 100644 --- a/src/libcharon/encoding/payloads/encodings.c +++ b/src/libcharon/encoding/payloads/encodings.c @@ -34,8 +34,6 @@ ENUM(encoding_type_names, U_INT_4, ENCRYPTED_DATA, "ATTRIBUTE_LENGTH_OR_VALUE", "ATTRIBUTE_LENGTH", "ATTRIBUTE_VALUE", - "TS_TYPE", - "ADDRESS", "CHUNK_DATA", "IKE_SPI", "ENCRYPTED_DATA", diff --git a/src/libcharon/encoding/payloads/encodings.h b/src/libcharon/encoding/payloads/encodings.h index fb0e9ca3d..e5ea62c72 100644 --- a/src/libcharon/encoding/payloads/encodings.h +++ b/src/libcharon/encoding/payloads/encodings.h @@ -255,32 +255,6 @@ enum encoding_type_t { */ ATTRIBUTE_VALUE, - /** - * Representing a Traffic selector type field. - * - * When generating it must be changed from host to network order. - * The value is read from the associated data struct. - * The current write position is moved 16 bit forward afterwards. - * - * When parsing it must be changed from network to host order. - * The value is written to the associated data struct. - * The current read pointer is moved 16 bit forward afterwards. - */ - TS_TYPE, - - /** - * Representing an address field in a traffic selector. - * - * Depending on the last field of type TS_TYPE - * this field is either 4 or 16 byte long. - * - * When generating the content of the chunk pointing to - * is written. - * - * When parsing 4 or 16 bytes are read and written into the chunk pointing to. - */ - ADDRESS, - /** * Representing a variable length byte field. */ diff --git a/src/libcharon/encoding/payloads/traffic_selector_substructure.c b/src/libcharon/encoding/payloads/traffic_selector_substructure.c index febf0c410..c4e5d2b1d 100644 --- a/src/libcharon/encoding/payloads/traffic_selector_substructure.c +++ b/src/libcharon/encoding/payloads/traffic_selector_substructure.c @@ -14,11 +14,33 @@ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ +/* + * Copyright (C) 2022 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #include "traffic_selector_substructure.h" #include -#include +#include +#include typedef struct private_traffic_selector_substructure_t private_traffic_selector_substructure_t; @@ -48,50 +70,32 @@ struct private_traffic_selector_substructure_t { uint16_t payload_length; /** - * Start port number. + * Port/address range or security label. */ - uint16_t start_port; - - /** - * End port number. - */ - uint16_t end_port; - - /** - * Starting address. - */ - chunk_t starting_address; - - /** - * Ending address. - */ - chunk_t ending_address; + chunk_t ts_data; }; /** - * Encoding rules to parse or generate a TS payload + * Encoding rules to parse or generate a TS payload. + * + * Due to the generic nature of security labels, the actual structure of regular + * TS is not parsed with these rules. * * The defined offsets are the positions in a object of type * private_traffic_selector_substructure_t. */ static encoding_rule_t encodings[] = { /* 1 Byte next ts type*/ - { TS_TYPE, offsetof(private_traffic_selector_substructure_t, ts_type) }, + { U_INT_8, offsetof(private_traffic_selector_substructure_t, ts_type) }, /* 1 Byte IP protocol id*/ { U_INT_8, offsetof(private_traffic_selector_substructure_t, ip_protocol_id) }, /* Length of the whole payload*/ { PAYLOAD_LENGTH,offsetof(private_traffic_selector_substructure_t, payload_length) }, - /* 2 Byte start port*/ - { U_INT_16, offsetof(private_traffic_selector_substructure_t, start_port) }, - /* 2 Byte end port*/ - { U_INT_16, offsetof(private_traffic_selector_substructure_t, end_port) }, - /* starting address is either 4 or 16 byte */ - { ADDRESS, offsetof(private_traffic_selector_substructure_t, starting_address) }, - /* ending address is either 4 or 16 byte */ - { ADDRESS, offsetof(private_traffic_selector_substructure_t, ending_address) } + /* traffic selector data, length is defined in PAYLOAD_LENGTH */ + { CHUNK_DATA, offsetof(private_traffic_selector_substructure_t, ts_data) }, }; -/* +/* Regular traffic selectors for address ranges: 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -107,48 +111,91 @@ static encoding_rule_t encodings[] = { ~ Ending Address* ~ ! ! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + * Security labels: + 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +---------------+---------------+-------------------------------+ + | TS Type | Reserved | Selector Length | + +---------------+---------------+-------------------------------+ + | | + ~ Security Label* ~ + | | + +---------------------------------------------------------------+ */ +/** + * Parse the data of a regular address range traffic selector. + */ +static bool parse_ts_data(private_traffic_selector_substructure_t *this, + uint16_t *start_port, uint16_t *end_port, + chunk_t *start_addr, chunk_t *end_addr) +{ + bio_reader_t *reader; + int addr_len; + + switch (this->ts_type) + { + case TS_IPV4_ADDR_RANGE: + addr_len = 4; + break; + case TS_IPV6_ADDR_RANGE: + addr_len = 16; + break; + default: + return FALSE; + } + + reader = bio_reader_create(this->ts_data); + if (!reader->read_uint16(reader, start_port) || + !reader->read_uint16(reader, end_port) || + !reader->read_data(reader, addr_len, start_addr) || + !reader->read_data(reader, addr_len, end_addr) || + reader->remaining(reader) > 0) + { + reader->destroy(reader); + return FALSE; + } + reader->destroy(reader); + return TRUE; +} + METHOD(payload_t, verify, status_t, private_traffic_selector_substructure_t *this) { - if (this->start_port > this->end_port) - { - /* OPAQUE ports are the only exception */ - if (this->start_port != 0xffff && this->end_port != 0) - { - return FAILED; - } - } switch (this->ts_type) { case TS_IPV4_ADDR_RANGE: - { - if ((this->starting_address.len != 4) || - (this->ending_address.len != 4)) - { - /* ipv4 address must be 4 bytes long */ - return FAILED; - } - break; - } case TS_IPV6_ADDR_RANGE: { - if ((this->starting_address.len != 16) || - (this->ending_address.len != 16)) + uint16_t start_port, end_port; + chunk_t start_addr, end_addr; + + if (!parse_ts_data(this, &start_port, &end_port, &start_addr, + &end_addr)) { - /* ipv6 address must be 16 bytes long */ return FAILED; } + if (start_port > end_port) + { + /* OPAQUE ports are the only exception */ + if (start_port != 0xffff && end_port != 0) + { + return FAILED; + } + } break; } + case TS_SECLABEL: + if (!this->ts_data.len) + { + return FAILED; + } + break; default: - { - /* not supported ts type */ - return FAILED; - } + /* unsupported TS type, just ignored later */ + break; } - return SUCCESS; } @@ -162,7 +209,7 @@ METHOD(payload_t, get_encoding_rules, int, METHOD(payload_t, get_header_length, int, private_traffic_selector_substructure_t *this) { - return 8; + return 4; } METHOD(payload_t, get_type, payload_type_t, @@ -191,17 +238,32 @@ METHOD(payload_t, get_length, size_t, METHOD(traffic_selector_substructure_t, get_traffic_selector, traffic_selector_t*, private_traffic_selector_substructure_t *this) { + uint16_t start_port, end_port; + chunk_t start_addr, end_addr; + + if (!parse_ts_data(this, &start_port, &end_port, &start_addr, &end_addr)) + { + return NULL; + } return traffic_selector_create_from_bytes( this->ip_protocol_id, this->ts_type, - this->starting_address, this->start_port, - this->ending_address, this->end_port); + start_addr, start_port, end_addr, end_port); +} + +METHOD(traffic_selector_substructure_t, get_sec_label, sec_label_t*, + private_traffic_selector_substructure_t *this) +{ + if (this->ts_type != TS_SECLABEL) + { + return NULL; + } + return sec_label_from_encoding(this->ts_data); } METHOD2(payload_t, traffic_selector_substructure_t, destroy, void, private_traffic_selector_substructure_t *this) { - free(this->starting_address.ptr); - free(this->ending_address.ptr); + free(this->ts_data.ptr); free(this); } @@ -225,6 +287,7 @@ traffic_selector_substructure_t *traffic_selector_substructure_create() .destroy = _destroy, }, .get_traffic_selector = _get_traffic_selector, + .get_sec_label = _get_sec_label, .destroy = _destroy, }, .payload_length = get_header_length(this), @@ -241,16 +304,34 @@ traffic_selector_substructure_t *traffic_selector_substructure_create_from_traff traffic_selector_t *ts) { private_traffic_selector_substructure_t *this; + bio_writer_t *writer; this = (private_traffic_selector_substructure_t*)traffic_selector_substructure_create(); this->ts_type = ts->get_type(ts); this->ip_protocol_id = ts->get_protocol(ts); - this->start_port = ts->get_from_port(ts); - this->end_port = ts->get_to_port(ts); - this->starting_address = chunk_clone(ts->get_from_address(ts)); - this->ending_address = chunk_clone(ts->get_to_address(ts)); - this->payload_length = get_header_length(this) + - this->ending_address.len + this->starting_address.len; + writer = bio_writer_create(this->ts_type == TS_IPV4_ADDR_RANGE ? 12 : 36); + writer->write_uint16(writer, ts->get_from_port(ts)); + writer->write_uint16(writer, ts->get_to_port(ts)); + writer->write_data(writer, ts->get_from_address(ts)); + writer->write_data(writer, ts->get_to_address(ts)); + this->ts_data = writer->extract_buf(writer); + this->payload_length += this->ts_data.len; + writer->destroy(writer); + return &this->public; +} + +/* + * Described in header + */ +traffic_selector_substructure_t *traffic_selector_substructure_create_from_sec_label( + sec_label_t *label) +{ + private_traffic_selector_substructure_t *this; + + this = (private_traffic_selector_substructure_t*)traffic_selector_substructure_create(); + this->ts_type = TS_SECLABEL; + this->ts_data = chunk_clone(label->get_encoding(label)); + this->payload_length += this->ts_data.len; return &this->public; } diff --git a/src/libcharon/encoding/payloads/traffic_selector_substructure.h b/src/libcharon/encoding/payloads/traffic_selector_substructure.h index d9698f86f..a22c7a935 100644 --- a/src/libcharon/encoding/payloads/traffic_selector_substructure.h +++ b/src/libcharon/encoding/payloads/traffic_selector_substructure.h @@ -13,6 +13,27 @@ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ +/* + * Copyright (C) 2022 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ /** * @defgroup traffic_selector_substructure traffic_selector_substructure @@ -27,6 +48,7 @@ typedef struct traffic_selector_substructure_t traffic_selector_substructure_t; #include #include #include +#include #include /** @@ -35,88 +57,29 @@ typedef struct traffic_selector_substructure_t traffic_selector_substructure_t; * The TRAFFIC SELECTOR format is described in RFC section 3.13.1. */ struct traffic_selector_substructure_t { + /** * The payload_t interface. */ payload_t payload_interface; /** - * Get the type of Traffic selector. + * Get a traffic_selector_t from this substructure if possible. * - * @return type of traffic selector + * @warning the returned object must be destroyed after use * + * @return contained traffic_selector_t (NULL if type mismatch) */ - ts_type_t (*get_ts_type) (traffic_selector_substructure_t *this); + traffic_selector_t *(*get_traffic_selector)(traffic_selector_substructure_t *this); /** - * Set the type of Traffic selector. + * Get a sec_label_t from this substructure if possible. * - * @param ts_type type of traffic selector + * @warning the returned object must be destroyed after use + * + * @return contained sec_label_t (NULL if type mismatch) */ - void (*set_ts_type) (traffic_selector_substructure_t *this, - ts_type_t ts_type); - - /** - * Get the IP protocol ID of Traffic selector. - * - * @return type of traffic selector - * - */ - uint8_t (*get_protocol_id) (traffic_selector_substructure_t *this); - - /** - * Set the IP protocol ID of Traffic selector - * - * @param protocol_id protocol ID of traffic selector - */ - void (*set_protocol_id) (traffic_selector_substructure_t *this, - uint8_t protocol_id); - - /** - * Get the start port and address as host_t object. - * - * Returned host_t object has to get destroyed by the caller. - * - * @return start host as host_t object - * - */ - host_t *(*get_start_host) (traffic_selector_substructure_t *this); - - /** - * Set the start port and address as host_t object. - * - * @param start_host start host as host_t object - */ - void (*set_start_host) (traffic_selector_substructure_t *this, - host_t *start_host); - - /** - * Get the end port and address as host_t object. - * - * Returned host_t object has to get destroyed by the caller. - * - * @return end host as host_t object - * - */ - host_t *(*get_end_host) (traffic_selector_substructure_t *this); - - /** - * Set the end port and address as host_t object. - * - * @param end_host end host as host_t object - */ - void (*set_end_host) (traffic_selector_substructure_t *this, - host_t *end_host); - - /** - * Get a traffic_selector_t from this substructure. - * - * @warning traffic_selector_t must be destroyed after usage. - * - * @return contained traffic_selector_t - */ - traffic_selector_t *(*get_traffic_selector) ( - traffic_selector_substructure_t *this); + sec_label_t *(*get_sec_label)(traffic_selector_substructure_t *this); /** * Destroys an traffic_selector_substructure_t object. @@ -134,13 +97,21 @@ struct traffic_selector_substructure_t { traffic_selector_substructure_t *traffic_selector_substructure_create(void); /** - * Creates an initialized traffic selector substructure using - * the values from a traffic_selector_t. + * Creates a traffic selector substructure based on a traffic_selector_t. * - * @param traffic_selector traffic_selector_t to use for initialization + * @param traffic_selector data to use * @return traffic_selector_substructure_t object */ traffic_selector_substructure_t *traffic_selector_substructure_create_from_traffic_selector( traffic_selector_t *traffic_selector); +/** + * Creates a traffic selector substructure based on a sec_label_t. + * + * @param label data to use + * @return traffic_selector_substructure_t object + */ +traffic_selector_substructure_t *traffic_selector_substructure_create_from_sec_label( + sec_label_t *label); + #endif /** TRAFFIC_SELECTOR_SUBSTRUCTURE_H_ @}*/ diff --git a/src/libcharon/encoding/payloads/ts_payload.c b/src/libcharon/encoding/payloads/ts_payload.c index 2862b6acf..3ea1aaab7 100644 --- a/src/libcharon/encoding/payloads/ts_payload.c +++ b/src/libcharon/encoding/payloads/ts_payload.c @@ -218,10 +218,11 @@ METHOD(ts_payload_t, set_initiator, void, this->is_initiator = is_initiator; } -METHOD(ts_payload_t, get_traffic_selectors, linked_list_t*, - private_ts_payload_t *this) +/** + * Get a list of either traffic selectors or labels + */ +static linked_list_t *get_list(private_ts_payload_t *this, bool labels) { - traffic_selector_t *ts; enumerator_t *enumerator; traffic_selector_substructure_t *subst; linked_list_t *list; @@ -230,14 +231,42 @@ METHOD(ts_payload_t, get_traffic_selectors, linked_list_t*, enumerator = this->substrs->create_enumerator(this->substrs); while (enumerator->enumerate(enumerator, &subst)) { - ts = subst->get_traffic_selector(subst); - list->insert_last(list, ts); + if (labels) + { + sec_label_t *label = subst->get_sec_label(subst); + + if (label) + { + list->insert_last(list, label); + } + } + else + { + traffic_selector_t *ts = subst->get_traffic_selector(subst); + + if (ts) + { + list->insert_last(list, ts); + } + } } enumerator->destroy(enumerator); return list; } +METHOD(ts_payload_t, get_traffic_selectors, linked_list_t*, + private_ts_payload_t *this) +{ + return get_list(this, FALSE); +} + +METHOD(ts_payload_t, get_sec_labels, linked_list_t*, + private_ts_payload_t *this) +{ + return get_list(this, TRUE); +} + METHOD2(payload_t, ts_payload_t, destroy, void, private_ts_payload_t *this) { @@ -267,6 +296,7 @@ ts_payload_t *ts_payload_create(bool is_initiator) .get_initiator = _get_initiator, .set_initiator = _set_initiator, .get_traffic_selectors = _get_traffic_selectors, + .get_sec_labels = _get_sec_labels, .destroy = _destroy, }, .next_payload = PL_NONE, @@ -281,7 +311,8 @@ ts_payload_t *ts_payload_create(bool is_initiator) * Described in header */ ts_payload_t *ts_payload_create_from_traffic_selectors(bool is_initiator, - linked_list_t *traffic_selectors) + linked_list_t *traffic_selectors, + sec_label_t *label) { enumerator_t *enumerator; traffic_selector_t *ts; @@ -297,6 +328,12 @@ ts_payload_t *ts_payload_create_from_traffic_selectors(bool is_initiator, this->substrs->insert_last(this->substrs, subst); } enumerator->destroy(enumerator); + + if (label) + { + subst = traffic_selector_substructure_create_from_sec_label(label); + this->substrs->insert_last(this->substrs, subst); + } compute_length(this); return &this->public; diff --git a/src/libcharon/encoding/payloads/ts_payload.h b/src/libcharon/encoding/payloads/ts_payload.h index 8b7824849..7038bb26b 100644 --- a/src/libcharon/encoding/payloads/ts_payload.h +++ b/src/libcharon/encoding/payloads/ts_payload.h @@ -63,11 +63,20 @@ struct ts_payload_t { /** * Get a list of nested traffic selectors as traffic_selector_t. * - * Resulting list and its traffic selectors must be destroyed after usage + * Resulting list and its traffic selectors must be destroyed after use. * * @return list of traffic selectors */ - linked_list_t *(*get_traffic_selectors) (ts_payload_t *this); + linked_list_t *(*get_traffic_selectors)(ts_payload_t *this); + + /** + * Get a list of security labels as sec_label_t. + * + * Resulting list and its security labels must be destroyed after use. + * + * @return list of security labels + */ + linked_list_t *(*get_sec_labels)(ts_payload_t *this); /** * Destroys an ts_payload_t object. @@ -84,13 +93,16 @@ struct ts_payload_t { ts_payload_t *ts_payload_create(bool is_initiator); /** - * Creates ts_payload with a list of traffic_selector_t + * Creates ts_payload with a list of traffic_selector_t and an optional security + * label. * * @param is_initiator TRUE for TSi, FALSE for TSr payload type * @param traffic_selectors list of traffic selectors to include + * @param label optional security label to include * @return ts_payload_t object */ ts_payload_t *ts_payload_create_from_traffic_selectors(bool is_initiator, - linked_list_t *traffic_selectors); + linked_list_t *traffic_selectors, + sec_label_t *label); #endif /** TS_PAYLOAD_H_ @}*/ diff --git a/src/libcharon/kernel/kernel_handler.c b/src/libcharon/kernel/kernel_handler.c index 006304d5e..2f881d746 100644 --- a/src/libcharon/kernel/kernel_handler.c +++ b/src/libcharon/kernel/kernel_handler.c @@ -53,20 +53,27 @@ static inline protocol_id_t proto_ip2ike(uint8_t protocol) } METHOD(kernel_listener_t, acquire, bool, - private_kernel_handler_t *this, uint32_t reqid, - traffic_selector_t *src_ts, traffic_selector_t *dst_ts) + private_kernel_handler_t *this, uint32_t reqid, kernel_acquire_data_t *data) { - if (src_ts && dst_ts) + char buf[BUF_LEN] = ""; + + if (data->label) + { + snprintf(buf, sizeof(buf), ", label {%s}", + data->label->get_string(data->label)); + } + if (data->src && data->dst) { DBG1(DBG_KNL, "creating acquire job for policy %R === %R with " - "reqid {%u}", src_ts, dst_ts, reqid); + "reqid {%u}%s", data->src, data->dst, reqid, buf); } else { - DBG1(DBG_KNL, "creating acquire job for policy with reqid {%u}", reqid); + DBG1(DBG_KNL, "creating acquire job for policy with reqid {%u}%s", + reqid, buf); } lib->processor->queue_job(lib->processor, - (job_t*)acquire_job_create(reqid, src_ts, dst_ts)); + (job_t*)acquire_job_create(reqid, data)); return TRUE; } diff --git a/src/libcharon/kernel/kernel_interface.c b/src/libcharon/kernel/kernel_interface.c index 300796f8e..01c814ab5 100644 --- a/src/libcharon/kernel/kernel_interface.c +++ b/src/libcharon/kernel/kernel_interface.c @@ -208,6 +208,8 @@ typedef struct { uint32_t if_id_in; /** outbound interface ID used for SA */ uint32_t if_id_out; + /** security label */ + sec_label_t *label; /** local traffic selectors */ array_t *local; /** remote traffic selectors */ @@ -221,19 +223,49 @@ static void reqid_entry_destroy(reqid_entry_t *entry) { array_destroy_offset(entry->local, offsetof(traffic_selector_t, destroy)); array_destroy_offset(entry->remote, offsetof(traffic_selector_t, destroy)); + DESTROY_IF(entry->label); free(entry); } +/** + * Hash the shared data of reqid entries + */ +static u_int entry_hash_shared(reqid_entry_t *entry) +{ + u_int hash; + + hash = chunk_hash_inc(chunk_from_thing(entry->mark_in), + chunk_hash_inc(chunk_from_thing(entry->mark_out), + chunk_hash_inc(chunk_from_thing(entry->if_id_in), + chunk_hash(chunk_from_thing(entry->if_id_out))))); + if (entry->label) + { + hash = entry->label->hash(entry->label, hash); + } + return hash; +} + +/** + * Compare the shared properties of reqid entries + */ +static bool entry_equals_shared(reqid_entry_t *a, reqid_entry_t *b) +{ + return a->mark_in.value == b->mark_in.value && + a->mark_in.mask == b->mark_in.mask && + a->mark_out.value == b->mark_out.value && + a->mark_out.mask == b->mark_out.mask && + a->if_id_in == b->if_id_in && + a->if_id_out == b->if_id_out && + sec_labels_equal(a->label, b->label); +} + /** * Hashtable hash function for reqid entries using reqid as key */ static u_int hash_reqid(reqid_entry_t *entry) { return chunk_hash_inc(chunk_from_thing(entry->reqid), - chunk_hash_inc(chunk_from_thing(entry->mark_in), - chunk_hash_inc(chunk_from_thing(entry->mark_out), - chunk_hash_inc(chunk_from_thing(entry->if_id_in), - chunk_hash(chunk_from_thing(entry->if_id_out)))))); + entry_hash_shared(entry)); } /** @@ -241,13 +273,11 @@ static u_int hash_reqid(reqid_entry_t *entry) */ static bool equals_reqid(reqid_entry_t *a, reqid_entry_t *b) { - return a->reqid == b->reqid && - a->mark_in.value == b->mark_in.value && - a->mark_in.mask == b->mark_in.mask && - a->mark_out.value == b->mark_out.value && - a->mark_out.mask == b->mark_out.mask && - a->if_id_in == b->if_id_in && - a->if_id_out == b->if_id_out; + if (a->reqid == b->reqid) + { + return entry_equals_shared(a, b); + } + return FALSE; } /** @@ -273,11 +303,9 @@ static u_int hash_ts_array(array_t *array, u_int hash) */ static u_int hash_reqid_by_ts(reqid_entry_t *entry) { - return hash_ts_array(entry->local, hash_ts_array(entry->remote, - chunk_hash_inc(chunk_from_thing(entry->mark_in), - chunk_hash_inc(chunk_from_thing(entry->mark_out), - chunk_hash_inc(chunk_from_thing(entry->if_id_in), - chunk_hash(chunk_from_thing(entry->if_id_out))))))); + return hash_ts_array(entry->local, + hash_ts_array(entry->remote, + entry_hash_shared(entry))); } /** @@ -311,14 +339,12 @@ static bool ts_array_equals(array_t *a, array_t *b) */ static bool equals_reqid_by_ts(reqid_entry_t *a, reqid_entry_t *b) { - return ts_array_equals(a->local, b->local) && - ts_array_equals(a->remote, b->remote) && - a->mark_in.value == b->mark_in.value && - a->mark_in.mask == b->mark_in.mask && - a->mark_out.value == b->mark_out.value && - a->mark_out.mask == b->mark_out.mask && - a->if_id_in == b->if_id_in && - a->if_id_out == b->if_id_out; + if (ts_array_equals(a->local, b->local) && + ts_array_equals(a->remote, b->remote)) + { + return entry_equals_shared(a, b); + } + return FALSE; } /** @@ -346,7 +372,7 @@ METHOD(kernel_interface_t, alloc_reqid, status_t, private_kernel_interface_t *this, linked_list_t *local_ts, linked_list_t *remote_ts, mark_t mark_in, mark_t mark_out, uint32_t if_id_in, uint32_t if_id_out, - uint32_t *reqid) + sec_label_t *label, uint32_t *reqid) { static uint32_t counter = 0; reqid_entry_t *entry = NULL, *tmpl; @@ -359,6 +385,7 @@ METHOD(kernel_interface_t, alloc_reqid, status_t, .mark_out = mark_out, .if_id_in = if_id_in, .if_id_out = if_id_out, + .label = label ? label->clone(label) : NULL, .reqid = *reqid, ); @@ -404,7 +431,8 @@ METHOD(kernel_interface_t, alloc_reqid, status_t, METHOD(kernel_interface_t, release_reqid, status_t, private_kernel_interface_t *this, uint32_t reqid, - mark_t mark_in, mark_t mark_out, uint32_t if_id_in, uint32_t if_id_out) + mark_t mark_in, mark_t mark_out, uint32_t if_id_in, uint32_t if_id_out, + sec_label_t *label) { reqid_entry_t *entry, tmpl = { .reqid = reqid, @@ -412,6 +440,7 @@ METHOD(kernel_interface_t, release_reqid, status_t, .mark_out = mark_out, .if_id_in = if_id_in, .if_id_out = if_id_out, + .label = label, }; this->mutex->lock(this->mutex); @@ -827,7 +856,7 @@ METHOD(kernel_interface_t, remove_listener, void, METHOD(kernel_interface_t, acquire, void, private_kernel_interface_t *this, uint32_t reqid, - traffic_selector_t *src_ts, traffic_selector_t *dst_ts) + kernel_acquire_data_t *data) { kernel_listener_t *listener; enumerator_t *enumerator; @@ -835,8 +864,7 @@ METHOD(kernel_interface_t, acquire, void, enumerator = this->listeners->create_enumerator(this->listeners); while (enumerator->enumerate(enumerator, &listener)) { - if (listener->acquire && - !listener->acquire(listener, reqid, src_ts, dst_ts)) + if (listener->acquire && !listener->acquire(listener, reqid, data)) { this->listeners->remove_at(this->listeners, enumerator); } diff --git a/src/libcharon/kernel/kernel_interface.h b/src/libcharon/kernel/kernel_interface.h index 50c1cac53..fd5685347 100644 --- a/src/libcharon/kernel/kernel_interface.h +++ b/src/libcharon/kernel/kernel_interface.h @@ -145,13 +145,15 @@ struct kernel_interface_t { * @param mark_out outbound mark on SA * @param if_id_in inbound interface ID on SA * @param if_id_out outbound interface ID on SA + * @param label security label (usually the one on the policy, not SA) * @param reqid allocated reqid * @return SUCCESS if reqid allocated */ status_t (*alloc_reqid)(kernel_interface_t *this, linked_list_t *local_ts, linked_list_t *remote_ts, mark_t mark_in, mark_t mark_out, uint32_t if_id_in, - uint32_t if_id_out, uint32_t *reqid); + uint32_t if_id_out, sec_label_t *label, + uint32_t *reqid); /** * Release a previously allocated reqid. @@ -161,11 +163,13 @@ struct kernel_interface_t { * @param mark_out outbound mark on SA * @param if_id_in inbound interface ID on SA * @param if_id_out outbound interface ID on SA + * @param label security label (usually the one on the policy, not SA) * @return SUCCESS if reqid released */ status_t (*release_reqid)(kernel_interface_t *this, uint32_t reqid, mark_t mark_in, mark_t mark_out, - uint32_t if_id_in, uint32_t if_id_out); + uint32_t if_id_in, uint32_t if_id_out, + sec_label_t *label); /** * Add an SA to the SAD. @@ -513,11 +517,10 @@ struct kernel_interface_t { * Raise an acquire event. * * @param reqid reqid of the policy to acquire - * @param src_ts source traffic selector - * @param dst_ts destination traffic selector + * @param data data from the acquire */ void (*acquire)(kernel_interface_t *this, uint32_t reqid, - traffic_selector_t *src_ts, traffic_selector_t *dst_ts); + kernel_acquire_data_t *data); /** * Raise an expire event. diff --git a/src/libcharon/kernel/kernel_ipsec.h b/src/libcharon/kernel/kernel_ipsec.h index 70ff2eb12..748dd433f 100644 --- a/src/libcharon/kernel/kernel_ipsec.h +++ b/src/libcharon/kernel/kernel_ipsec.h @@ -38,6 +38,7 @@ typedef struct kernel_ipsec_query_policy_t kernel_ipsec_query_policy_t; #include #include #include +#include #include #include @@ -97,6 +98,8 @@ struct kernel_ipsec_add_sa_t { hw_offload_t hw_offload; /** Mark the SA should apply to packets after processing */ mark_t mark; + /** Security label to match or apply */ + sec_label_t *label; /** TRUE to use Extended Sequence Numbers */ bool esn; /** TRUE to copy the DF bit to the outer IPv4 header in tunnel mode */ @@ -127,6 +130,8 @@ struct kernel_ipsec_update_sa_t { bool encap; /** TRUE to enable UDP encapsulation */ bool new_encap; + /** New reqid, or 0 if unchanged */ + uint32_t new_reqid; }; /** @@ -160,6 +165,8 @@ struct kernel_ipsec_policy_id_t { uint32_t if_id; /** Network interface restricting policy */ char *interface; + /** Security label restricting policy */ + sec_label_t *label; }; /** diff --git a/src/libcharon/kernel/kernel_listener.h b/src/libcharon/kernel/kernel_listener.h index 71868511f..226b32f1e 100644 --- a/src/libcharon/kernel/kernel_listener.h +++ b/src/libcharon/kernel/kernel_listener.h @@ -22,12 +22,25 @@ #define KERNEL_LISTENER_H_ typedef struct kernel_listener_t kernel_listener_t; +typedef struct kernel_acquire_data_t kernel_acquire_data_t; #include #include #include #include +/** + * Data received with a kernel's acquire, has to be cloned/copied by listener. + */ +struct kernel_acquire_data_t { + /** Optional source of the triggering packet */ + traffic_selector_t *src; + /** Optional destination of the triggering packet */ + traffic_selector_t *dst; + /** Optional security label of the triggering packet */ + sec_label_t *label; +}; + /** * Interface for components interested in kernel events. * @@ -39,12 +52,11 @@ struct kernel_listener_t { * Hook called if an acquire event for a policy is received. * * @param reqid reqid of the policy to acquire - * @param src_ts source traffic selector - * @param dst_ts destination traffic selector + * @param data data from the acquire * @return TRUE to remain registered, FALSE to unregister */ bool (*acquire)(kernel_listener_t *this, uint32_t reqid, - traffic_selector_t *src_ts, traffic_selector_t *dst_ts); + kernel_acquire_data_t *data); /** * Hook called if an expire event for an IPsec SA is received. diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 339ce2a59..ef9ba35f7 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -577,6 +577,9 @@ struct policy_entry_t { /** Optional interface ID */ uint32_t if_id; + /** Optional security label */ + sec_label_t *label; + /** Associated route installed for this policy */ route_entry_t *route; @@ -609,6 +612,7 @@ static void policy_entry_destroy(private_kernel_netlink_ipsec_t *this, policy->direction, this); policy->used_by->destroy(policy->used_by); } + DESTROY_IF(policy->label); free(policy); } @@ -618,8 +622,15 @@ static void policy_entry_destroy(private_kernel_netlink_ipsec_t *this, static u_int policy_hash(policy_entry_t *key) { chunk_t chunk = chunk_from_thing(key->sel); - return chunk_hash_inc(chunk, chunk_hash_inc(chunk_from_thing(key->mark), + u_int hash; + + hash = chunk_hash_inc(chunk, chunk_hash_inc(chunk_from_thing(key->mark), chunk_hash(chunk_from_thing(key->if_id)))); + if (key->label) + { + hash = key->label->hash(key->label, hash); + } + return hash; } /** @@ -630,7 +641,8 @@ static bool policy_equals(policy_entry_t *key, policy_entry_t *other_key) return memeq(&key->sel, &other_key->sel, sizeof(struct xfrm_selector)) && key->mark == other_key->mark && key->if_id == other_key->if_id && - key->direction == other_key->direction; + key->direction == other_key->direction && + sec_labels_equal(key->label, other_key->label); } /** @@ -897,11 +909,13 @@ static void process_acquire(private_kernel_netlink_ipsec_t *this, struct xfrm_user_acquire *acquire; struct rtattr *rta; size_t rtasize; - traffic_selector_t *src_ts, *dst_ts; + kernel_acquire_data_t data = {}; + chunk_t label = chunk_empty; uint32_t reqid = 0; - int proto = 0; + uint8_t proto; acquire = NLMSG_DATA(hdr); + proto = acquire->id.proto; rta = XFRM_RTA(hdr, struct xfrm_user_acquire); rtasize = XFRM_PAYLOAD(hdr, struct xfrm_user_acquire); @@ -913,11 +927,21 @@ static void process_acquire(private_kernel_netlink_ipsec_t *this, if (rta->rta_type == XFRMA_TMPL) { - struct xfrm_user_tmpl* tmpl; - tmpl = (struct xfrm_user_tmpl*)RTA_DATA(rta); + struct xfrm_user_tmpl* tmpl = RTA_DATA(rta); reqid = tmpl->reqid; - proto = tmpl->id.proto; } +#ifdef USE_SELINUX + if (rta->rta_type == XFRMA_SEC_CTX) + { + struct xfrm_user_sec_ctx *ctx = RTA_DATA(rta); + + if (ctx->ctx_doi == XFRM_SC_DOI_LSM && + ctx->ctx_alg == XFRM_SC_ALG_SELINUX) + { + label = chunk_create((void*)(ctx + 1), ctx->ctx_len); + } + } +#endif rta = RTA_NEXT(rta, rtasize); } switch (proto) @@ -928,12 +952,18 @@ static void process_acquire(private_kernel_netlink_ipsec_t *this, break; default: /* acquire for AH/ESP only, not for IPCOMP */ + return; } - src_ts = selector2ts(&acquire->sel, TRUE); - dst_ts = selector2ts(&acquire->sel, FALSE); + data.src = selector2ts(&acquire->sel, TRUE); + data.dst = selector2ts(&acquire->sel, FALSE); + data.label = label.len ? sec_label_from_encoding(label) : NULL; - charon->kernel->acquire(charon->kernel, reqid, src_ts, dst_ts); + charon->kernel->acquire(charon->kernel, reqid, &data); + + DESTROY_IF(data.src); + DESTROY_IF(data.dst); + DESTROY_IF(data.label); } /** @@ -1283,6 +1313,47 @@ static bool add_mark(struct nlmsghdr *hdr, int buflen, mark_t mark) return TRUE; } +/** + * Format the security label for debug messages + */ +static void format_label(char *buf, int buflen, sec_label_t *label) +{ + if (label) + { + snprintf(buf, buflen, " (ctx %s)", label->get_string(label)); + } +} + +/** + * Add a security label to message if required + */ +static bool add_label(struct nlmsghdr *hdr, int buflen, sec_label_t *label) +{ + if (label) + { +#ifdef USE_SELINUX + struct xfrm_user_sec_ctx *ctx; + chunk_t enc = label->get_encoding(label); + int len = sizeof(*ctx) + enc.len; + + ctx = netlink_reserve(hdr, buflen, XFRMA_SEC_CTX, len); + if (!ctx) + { + return FALSE; + } + /* this attribute for some reason duplicates the generic header */ + ctx->exttype = XFRMA_SEC_CTX; + ctx->len = len; + + ctx->ctx_doi = XFRM_SC_DOI_LSM; + ctx->ctx_alg = XFRM_SC_ALG_SELINUX; + ctx->ctx_len = enc.len; + memcpy((void*)(ctx + 1), enc.ptr, enc.len); +#endif + } + return TRUE; +} + /** * Add a uint32 attribute to message */ @@ -1871,6 +1942,11 @@ METHOD(kernel_ipsec_t, add_sa, status_t, goto failed; } + if (!add_label(hdr, sizeof(request), data->label)) + { + goto failed; + } + if (ipcomp == IPCOMP_NONE && (data->mark.value | data->mark.mask)) { if (!add_uint32(hdr, sizeof(request), XFRMA_SET_MARK, @@ -2268,6 +2344,7 @@ METHOD(kernel_ipsec_t, update_sa, status_t, kernel_ipsec_update_sa_t ipcomp = { .new_src = data->new_src, .new_dst = data->new_dst, + .new_reqid = data->new_reqid, }; update_sa(this, &ipcomp_id, &ipcomp); } @@ -2356,6 +2433,10 @@ METHOD(kernel_ipsec_t, update_sa, status_t, sa = NLMSG_DATA(hdr); memcpy(sa, NLMSG_DATA(out_hdr), sizeof(struct xfrm_usersa_info)); sa->family = data->new_dst->get_family(data->new_dst); + if (data->new_reqid) + { + sa->reqid = data->new_reqid; + } if (!id->src->ip_equals(id->src, data->new_src)) { @@ -2744,7 +2825,9 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this, } tmpl->reqid = ipsec->cfg.reqid; tmpl->id.proto = protos[i].proto; - if (policy->direction == POLICY_OUT) + /* in order to match SAs with all matching labels, we can't have the + * SPI in the template */ + if (policy->direction == POLICY_OUT && !policy->label) { tmpl->id.spi = protos[i].spi; } @@ -2778,6 +2861,11 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this, policy_change_done(this, policy); return FAILED; } + if (!add_label(hdr, sizeof(request), policy->label)) + { + policy_change_done(this, policy); + return FAILED; + } this->mutex->unlock(this->mutex); status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr); @@ -2823,7 +2911,7 @@ METHOD(kernel_ipsec_t, add_policy, status_t, policy_sa_t *assigned_sa, *current_sa; enumerator_t *enumerator; bool found = FALSE, update = TRUE; - char markstr[32] = ""; + char markstr[32] = "", labelstr[128] = ""; uint32_t cur_priority = 0; int use_count; @@ -2832,31 +2920,21 @@ METHOD(kernel_ipsec_t, add_policy, status_t, .sel = ts2selector(id->src_ts, id->dst_ts, id->interface), .mark = id->mark.value & id->mark.mask, .if_id = id->if_id, + .label = id->label ? id->label->clone(id->label) : NULL, .direction = id->dir, .reqid = data->sa->reqid, ); format_mark(markstr, sizeof(markstr), id->mark); + format_label(labelstr, sizeof(labelstr), id->label); /* find the policy, which matches EXACTLY */ this->mutex->lock(this->mutex); current = this->policies->get(this->policies, policy); if (current) - { - if (current->reqid && data->sa->reqid && - current->reqid != data->sa->reqid) - { - DBG1(DBG_CFG, "unable to install policy %R === %R %N%s for reqid " - "%u, the same policy for reqid %u exists", - id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr, - data->sa->reqid, current->reqid); - policy_entry_destroy(this, policy); - this->mutex->unlock(this->mutex); - return INVALID_STATE; - } - /* use existing policy */ - DBG2(DBG_KNL, "policy %R === %R %N%s already exists, increasing " + { /* use existing policy */ + DBG2(DBG_KNL, "policy %R === %R %N%s%s already exists, increasing " "refcount", id->src_ts, id->dst_ts, policy_dir_names, id->dir, - markstr); + markstr, labelstr); policy_entry_destroy(this, policy); policy = current; found = TRUE; @@ -2920,27 +2998,34 @@ METHOD(kernel_ipsec_t, add_policy, status_t, { /* we don't update the policy if the priority is lower than that of * the currently installed one */ policy_change_done(this, policy); - DBG2(DBG_KNL, "not updating policy %R === %R %N%s [priority %u, " + DBG2(DBG_KNL, "not updating policy %R === %R %N%s%s [priority %u, " "refcount %d]", id->src_ts, id->dst_ts, policy_dir_names, - id->dir, markstr, cur_priority, use_count); + id->dir, markstr, labelstr, cur_priority, use_count); return SUCCESS; } - policy->reqid = assigned_sa->sa->cfg.reqid; + if (policy->reqid != assigned_sa->sa->cfg.reqid) + { + DBG1(DBG_CFG, "updating reqid for policy %R === %R %N%s%s from %u " + "to %u", id->src_ts, id->dst_ts, policy_dir_names, id->dir, + markstr, labelstr, policy->reqid, assigned_sa->sa->cfg.reqid); + policy->reqid = assigned_sa->sa->cfg.reqid; + } if (this->policy_update) { found = TRUE; } - DBG2(DBG_KNL, "%s policy %R === %R %N%s [priority %u, refcount %d]", + DBG2(DBG_KNL, "%s policy %R === %R %N%s%s [priority %u, refcount %d]", found ? "updating" : "adding", id->src_ts, id->dst_ts, - policy_dir_names, id->dir, markstr, assigned_sa->priority, use_count); + policy_dir_names, id->dir, markstr, labelstr, assigned_sa->priority, + use_count); if (add_policy_internal(this, policy, assigned_sa, found) != SUCCESS) { - DBG1(DBG_KNL, "unable to %s policy %R === %R %N%s", + DBG1(DBG_KNL, "unable to %s policy %R === %R %N%s%s", found ? "update" : "add", id->src_ts, id->dst_ts, - policy_dir_names, id->dir, markstr); + policy_dir_names, id->dir, markstr, labelstr); return FAILED; } return SUCCESS; @@ -2955,13 +3040,14 @@ METHOD(kernel_ipsec_t, query_policy, status_t, struct xfrm_userpolicy_id *policy_id; struct xfrm_userpolicy_info *policy = NULL; size_t len; - char markstr[32] = ""; + char markstr[32] = "", labelstr[128] = ""; memset(&request, 0, sizeof(request)); format_mark(markstr, sizeof(markstr), id->mark); + format_label(labelstr, sizeof(labelstr), id->label); - DBG2(DBG_KNL, "querying policy %R === %R %N%s", id->src_ts, id->dst_ts, - policy_dir_names, id->dir, markstr); + DBG2(DBG_KNL, "querying policy %R === %R %N%s%s", id->src_ts, id->dst_ts, + policy_dir_names, id->dir, markstr, labelstr); hdr = &request.hdr; hdr->nlmsg_flags = NLM_F_REQUEST; @@ -2980,6 +3066,10 @@ METHOD(kernel_ipsec_t, query_policy, status_t, { return FAILED; } + if (!add_label(hdr, sizeof(request), id->label)) + { + return FAILED; + } if (this->socket_xfrm->send(this->socket_xfrm, hdr, &out, &len) == SUCCESS) { @@ -3051,20 +3141,22 @@ METHOD(kernel_ipsec_t, del_policy, status_t, .if_id = id->if_id, .cfg = *data->sa, }; - char markstr[32] = ""; + char markstr[32] = "", labelstr[128] = ""; int use_count; status_t status = SUCCESS; format_mark(markstr, sizeof(markstr), id->mark); + format_label(labelstr, sizeof(labelstr), id->label); - DBG2(DBG_KNL, "deleting policy %R === %R %N%s", id->src_ts, id->dst_ts, - policy_dir_names, id->dir, markstr); + DBG2(DBG_KNL, "deleting policy %R === %R %N%s%s", id->src_ts, id->dst_ts, + policy_dir_names, id->dir, markstr, labelstr); /* create a policy */ memset(&policy, 0, sizeof(policy_entry_t)); policy.sel = ts2selector(id->src_ts, id->dst_ts, id->interface); policy.mark = id->mark.value & id->mark.mask; policy.if_id = id->if_id; + policy.label = id->label; policy.direction = id->dir; /* find the policy */ @@ -3072,8 +3164,9 @@ METHOD(kernel_ipsec_t, del_policy, status_t, current = this->policies->get(this->policies, &policy); if (!current) { - DBG1(DBG_KNL, "deleting policy %R === %R %N%s failed, not found", - id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr); + DBG1(DBG_KNL, "deleting policy %R === %R %N%s%s failed, not found", + id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr, + labelstr); this->mutex->unlock(this->mutex); return NOT_FOUND; } @@ -3086,7 +3179,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t, current->waiting--; /* remove mapping to SA by reqid and priority */ - auto_priority = get_priority(current, data->prio,id->interface); + auto_priority = get_priority(current, data->prio, id->interface); priority = this->get_priority ? this->get_priority(id, data) : data->manual_prio; priority = priority ?: auto_priority; @@ -3118,22 +3211,29 @@ METHOD(kernel_ipsec_t, del_policy, status_t, if (!is_installed) { /* no need to update as the policy was not installed for this SA */ policy_change_done(this, current); - DBG2(DBG_KNL, "not updating policy %R === %R %N%s [priority %u, " + DBG2(DBG_KNL, "not updating policy %R === %R %N%s%s [priority %u, " "refcount %d]", id->src_ts, id->dst_ts, policy_dir_names, - id->dir, markstr, cur_priority, use_count); + id->dir, markstr, labelstr, cur_priority, use_count); return SUCCESS; } current->used_by->get_first(current->used_by, (void**)&mapping); - current->reqid = mapping->sa->cfg.reqid; + if (current->reqid != mapping->sa->cfg.reqid) + { + DBG1(DBG_CFG, "updating reqid for policy %R === %R %N%s%s from %u " + "to %u", id->src_ts, id->dst_ts, policy_dir_names, id->dir, + markstr, labelstr, current->reqid, mapping->sa->cfg.reqid); + current->reqid = mapping->sa->cfg.reqid; + } - DBG2(DBG_KNL, "updating policy %R === %R %N%s [priority %u, " + DBG2(DBG_KNL, "updating policy %R === %R %N%s%s [priority %u, " "refcount %d]", id->src_ts, id->dst_ts, policy_dir_names, id->dir, - markstr, mapping->priority, use_count); + markstr, labelstr, mapping->priority, use_count); if (add_policy_internal(this, current, mapping, TRUE) != SUCCESS) { - DBG1(DBG_KNL, "unable to update policy %R === %R %N%s", - id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr); + DBG1(DBG_KNL, "unable to update policy %R === %R %N%s%s", + id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr, + labelstr); return FAILED; } return SUCCESS; @@ -3160,6 +3260,11 @@ METHOD(kernel_ipsec_t, del_policy, status_t, policy_change_done(this, current); return FAILED; } + if (!add_label(hdr, sizeof(request), id->label)) + { + policy_change_done(this, current); + return FAILED; + } if (current->route) { @@ -3170,16 +3275,16 @@ METHOD(kernel_ipsec_t, del_policy, status_t, route->pass) != SUCCESS) { DBG1(DBG_KNL, "error uninstalling route installed with policy " - "%R === %R %N%s", id->src_ts, id->dst_ts, policy_dir_names, - id->dir, markstr); + "%R === %R %N%s%s", id->src_ts, id->dst_ts, policy_dir_names, + id->dir, markstr, labelstr); } } this->mutex->unlock(this->mutex); if (this->socket_xfrm->send_ack(this->socket_xfrm, hdr) != SUCCESS) { - DBG1(DBG_KNL, "unable to delete policy %R === %R %N%s", id->src_ts, - id->dst_ts, policy_dir_names, id->dir, markstr); + DBG1(DBG_KNL, "unable to delete policy %R === %R %N%s%s", id->src_ts, + id->dst_ts, policy_dir_names, id->dir, markstr, labelstr); status = FAILED; } diff --git a/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c b/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c index f32c4f83d..30688a427 100644 --- a/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c +++ b/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c @@ -1318,8 +1318,8 @@ static void process_acquire(private_kernel_pfkey_ipsec_t *this, struct sadb_msg* msg) { pfkey_msg_t response; + kernel_acquire_data_t data = {}; uint32_t index, reqid = 0; - traffic_selector_t *src_ts, *dst_ts; policy_entry_t *policy; policy_sa_t *sa; @@ -1363,10 +1363,16 @@ static void process_acquire(private_kernel_pfkey_ipsec_t *this, this->mutex->unlock(this->mutex); } - src_ts = sadb_address2ts(response.src); - dst_ts = sadb_address2ts(response.dst); + if (reqid) + { + data.src = sadb_address2ts(response.src); + data.dst = sadb_address2ts(response.dst); - charon->kernel->acquire(charon->kernel, reqid, src_ts, dst_ts); + charon->kernel->acquire(charon->kernel, reqid, &data); + + data.src->destroy(data.src); + data.dst->destroy(data.dst); + } } /** @@ -1954,6 +1960,12 @@ METHOD(kernel_ipsec_t, update_sa, status_t, size_t len; status_t status = FAILED; + if (data->new_reqid) + { + DBG1(DBG_KNL, "unable to update SAD entry with SPI %.8x: reqid " + "change is not supported", ntohl(id->spi)); + return NOT_SUPPORTED; + } #ifndef SADB_X_EXT_NEW_ADDRESS_SRC /* we can't update the SA if any of the ip addresses have changed. * that's because we can't use SADB_UPDATE and by deleting and readding the diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 19d4f3ef4..3eb178599 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -820,6 +820,8 @@ static bool install_sps(private_kernel_wfp_ipsec_t *this, case TS_IPV6_ADDR_RANGE: has_v6 = TRUE; break; + default: + continue; } /* inbound policy */ @@ -1634,6 +1636,7 @@ static u_int hash_trap(trap_t *trap) static void acquire(private_kernel_wfp_ipsec_t *this, UINT64 filter_id, traffic_selector_t *src, traffic_selector_t *dst) { + kernel_acquire_data_t data = {}; uint32_t reqid = 0; trap_t *trap, key = { .filter_id = filter_id, @@ -1649,9 +1652,13 @@ static void acquire(private_kernel_wfp_ipsec_t *this, UINT64 filter_id, if (reqid) { - src = src ? src->clone(src) : NULL; - dst = dst ? dst->clone(dst) : NULL; - charon->kernel->acquire(charon->kernel, reqid, src, dst); + data.src = src ? src->clone(src) : NULL; + data.dst = dst ? dst->clone(dst) : NULL; + + charon->kernel->acquire(charon->kernel, reqid, &data); + + DESTROY_IF(data.src); + DESTROY_IF(data.dst); } } @@ -2219,8 +2226,8 @@ METHOD(kernel_ipsec_t, update_sa, status_t, { /* inbound entry, do update */ sa_id = entry->sa_id; - ports.localUdpEncapPort = entry->local->get_port(entry->local); - ports.remoteUdpEncapPort = entry->remote->get_port(entry->remote); + ports.localUdpEncapPort = data->new_dst->get_port(data->new_dst); + ports.remoteUdpEncapPort = data->new_src->get_port(data->new_src); } this->mutex->unlock(this->mutex); @@ -2273,6 +2280,10 @@ METHOD(kernel_ipsec_t, update_sa, status_t, key.dst = entry->osa.dst; this->osas->remove(this->osas, &key); + if (data->new_reqid) + { + entry->reqid = data->new_reqid; + } entry->local->destroy(entry->local); entry->remote->destroy(entry->remote); entry->local = data->new_dst->clone(data->new_dst); diff --git a/src/libcharon/plugins/selinux/Makefile.am b/src/libcharon/plugins/selinux/Makefile.am new file mode 100644 index 000000000..8bf821b5e --- /dev/null +++ b/src/libcharon/plugins/selinux/Makefile.am @@ -0,0 +1,18 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libcharon + +AM_CFLAGS = \ + $(PLUGIN_CFLAGS) + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-selinux.la +else +plugin_LTLIBRARIES = libstrongswan-selinux.la +endif + +libstrongswan_selinux_la_SOURCES = \ + selinux_plugin.h selinux_plugin.c \ + selinux_listener.h selinux_listener.c + +libstrongswan_selinux_la_LDFLAGS = -module -avoid-version diff --git a/src/libcharon/plugins/selinux/selinux_listener.c b/src/libcharon/plugins/selinux/selinux_listener.c new file mode 100644 index 000000000..bce309236 --- /dev/null +++ b/src/libcharon/plugins/selinux/selinux_listener.c @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2022 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "selinux_listener.h" + +#include +#include +#include + +typedef struct private_selinux_listener_t private_selinux_listener_t; + +/** + * Private data. + */ +struct private_selinux_listener_t { + + /** + * Public interface. + */ + selinux_listener_t public; + + /** + * IKE_SAs with attached trap policies, ike_sa_id_t => entry_t. + */ + hashtable_t *sas; +}; + +/** + * Entry to keep track of trap policies. + */ +typedef struct { + + /** + * IKE_SA ID. + */ + ike_sa_id_t *id; + + /** + * Installed trap policies. + */ + array_t *traps; + +} entry_t; + +/** + * Destroy the given entry. + */ +static void destroy_entry(entry_t *entry) +{ + entry->id->destroy(entry->id); + array_destroy(entry->traps); + free(entry); +} + +/** + * Hashtable hash function + */ +static u_int hash(const void *key) +{ + ike_sa_id_t *id = (ike_sa_id_t*)key; + uint64_t spi_i = id->get_initiator_spi(id), + spi_r = id->get_responder_spi(id); + return chunk_hash_inc(chunk_from_thing(spi_i), + chunk_hash(chunk_from_thing(spi_r))); +} + +/** + * Hashtable equals function + */ +static bool equals(const void *a_pub, const void *b) +{ + ike_sa_id_t *a = (ike_sa_id_t*)a_pub; + return a->equals(a, (ike_sa_id_t*)b); +} + +/** + * Install a trap policy for the generic SELinux label. + */ +static bool install_generic_trap(ike_sa_t *ike_sa, child_sa_t *child_sa) +{ + linked_list_t *local, *remote; + sec_label_t *label; + bool success; + + label = child_sa->get_label(child_sa); + DBG1(DBG_IKE, "installing trap %s{%d} with generic security label '%s'", + child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa), + label->get_string(label)); + + local = ike_sa_get_dynamic_hosts(ike_sa, TRUE); + remote = ike_sa_get_dynamic_hosts(ike_sa, FALSE); + success = charon->traps->install_external(charon->traps, + ike_sa->get_peer_cfg(ike_sa), + child_sa, local, remote); + local->destroy(local); + remote->destroy(remote); + return success; +} + +METHOD(listener_t, ike_updown, bool, + private_selinux_listener_t *this, ike_sa_t *ike_sa, bool up) +{ + enumerator_t *enumerator; + peer_cfg_t *peer_cfg; + child_cfg_t *child_cfg; + child_sa_t *child_sa; + entry_t *entry; + + if (up) + { + child_sa_create_t child = { + .if_id_in_def = ike_sa->get_if_id(ike_sa, TRUE), + .if_id_out_def = ike_sa->get_if_id(ike_sa, FALSE), + }; + + INIT(entry, + .id = ike_sa->get_id(ike_sa), + ); + entry->id = entry->id->clone(entry->id); + + peer_cfg = ike_sa->get_peer_cfg(ike_sa); + enumerator = peer_cfg->create_child_cfg_enumerator(peer_cfg); + while (enumerator->enumerate(enumerator, &child_cfg)) + { + if (child_cfg->get_label(child_cfg) && + child_cfg->get_label_mode(child_cfg) == SEC_LABEL_MODE_SELINUX) + { + child_sa = child_sa_create(ike_sa->get_my_host(ike_sa), + ike_sa->get_other_host(ike_sa), + child_cfg, &child); + if (install_generic_trap(ike_sa, child_sa)) + { + array_insert_create(&entry->traps, ARRAY_TAIL, child_sa); + } + else + { + child_sa->destroy(child_sa); + } + } + } + enumerator->destroy(enumerator); + + if (array_count(entry->traps)) + { + this->sas->put(this->sas, entry->id, entry); + } + else + { + destroy_entry(entry); + } + } + else + { + entry = this->sas->remove(this->sas, ike_sa->get_id(ike_sa)); + if (entry) + { + while (array_remove(entry->traps, ARRAY_TAIL, &child_sa)) + { + sec_label_t *label = child_sa->get_label(child_sa); + + DBG1(DBG_IKE, "uninstalling trap %s{%d} with generic security " + "label '%s'", child_sa->get_name(child_sa), + child_sa->get_unique_id(child_sa), + label->get_string(label)); + charon->traps->remove_external(charon->traps, child_sa); + child_sa->destroy(child_sa); + } + destroy_entry(entry); + } + } + return TRUE; +} + +METHOD(listener_t, ike_rekey, bool, + private_selinux_listener_t *this, ike_sa_t *old, ike_sa_t *new) +{ + entry_t *entry; + + entry = this->sas->remove(this->sas, old->get_id(old)); + if (entry) + { + entry->id->destroy(entry->id); + entry->id = new->get_id(new); + entry->id = entry->id->clone(entry->id); + this->sas->put(this->sas, entry->id, entry); + } + return TRUE; +} + +METHOD(listener_t, ike_update, bool, + private_selinux_listener_t *this, ike_sa_t *ike_sa, + host_t *local, host_t *remote) +{ + entry_t *entry; + child_sa_t *child_sa; + linked_list_t *vips; + int i; + + entry = this->sas->get(this->sas, ike_sa->get_id(ike_sa)); + if (entry) + { + vips = linked_list_create_from_enumerator( + ike_sa->create_virtual_ip_enumerator(ike_sa, local)); + for (i = 0; i < array_count(entry->traps); i++) + { + array_get(entry->traps, i, &child_sa); + child_sa->update(child_sa, local, remote, vips, + ike_sa->has_condition(ike_sa, COND_NAT_ANY)); + } + vips->destroy(vips); + } + return TRUE; +} + +METHOD(selinux_listener_t, destroy, void, + private_selinux_listener_t *this) +{ + this->sas->destroy(this->sas); + free(this); +} + +/* + * Described in header + */ +selinux_listener_t *selinux_listener_create() +{ + private_selinux_listener_t *this; + + INIT(this, + .public = { + .listener = { + .ike_updown = _ike_updown, + .ike_rekey = _ike_rekey, + .ike_update = _ike_update, + }, + .destroy = _destroy, + }, + .sas = hashtable_create(hash, equals, 32), + ); + + return &this->public; +} diff --git a/src/libcharon/plugins/selinux/selinux_listener.h b/src/libcharon/plugins/selinux/selinux_listener.h new file mode 100644 index 000000000..31deed214 --- /dev/null +++ b/src/libcharon/plugins/selinux/selinux_listener.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @defgroup selinux_listener selinux_listener + * @{ @ingroup selinux + */ + +#ifndef SELINUX_LISTENER_H_ +#define SELINUX_LISTENER_H_ + +#include + +typedef struct selinux_listener_t selinux_listener_t; + +/** + * Listener to manage trap policies for generic SELinux labels. + */ +struct selinux_listener_t { + + /** + * Implements listener_t interface. + */ + listener_t listener; + + /** + * Destroy a selinux_listener_t. + */ + void (*destroy)(selinux_listener_t *this); +}; + +/** + * Create a listener instance. + */ +selinux_listener_t *selinux_listener_create(); + +#endif /** SELINUX_LISTENER_H_ @}*/ diff --git a/src/libcharon/plugins/selinux/selinux_plugin.c b/src/libcharon/plugins/selinux/selinux_plugin.c new file mode 100644 index 000000000..80919ea25 --- /dev/null +++ b/src/libcharon/plugins/selinux/selinux_plugin.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "selinux_plugin.h" +#include "selinux_listener.h" + +#include + +typedef struct private_selinux_plugin_t private_selinux_plugin_t; + +/** + * Private data + */ +struct private_selinux_plugin_t { + + /** + * Public interface + */ + selinux_plugin_t public; + + /** + * Listener + */ + selinux_listener_t *listener; +}; + +METHOD(plugin_t, get_name, char*, + private_selinux_plugin_t *this) +{ + return "selinux"; +} + +/** + * Register handler + */ +static bool plugin_cb(private_selinux_plugin_t *this, + plugin_feature_t *feature, bool reg, void *cb_data) +{ + if (reg) + { + charon->bus->add_listener(charon->bus, &this->listener->listener); + } + else + { + charon->bus->remove_listener(charon->bus, &this->listener->listener); + } + return TRUE; +} + +METHOD(plugin_t, get_features, int, + private_selinux_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_CALLBACK((plugin_feature_callback_t)plugin_cb, NULL), + PLUGIN_PROVIDE(CUSTOM, "selinux"), + }; + *features = f; + return countof(f); +} + +METHOD(plugin_t, destroy, void, + private_selinux_plugin_t *this) +{ + this->listener->destroy(this->listener); + free(this); +} + +/* + * Described in header + */ +plugin_t *selinux_plugin_create() +{ + private_selinux_plugin_t *this; + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .destroy = _destroy, + }, + }, + .listener = selinux_listener_create(), + ); + + return &this->public.plugin; +} diff --git a/src/libcharon/plugins/selinux/selinux_plugin.h b/src/libcharon/plugins/selinux/selinux_plugin.h new file mode 100644 index 000000000..46d413a9e --- /dev/null +++ b/src/libcharon/plugins/selinux/selinux_plugin.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @defgroup selinux selinux + * @ingroup cplugins + * + * @defgroup selinux_plugin selinux_plugin + * @{ @ingroup selinux + */ + +#ifndef SELINUX_PLUGIN_H_ +#define SELINUX_PLUGIN_H_ + +#include + +typedef struct selinux_plugin_t selinux_plugin_t; + +/** + * Plugin managing trap policies with generic SELinux labels. + */ +struct selinux_plugin_t { + + /** + * Implements plugin interface + */ + plugin_t plugin; +}; + +#endif /** SELINUX_PLUGIN_H_ @}*/ diff --git a/src/libcharon/plugins/stroke/stroke_config.c b/src/libcharon/plugins/stroke/stroke_config.c index 175b6b549..a15c9f5a4 100644 --- a/src/libcharon/plugins/stroke/stroke_config.c +++ b/src/libcharon/plugins/stroke/stroke_config.c @@ -1042,9 +1042,9 @@ static action_t map_action(int starter_action) switch (starter_action) { case 2: /* =hold */ - return ACTION_ROUTE; + return ACTION_TRAP; case 3: /* =restart */ - return ACTION_RESTART; + return ACTION_START; default: return ACTION_NONE; } diff --git a/src/libcharon/plugins/vici/README.md b/src/libcharon/plugins/vici/README.md index 11c6e8166..6b75711fa 100644 --- a/src/libcharon/plugins/vici/README.md +++ b/src/libcharon/plugins/vici/README.md @@ -277,7 +277,7 @@ Terminates an SA while streaming _control-log_ events. { child = ike = - child-id = + child-id = ike-id = force = @@ -300,7 +300,7 @@ Initiate the rekeying of an SA. { child = ike = - child-id = + child-id = ike-id = reauth = } => { @@ -361,6 +361,8 @@ events. noblock = ike = ike-id = + child = + child-id = } => { # completes after streaming list-sa events } @@ -817,6 +819,7 @@ command. mark-mask-out = if-id-in = if-id-out = + label = encr-alg = encr-keysize = integ-alg = @@ -854,6 +857,7 @@ _list-policies_ command. child = ike = mode = + label = local-ts = [ ] @@ -903,6 +907,7 @@ _list-conns_ command. children = { * = { mode = + label = rekey_time = rekey_bytes = rekey_packets = diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index aa6fbafbe..3a783b822 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -563,6 +563,9 @@ static void log_child_data(child_data_t *data, char *name) cfg->set_mark_in.value, cfg->set_mark_in.mask); DBG2(DBG_CFG, " set_mark_out = %u/%u", cfg->set_mark_out.value, cfg->set_mark_out.mask); + DBG2(DBG_CFG, " label = %s", + cfg->label ? cfg->label->get_string(cfg->label) : NULL); + DBG2(DBG_CFG, " label_mode = %N", sec_label_mode_names, cfg->label_mode); DBG2(DBG_CFG, " inactivity = %llu", cfg->inactivity); DBG2(DBG_CFG, " proposals = %#P", data->proposals); DBG2(DBG_CFG, " local_ts = %#R", data->local_ts); @@ -585,6 +588,7 @@ static void free_child_data(child_data_t *data) offsetof(traffic_selector_t, destroy)); data->remote_ts->destroy_offset(data->remote_ts, offsetof(traffic_selector_t, destroy)); + DESTROY_IF(data->cfg.label); free(data->cfg.updown); free(data->cfg.interface); } @@ -1004,13 +1008,14 @@ CALLBACK(parse_action, bool, action_t *out, chunk_t v) { enum_map_t map[] = { - { "start", ACTION_RESTART }, - { "restart", ACTION_RESTART }, - { "route", ACTION_ROUTE }, - { "trap", ACTION_ROUTE }, + { "start", ACTION_START }, + { "restart", ACTION_START }, + { "route", ACTION_TRAP }, + { "trap", ACTION_TRAP }, { "none", ACTION_NONE }, { "clear", ACTION_NONE }, }; + char buf[BUF_LEN]; int d; if (parse_map(map, countof(map), &d, v)) @@ -1018,6 +1023,14 @@ CALLBACK(parse_action, bool, *out = d; return TRUE; } + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + if (enum_flags_from_string(action_names, buf, out)) + { + return TRUE; + } return FALSE; } @@ -1261,6 +1274,38 @@ CALLBACK(parse_if_id, bool, return if_id_from_string(buf, out); } +/** + * Parse security label + */ +CALLBACK(parse_label, bool, + sec_label_t **out, chunk_t v) +{ + char buf[BUF_LEN]; + + if (!vici_stringify(v, buf, sizeof(buf))) + { + return FALSE; + } + *out = sec_label_from_string(buf); + return *out != NULL; +} + +/** + * Parse security label mode + */ +CALLBACK(parse_label_mode, bool, + sec_label_mode_t *out, chunk_t v) +{ + char buf[BUF_LEN]; + + if (!vici_stringify(v, buf, sizeof(buf)) || + !sec_label_mode_from_string(buf, out)) + { + return FALSE; + } + return TRUE; +} + /** * Parse TFC padding option */ @@ -1760,6 +1805,8 @@ CALLBACK(child_kv, bool, { "copy_dscp", parse_copy_dscp, &child->cfg.copy_dscp }, { "if_id_in", parse_if_id, &child->cfg.if_id_in }, { "if_id_out", parse_if_id, &child->cfg.if_id_out }, + { "label", parse_label, &child->cfg.label }, + { "label_mode", parse_label_mode, &child->cfg.label_mode }, }; return parse_rules(rules, countof(rules), name, value, @@ -2143,30 +2190,33 @@ CALLBACK(peer_sn, bool, static void run_start_action(private_vici_config_t *this, peer_cfg_t *peer_cfg, child_cfg_t *child_cfg) { - switch (child_cfg->get_start_action(child_cfg)) + action_t action; + + action = child_cfg->get_start_action(child_cfg); + + if (action & ACTION_TRAP) { - case ACTION_RESTART: - DBG1(DBG_CFG, "initiating '%s'", child_cfg->get_name(child_cfg)); - charon->controller->initiate(charon->controller, + DBG1(DBG_CFG, "installing '%s'", child_cfg->get_name(child_cfg)); + switch (child_cfg->get_mode(child_cfg)) + { + case MODE_PASS: + case MODE_DROP: + charon->shunts->install(charon->shunts, + peer_cfg->get_name(peer_cfg), child_cfg); + /* no need to check for ACTION_START */ + return; + default: + charon->traps->install(charon->traps, peer_cfg, child_cfg); + break; + } + } + + if (action & ACTION_START) + { + DBG1(DBG_CFG, "initiating '%s'", child_cfg->get_name(child_cfg)); + charon->controller->initiate(charon->controller, peer_cfg->get_ref(peer_cfg), child_cfg->get_ref(child_cfg), NULL, NULL, 0, FALSE); - break; - case ACTION_ROUTE: - DBG1(DBG_CFG, "installing '%s'", child_cfg->get_name(child_cfg)); - switch (child_cfg->get_mode(child_cfg)) - { - case MODE_PASS: - case MODE_DROP: - charon->shunts->install(charon->shunts, - peer_cfg->get_name(peer_cfg), child_cfg); - break; - default: - charon->traps->install(charon->traps, peer_cfg, child_cfg); - break; - } - break; - default: - break; } } @@ -2181,100 +2231,101 @@ static void clear_start_action(private_vici_config_t *this, char *peer_name, ike_sa_t *ike_sa; uint32_t id = 0, others; array_t *ids = NULL, *ikeids = NULL; + action_t action; char *name; name = child_cfg->get_name(child_cfg); + action = child_cfg->get_start_action(child_cfg); - switch (child_cfg->get_start_action(child_cfg)) + if (action & ACTION_TRAP) { - case ACTION_RESTART: - enumerator = charon->controller->create_ike_sa_enumerator( + DBG1(DBG_CFG, "uninstalling '%s'", name); + switch (child_cfg->get_mode(child_cfg)) + { + case MODE_PASS: + case MODE_DROP: + charon->shunts->uninstall(charon->shunts, peer_name, name); + /* no need to check for ACTION_START */ + return; + default: + charon->traps->uninstall(charon->traps, peer_name, name); + break; + } + } + + if (action & ACTION_START) + { + enumerator = charon->controller->create_ike_sa_enumerator( charon->controller, TRUE); - while (enumerator->enumerate(enumerator, &ike_sa)) + while (enumerator->enumerate(enumerator, &ike_sa)) + { + if (!streq(ike_sa->get_name(ike_sa), peer_name)) { - if (!streq(ike_sa->get_name(ike_sa), peer_name)) + continue; + } + others = id = 0; + children = ike_sa->create_child_sa_enumerator(ike_sa); + while (children->enumerate(children, &child_sa)) + { + if (child_sa->get_state(child_sa) != CHILD_DELETING && + child_sa->get_state(child_sa) != CHILD_DELETED) { - continue; + if (streq(name, child_sa->get_name(child_sa))) + { + id = child_sa->get_unique_id(child_sa); + } + else + { + others++; + } } - others = id = 0; + } + children->destroy(children); + + if (!ike_sa->get_child_count(ike_sa) || (id && !others)) + { + /* found no children or only matching, delete IKE_SA */ + id = ike_sa->get_unique_id(ike_sa); + array_insert_create_value(&ikeids, sizeof(id), + ARRAY_TAIL, &id); + } + else + { children = ike_sa->create_child_sa_enumerator(ike_sa); while (children->enumerate(children, &child_sa)) { - if (child_sa->get_state(child_sa) != CHILD_DELETING && - child_sa->get_state(child_sa) != CHILD_DELETED) + if (streq(name, child_sa->get_name(child_sa))) { - if (streq(name, child_sa->get_name(child_sa))) - { - id = child_sa->get_unique_id(child_sa); - } - else - { - others++; - } + id = child_sa->get_unique_id(child_sa); + array_insert_create_value(&ids, sizeof(id), + ARRAY_TAIL, &id); } } children->destroy(children); + } + } + enumerator->destroy(enumerator); - if (!ike_sa->get_child_count(ike_sa) || (id && !others)) - { - /* found no children or only matching, delete IKE_SA */ - id = ike_sa->get_unique_id(ike_sa); - array_insert_create_value(&ikeids, sizeof(id), - ARRAY_TAIL, &id); - } - else - { - children = ike_sa->create_child_sa_enumerator(ike_sa); - while (children->enumerate(children, &child_sa)) - { - if (streq(name, child_sa->get_name(child_sa))) - { - id = child_sa->get_unique_id(child_sa); - array_insert_create_value(&ids, sizeof(id), - ARRAY_TAIL, &id); - } - } - children->destroy(children); - } - } - enumerator->destroy(enumerator); - - if (array_count(ids)) + if (array_count(ids)) + { + while (array_remove(ids, ARRAY_HEAD, &id)) { - while (array_remove(ids, ARRAY_HEAD, &id)) - { - DBG1(DBG_CFG, "closing '%s' #%u", name, id); - charon->controller->terminate_child(charon->controller, - id, NULL, NULL, 0); - } - array_destroy(ids); + DBG1(DBG_CFG, "closing '%s' #%u", name, id); + charon->controller->terminate_child(charon->controller, + id, NULL, NULL, 0); } - if (array_count(ikeids)) + array_destroy(ids); + } + if (array_count(ikeids)) + { + while (array_remove(ikeids, ARRAY_HEAD, &id)) { - while (array_remove(ikeids, ARRAY_HEAD, &id)) - { - DBG1(DBG_CFG, "closing IKE_SA #%u", id); - charon->controller->terminate_ike(charon->controller, id, - FALSE, NULL, NULL, 0); - } - array_destroy(ikeids); + DBG1(DBG_CFG, "closing IKE_SA #%u", id); + charon->controller->terminate_ike(charon->controller, id, + FALSE, NULL, NULL, 0); } - break; - case ACTION_ROUTE: - DBG1(DBG_CFG, "uninstalling '%s'", name); - switch (child_cfg->get_mode(child_cfg)) - { - case MODE_PASS: - case MODE_DROP: - charon->shunts->uninstall(charon->shunts, peer_name, name); - break; - default: - charon->traps->uninstall(charon->traps, peer_name, name); - break; - } - break; - default: - break; + array_destroy(ikeids); + } } } diff --git a/src/libcharon/plugins/vici/vici_query.c b/src/libcharon/plugins/vici/vici_query.c index fb65b1447..ff2d36b21 100644 --- a/src/libcharon/plugins/vici/vici_query.c +++ b/src/libcharon/plugins/vici/vici_query.c @@ -150,6 +150,29 @@ static void list_mode(vici_builder_t *b, child_sa_t *child, child_cfg_t *cfg) } } +/** + * List the security label of a CHILD_SA or config + */ +static void list_label(vici_builder_t *b, child_sa_t *child, child_cfg_t *cfg) +{ + sec_label_t *label = NULL; + chunk_t enc; + + if (child) + { + label = child->get_label(child); + } + else if (cfg) + { + label = cfg->get_label(cfg); + } + if (label) + { + enc = label->get_encoding(label); + b->add_kv(b, "label", "%+B", &enc); + } +} + /** * List IPsec-related details about a CHILD_SA */ @@ -275,6 +298,7 @@ static void list_child(private_vici_query_t *this, vici_builder_t *b, state = child->get_state(child); b->add_kv(b, "state", "%N", child_sa_state_names, state); list_mode(b, child, NULL); + list_label(b, child, NULL); switch (state) { @@ -505,15 +529,16 @@ CALLBACK(list_sas, vici_message_t*, ike_sa_t *ike_sa; child_sa_t *child_sa; time_t now; - char *ike; - u_int ike_id; + char *ike, *child; + u_int ike_id, child_id; bool bl; char buf[BUF_LEN]; - bl = request->get_str(request, NULL, "noblock") == NULL; ike = request->get_str(request, NULL, "ike"); ike_id = request->get_int(request, 0, "ike-id"); + child = request->get_str(request, NULL, "child"); + child_id = request->get_int(request, 0, "child-id"); isas = charon->controller->create_ike_sa_enumerator(charon->controller, bl); while (isas->enumerate(isas, &ike_sa)) @@ -538,6 +563,15 @@ CALLBACK(list_sas, vici_message_t*, csas = ike_sa->create_child_sa_enumerator(ike_sa); while (csas->enumerate(csas, &child_sa)) { + if (child && !streq(child, child_sa->get_name(child_sa))) + { + continue; + } + if (child_id && child_sa->get_unique_id(child_sa) != child_id) + { + continue; + } + snprintf(buf, sizeof(buf), "%s-%u", child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa)); b->begin_section(b, buf); @@ -576,6 +610,7 @@ static void raise_policy(private_vici_query_t *this, u_int id, char *ike, b->add_kv(b, "ike", "%s", ike); list_mode(b, child, NULL); + list_label(b, child, NULL); b->begin_list(b, "local-ts"); enumerator = child->create_ts_enumerator(child, TRUE); @@ -624,6 +659,7 @@ static void raise_policy_cfg(private_vici_query_t *this, u_int id, char *ike, } list_mode(b, NULL, cfg); + list_label(b, NULL, cfg); b->begin_list(b, "local-ts"); list = cfg->get_traffic_selectors(cfg, TRUE, NULL, NULL, FALSE); @@ -930,6 +966,7 @@ CALLBACK(list_conns, vici_message_t*, b->begin_section(b, child_cfg->get_name(child_cfg)); list_mode(b, NULL, child_cfg); + list_label(b, NULL, child_cfg); lft = child_cfg->get_lifetime(child_cfg, FALSE); b->add_kv(b, "rekey_time", "%"PRIu64, lft->time.rekey); diff --git a/src/libcharon/processing/jobs/acquire_job.c b/src/libcharon/processing/jobs/acquire_job.c index c92c03f8f..5142bb297 100644 --- a/src/libcharon/processing/jobs/acquire_job.c +++ b/src/libcharon/processing/jobs/acquire_job.c @@ -30,34 +30,29 @@ struct private_acquire_job_t { acquire_job_t public; /** - * reqid of the child to rekey + * reqid of the triggered policy */ uint32_t reqid; /** - * acquired source traffic selector + * Data from the acquire */ - traffic_selector_t *src_ts; - - /** - * acquired destination traffic selector - */ - traffic_selector_t *dst_ts; + kernel_acquire_data_t data; }; METHOD(job_t, destroy, void, private_acquire_job_t *this) { - DESTROY_IF(this->src_ts); - DESTROY_IF(this->dst_ts); + DESTROY_IF(this->data.src); + DESTROY_IF(this->data.dst); + DESTROY_IF(this->data.label); free(this); } METHOD(job_t, execute, job_requeue_t, private_acquire_job_t *this) { - charon->traps->acquire(charon->traps, this->reqid, - this->src_ts, this->dst_ts); + charon->traps->acquire(charon->traps, this->reqid, &this->data); return JOB_REQUEUE_NONE; } @@ -70,9 +65,7 @@ METHOD(job_t, get_priority, job_priority_t, /* * Described in header */ -acquire_job_t *acquire_job_create(uint32_t reqid, - traffic_selector_t *src_ts, - traffic_selector_t *dst_ts) +acquire_job_t *acquire_job_create(uint32_t reqid, kernel_acquire_data_t *data) { private_acquire_job_t *this; @@ -85,10 +78,21 @@ acquire_job_t *acquire_job_create(uint32_t reqid, }, }, .reqid = reqid, - .src_ts = src_ts, - .dst_ts = dst_ts, + .data = *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/processing/jobs/acquire_job.h b/src/libcharon/processing/jobs/acquire_job.h index d45f72b46..f6b472397 100644 --- a/src/libcharon/processing/jobs/acquire_job.h +++ b/src/libcharon/processing/jobs/acquire_job.h @@ -24,7 +24,7 @@ typedef struct acquire_job_t acquire_job_t; #include -#include +#include #include /** @@ -42,13 +42,10 @@ struct acquire_job_t { /** * Creates a job of type ACQUIRE. * - * @param reqid reqid of the trapped CHILD_SA to acquire - * @param src_ts source traffic selector - * @param dst_ts destination traffic selector + * @param reqid reqid of the triggered policy + * @param data data from the acquire * @return acquire_job_t object */ -acquire_job_t *acquire_job_create(uint32_t reqid, - traffic_selector_t *src_ts, - traffic_selector_t *dst_ts); +acquire_job_t *acquire_job_create(uint32_t reqid, kernel_acquire_data_t *data); #endif /** REKEY_CHILD_SA_JOB_H_ @}*/ diff --git a/src/libcharon/processing/jobs/adopt_children_job.c b/src/libcharon/processing/jobs/adopt_children_job.c index e2a7f6b20..f969419fd 100644 --- a/src/libcharon/processing/jobs/adopt_children_job.c +++ b/src/libcharon/processing/jobs/adopt_children_job.c @@ -253,7 +253,7 @@ METHOD(job_t, execute, job_requeue_t, task->migrate(task, ike_sa); ike_sa->queue_task(ike_sa, task); } - if (ike_sa->initiate(ike_sa, NULL, 0, NULL, NULL) == DESTROY_ME) + if (ike_sa->initiate(ike_sa, NULL, NULL) == DESTROY_ME) { charon->ike_sa_manager->checkin_and_destroy( charon->ike_sa_manager, ike_sa); diff --git a/src/libcharon/processing/jobs/initiate_tasks_job.c b/src/libcharon/processing/jobs/initiate_tasks_job.c index 001e71fd1..442cd3163 100644 --- a/src/libcharon/processing/jobs/initiate_tasks_job.c +++ b/src/libcharon/processing/jobs/initiate_tasks_job.c @@ -55,7 +55,7 @@ METHOD(job_t, execute, job_requeue_t, this->ike_sa_id); if (ike_sa) { - if (ike_sa->initiate(ike_sa, NULL, 0, NULL, NULL) == DESTROY_ME) + if (ike_sa->initiate(ike_sa, NULL, NULL) == DESTROY_ME) { charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, ike_sa); diff --git a/src/libcharon/processing/jobs/start_action_job.c b/src/libcharon/processing/jobs/start_action_job.c index 3a0ed879f..31e154a77 100644 --- a/src/libcharon/processing/jobs/start_action_job.c +++ b/src/libcharon/processing/jobs/start_action_job.c @@ -42,8 +42,7 @@ METHOD(job_t, execute, job_requeue_t, enumerator_t *enumerator, *children; peer_cfg_t *peer_cfg; child_cfg_t *child_cfg; - ipsec_mode_t mode; - char *name; + action_t action; enumerator = charon->backends->create_peer_cfg_enumerator(charon->backends, NULL, NULL, NULL, NULL, IKE_ANY); @@ -52,34 +51,39 @@ METHOD(job_t, execute, job_requeue_t, children = peer_cfg->create_child_cfg_enumerator(peer_cfg); while (children->enumerate(children, &child_cfg)) { - name = child_cfg->get_name(child_cfg); - - switch (child_cfg->get_start_action(child_cfg)) + action = child_cfg->get_start_action(child_cfg); + if (action == ACTION_NONE) { - case ACTION_RESTART: - DBG1(DBG_JOB, "start action: initiate '%s'", name); - charon->controller->initiate(charon->controller, - peer_cfg->get_ref(peer_cfg), - child_cfg->get_ref(child_cfg), - NULL, NULL, 0, FALSE); - break; - case ACTION_ROUTE: - DBG1(DBG_JOB, "start action: route '%s'", name); - mode = child_cfg->get_mode(child_cfg); - if (mode == MODE_PASS || mode == MODE_DROP) - { + continue; + } + + DBG1(DBG_JOB, "start action: %N '%s'", action_names, action, + child_cfg->get_name(child_cfg)); + + if (action & ACTION_TRAP) + { + switch (child_cfg->get_mode(child_cfg)) + { + case MODE_PASS: + case MODE_DROP: charon->shunts->install(charon->shunts, peer_cfg->get_name(peer_cfg), child_cfg); - } - else - { + /* no need to check for ACTION_START */ + continue; + default: charon->traps->install(charon->traps, peer_cfg, child_cfg); - } - break; - case ACTION_NONE: - break; + break; + } + } + + if (action & ACTION_START) + { + charon->controller->initiate(charon->controller, + peer_cfg->get_ref(peer_cfg), + child_cfg->get_ref(child_cfg), + NULL, NULL, 0, FALSE); } } children->destroy(children); diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 9b06ebc3c..eb583944f 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -42,6 +42,7 @@ ENUM(child_sa_state_names, CHILD_CREATED, CHILD_DESTROYING, ); ENUM_FLAGS(child_sa_outbound_state_names, CHILD_OUTBOUND_REGISTERED, CHILD_OUTBOUND_POLICIES, + "NONE", "REGISTERED", "SA", "POLICIES", @@ -178,6 +179,11 @@ struct private_child_sa_t { */ mark_t mark_out; + /** + * Security label + */ + sec_label_t *label; + /** * absolute time when rekeying is scheduled */ @@ -297,6 +303,46 @@ static inline mark_t mark_in_sa(private_child_sa_t *this) return (mark_t){}; } +/** + * Possible uses for security labels + */ +typedef enum { + LABEL_USE_REQID, + LABEL_USE_POLICY, + LABEL_USE_SA, +} label_use_t; + +/** + * Returns the security label for either policies, SAs or reqids. + */ +static inline sec_label_t *label_for(private_child_sa_t *this, label_use_t use) +{ + /* For SELinux we use the configured label for policies and reqid but the + * negotiated one for the SAs. That's because the label on the policies is + * usually a generic one that matches specific labels on flows, which will + * trigger an acquire if no matching SA with that label exists yet. + * When that SA is later installed, we want to avoid having to install + * policies in the kernel that will never get used, so we use the configured + * label again. + * Note that while the labels don't have to be equal, they are both either + * NULL or defined. + */ + if (this->config->get_label_mode(this->config) == SEC_LABEL_MODE_SELINUX) + { + switch (use) + { + case LABEL_USE_REQID: + case LABEL_USE_POLICY: + return this->config->get_label(this->config); + case LABEL_USE_SA: + return this->label; + } + } + /* for the simple label mode we don't pass labels to the kernel, so we don't + * use it to acquire unique reqids either */ + return NULL; +} + METHOD(child_sa_t, get_name, char*, private_child_sa_t *this) { @@ -512,8 +558,12 @@ METHOD(enumerator_t, policy_destroy, void, free(this); } -METHOD(child_sa_t, create_policy_enumerator, enumerator_t*, - private_child_sa_t *this) +/** + * Create an enumerator over two lists of traffic selectors, returning all the + * pairs of traffic selectors from the first and second list. + */ +static enumerator_t *create_policy_enumerator_internal(array_t *my_ts, + array_t *other_ts) { policy_enumerator_t *e; @@ -523,15 +573,21 @@ METHOD(child_sa_t, create_policy_enumerator, enumerator_t*, .venumerate = _policy_enumerate, .destroy = _policy_destroy, }, - .mine = array_create_enumerator(this->my_ts), - .other = array_create_enumerator(this->other_ts), - .array = this->other_ts, + .mine = array_create_enumerator(my_ts), + .other = array_create_enumerator(other_ts), + .array = other_ts, .ts = NULL, ); return &e->public; } +METHOD(child_sa_t, create_policy_enumerator, enumerator_t*, + private_child_sa_t *this) +{ + return create_policy_enumerator_internal(this->my_ts, this->other_ts); +} + /** * update the cached usebytes * returns SUCCESS if the usebytes have changed, FAILED if not or no SPIs @@ -637,6 +693,7 @@ static bool update_usetime(private_child_sa_t *this, bool inbound) .dst_ts = my_ts, .mark = this->mark_in, .if_id = this->if_id_in, + .label = label_for(this, LABEL_USE_POLICY), }; kernel_ipsec_query_policy_t query = {}; @@ -664,6 +721,7 @@ static bool update_usetime(private_child_sa_t *this, bool inbound) .mark = this->mark_out, .if_id = this->if_id_out, .interface = this->config->get_interface(this->config), + .label = label_for(this, LABEL_USE_POLICY), }; kernel_ipsec_query_policy_t query = {}; @@ -735,6 +793,12 @@ METHOD(child_sa_t, get_if_id, uint32_t, return inbound ? this->if_id_in : this->if_id_out; } +METHOD(child_sa_t, get_label, sec_label_t*, + private_child_sa_t *this) +{ + return this->label ?: this->config->get_label(this->config); +} + METHOD(child_sa_t, get_lifetime, time_t, private_child_sa_t *this, bool hard) { @@ -855,7 +919,8 @@ static status_t install_internal(private_child_sa_t *this, chunk_t encr, { status = charon->kernel->alloc_reqid(charon->kernel, my_ts, other_ts, this->mark_in, this->mark_out, this->if_id_in, - this->if_id_out, &this->reqid); + this->if_id_out, label_for(this, LABEL_USE_REQID), + &this->reqid); if (status != SUCCESS) { my_ts->destroy(my_ts); @@ -919,6 +984,7 @@ static status_t install_internal(private_child_sa_t *this, chunk_t encr, .copy_df = !this->config->has_option(this->config, OPT_NO_COPY_DF), .copy_ecn = !this->config->has_option(this->config, OPT_NO_COPY_ECN), .copy_dscp = this->config->get_copy_dscp(this->config), + .label = label_for(this, LABEL_USE_SA), .initiator = initiator, .inbound = inbound, .update = update, @@ -1016,6 +1082,7 @@ static status_t install_policies_inbound(private_child_sa_t *this, .dst_ts = my_ts, .mark = this->mark_in, .if_id = this->if_id_in, + .label = label_for(this, LABEL_USE_POLICY), }; kernel_ipsec_manage_policy_t in_policy = { .type = type, @@ -1052,6 +1119,7 @@ static status_t install_policies_outbound(private_child_sa_t *this, .mark = this->mark_out, .if_id = this->if_id_out, .interface = this->config->get_interface(this->config), + .label = label_for(this, LABEL_USE_POLICY), }; kernel_ipsec_manage_policy_t out_policy = { .type = type, @@ -1061,6 +1129,7 @@ static status_t install_policies_outbound(private_child_sa_t *this, .dst = other_addr, .sa = other_sa, }; + uint32_t reqid = other_sa->reqid; status_t status = SUCCESS; status |= charon->kernel->add_policy(charon->kernel, &out_id, &out_policy); @@ -1084,7 +1153,7 @@ static status_t install_policies_outbound(private_child_sa_t *this, status |= charon->kernel->add_policy(charon->kernel, &out_id, &out_policy); /* reset the reqid for any other further policies */ - other_sa->reqid = this->reqid; + other_sa->reqid = reqid; } return status; } @@ -1125,6 +1194,7 @@ static void del_policies_inbound(private_child_sa_t *this, .dst_ts = my_ts, .mark = this->mark_in, .if_id = this->if_id_in, + .label = label_for(this, LABEL_USE_POLICY), }; kernel_ipsec_manage_policy_t in_policy = { .type = type, @@ -1160,6 +1230,7 @@ static void del_policies_outbound(private_child_sa_t *this, .mark = this->mark_out, .if_id = this->if_id_out, .interface = this->config->get_interface(this->config), + .label = label_for(this, LABEL_USE_POLICY), }; kernel_ipsec_manage_policy_t out_policy = { .type = type, @@ -1169,6 +1240,7 @@ static void del_policies_outbound(private_child_sa_t *this, .dst = other_addr, .sa = other_sa, }; + uint32_t reqid = other_sa->reqid; charon->kernel->del_policy(charon->kernel, &out_id, &out_policy); @@ -1181,7 +1253,7 @@ static void del_policies_outbound(private_child_sa_t *this, out_policy.prio = POLICY_PRIORITY_ROUTED; } charon->kernel->del_policy(charon->kernel, &out_id, &out_policy); - other_sa->reqid = this->reqid; + other_sa->reqid = reqid; } } @@ -1239,27 +1311,38 @@ METHOD(child_sa_t, set_policies, void, array_sort(this->other_ts, (void*)traffic_selector_cmp, NULL); } +/** + * Allocate a reqid for the given local and remote traffic selectors. + */ +static status_t alloc_reqid(private_child_sa_t *this, array_t *my_ts, + array_t *other_ts, uint32_t *reqid) +{ + linked_list_t *my_ts_list, *other_ts_list; + status_t status; + + my_ts_list = linked_list_create_from_enumerator(array_create_enumerator(my_ts)); + other_ts_list = linked_list_create_from_enumerator(array_create_enumerator(other_ts)); + status = charon->kernel->alloc_reqid( + charon->kernel, my_ts_list, other_ts_list, + this->mark_in, this->mark_out, this->if_id_in, + this->if_id_out, label_for(this, LABEL_USE_REQID), + reqid); + my_ts_list->destroy(my_ts_list); + other_ts_list->destroy(other_ts_list); + return status; +} + METHOD(child_sa_t, install_policies, status_t, private_child_sa_t *this) { enumerator_t *enumerator; - linked_list_t *my_ts_list, *other_ts_list; traffic_selector_t *my_ts, *other_ts; status_t status = SUCCESS; bool install_outbound = FALSE; if (!this->reqid_allocated && !this->static_reqid) { - my_ts_list = linked_list_create_from_enumerator( - array_create_enumerator(this->my_ts)); - other_ts_list = linked_list_create_from_enumerator( - array_create_enumerator(this->other_ts)); - status = charon->kernel->alloc_reqid( - charon->kernel, my_ts_list, other_ts_list, - this->mark_in, this->mark_out, this->if_id_in, - this->if_id_out, &this->reqid); - my_ts_list->destroy(my_ts_list); - other_ts_list->destroy(other_ts_list); + status = alloc_reqid(this, this->my_ts, this->other_ts, &this->reqid); if (status != SUCCESS) { return status; @@ -1311,15 +1394,36 @@ METHOD(child_sa_t, install_policies, status_t, return status; } +/** + * Check if we can install the outbound SA immediately. + * + * If the kernel supports installing SPIs with policies, we can do so as it + * will only be used once we update the policies. + * + * However, if we use labels with SELinux, we can't as we don't set SPIs + * on the policy in order to match SAs with other labels that match the generic + * label that's used on the policies. + */ +static bool install_outbound_immediately(private_child_sa_t *this) +{ + if (charon->kernel->get_features(charon->kernel) & KERNEL_POLICY_SPI) + { + if (this->config->get_label_mode(this->config) == SEC_LABEL_MODE_SELINUX) + { + return !this->label; + } + return TRUE; + } + return FALSE; +} + METHOD(child_sa_t, register_outbound, status_t, private_child_sa_t *this, chunk_t encr, chunk_t integ, uint32_t spi, uint16_t cpi, bool tfcv3) { status_t status; - /* if the kernel supports installing SPIs with policies we install the - * SA immediately as it will only be used once we update the policies */ - if (charon->kernel->get_features(charon->kernel) & KERNEL_POLICY_SPI) + if (install_outbound_immediately(this)) { status = install_internal(this, encr, integ, spi, cpi, FALSE, FALSE, tfcv3); @@ -1472,7 +1576,7 @@ CALLBACK(reinstall_vip, void, * Update addresses and encap state of IPsec SAs in the kernel */ static status_t update_sas(private_child_sa_t *this, host_t *me, host_t *other, - bool encap) + bool encap, uint32_t reqid) { /* update our (initiator) SA */ if (this->my_spi && this->inbound_installed) @@ -1491,6 +1595,7 @@ static status_t update_sas(private_child_sa_t *this, host_t *me, host_t *other, .new_dst = me, .encap = this->encap, .new_encap = encap, + .new_reqid = reqid, }; if (charon->kernel->update_sa(charon->kernel, &id, &sa) == NOT_SUPPORTED) @@ -1516,6 +1621,7 @@ static status_t update_sas(private_child_sa_t *this, host_t *me, host_t *other, .new_dst = other, .encap = this->encap, .new_encap = encap, + .new_reqid = reqid, }; if (charon->kernel->update_sa(charon->kernel, &id, &sa) == NOT_SUPPORTED) @@ -1527,6 +1633,30 @@ static status_t update_sas(private_child_sa_t *this, host_t *me, host_t *other, return SUCCESS; } +/** + * Fill the second list with copies of the given traffic selectors updating + * dynamic traffic selectors based on the given addresses. + */ +static void update_ts(host_t *old_host, host_t *new_host, array_t *old_list, + array_t *new_list) +{ + enumerator_t *enumerator; + traffic_selector_t *old_ts, *new_ts; + + enumerator = array_create_enumerator(old_list); + while (enumerator->enumerate(enumerator, &old_ts)) + { + new_ts = old_ts->clone(old_ts); + if (new_ts->is_host(new_ts, old_host)) + { + new_ts->set_address(new_ts, new_host); + } + array_insert(new_list, ARRAY_TAIL, new_ts); + } + enumerator->destroy(enumerator); + array_sort(new_list, (void*)traffic_selector_cmp, NULL); +} + METHOD(child_sa_t, update, status_t, private_child_sa_t *this, host_t *me, host_t *other, linked_list_t *vips, bool encap) @@ -1554,17 +1684,31 @@ METHOD(child_sa_t, update, status_t, ipsec_sa_cfg_t my_sa, other_sa; enumerator_t *enumerator; traffic_selector_t *my_ts, *other_ts; - uint32_t manual_prio; + array_t *new_my_ts = NULL, *new_other_ts = NULL; + policy_priority_t priority; + uint32_t manual_prio, new_reqid = 0; status_t state; bool outbound; prepare_sa_cfg(this, &my_sa, &other_sa); manual_prio = this->config->get_manual_prio(this->config); - outbound = (this->outbound_state & CHILD_OUTBOUND_POLICIES); + priority = this->trap ? POLICY_PRIORITY_ROUTED + : POLICY_PRIORITY_DEFAULT; + outbound = (this->outbound_state & CHILD_OUTBOUND_POLICIES) || this->trap; enumerator = create_policy_enumerator(this); while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) { + if (!new_my_ts && !me->ip_equals(me, this->my_addr) && + my_ts->is_host(my_ts, this->my_addr)) + { + new_my_ts = array_create(0, 0); + } + if (!new_other_ts && !other->ip_equals(other, this->other_addr) && + other_ts->is_host(other_ts, this->other_addr)) + { + new_other_ts = array_create(0, 0); + } /* install drop policy to avoid traffic leaks, acquires etc. */ if (outbound) { @@ -1575,77 +1719,130 @@ METHOD(child_sa_t, update, status_t, /* remove old policies */ del_policies_internal(this, this->my_addr, this->other_addr, my_ts, other_ts, &my_sa, &other_sa, POLICY_IPSEC, - POLICY_PRIORITY_DEFAULT, manual_prio, outbound); + priority, manual_prio, outbound); } enumerator->destroy(enumerator); + if (new_my_ts) + { + update_ts(this->my_addr, me, this->my_ts, new_my_ts); + } + if (new_other_ts) + { + update_ts(this->other_addr, other, this->other_ts, new_other_ts); + } + if (this->reqid_allocated && (new_my_ts || new_other_ts)) + { + /* if we allocated a reqid with the previous TS, we have to get a + * new one that matches the updated TS */ + if (alloc_reqid(this, new_my_ts ?: this->my_ts, + new_other_ts ?: this->other_ts, &new_reqid) != SUCCESS) + { + DBG1(DBG_CHD, "allocating new reqid for updated SA failed"); + } + } + /* update the IPsec SAs */ - state = update_sas(this, me, other, encap); + state = update_sas(this, me, other, encap, new_reqid); + + /* install new/updated policies only if we were able to update the + * SAs, otherwise we reinstall the old policies further below */ + if (state != NOT_SUPPORTED) + { + /* we reinstall the virtual IP to handle interface roaming + * correctly */ + if (vips) + { + vips->invoke_function(vips, reinstall_vip, me); + } + if (new_reqid) + { + my_sa.reqid = other_sa.reqid = new_reqid; + } + enumerator = create_policy_enumerator_internal( + new_my_ts ?: this->my_ts, + new_other_ts ?: this->other_ts); + while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) + { + install_policies_internal(this, me, other, my_ts, other_ts, + &my_sa, &other_sa, POLICY_IPSEC, + priority, manual_prio, outbound); + } + enumerator->destroy(enumerator); + if (new_reqid) + { + my_sa.reqid = other_sa.reqid = this->reqid; + } + } enumerator = create_policy_enumerator(this); while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) { - traffic_selector_t *old_my_ts = NULL, *old_other_ts = NULL; - /* reinstall the previous policies if we can't update the SAs */ if (state == NOT_SUPPORTED) { install_policies_internal(this, this->my_addr, this->other_addr, my_ts, other_ts, &my_sa, &other_sa, POLICY_IPSEC, - POLICY_PRIORITY_DEFAULT, manual_prio, outbound); - } - else - { - /* check if we have to update a "dynamic" traffic selector */ - if (!me->ip_equals(me, this->my_addr) && - my_ts->is_host(my_ts, this->my_addr)) - { - old_my_ts = my_ts->clone(my_ts); - my_ts->set_address(my_ts, me); - } - if (!other->ip_equals(other, this->other_addr) && - other_ts->is_host(other_ts, this->other_addr)) - { - old_other_ts = other_ts->clone(other_ts); - other_ts->set_address(other_ts, other); - } - - /* we reinstall the virtual IP to handle interface roaming - * correctly */ - if (vips) - { - vips->invoke_function(vips, reinstall_vip, me); - } - - /* reinstall updated policies */ - install_policies_internal(this, me, other, my_ts, other_ts, - &my_sa, &other_sa, POLICY_IPSEC, - POLICY_PRIORITY_DEFAULT, manual_prio, outbound); + priority, manual_prio, outbound); } /* remove the drop policy */ if (outbound) { del_policies_outbound(this, this->my_addr, this->other_addr, - old_my_ts ?: my_ts, old_other_ts ?: other_ts, - &my_sa, &other_sa, POLICY_DROP, + my_ts, other_ts, &my_sa, &other_sa, POLICY_DROP, POLICY_PRIORITY_DEFAULT, manual_prio); } - - DESTROY_IF(old_my_ts); - DESTROY_IF(old_other_ts); } enumerator->destroy(enumerator); if (state == NOT_SUPPORTED) { + if (new_reqid && + charon->kernel->release_reqid(charon->kernel, + new_reqid, this->mark_in, this->mark_out, + this->if_id_in, this->if_id_out, + label_for(this, LABEL_USE_REQID)) != SUCCESS) + { + DBG1(DBG_CHD, "releasing reqid %u failed", new_reqid); + } + array_destroy_offset(new_my_ts, + offsetof(traffic_selector_t, destroy)); + array_destroy_offset(new_other_ts, + offsetof(traffic_selector_t, destroy)); set_state(this, old); return NOT_SUPPORTED; } + if (new_reqid) + { + if (charon->kernel->release_reqid(charon->kernel, + this->reqid, this->mark_in, this->mark_out, + this->if_id_in, this->if_id_out, + label_for(this, LABEL_USE_REQID)) != SUCCESS) + { + DBG1(DBG_CHD, "releasing reqid %u failed", this->reqid); + } + DBG1(DBG_CHD, "replaced reqid %u with reqid %u for updated " + "CHILD_SA %s{%d}", this->reqid, new_reqid, get_name(this), + this->unique_id); + this->reqid = new_reqid; + } + if (new_my_ts) + { + array_destroy_offset(this->my_ts, + offsetof(traffic_selector_t, destroy)); + this->my_ts = new_my_ts; + } + if (new_other_ts) + { + array_destroy_offset(this->other_ts, + offsetof(traffic_selector_t, destroy)); + this->other_ts = new_other_ts; + } } else if (!transport_proxy_mode) { - if (update_sas(this, me, other, encap) == NOT_SUPPORTED) + if (update_sas(this, me, other, encap, 0) == NOT_SUPPORTED) { set_state(this, old); return NOT_SUPPORTED; @@ -1743,7 +1940,8 @@ METHOD(child_sa_t, destroy, void, { if (charon->kernel->release_reqid(charon->kernel, this->reqid, this->mark_in, this->mark_out, - this->if_id_in, this->if_id_out) != SUCCESS) + this->if_id_in, this->if_id_out, + label_for(this, LABEL_USE_REQID)) != SUCCESS) { DBG1(DBG_CHD, "releasing reqid %u failed", this->reqid); } @@ -1754,6 +1952,7 @@ METHOD(child_sa_t, destroy, void, this->my_addr->destroy(this->my_addr); this->other_addr->destroy(this->other_addr); DESTROY_IF(this->proposal); + DESTROY_IF(this->label); this->config->destroy(this->config); chunk_clear(&this->encr_r); chunk_clear(&this->integ_r); @@ -1826,6 +2025,7 @@ child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, .get_usestats = _get_usestats, .get_mark = _get_mark, .get_if_id = _get_if_id, + .get_label = _get_label, .has_encap = _has_encap, .get_ipcomp = _get_ipcomp, .set_ipcomp = _set_ipcomp, @@ -1863,6 +2063,7 @@ child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, .mark_out = config->get_mark(config, FALSE), .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, .install_time = time_monotonic(NULL), .policies_fwd_out = config->has_option(config, OPT_FWD_OUT_POLICIES), ); diff --git a/src/libcharon/sa/child_sa.h b/src/libcharon/sa/child_sa.h index 8a2939efd..cf0504347 100644 --- a/src/libcharon/sa/child_sa.h +++ b/src/libcharon/sa/child_sa.h @@ -369,6 +369,15 @@ struct child_sa_t { */ uint32_t (*get_if_id)(child_sa_t *this, bool inbound); + /** + * Get the security label used with this CHILD_SA. + * + * This might be different than the configured label. + * + * @return security label used with this CHILD_SA + */ + sec_label_t *(*get_label)(child_sa_t *this); + /** * Create an enumerator over traffic selectors of one side. * @@ -533,6 +542,8 @@ struct child_sa_create_t { /** Optional default outbound interface ID, if neither if_id_out, nor config * sets one */ uint32_t if_id_out_def; + /** Optional security label to apply on SAs (cloned) */ + sec_label_t *label; /** TRUE to enable UDP encapsulation (NAT traversal) */ bool encap; }; @@ -546,7 +557,7 @@ struct child_sa_create_t { * @param data data for this CHILD_SA * @return child_sa_t object */ -child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, +child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, child_sa_create_t *data); #endif /** CHILD_SA_H_ @}*/ diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index a891aff49..b7db06946 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -1531,8 +1531,7 @@ static void resolve_hosts(private_ike_sa_t *this) } METHOD(ike_sa_t, initiate, status_t, - private_ike_sa_t *this, child_cfg_t *child_cfg, uint32_t reqid, - traffic_selector_t *tsi, traffic_selector_t *tsr) + private_ike_sa_t *this, child_cfg_t *child_cfg, child_init_args_t *args) { bool defer_initiate = FALSE; @@ -1587,8 +1586,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, reqid, - tsi, tsr); + this->task_manager->queue_child(this->task_manager, child_cfg, args); #ifdef ME if (this->peer_cfg->get_mediated_by(this->peer_cfg)) { @@ -1621,7 +1619,7 @@ METHOD(ike_sa_t, retry_initiate, status_t, if (this->retry_initiate_queued) { this->retry_initiate_queued = FALSE; - return initiate(this, NULL, 0, NULL, NULL); + return initiate(this, NULL, NULL); } return SUCCESS; } @@ -2062,7 +2060,7 @@ static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new, } if (force) { - action = ACTION_RESTART; + action = ACTION_START; } else { /* only restart CHILD_SAs that are configured accordingly */ @@ -2075,15 +2073,18 @@ static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new, action = child_sa->get_dpd_action(child_sa); } } - if (action == ACTION_RESTART) + if (action & ACTION_START) { + child_init_args_t args = { + .reqid = child_sa->get_reqid(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), - child_sa->get_reqid(child_sa), - NULL, NULL); + &args); } } enumerator->destroy(enumerator); @@ -2091,7 +2092,7 @@ static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new, /* adopt any active or queued CHILD-creating tasks */ new->adopt_child_tasks(new, &this->public); - return new->initiate(new, NULL, 0, NULL, NULL); + return new->initiate(new, NULL, NULL); } METHOD(ike_sa_t, reestablish, status_t, @@ -2150,17 +2151,14 @@ METHOD(ike_sa_t, reestablish, status_t, { action = child_sa->get_dpd_action(child_sa); } - switch (action) + if (action & ACTION_TRAP) { - case ACTION_RESTART: - restart = TRUE; - break; - case ACTION_ROUTE: - charon->traps->install(charon->traps, this->peer_cfg, - child_sa->get_config(child_sa)); - break; - default: - break; + charon->traps->install(charon->traps, this->peer_cfg, + child_sa->get_config(child_sa)); + } + if (action & ACTION_START) + { + restart = TRUE; } } enumerator->destroy(enumerator); @@ -2224,7 +2222,7 @@ METHOD(ike_sa_t, reestablish, status_t, #ifdef ME if (this->peer_cfg->is_mediation(this->peer_cfg)) { - status = new->initiate(new, NULL, 0, NULL, NULL); + status = new->initiate(new, NULL, NULL); } else #endif /* ME */ @@ -3264,3 +3262,52 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator, } return &this->public; } + +/** + * Check if we have a an address pool configured. + */ +static bool have_pool(private_ike_sa_t *this) +{ + enumerator_t *enumerator; + bool found = FALSE; + + if (this->peer_cfg) + { + enumerator = this->peer_cfg->create_pool_enumerator(this->peer_cfg); + found = enumerator->enumerate(enumerator, NULL); + enumerator->destroy(enumerator); + } + return found; +} + +/* + * Described in header + */ +linked_list_t *ike_sa_get_dynamic_hosts(ike_sa_t *ike_sa, bool local) +{ + private_ike_sa_t *this = (private_ike_sa_t*)ike_sa; + enumerator_t *enumerator; + linked_list_t *list; + host_t *host; + + list = linked_list_create(); + enumerator = create_virtual_ip_enumerator(this, local); + while (enumerator->enumerate(enumerator, &host)) + { + list->insert_last(list, host); + } + enumerator->destroy(enumerator); + + if (!list->get_count(list)) + { /* no virtual IPs assigned */ + if (local) + { + list->insert_last(list, this->my_host); + } + else if (!have_pool(this)) + { /* use remote host only if we don't have a pool configured */ + list->insert_last(list, this->other_host); + } + } + return list; +} diff --git a/src/libcharon/sa/ike_sa.h b/src/libcharon/sa/ike_sa.h index ada8e89b6..8e4549258 100644 --- a/src/libcharon/sa/ike_sa.h +++ b/src/libcharon/sa/ike_sa.h @@ -29,6 +29,7 @@ typedef enum ike_condition_t ike_condition_t; typedef enum ike_sa_state_t ike_sa_state_t; typedef enum statistic_t statistic_t; typedef enum update_hosts_flag_t update_hosts_flag_t; +typedef struct child_init_args_t child_init_args_t; typedef struct ike_sa_t ike_sa_t; #include @@ -369,6 +370,20 @@ enum ike_sa_state_t { */ extern enum_name_t *ike_sa_state_names; +/** + * Optional arguments passed when initiating a CHILD_SA. + */ +struct child_init_args_t { + /** Reqid to use for CHILD_SA, 0 to assign automatically */ + uint32_t reqid; + /** Optional source of triggering packet */ + traffic_selector_t *src; + /** Optional destination of triggering packet */ + traffic_selector_t *dst; + /** Optional security label of triggering packet */ + sec_label_t *label; +}; + /** * Class ike_sa_t representing an IKE_SA. * @@ -787,16 +802,13 @@ struct ike_sa_t { * to the CHILD_SA. * * @param child_cfg child config to create CHILD from - * @param reqid reqid to use for CHILD_SA, 0 assign uniquely - * @param tsi source of triggering packet - * @param tsr destination of triggering packet. + * @param args optional arguments for the CHILD initiation * @return * - SUCCESS if initialization started * - DESTROY_ME if initialization failed */ status_t (*initiate) (ike_sa_t *this, child_cfg_t *child_cfg, - uint32_t reqid, traffic_selector_t *tsi, - traffic_selector_t *tsr); + child_init_args_t *args); /** * Retry initiation of this IKE_SA after it got deferred previously. @@ -1238,7 +1250,7 @@ struct ike_sa_t { * @param ike_sa_id ike_sa_id_t to associate with new IKE_SA/ISAKMP_SA * @param initiator TRUE to create this IKE_SA as initiator * @param version IKE version of this SA - * @return ike_sa_t object + * @return ike_sa_t object */ ike_sa_t *ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator, ike_version_t version); @@ -1247,8 +1259,18 @@ ike_sa_t *ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator, * Check if the given IKE_SA can be reauthenticated actively or if config * parameters or the authentication method prevent it. * - * @return TRUE if active reauthentication is possible + * @param this IKE_SA to check + * @return TRUE if active reauthentication is possible */ bool ike_sa_can_reauthenticate(ike_sa_t *this); +/** + * Get hosts, virtual or physical, for deriving dynamic traffic selectors. + * + * @param this IKE_SA to retrieve addresses from + * @param local TRUE to get local hosts + * @return list of hosts (internal objects) + */ +linked_list_t *ike_sa_get_dynamic_hosts(ike_sa_t *this, bool local); + #endif /** IKE_SA_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/task_manager_v1.c b/src/libcharon/sa/ikev1/task_manager_v1.c index 8df2862dd..cafeabb84 100644 --- a/src/libcharon/sa/ikev1/task_manager_v1.c +++ b/src/libcharon/sa/ikev1/task_manager_v1.c @@ -1685,7 +1685,7 @@ METHOD(task_manager_t, queue_ike_reauth, void, enumerator->destroy(enumerator); } - if (new->initiate(new, NULL, 0, NULL, NULL) != DESTROY_ME) + if (new->initiate(new, NULL, NULL) != DESTROY_ME) { charon->ike_sa_manager->checkin(charon->ike_sa_manager, new); this->ike_sa->set_state(this->ike_sa, IKE_REKEYING); @@ -1732,14 +1732,19 @@ METHOD(task_manager_t, queue_mobike, void, } METHOD(task_manager_t, queue_child, void, - private_task_manager_t *this, child_cfg_t *cfg, uint32_t reqid, - traffic_selector_t *tsi, traffic_selector_t *tsr) + private_task_manager_t *this, child_cfg_t *cfg, child_init_args_t *args) { quick_mode_t *task; - task = quick_mode_create(this->ike_sa, cfg, tsi, tsr); - task->use_reqid(task, reqid); - + if (args) + { + task = quick_mode_create(this->ike_sa, cfg, args->src, args->dst); + task->use_reqid(task, args->reqid); + } + else + { + task = quick_mode_create(this->ike_sa, cfg, NULL, NULL); + } queue_task(this, &task->task); } diff --git a/src/libcharon/sa/ikev1/tasks/quick_delete.c b/src/libcharon/sa/ikev1/tasks/quick_delete.c index 7dff247fb..9d09ee42f 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_delete.c +++ b/src/libcharon/sa/ikev1/tasks/quick_delete.c @@ -149,23 +149,24 @@ static status_t delete_child(private_quick_delete_t *this, if (remote_close) { + child_init_args_t args = { + .reqid = child_sa->get_reqid(child_sa), + }; + action_t action; + + action = child_sa->get_close_action(child_sa); child_cfg = child_sa->get_config(child_sa); child_cfg->get_ref(child_cfg); - - switch (child_sa->get_close_action(child_sa)) + if (action & ACTION_TRAP) { - case ACTION_RESTART: - child_cfg->get_ref(child_cfg); - status = this->ike_sa->initiate(this->ike_sa, child_cfg, - child_sa->get_reqid(child_sa), NULL, NULL); - break; - case ACTION_ROUTE: - charon->traps->install(charon->traps, - this->ike_sa->get_peer_cfg(this->ike_sa), - child_cfg); - break; - default: - break; + charon->traps->install(charon->traps, + this->ike_sa->get_peer_cfg(this->ike_sa), + child_cfg); + } + if (action & ACTION_START) + { + child_cfg->get_ref(child_cfg); + status = this->ike_sa->initiate(this->ike_sa, child_cfg, &args); } child_cfg->destroy(child_cfg); } diff --git a/src/libcharon/sa/ikev1/tasks/quick_mode.c b/src/libcharon/sa/ikev1/tasks/quick_mode.c index f75775357..5e4bf8620 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_mode.c +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.c @@ -1096,7 +1096,7 @@ METHOD(task_t, process_r, status_t, hostsi = get_dynamic_hosts(this->ike_sa, FALSE); hostsr = get_dynamic_hosts(this->ike_sa, TRUE); this->config = peer_cfg->select_child_cfg(peer_cfg, tsr, tsi, - hostsr, hostsi); + hostsr, hostsi, NULL, NULL); hostsi->destroy(hostsi); hostsr->destroy(hostsr); if (this->config) @@ -1475,7 +1475,6 @@ METHOD(task_t, migrate, void, this->dh = NULL; this->spi_i = 0; this->spi_r = 0; - this->child = (child_sa_create_t){}; if (!this->initiator) { diff --git a/src/libcharon/sa/ikev2/task_manager_v2.c b/src/libcharon/sa/ikev2/task_manager_v2.c index f45d074e1..555fb86c7 100644 --- a/src/libcharon/sa/ikev2/task_manager_v2.c +++ b/src/libcharon/sa/ikev2/task_manager_v2.c @@ -1983,7 +1983,7 @@ static void trigger_mbb_reauth(private_task_manager_t *this) /* suspend online revocation checking until the SA is established */ new->set_condition(new, COND_ONLINE_VALIDATION_SUSPENDED, TRUE); - if (new->initiate(new, NULL, 0, NULL, NULL) != DESTROY_ME) + if (new->initiate(new, NULL, NULL) != DESTROY_ME) { new->queue_task(new, (task_t*)ike_verify_peer_cert_create(new)); new->queue_task(new, (task_t*)ike_reauth_complete_create(new, @@ -2102,15 +2102,19 @@ METHOD(task_manager_t, queue_dpd, void, } METHOD(task_manager_t, queue_child, void, - private_task_manager_t *this, child_cfg_t *cfg, uint32_t reqid, - traffic_selector_t *tsi, traffic_selector_t *tsr) + private_task_manager_t *this, child_cfg_t *cfg, child_init_args_t *args) { child_create_t *task; - task = child_create_create(this->ike_sa, cfg, FALSE, tsi, tsr); - if (reqid) + if (args) { - task->use_reqid(task, reqid); + task = child_create_create(this->ike_sa, cfg, FALSE, args->src, args->dst); + task->use_reqid(task, args->reqid); + task->use_label(task, args->label); + } + else + { + task = child_create_create(this->ike_sa, cfg, FALSE, NULL, NULL); } 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 64dd55167..555854598 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -84,15 +84,25 @@ struct private_child_create_t { proposal_t *proposal; /** - * traffic selectors for initiators side + * traffic selectors for initiator side */ linked_list_t *tsi; /** - * traffic selectors for responders side + * traffic selectors for responder side */ linked_list_t *tsr; + /** + * labels for initiator side + */ + linked_list_t *labels_i; + + /** + * labels for responder side + */ + linked_list_t *labels_r; + /** * source of triggering packet */ @@ -210,6 +220,7 @@ static void schedule_delayed_retry(private_child_create_t *this) 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); DBG1(DBG_IKE, "creating CHILD_SA failed, trying again in %d seconds", retry); @@ -358,62 +369,6 @@ static void schedule_inactivity_timeout(private_child_create_t *this) } } -/** - * Check if we have a an address pool configured - */ -static bool have_pool(ike_sa_t *ike_sa) -{ - enumerator_t *enumerator; - peer_cfg_t *peer_cfg; - char *pool; - bool found = FALSE; - - peer_cfg = ike_sa->get_peer_cfg(ike_sa); - if (peer_cfg) - { - enumerator = peer_cfg->create_pool_enumerator(peer_cfg); - if (enumerator->enumerate(enumerator, &pool)) - { - found = TRUE; - } - enumerator->destroy(enumerator); - } - return found; -} - -/** - * Get hosts to use for dynamic traffic selectors - */ -static linked_list_t *get_dynamic_hosts(ike_sa_t *ike_sa, bool local) -{ - enumerator_t *enumerator; - linked_list_t *list; - host_t *host; - - list = linked_list_create(); - enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, local); - while (enumerator->enumerate(enumerator, &host)) - { - list->insert_last(list, host); - } - enumerator->destroy(enumerator); - - if (list->get_count(list) == 0) - { /* no virtual IPs assigned */ - if (local) - { - host = ike_sa->get_my_host(ike_sa); - list->insert_last(list, host); - } - else if (!have_pool(ike_sa)) - { /* use host only if we don't have a pool configured */ - host = ike_sa->get_other_host(ike_sa); - list->insert_last(list, host); - } - } - return list; -} - /** * Substitute any host address with NATed address in traffic selector */ @@ -468,7 +423,7 @@ static linked_list_t* narrow_ts(private_child_create_t *this, bool local, ike_condition_t cond; cond = local ? COND_NAT_HERE : COND_NAT_THERE; - hosts = get_dynamic_hosts(this->ike_sa, local); + hosts = ike_sa_get_dynamic_hosts(this->ike_sa, local); if (this->mode == MODE_TRANSPORT && this->ike_sa->has_condition(this->ike_sa, cond)) @@ -891,9 +846,11 @@ static bool build_payloads(private_child_create_t *this, message_t *message) } /* add TSi/TSr payloads */ - ts_payload = ts_payload_create_from_traffic_selectors(TRUE, this->tsi); + ts_payload = ts_payload_create_from_traffic_selectors(TRUE, this->tsi, + this->child.label); message->add_payload(message, (payload_t*)ts_payload); - ts_payload = ts_payload_create_from_traffic_selectors(FALSE, this->tsr); + ts_payload = ts_payload_create_from_traffic_selectors(FALSE, this->tsr, + this->child.label); message->add_payload(message, (payload_t*)ts_payload); /* add a notify if we are not in tunnel mode */ @@ -1039,10 +996,12 @@ static void process_payloads(private_child_create_t *this, message_t *message) case PLV2_TS_INITIATOR: ts_payload = (ts_payload_t*)payload; this->tsi = ts_payload->get_traffic_selectors(ts_payload); + this->labels_i = ts_payload->get_sec_labels(ts_payload); break; case PLV2_TS_RESPONDER: ts_payload = (ts_payload_t*)payload; this->tsr = ts_payload->get_traffic_selectors(ts_payload); + this->labels_r = ts_payload->get_sec_labels(ts_payload); break; case PLV2_NOTIFY: handle_notify(this, (notify_payload_t*)payload); @@ -1054,6 +1013,16 @@ static void process_payloads(private_child_create_t *this, message_t *message) enumerator->destroy(enumerator); } +/** + * Check if we have only the generic label available when using SELinux and not + * a specific one from an acquire. + */ +static bool generic_label_only(private_child_create_t *this) +{ + return this->config->get_label(this->config) && !this->child.label && + this->config->get_label_mode(this->config) == SEC_LABEL_MODE_SELINUX; +} + /** * Check if we should defer the creation of this CHILD_SA until after the * IKE_SA has been established childless. @@ -1061,17 +1030,23 @@ static void process_payloads(private_child_create_t *this, message_t *message) static status_t defer_child_sa(private_child_create_t *this) { ike_cfg_t *ike_cfg; + childless_t policy; ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); + policy = ike_cfg->childless(ike_cfg); if (this->ike_sa->supports_extension(this->ike_sa, EXT_IKE_CHILDLESS)) { - if (ike_cfg->childless(ike_cfg) == CHILDLESS_FORCE) + /* with SELinux, we prefer not to create a CHILD_SA when we only have + * the generic label available. if the peer does not support it, + * creating the SA will most likely fail */ + if (policy == CHILDLESS_FORCE || + generic_label_only(this)) { return NEED_MORE; } } - else if (ike_cfg->childless(ike_cfg) == CHILDLESS_FORCE) + else if (policy == CHILDLESS_FORCE) { DBG1(DBG_IKE, "peer does not support childless IKE_SA initiation"); return DESTROY_ME; @@ -1079,6 +1054,88 @@ static status_t defer_child_sa(private_child_create_t *this) return NOT_SUPPORTED; } +/** + * Compare two CHILD_SA objects for equality + */ +static bool child_sa_equals(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)); +} + +/** + * Check if there is a duplicate CHILD_SA already established and we can abort + * initiating this one. + */ +static bool check_for_duplicate(private_child_create_t *this) +{ + enumerator_t *enumerator; + child_sa_t *child_sa, *found = NULL; + + enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa); + while (enumerator->enumerate(enumerator, (void**)&child_sa)) + { + if (child_sa->get_state(child_sa) == CHILD_INSTALLED && + child_sa_equals(child_sa, this->child_sa)) + { + found = child_sa; + break; + } + } + enumerator->destroy(enumerator); + + if (found) + { + linked_list_t *my_ts, *other_ts; + + my_ts = linked_list_create_from_enumerator( + found->create_ts_enumerator(found, TRUE)); + other_ts = linked_list_create_from_enumerator( + found->create_ts_enumerator(found, FALSE)); + + DBG1(DBG_IKE, "not establishing CHILD_SA %s{%d} due to existing " + "duplicate {%d} with SPIs %.8x_i %.8x_o and TS %#R === %#R", + this->child_sa->get_name(this->child_sa), + this->child_sa->get_unique_id(this->child_sa), + found->get_unique_id(found), + ntohl(found->get_spi(found, TRUE)), + ntohl(found->get_spi(found, FALSE)), my_ts, other_ts); + + my_ts->destroy(my_ts); + other_ts->destroy(other_ts); + } + return found; +} + +/** + * Check if this is an attempt to create an SA with generic label and should + * be aborted. + */ +static bool check_for_generic_label(private_child_create_t *this) +{ + if (generic_label_only(this)) + { + sec_label_t *label; + + label = this->config->get_label(this->config); + DBG1(DBG_IKE, "not establishing CHILD_SA %s{%d} with generic " + "label '%s'", this->child_sa->get_name(this->child_sa), + this->child_sa->get_unique_id(this->child_sa), + label->get_string(label)); + return TRUE; + } + return FALSE; +} + METHOD(task_t, build_i, status_t, private_child_create_t *this, message_t *message) { @@ -1094,8 +1151,7 @@ METHOD(task_t, build_i, status_t, case CREATE_CHILD_SA: if (!generate_nonce(this)) { - message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, - chunk_empty); + message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED); return SUCCESS; } if (!this->retry && this->dh_group == MODP_NONE) @@ -1150,12 +1206,12 @@ METHOD(task_t, build_i, status_t, else { /* no virtual IPs configured */ list->destroy(list); - list = get_dynamic_hosts(this->ike_sa, TRUE); + 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 = get_dynamic_hosts(this->ike_sa, FALSE); + 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); @@ -1170,6 +1226,22 @@ METHOD(task_t, build_i, status_t, this->tsr->insert_first(this->tsr, this->packet_tsr->clone(this->packet_tsr)); } + + if (!generic_label_only(this) && !this->child.label) + { /* in the simple label mode we propose the configured label as we + * won't have labels from acquires */ + this->child.label = this->config->get_label(this->config); + if (this->child.label) + { + this->child.label = this->child.label->clone(this->child.label); + } + } + if (this->child.label) + { + DBG2(DBG_CFG, "proposing security label '%s'", + this->child.label->get_string(this->child.label)); + } + this->proposals = this->config->get_proposals(this->config, this->dh_group == MODP_NONE); this->mode = this->config->get_mode(this->config); @@ -1181,6 +1253,16 @@ METHOD(task_t, build_i, status_t, this->ike_sa->get_other_host(this->ike_sa), this->config, &this->child); + /* check this after creating the object so that its destruction is detected + * by controller and trap manager */ + if (!this->rekey && + message->get_exchange_type(message) == CREATE_CHILD_SA && + (check_for_generic_label(this) || check_for_duplicate(this))) + { + message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED); + return SUCCESS; + } + if (this->child.reqid) { DBG0(DBG_IKE, "establishing CHILD_SA %s{%d} reqid %d", @@ -1340,11 +1422,11 @@ static child_cfg_t* select_child_cfg(private_child_create_t *this) tsr = get_ts_if_nat_transport(this, TRUE, this->tsr); tsi = get_ts_if_nat_transport(this, FALSE, this->tsi); - listr = get_dynamic_hosts(this->ike_sa, TRUE); - listi = get_dynamic_hosts(this->ike_sa, FALSE); + listr = ike_sa_get_dynamic_hosts(this->ike_sa, TRUE); + listi = ike_sa_get_dynamic_hosts(this->ike_sa, FALSE); child_cfg = peer_cfg->select_child_cfg(peer_cfg, - tsr ?: this->tsr, tsi ?: this->tsi, - listr, listi); + tsr ?: this->tsr, tsi ?: this->tsi, + listr, listi, this->labels_r, this->labels_i); if ((tsi || tsr) && child_cfg && child_cfg->get_mode(child_cfg) != MODE_TRANSPORT) { @@ -1356,7 +1438,8 @@ static child_cfg_t* select_child_cfg(private_child_create_t *this) { /* no match for the substituted NAT selectors, try it without */ child_cfg = peer_cfg->select_child_cfg(peer_cfg, - this->tsr, this->tsi, listr, listi); + this->tsr, this->tsi, + listr, listi, this->labels_r, this->labels_i); } listr->destroy(listr); listi->destroy(listi); @@ -1398,6 +1481,49 @@ static status_t handle_childless(private_child_create_t *this) return NOT_SUPPORTED; } +/** + * Select a security label. + * + * We already know that the proposed labels match the selected config, just make + * sure that the proposed/returned labels are the same. + */ +static bool select_label(private_child_create_t *this) +{ + sec_label_t *li, *lr; + + if (!this->config->select_label(this->config, this->labels_i, FALSE, &li, NULL) || + !this->config->select_label(this->config, this->labels_r, FALSE, &lr, NULL)) + { /* sanity check */ + return FALSE; + } + + if (li) + { + if (!li->equals(li, lr)) + { + DBG1(DBG_CHD, "security labels in TSi and TSr don't match"); + return FALSE; + } + else if (!this->child.label) + { + this->child.label = li->clone(li); + } + else if (!this->child.label->equals(this->child.label, li)) + { + DBG1(DBG_CHD, "returned security label '%s' doesn't match proposed " + "'%s'", li->get_string(li), + this->child.label->get_string(this->child.label)); + return FALSE; + } + } + if (this->child.label) + { + DBG1(DBG_CFG, "selected security label: %s", + this->child.label->get_string(this->child.label)); + } + return TRUE; +} + METHOD(task_t, build_r, status_t, private_child_create_t *this, message_t *message) { @@ -1505,6 +1631,13 @@ METHOD(task_t, build_r, status_t, } enumerator->destroy(enumerator); + if (!select_label(this)) + { + message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty); + handle_child_sa_failure(this, message); + return SUCCESS; + } + this->child.if_id_in_def = this->ike_sa->get_if_id(this->ike_sa, TRUE); this->child.if_id_out_def = this->ike_sa->get_if_id(this->ike_sa, FALSE); this->child.encap = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY); @@ -1762,6 +1895,12 @@ METHOD(task_t, process_i, status_t, return delete_failed_sa(this); } + if (!select_label(this)) + { + handle_child_sa_failure(this, message); + return delete_failed_sa(this); + } + if (select_and_install(this, no_dh, ike_auth) == SUCCESS) { if (!this->rekey) @@ -1797,6 +1936,13 @@ METHOD(child_create_t, use_if_ids, void, this->child.if_id_out = out; } +METHOD(child_create_t, use_label, void, + private_child_create_t *this, sec_label_t *label) +{ + DESTROY_IF(this->child.label); + this->child.label = label ? label->clone(label) : NULL; +} + METHOD(child_create_t, use_dh_group, void, private_child_create_t *this, diffie_hellman_group_t dh_group) { @@ -1849,6 +1995,14 @@ METHOD(task_t, migrate, void, { 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_IF(this->child_sa); DESTROY_IF(this->proposal); DESTROY_IF(this->nonceg); @@ -1876,7 +2030,6 @@ METHOD(task_t, migrate, void, this->ipcomp_received = IPCOMP_NONE; this->other_cpi = 0; this->established = FALSE; - this->child = (child_sa_create_t){}; } METHOD(task_t, destroy, void, @@ -1892,6 +2045,14 @@ METHOD(task_t, destroy, void, { 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)); + } if (!this->established) { DESTROY_IF(this->child_sa); @@ -1906,6 +2067,7 @@ METHOD(task_t, destroy, void, } DESTROY_IF(this->config); DESTROY_IF(this->nonceg); + DESTROY_IF(this->child.label); free(this); } @@ -1926,6 +2088,7 @@ child_create_t *child_create_create(ike_sa_t *ike_sa, .use_reqid = _use_reqid, .use_marks = _use_marks, .use_if_ids = _use_if_ids, + .use_label = _use_label, .use_dh_group = _use_dh_group, .task = { .get_type = _get_type, diff --git a/src/libcharon/sa/ikev2/tasks/child_create.h b/src/libcharon/sa/ikev2/tasks/child_create.h index eae1f3532..40e25d89a 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.h +++ b/src/libcharon/sa/ikev2/tasks/child_create.h @@ -68,6 +68,13 @@ struct child_create_t { */ void (*use_if_ids)(child_create_t *this, uint32_t in, uint32_t out); + /** + * Use specific security label, overriding configuration. + * + * @param label security label + */ + void (*use_label)(child_create_t *this, sec_label_t *label); + /** * Initially propose a specific DH group to override configuration. * diff --git a/src/libcharon/sa/ikev2/tasks/child_delete.c b/src/libcharon/sa/ikev2/tasks/child_delete.c index f3881ceaa..b570a36e0 100644 --- a/src/libcharon/sa/ikev2/tasks/child_delete.c +++ b/src/libcharon/sa/ikev2/tasks/child_delete.c @@ -312,12 +312,13 @@ static void process_payloads(private_child_delete_t *this, message_t *message) */ static status_t destroy_and_reestablish(private_child_delete_t *this) { + child_init_args_t args = {}; enumerator_t *enumerator; entry_t *entry; child_sa_t *child_sa; child_cfg_t *child_cfg; protocol_id_t protocol; - uint32_t spi, reqid; + uint32_t spi; action_t action; status_t status = SUCCESS; time_t now, expire; @@ -362,32 +363,34 @@ static status_t destroy_and_reestablish(private_child_delete_t *this) /* no delay and no lifetime, destroy it immediately */ } spi = child_sa->get_spi(child_sa, TRUE); - reqid = child_sa->get_reqid(child_sa); child_cfg = child_sa->get_config(child_sa); child_cfg->get_ref(child_cfg); + args.reqid = child_sa->get_reqid(child_sa); + args.label = child_sa->get_label(child_sa); + if (args.label) + { + args.label = args.label->clone(args.label); + } action = child_sa->get_close_action(child_sa); this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi); if (entry->check_delete_action) { /* enforce child_cfg policy if deleted passively */ - switch (action) + if (action & ACTION_TRAP) { - case ACTION_RESTART: - child_cfg->get_ref(child_cfg); - status = this->ike_sa->initiate(this->ike_sa, child_cfg, - reqid, NULL, NULL); - break; - case ACTION_ROUTE: - charon->traps->install(charon->traps, - this->ike_sa->get_peer_cfg(this->ike_sa), - child_cfg); - break; - default: - break; + charon->traps->install(charon->traps, + this->ike_sa->get_peer_cfg(this->ike_sa), + child_cfg); + } + if (action & ACTION_START) + { + child_cfg->get_ref(child_cfg); + status = this->ike_sa->initiate(this->ike_sa, child_cfg, &args); } } child_cfg->destroy(child_cfg); + DESTROY_IF(args.label); if (status != SUCCESS) { break; diff --git a/src/libcharon/sa/ikev2/tasks/child_rekey.c b/src/libcharon/sa/ikev2/tasks/child_rekey.c index 37f7ea9bd..37b05c943 100644 --- a/src/libcharon/sa/ikev2/tasks/child_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/child_rekey.c @@ -210,6 +210,8 @@ METHOD(task_t, build_i, status_t, this->child_create->use_if_ids(this->child_create, this->child_sa->get_if_id(this->child_sa, TRUE), this->child_sa->get_if_id(this->child_sa, FALSE)); + this->child_create->use_label(this->child_create, + this->child_sa->get_label(this->child_sa)); if (this->child_create->task.build(&this->child_create->task, message) != NEED_MORE) @@ -272,6 +274,8 @@ METHOD(task_t, build_r, status_t, this->child_create->use_if_ids(this->child_create, this->child_sa->get_if_id(this->child_sa, TRUE), this->child_sa->get_if_id(this->child_sa, FALSE)); + this->child_create->use_label(this->child_create, + this->child_sa->get_label(this->child_sa)); config = this->child_sa->get_config(this->child_sa); this->child_create->set_config(this->child_create, config->get_ref(config)); this->child_create->task.build(&this->child_create->task, message); @@ -391,7 +395,8 @@ METHOD(task_t, process_i, status_t, if (message->get_notify(message, CHILD_SA_NOT_FOUND)) { child_cfg_t *child_cfg; - uint32_t reqid; + child_init_args_t args = {}; + status_t status; if (this->collision && this->collision->get_type(this->collision) == TASK_CHILD_DELETE) @@ -406,15 +411,21 @@ METHOD(task_t, process_i, status_t, * that (we could go by name, but that might be tricky e.g. due to * narrowing) */ spi = this->child_sa->get_spi(this->child_sa, TRUE); - reqid = this->child_sa->get_reqid(this->child_sa); protocol = this->child_sa->get_protocol(this->child_sa); child_cfg = this->child_sa->get_config(this->child_sa); child_cfg->get_ref(child_cfg); + args.reqid = this->child_sa->get_reqid(this->child_sa); + args.label = this->child_sa->get_label(this->child_sa); + if (args.label) + { + args.label = args.label->clone(args.label); + } charon->bus->child_updown(charon->bus, this->child_sa, FALSE); this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi); - return this->ike_sa->initiate(this->ike_sa, - child_cfg->get_ref(child_cfg), reqid, - NULL, NULL); + status = this->ike_sa->initiate(this->ike_sa, + child_cfg->get_ref(child_cfg), &args); + DESTROY_IF(args.label); + return status; } if (this->child_create->task.process(&this->child_create->task, diff --git a/src/libcharon/sa/task_manager.h b/src/libcharon/sa/task_manager.h index f9225c56a..8c6496adf 100644 --- a/src/libcharon/sa/task_manager.h +++ b/src/libcharon/sa/task_manager.h @@ -172,12 +172,10 @@ struct task_manager_t { * Queue CHILD_SA establishing tasks. * * @param cfg CHILD_SA config to establish - * @param reqid reqid to use for CHILD_SA - * @param tsi initiator traffic selector, if packet-triggered - * @param tsr responder traffic selector, if packet-triggered + * @param args optional arguments for the initiation */ - void (*queue_child)(task_manager_t *this, child_cfg_t *cfg, uint32_t reqid, - traffic_selector_t *tsi, traffic_selector_t *tsr); + void (*queue_child)(task_manager_t *this, child_cfg_t *cfg, + child_init_args_t *args); /** * Queue CHILD_SA rekeying tasks. diff --git a/src/libcharon/sa/trap_manager.c b/src/libcharon/sa/trap_manager.c index f9f78acab..e45c8ff3f 100644 --- a/src/libcharon/sa/trap_manager.c +++ b/src/libcharon/sa/trap_manager.c @@ -106,6 +106,8 @@ typedef struct { child_sa_t *child_sa; /** TRUE in case of wildcard Transport Mode SA */ bool wildcard; + /** TRUE for CHILD_SAs that are externally managed */ + bool external; } entry_t; /** @@ -118,6 +120,8 @@ typedef struct { uint32_t reqid; /** destination address (wildcard case) */ host_t *dst; + /** security label, if any */ + sec_label_t *label; } acquire_t; /** @@ -125,7 +129,10 @@ typedef struct { */ static void destroy_entry(entry_t *this) { - this->child_sa->destroy(this->child_sa); + if (!this->external) + { + this->child_sa->destroy(this->child_sa); + } this->peer_cfg->destroy(this->peer_cfg); free(this->name); free(this); @@ -137,6 +144,7 @@ static void destroy_entry(entry_t *this) static void destroy_acquire(acquire_t *this) { DESTROY_IF(this->dst); + DESTROY_IF(this->label); free(this); } @@ -144,9 +152,10 @@ CALLBACK(acquire_by_reqid, bool, acquire_t *this, va_list args) { uint32_t reqid; + sec_label_t *label; - VA_ARGS_VGET(args, reqid); - return this->reqid == reqid; + VA_ARGS_VGET(args, reqid, label); + return this->reqid == reqid && sec_labels_equal(this->label, label); } CALLBACK(acquire_by_dst, bool, @@ -183,6 +192,41 @@ static bool dynamic_remote_ts(child_cfg_t *child) return found; } +/** + * Install the given trap + */ +static status_t install_trap(child_sa_t *child_sa, linked_list_t *local, + linked_list_t *remote) +{ + linked_list_t *my_ts, *other_ts, *proposals; + proposal_t *proposal; + child_cfg_t *child; + protocol_id_t proto = PROTO_ESP; + + 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); + + /* we don't know the finally negotiated protocol (ESP|AH), we install + * the SA with the protocol of the first proposal */ + proposals = child->get_proposals(child, TRUE); + if (proposals->get_first(proposals, (void**)&proposal) == SUCCESS) + { + proto = proposal->get_protocol(proposal); + } + proposals->destroy_offset(proposals, offsetof(proposal_t, destroy)); + + child_sa->set_protocol(child_sa, proto); + child_sa->set_mode(child_sa, child->get_mode(child)); + + child_sa->set_policies(child_sa, my_ts, other_ts); + my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy)); + other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy)); + + return child_sa->install_policies(child_sa); +} + METHOD(trap_manager_t, install, bool, private_trap_manager_t *this, peer_cfg_t *peer, child_cfg_t *child) { @@ -190,12 +234,9 @@ METHOD(trap_manager_t, install, bool, ike_cfg_t *ike_cfg; child_sa_t *child_sa; host_t *me, *other; - linked_list_t *my_ts, *other_ts, *list; + linked_list_t *local, *remote; enumerator_t *enumerator; status_t status; - linked_list_t *proposals; - proposal_t *proposal; - protocol_id_t proto = PROTO_ESP; bool result = FALSE, wildcard = FALSE; /* try to resolve addresses */ @@ -254,13 +295,14 @@ METHOD(trap_manager_t, install, bool, enumerator = this->traps->create_enumerator(this->traps); while (enumerator->enumerate(enumerator, &entry)) { - if (streq(entry->name, child->get_name(child)) && + if (!entry->external && + streq(entry->name, child->get_name(child)) && streq(entry->peer_cfg->get_name(entry->peer_cfg), peer->get_name(peer))) { found = entry; if (entry->child_sa) - { /* replace it with an updated version, if already installed */ + { /* replace it with an updated version if already installed */ this->traps->remove_at(this->traps, enumerator); } break; @@ -302,28 +344,13 @@ METHOD(trap_manager_t, install, bool, }; child_sa = child_sa_create(me, other, child, &child_data); - list = linked_list_create_with_items(me, NULL); - my_ts = child->get_traffic_selectors(child, TRUE, NULL, list, FALSE); - list->destroy_offset(list, offsetof(host_t, destroy)); + local = linked_list_create_with_items(me, NULL); + remote = linked_list_create_with_items(other, NULL); - list = linked_list_create_with_items(other, NULL); - other_ts = child->get_traffic_selectors(child, FALSE, NULL, list, FALSE); - list->destroy_offset(list, offsetof(host_t, destroy)); + status = install_trap(child_sa, local, remote); - /* We don't know the finally negotiated protocol (ESP|AH), we install - * the SA with the protocol of the first proposal */ - proposals = child->get_proposals(child, TRUE); - if (proposals->get_first(proposals, (void**)&proposal) == SUCCESS) - { - proto = proposal->get_protocol(proposal); - } - proposals->destroy_offset(proposals, offsetof(proposal_t, destroy)); - child_sa->set_protocol(child_sa, proto); - child_sa->set_mode(child_sa, child->get_mode(child)); - child_sa->set_policies(child_sa, my_ts, other_ts); - status = child_sa->install_policies(child_sa); - my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy)); - other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy)); + local->destroy_offset(local, offsetof(host_t, destroy)); + remote->destroy_offset(remote, offsetof(host_t, destroy)); if (status != SUCCESS) { DBG1(DBG_CFG, "installing trap failed"); @@ -366,8 +393,71 @@ METHOD(trap_manager_t, uninstall, bool, enumerator = this->traps->create_enumerator(this->traps); while (enumerator->enumerate(enumerator, &entry)) { - if (streq(entry->name, child) && - (!peer || streq(peer, entry->peer_cfg->get_name(entry->peer_cfg)))) + if (!entry->external && + streq(entry->name, child) && + (!peer || streq(peer, entry->peer_cfg->get_name(entry->peer_cfg)))) + { + this->traps->remove_at(this->traps, enumerator); + found = entry; + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + + if (!found) + { + return FALSE; + } + destroy_entry(found); + return TRUE; +} + +METHOD(trap_manager_t, install_external, bool, + private_trap_manager_t *this, peer_cfg_t *peer, child_sa_t *child, + linked_list_t *local, linked_list_t *remote) +{ + entry_t *entry; + + this->lock->write_lock(this->lock); + if (this->installing == INSTALL_DISABLED) + { /* flush() has been called */ + this->lock->unlock(this->lock); + return FALSE; + } + + INIT(entry, + .name = strdup(child->get_name(child)), + .peer_cfg = peer->get_ref(peer), + .child_sa = child, + .external = TRUE, + ); + this->traps->insert_first(this->traps, entry); + this->lock->unlock(this->lock); + + if (install_trap(child, local, remote) != SUCCESS) + { + DBG1(DBG_CFG, "installing trap failed"); + this->lock->write_lock(this->lock); + this->traps->remove(this->traps, entry, NULL); + this->lock->unlock(this->lock); + destroy_entry(entry); + return FALSE; + } + return TRUE; +} + +METHOD(trap_manager_t, remove_external, bool, + private_trap_manager_t *this, child_sa_t *child) +{ + enumerator_t *enumerator; + entry_t *entry, *found = NULL; + + this->lock->write_lock(this->lock); + enumerator = this->traps->create_enumerator(this->traps); + while (enumerator->enumerate(enumerator, &entry)) + { + if (entry->external && entry->child_sa == child) { this->traps->remove_at(this->traps, enumerator); found = entry; @@ -396,8 +486,9 @@ CALLBACK(trap_filter, bool, while (orig->enumerate(orig, &entry)) { - if (!entry->child_sa) - { /* skip entries that are currently being installed */ + if (!entry->child_sa || entry->external) + { /* skip entries that are currently being installed or are managed + * externally */ continue; } if (peer_cfg) @@ -423,8 +514,7 @@ METHOD(trap_manager_t, create_enumerator, enumerator_t*, } METHOD(trap_manager_t, acquire, void, - private_trap_manager_t *this, uint32_t reqid, - traffic_selector_t *src, traffic_selector_t *dst) + private_trap_manager_t *this, uint32_t reqid, kernel_acquire_data_t *data) { enumerator_t *enumerator; entry_t *entry, *found = NULL; @@ -454,7 +544,6 @@ METHOD(trap_manager_t, acquire, void, this->lock->unlock(this->lock); return; } - reqid = found->child_sa->get_reqid(found->child_sa); wildcard = found->wildcard; this->mutex->lock(this->mutex); @@ -463,7 +552,7 @@ METHOD(trap_manager_t, acquire, void, * with the same peer */ uint8_t mask; - dst->to_subnet(dst, &host, &mask); + data->dst->to_subnet(data->dst, &host, &mask); if (this->acquires->find_first(this->acquires, acquire_by_dst, (void**)&acquire, host)) { @@ -482,7 +571,7 @@ METHOD(trap_manager_t, acquire, void, else { if (this->acquires->find_first(this->acquires, acquire_by_reqid, - (void**)&acquire, reqid)) + (void**)&acquire, reqid, data->label)) { ignore = TRUE; } @@ -490,6 +579,7 @@ METHOD(trap_manager_t, acquire, void, { INIT(acquire, .reqid = reqid, + .label = data->label ? data->label->clone(data->label) : NULL, ); this->acquires->insert_last(this->acquires, acquire); } @@ -497,7 +587,8 @@ METHOD(trap_manager_t, acquire, void, this->mutex->unlock(this->mutex); if (ignore) { - DBG1(DBG_CFG, "ignoring acquire, connection attempt pending"); + DBG1(DBG_CFG, "ignoring acquire for reqid %u, connection attempt " + "pending", reqid); this->lock->unlock(this->lock); return; } @@ -521,12 +612,12 @@ METHOD(trap_manager_t, acquire, void, ike_cfg = ike_sa->get_ike_cfg(ike_sa); port = ike_cfg->get_other_port(ike_cfg); - dst->to_subnet(dst, &host, &mask); + data->dst->to_subnet(data->dst, &host, &mask); host->set_port(host, port); ike_sa->set_other_host(ike_sa, host); port = ike_cfg->get_my_port(ike_cfg); - src->to_subnet(src, &host, &mask); + data->src->to_subnet(data->src, &host, &mask); host->set_port(host, port); ike_sa->set_my_host(ike_sa, host); @@ -542,17 +633,24 @@ METHOD(trap_manager_t, acquire, void, if (ike_sa) { + child_init_args_t args = { + .reqid = reqid, + .src = data->src, + .dst = data->dst, + .label = data->label, + }; + if (this->ignore_acquire_ts || ike_sa->get_version(ike_sa) == IKEV1) { /* in IKEv1, don't prepend the acquiring packet TS, as we only * have a single TS that we can establish in a Quick Mode. */ - src = dst = NULL; + args.src = args.dst = NULL; } this->mutex->lock(this->mutex); acquire->ike_sa = ike_sa; this->mutex->unlock(this->mutex); - if (ike_sa->initiate(ike_sa, child, reqid, src, dst) != DESTROY_ME) + if (ike_sa->initiate(ike_sa, child, &args) != DESTROY_ME) { charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); } @@ -600,6 +698,11 @@ static void complete(private_trap_manager_t *this, ike_sa_t *ike_sa, { 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); @@ -673,6 +776,8 @@ trap_manager_t *trap_manager_create(void) .public = { .install = _install, .uninstall = _uninstall, + .install_external = _install_external, + .remove_external = _remove_external, .create_enumerator = _create_enumerator, .acquire = _acquire, .flush = _flush, diff --git a/src/libcharon/sa/trap_manager.h b/src/libcharon/sa/trap_manager.h index f226798fc..60dbc2f12 100644 --- a/src/libcharon/sa/trap_manager.h +++ b/src/libcharon/sa/trap_manager.h @@ -25,6 +25,7 @@ #include #include #include +#include typedef struct trap_manager_t trap_manager_t; @@ -55,7 +56,31 @@ struct trap_manager_t { bool (*uninstall)(trap_manager_t *this, char *peer, char *child); /** - * Create an enumerator over all installed traps. + * Install and register an externally managed trap policy using the two + * lists of local and remote addresses when deriving traffic selectors. + * + * @param peer peer configuration to register + * @param child CHILD_SA to install and register + * @param local list of local addresses (virtual or physical) + * @param remote list of remote addresses (virtual or physical) + * @return TRUE if successfully installed and registered + */ + bool (*install_external)(trap_manager_t *this, peer_cfg_t *peer, + child_sa_t *child, linked_list_t *local, + linked_list_t *remote); + + /** + * Remove and uninstall a previously registered externally managed trap + * policy. + * + * @param child CHILD_SA to remove + * @return TRUE if successfully removed + */ + bool (*remove_external)(trap_manager_t *this, child_sa_t *child); + + /** + * Create an enumerator over all installed traps (does not include + * externally managed trap policies). * * @return enumerator over (peer_cfg_t, child_sa_t) */ @@ -64,12 +89,11 @@ struct trap_manager_t { /** * Acquire an SA triggered by an installed trap. * - * @param reqid reqid of the triggering CHILD_SA - * @param src source of the triggering packet - * @param dst destination of the triggering packet + * @param reqid reqid of the triggered policy + * @param data data from the acquire */ void (*acquire)(trap_manager_t *this, uint32_t reqid, - traffic_selector_t *src, traffic_selector_t *dst); + kernel_acquire_data_t *data); /** * Clear any installed trap. diff --git a/src/libcharon/tests/suites/test_child_create.c b/src/libcharon/tests/suites/test_child_create.c index 20a47f6bf..07ce07ae9 100644 --- a/src/libcharon/tests/suites/test_child_create.c +++ b/src/libcharon/tests/suites/test_child_create.c @@ -21,6 +21,47 @@ #include #include +/** + * The peers try to create a new CHILD_SA that looks exactly the same + * as the existing one, so it won't get initiated. + */ +START_TEST(test_duplicate) +{ + child_cfg_t *child_cfg; + child_cfg_create_t child = { + .mode = MODE_TUNNEL, + }; + ike_sa_t *a, *b; + + exchange_test_helper->establish_sa(exchange_test_helper, + &a, &b, NULL); + + assert_no_jobs_scheduled(); + assert_hook_not_called(child_updown); + assert_hook_not_called(message); + child_cfg = child_cfg_create("child", &child); + child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP)); + child_cfg->add_traffic_selector(child_cfg, TRUE, + traffic_selector_create_dynamic(0, 0, 65535)); + child_cfg->add_traffic_selector(child_cfg, FALSE, + traffic_selector_create_dynamic(0, 0, 65535)); + child_cfg->get_ref(child_cfg); + call_ikesa(a, initiate, child_cfg, NULL); + assert_child_sa_count(a, 1); + assert_sa_idle(a); + + call_ikesa(b, initiate, child_cfg, NULL); + assert_child_sa_count(b, 1); + assert_sa_idle(b); + assert_hook(); + assert_hook(); + assert_scheduler(); + + call_ikesa(a, destroy); + call_ikesa(b, destroy); +} +END_TEST + /** * One of the peers tries to create a new CHILD_SA while the other concurrently * started to rekey the IKE_SA. TEMPORARY_FAILURE should be returned on both @@ -31,6 +72,8 @@ START_TEST(test_collision_ike_rekey) child_cfg_t *child_cfg; child_cfg_create_t child = { .mode = MODE_TUNNEL, + /* make sure this is not a duplicate of the initial CHILD_SA */ + .mark_out = { .value = 42, .mask = 0xffffffff }, }; ike_sa_t *a, *b; @@ -44,7 +87,7 @@ START_TEST(test_collision_ike_rekey) traffic_selector_create_dynamic(0, 0, 65535)); child_cfg->add_traffic_selector(child_cfg, FALSE, traffic_selector_create_dynamic(0, 0, 65535)); - call_ikesa(a, initiate, child_cfg, 0, NULL, NULL); + call_ikesa(a, initiate, child_cfg, NULL); assert_child_sa_count(a, 1); assert_hook(); @@ -81,7 +124,7 @@ START_TEST(test_collision_ike_rekey) ck_assert(!exchange_test_helper->sender->dequeue(exchange_test_helper->sender)); assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE); assert_num_tasks(a, 1, TASK_QUEUE_QUEUED); - call_ikesa(a, initiate, NULL, 0, NULL, NULL); + call_ikesa(a, initiate, NULL, NULL); assert_num_tasks(a, 0, TASK_QUEUE_ACTIVE); assert_sa_idle(b); @@ -98,6 +141,10 @@ Suite *child_create_suite_create() s = suite_create("child create"); + tc = tcase_create("initiate duplicate"); + tcase_add_test(tc, test_duplicate); + suite_add_tcase(s, tc); + tc = tcase_create("collisions ike rekey"); tcase_add_test(tc, test_collision_ike_rekey); suite_add_tcase(s, tc); diff --git a/src/libcharon/tests/suites/test_childless.c b/src/libcharon/tests/suites/test_childless.c index 6ac02aad8..931abbb5e 100644 --- a/src/libcharon/tests/suites/test_childless.c +++ b/src/libcharon/tests/suites/test_childless.c @@ -44,7 +44,7 @@ START_TEST(test_regular) id_a = a->get_id(a); id_b = b->get_id(b); - call_ikesa(a, initiate, child_cfg, 0, NULL, NULL); + call_ikesa(a, initiate, child_cfg, NULL); /* IKE_SA_INIT --> */ id_b->set_initiator_spi(id_b, id_a->get_initiator_spi(id_a)); @@ -115,7 +115,7 @@ START_TEST(test_regular_manual) id_a = a->get_id(a); id_b = b->get_id(b); - call_ikesa(a, initiate, NULL, 0, NULL, NULL); + call_ikesa(a, initiate, NULL, NULL); /* IKE_SA_INIT --> */ id_b->set_initiator_spi(id_b, id_a->get_initiator_spi(id_a)); @@ -144,7 +144,7 @@ START_TEST(test_regular_manual) assert_sa_idle(a); assert_sa_idle(b); - call_ikesa(a, initiate, child_cfg, 0, NULL, NULL); + call_ikesa(a, initiate, child_cfg, NULL); /* CREATE_CHILD_SA { SA, Ni, KEi, TSi, TSr } --> */ assert_hook_called(child_updown); @@ -192,7 +192,7 @@ START_TEST(test_failure_init) id_a = a->get_id(a); id_b = b->get_id(b); - call_ikesa(a, initiate, child_cfg, 0, NULL, NULL); + call_ikesa(a, initiate, child_cfg, NULL); /* IKE_SA_INIT --> */ id_b->set_initiator_spi(id_b, id_a->get_initiator_spi(id_a)); @@ -233,7 +233,7 @@ START_TEST(test_failure_resp) id_a = a->get_id(a); id_b = b->get_id(b); - call_ikesa(a, initiate, child_cfg, 0, NULL, NULL); + call_ikesa(a, initiate, child_cfg, NULL); /* IKE_SA_INIT --> */ id_b->set_initiator_spi(id_b, id_a->get_initiator_spi(id_a)); diff --git a/src/libcharon/tests/suites/test_ike_mid_sync.c b/src/libcharon/tests/suites/test_ike_mid_sync.c index 3776f39e9..cfebf4b5a 100644 --- a/src/libcharon/tests/suites/test_ike_mid_sync.c +++ b/src/libcharon/tests/suites/test_ike_mid_sync.c @@ -493,7 +493,7 @@ START_TEST(test_active) charon->bus->remove_listener(charon->bus, &mid.listener); /* the active task was queued again */ - call_ikesa(a, initiate, NULL, 0, NULL, NULL); + call_ikesa(a, initiate, NULL, NULL); exchange_test_helper->process_message(exchange_test_helper, b, NULL); exchange_test_helper->process_message(exchange_test_helper, a, NULL); send_dpd(b, a); diff --git a/src/libcharon/tests/utils/exchange_test_helper.c b/src/libcharon/tests/utils/exchange_test_helper.c index be55a1387..1448504ab 100644 --- a/src/libcharon/tests/utils/exchange_test_helper.c +++ b/src/libcharon/tests/utils/exchange_test_helper.c @@ -273,7 +273,7 @@ METHOD(exchange_test_helper_t, establish_sa, void, id_i = sa_i->get_id(sa_i); id_r = sa_r->get_id(sa_r); - call_ikesa(sa_i, initiate, child_i, 0, NULL, NULL); + call_ikesa(sa_i, initiate, child_i, NULL); /* IKE_SA_INIT --> */ id_r->set_initiator_spi(id_r, id_i->get_initiator_spi(id_i)); diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk index 660382da2..c1001905c 100644 --- a/src/libstrongswan/Android.mk +++ b/src/libstrongswan/Android.mk @@ -41,7 +41,8 @@ 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/traffic_selector.c settings/settings.c settings/settings_types.c \ +selectors/sec_label.c selectors/traffic_selector.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 \ utils/lexparser.c utils/optionsfrom.c utils/capabilities.c utils/backtrace.c \ diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index a12567291..37fe4b1e5 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -39,7 +39,8 @@ 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/traffic_selector.c settings/settings.c settings/settings_types.c \ +selectors/sec_label.c selectors/traffic_selector.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 \ utils/lexparser.c utils/optionsfrom.c utils/capabilities.c utils/backtrace.c \ @@ -112,7 +113,8 @@ resolver/resolver.h resolver/resolver_response.h resolver/rr_set.h \ 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/traffic_selector.h \ +processing/scheduler.h processing/watcher.h \ +selectors/sec_label.h selectors/traffic_selector.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 \ @@ -132,7 +134,8 @@ library.lo : $(top_builddir)/config.status libstrongswan_la_LIBADD = \ $(DLLIB) $(ATOMICLIB) $(BTLIB) $(SOCKLIB) \ - $(RTLIB) $(BFDLIB) $(UNWINDLIB) + $(RTLIB) $(BFDLIB) $(UNWINDLIB) \ + $(selinux_LIBS) AM_CPPFLAGS = \ -I$(top_srcdir)/src/libstrongswan \ @@ -142,6 +145,7 @@ AM_CPPFLAGS = \ -DSTRONGSWAN_CONF=\"${strongswan_conf}\" AM_CFLAGS = \ + $(selinux_CFLAGS) \ @COVERAGE_CFLAGS@ AM_LDFLAGS = \ diff --git a/src/libstrongswan/selectors/sec_label.c b/src/libstrongswan/selectors/sec_label.c new file mode 100644 index 000000000..596e98c03 --- /dev/null +++ b/src/libstrongswan/selectors/sec_label.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2021 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#define _GNU_SOURCE +#include +#ifdef USE_SELINUX +#include +#endif + +#include "sec_label.h" + +ENUM(sec_label_mode_names, SEC_LABEL_MODE_SYSTEM, SEC_LABEL_MODE_SELINUX, + "system", + "simple", + "selinux", +); + +typedef struct private_sec_label_t private_sec_label_t; + +/** + * Private data. + */ +struct private_sec_label_t { + + /** + * Public interface + */ + sec_label_t public; + + /** + * Encoded label value + */ + chunk_t encoding; + + /** + * String representation of the label + */ + char *str; +}; + +static sec_label_t *create_sec_label(chunk_t encoding, char *str); + +METHOD(sec_label_t, get_encoding, chunk_t, + private_sec_label_t *this) +{ + return this->encoding; +} + +METHOD(sec_label_t, get_string, char*, + private_sec_label_t *this) +{ + return this->str; +} + +METHOD(sec_label_t, clone_, sec_label_t*, + private_sec_label_t *this) +{ + return create_sec_label(chunk_clone(this->encoding), strdup(this->str)); +} + +METHOD(sec_label_t, equals, bool, + private_sec_label_t *this, sec_label_t *other_pub) +{ + private_sec_label_t *other = (private_sec_label_t*)other_pub; + + if (!other_pub) + { + return FALSE; + } + return chunk_equals_const(this->encoding, other->encoding); +} + +METHOD(sec_label_t, matches, bool, + private_sec_label_t *this, sec_label_t *other_pub) +{ + if (!other_pub) + { + return FALSE; + } +#ifdef USE_SELINUX + if (is_selinux_enabled()) + { /* if disabled, the following matches anything against anything */ + private_sec_label_t *other = (private_sec_label_t*)other_pub; + return selinux_check_access(other->str, this->str, "association", + "polmatch", NULL) == 0; + } +#endif + return equals(this, other_pub); +} + +METHOD(sec_label_t, hash, u_int, + private_sec_label_t *this, u_int inc) +{ + return chunk_hash_inc(this->encoding, inc); +} + +METHOD(sec_label_t, destroy, void, + private_sec_label_t *this) +{ + chunk_free(&this->encoding); + free(this->str); + free(this); +} + +/** + * Internal constructor, data is adopted + */ +static sec_label_t *create_sec_label(chunk_t encoding, char *str) +{ + private_sec_label_t *this; + + INIT(this, + .public = { + .get_encoding = _get_encoding, + .get_string = _get_string, + .clone = _clone_, + .matches = _matches, + .equals = _equals, + .hash = _hash, + .destroy = _destroy, + }, + .encoding = encoding, + .str = str, + ); + return &this->public; +} + +/* + * Described in header + */ +sec_label_t *sec_label_from_encoding(const chunk_t value) +{ + chunk_t cloned, sanitized = chunk_empty; + char *str; + + if (!value.len || (value.len == 1 && !value.ptr[0])) + { + DBG1(DBG_LIB, "invalid empty security label"); + return NULL; + } + else if (value.ptr[value.len-1]) + { + DBG1(DBG_LIB, "adding null-terminator to security label"); + cloned = chunk_cat("cc", value, chunk_from_chars(0x00)); + } + else + { + cloned = chunk_clone(value); + } + + /* create a sanitized version while ignoring the null-terminator */ + if (!chunk_printable(chunk_create(cloned.ptr, cloned.len-1), &sanitized, '?')) + { +#ifdef USE_SELINUX + /* don't accept labels with non-printable characters if we use SELinux */ + DBG1(DBG_LIB, "invalid security label with non-printable characters %B", + &value); + chunk_free(&sanitized); + chunk_free(&cloned); + return NULL; +#endif + } + if (asprintf(&str, "%.*s", (int)sanitized.len, sanitized.ptr) <= 0) + { + chunk_free(&sanitized); + chunk_free(&cloned); + return NULL; + } + chunk_free(&sanitized); + + return create_sec_label(cloned, str); +} + +/* + * Described in header + */ +sec_label_t *sec_label_from_string(const char *value) +{ + if (!value) + { + return NULL; + } + return sec_label_from_encoding(chunk_create((char*)value, strlen(value)+1)); +} + +/* + * Described in header + */ +bool sec_label_mode_from_string(const char *value, sec_label_mode_t *mode) +{ + sec_label_mode_t def = sec_label_mode_default(); + + return enum_from_name(sec_label_mode_names, value, mode) && + (def == SEC_LABEL_MODE_SELINUX || *mode != SEC_LABEL_MODE_SELINUX); +} + +/* + * Described in header + */ +sec_label_mode_t sec_label_mode_default() +{ +#ifdef USE_SELINUX + if (is_selinux_enabled()) + { + return SEC_LABEL_MODE_SELINUX; + } +#endif + return SEC_LABEL_MODE_SIMPLE; +} diff --git a/src/libstrongswan/selectors/sec_label.h b/src/libstrongswan/selectors/sec_label.h new file mode 100644 index 000000000..0392db77f --- /dev/null +++ b/src/libstrongswan/selectors/sec_label.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2021 Tobias Brunner, codelabs GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @defgroup sec_label sec_label + * @{ @ingroup ipsec + */ + +#ifndef SEC_LABEL_H_ +#define SEC_LABEL_H_ + +typedef enum sec_label_mode_t sec_label_mode_t; +typedef struct sec_label_t sec_label_t; + +#include + +/** + * Mode in which security labels are used. + */ +enum sec_label_mode_t { + + /** + * System default. Simple mode if SELinux is not supported or disabled + * on the system. + */ + SEC_LABEL_MODE_SYSTEM, + + /** + * Simple mode that does establish regular CHILD_SAs, matches labels exactly + * and does not install them in the kernel. + */ + SEC_LABEL_MODE_SIMPLE, + + /** + * SELinux mode where configured labels are installed on (trap) policies, + * labels from acquires/peer on SAs, child-less IKE_SAs are initiated + * if there is no acquire, labels are also matched via polmatch. + */ + SEC_LABEL_MODE_SELINUX, +}; + +/** + * Names for security label modes. + */ +extern enum_name_t *sec_label_mode_names; + +/** + * Representation of a security label used on policies/SAs. + * + * For example, with SELinux this could be a value like + * system_u:object_r:ipsec_spd_t:s0. + */ +struct sec_label_t { + + /** + * Return a binary encoding of the security label as used for IKE. + * + * @return binary encoding (internal data) + */ + chunk_t (*get_encoding)(sec_label_t *this); + + /** + * Return a string representation of this security label. + * + * @return string representation (internal data) + */ + char *(*get_string)(sec_label_t *this); + + /** + * Clone this security label. + * + * @return clone of it + */ + sec_label_t *(*clone)(sec_label_t *this); + + /** + * Match two security labels. + * + * For SELinux this checks if this security label permits other in terms + * of association { polmatch }. + * + * @param other security label to match against this + * @return TRUE if matching, FALSE otherwise + */ + bool (*matches)(sec_label_t *this, sec_label_t *other); + + /** + * Compare two security labels for equality. + * + * @param other security label to compare with this + * @return TRUE if equal, FALSE otherwise + */ + bool (*equals)(sec_label_t *this, sec_label_t *other); + + /** + * Create a hash value for the security label. + * + * @param inc optional value for incremental hashing + * @return calculated hash value for the security label + */ + u_int (*hash)(sec_label_t *this, u_int inc); + + /** + * Destroys the object. + */ + void (*destroy)(sec_label_t *this); +}; + +/** + * Try to parse a sec_label_t from the given binary encoding. + * + * @param value encoding to parse + * @return security label instance, NULL if invalid + */ +sec_label_t *sec_label_from_encoding(const chunk_t value); + +/** + * Try to parse a sec_label_t from the given string. + * + * @param value string to parse + * @return security label instance, NULL if invalid + */ +sec_label_t *sec_label_from_string(const char *value); + +/** + * Compare two security labels for equality, accept if both are NULL. + * + * @param a first label + * @param b second label + * @return TRUE if labels are equal or both NULL + */ +static inline bool sec_labels_equal(sec_label_t *a, sec_label_t *b) +{ + return (!a && !b) || (a && a->equals(a, b)); +} + +/** + * Try to parse a security label mode from the given string. + * + * @param value string to parse + * @param mode parsed mode + * @return TRUE if mode is valid (and usable on system) + */ +bool sec_label_mode_from_string(const char *value, sec_label_mode_t *mode); + +/** + * Get the system default security label mode. + * + * @return default mode + */ +sec_label_mode_t sec_label_mode_default(); + +#endif /** SEC_LABEL_H_ @}*/ diff --git a/src/libstrongswan/selectors/traffic_selector.c b/src/libstrongswan/selectors/traffic_selector.c index 2735a5cc1..2e69e5ad8 100644 --- a/src/libstrongswan/selectors/traffic_selector.c +++ b/src/libstrongswan/selectors/traffic_selector.c @@ -31,10 +31,12 @@ #define NON_SUBNET_ADDRESS_RANGE 255 -ENUM(ts_type_name, TS_IPV4_ADDR_RANGE, TS_IPV6_ADDR_RANGE, +ENUM_BEGIN(ts_type_name, TS_IPV4_ADDR_RANGE, TS_IPV6_ADDR_RANGE, "TS_IPV4_ADDR_RANGE", - "TS_IPV6_ADDR_RANGE", -); + "TS_IPV6_ADDR_RANGE"); +ENUM_NEXT(ts_type_name, TS_SECLABEL, TS_SECLABEL, TS_IPV6_ADDR_RANGE, + "TS_SECLABEL"); +ENUM_END(ts_type_name, TS_SECLABEL); typedef struct private_traffic_selector_t private_traffic_selector_t; diff --git a/src/libstrongswan/selectors/traffic_selector.h b/src/libstrongswan/selectors/traffic_selector.h index 03f7a6d8c..a13aca106 100644 --- a/src/libstrongswan/selectors/traffic_selector.h +++ b/src/libstrongswan/selectors/traffic_selector.h @@ -50,7 +50,12 @@ enum ts_type_t { * (inclusive). All addresses falling between the two specified * addresses are considered to be within the list. */ - TS_IPV6_ADDR_RANGE = 8 + TS_IPV6_ADDR_RANGE = 8, + + /** + * A security label. + */ + TS_SECLABEL = 10, }; /** diff --git a/src/libstrongswan/tests/suites/test_enum.c b/src/libstrongswan/tests/suites/test_enum.c index 06b2c3ff3..83ff14be8 100644 --- a/src/libstrongswan/tests/suites/test_enum.c +++ b/src/libstrongswan/tests/suites/test_enum.c @@ -20,13 +20,13 @@ /******************************************************************************* * continuous enum */ -enum { +enum test_enum_cont { CONT1, CONT2, CONT3, CONT4, CONT5, -} test_enum_cont; +}; ENUM_BEGIN(test_enum_cont_names, CONT1, CONT5, "CONT1", "CONT2", "CONT3", "CONT4", "CONT5"); @@ -35,13 +35,13 @@ ENUM_END(test_enum_cont_names, CONT5); /******************************************************************************* * split enum */ -enum { +enum test_enum_split { SPLIT1 = 1, SPLIT2, SPLIT3 = 5, SPLIT4, SPLIT5 = 255, -} test_enum_split; +}; ENUM_BEGIN(test_enum_split_names, SPLIT1, SPLIT2, "SPLIT1", "SPLIT2"); @@ -54,7 +54,7 @@ ENUM_END(test_enum_split_names, SPLIT5); /******************************************************************************* * enum flags */ -enum { +enum test_enum_flags { FLAG1 = (1 << 0), FLAG2 = (1 << 1), FLAG3 = (1 << 2), @@ -67,18 +67,18 @@ enum { FLAG10 = (1 << 9), FLAG11 = (1 << 10), FLAG12 = (1 << 11), -} test_enum_flags; +}; ENUM_FLAGS(test_enum_flags_names, FLAG1, FLAG5, - "FLAG1", "FLAG2", "FLAG3", "FLAG4", "FLAG5"); + "(unset)", "FLAG1", "FLAG2", "FLAG3", "FLAG4", "FLAG5"); ENUM_FLAGS(test_enum_flags_incomplete_names, FLAG3, FLAG4, - "FLAG3", "FLAG4"); + "(unset)", "FLAG3", "FLAG4"); ENUM_FLAGS(test_enum_flags_null_names, FLAG1, FLAG4, - "FLAG1", NULL, "FLAG3", NULL); + "(unset)", "FLAG1", NULL, "FLAG3", NULL); -ENUM_FLAGS(test_enum_flags_overflow_names, FLAG1, FLAG12, +ENUM_FLAGS(test_enum_flags_overflow_names, FLAG1, FLAG12, "(unset)", "OVERFLOWFLAGLONGNAME1", "OVERFLOWFLAGLONGNAME2", "OVERFLOWFLAGLONGNAME3", "OVERFLOWFLAGLONGNAME4", "OVERFLOWFLAGLONGNAME5", "OVERFLOWFLAGLONGNAME6", "OVERFLOWFLAGLONGNAME7", "OVERFLOWFLAGLONGNAME8", "OVERFLOWFLAGLONGNAME9", @@ -263,6 +263,30 @@ static struct { }, enum_flags_to_string_tests[] = { {-1, NULL}, {6435, NULL}, +}, enum_flags_from_string_tests[] = { + {0, NULL}, + {0, ""}, + {0, "(unset)"}, + {FLAG1, "FLAG1"}, + {FLAG2, "flag2"}, + {FLAG3, "fLaG3"}, + {FLAG4, "FLAG4"}, + {FLAG5, "FLAG5"}, + {FLAG1 | FLAG3, "FLAG1 | FLAG3"}, + {FLAG1 | FLAG3, "flag3|flag1"}, + {FLAG1 | FLAG3, "flag1|flag3 | (unset)"}, + {FLAG1 | FLAG2 | FLAG3 | FLAG4 | FLAG5, "flag1|flag2|flag3|flag4|flag5"}, + {FLAG1 | FLAG2 | FLAG3 | FLAG4 | FLAG5, "flag3|flag4|flag5|flag2|flag1"}, + {FLAG5, "(unset)|flag5"}, + {FLAG1, "FLAG1 | flag1 | flAg1"}, + {-1, "FLAG6"}, + {-1, "flag1 | asdf"}, +}, enum_flags_from_string_noflagenum_tests[] = { + {0, NULL}, + {0, ""}, + {CONT2, "CONT2"}, + {CONT5, "CONT5"}, + {-1, "asdf"}, }; START_TEST(test_enum_printf_hook_cont) @@ -371,6 +395,38 @@ START_TEST(test_enum_flags_to_string_noflagenum) } END_TEST +START_TEST(test_enum_flags_from_string) +{ + enum test_enum_flags val; + + if (enum_flags_from_string(test_enum_flags_names, + enum_flags_from_string_tests[_i].str, &val)) + { + ck_assert_int_eq(enum_flags_from_string_tests[_i].val, val); + } + else + { + ck_assert_int_eq(enum_flags_from_string_tests[_i].val, -1); + } +} +END_TEST + +START_TEST(test_enum_flags_from_string_noflagenum) +{ + enum test_enum_cont val; + + if (enum_flags_from_string(test_enum_cont_names, + enum_flags_from_string_noflagenum_tests[_i].str, &val)) + { + ck_assert_int_eq(enum_flags_from_string_noflagenum_tests[_i].val, val); + } + else + { + ck_assert_int_eq(enum_flags_from_string_noflagenum_tests[_i].val, -1); + } +} +END_TEST + START_TEST(test_enum_printf_hook_width) { char buf[128]; @@ -406,6 +462,11 @@ Suite *enum_suite_create() tcase_add_loop_test(tc, test_enum_flags_to_string_noflagenum, 0, countof(printf_tests_flags_noflagenum)); suite_add_tcase(s, tc); + tc = tcase_create("enum_flags_from_string"); + tcase_add_loop_test(tc, test_enum_flags_from_string, 0, countof(enum_flags_from_string_tests)); + tcase_add_loop_test(tc, test_enum_flags_from_string_noflagenum, 0, countof(enum_flags_from_string_noflagenum_tests)); + suite_add_tcase(s, tc); + tc = tcase_create("enum_printf_hook"); tcase_add_loop_test(tc, test_enum_printf_hook_cont, 0, countof(printf_tests_cont)); tcase_add_loop_test(tc, test_enum_printf_hook_split, 0, countof(printf_tests_split)); diff --git a/src/libstrongswan/utils/enum.c b/src/libstrongswan/utils/enum.c index 25182f93d..79da450f0 100644 --- a/src/libstrongswan/utils/enum.c +++ b/src/libstrongswan/utils/enum.c @@ -17,6 +17,7 @@ #include #include +#include #include #include "enum.h" @@ -66,16 +67,17 @@ bool enum_from_name_as_int(enum_name_t *e, const char *name, int *val) /** * Get the position of a flag name using offset calculation */ -static int find_flag_pos(u_int val, u_int first) +static int find_flag_pos(u_int first, u_int val) { int offset = 0; - while (val != 0x01) + while (first != 0x01) { - val = val >> 1; + first = first >> 1; offset++; } - return first - offset; + /* skip the first name as that's used if no flag is set */ + return 1 + val - offset; } /** @@ -95,7 +97,7 @@ char *enum_flags_to_string(enum_name_t *e, u_int val, char *buf, size_t len) return buf; } - if (snprintf(buf, len, "(unset)") >= len) + if (snprintf(buf, len, e->names[0]) >= len) { return NULL; } @@ -134,6 +136,54 @@ char *enum_flags_to_string(enum_name_t *e, u_int val, char *buf, size_t len) return buf; } +/* + * Described in header + */ +bool enum_flags_from_string_as_int(enum_name_t *e, const char *str, u_int *val) +{ + enumerator_t *enumerator; + char *name; + + *val = 0; + + if (!str || !*str) + { + return TRUE; + } + else if (e->next != ENUM_FLAG_MAGIC) + { + return enum_from_name_as_int(e, str, val); + } + + enumerator = enumerator_create_token(str, "|", " "); + while (enumerator->enumerate(enumerator, &name)) + { + u_int flag, i; + bool found = FALSE; + + if (strcaseeq(name, e->names[0])) + { /* accept name used if no flags are set */ + continue; + } + for (i = 1, flag = e->first; flag <= e->last; i++, flag <<= 1) + { + if (e->names[i] && strcaseeq(name, e->names[i])) + { + *val |= flag; + found = TRUE; + break; + } + } + if (!found) + { + enumerator->destroy(enumerator); + return FALSE; + } + } + enumerator->destroy(enumerator); + return TRUE; +} + /** * See header. */ diff --git a/src/libstrongswan/utils/enum.h b/src/libstrongswan/utils/enum.h index 72dea495a..d6f909e15 100644 --- a/src/libstrongswan/utils/enum.h +++ b/src/libstrongswan/utils/enum.h @@ -130,13 +130,14 @@ struct enum_name_t { * @param name name of the enum_name list * @param first enum value of the first enum string * @param last enum value of the last enum string + * @param unset name used if no flags are set * @param ... a list of strings */ -#define ENUM_FLAGS(name, first, last, ...) \ +#define ENUM_FLAGS(name, first, last, unset, ...) \ static enum_name_t name##last = {first, last + \ BUILD_ASSERT((__builtin_ffs(last)-__builtin_ffs(first)+1) == \ countof(((char*[]){__VA_ARGS__}))), \ - ENUM_FLAG_MAGIC, { __VA_ARGS__ }}; ENUM_END(name, last) + ENUM_FLAG_MAGIC, { unset, __VA_ARGS__ }}; ENUM_END(name, last) /** * Convert a enum value to its string representation. @@ -157,7 +158,7 @@ char *enum_to_name(enum_name_t *e, int val); */ #define enum_from_name(e, name, valp) ({ \ int _val; \ - int _found = enum_from_name_as_int(e, name, &_val); \ + bool _found = enum_from_name_as_int(e, name, &_val); \ if (_found) \ { \ *(valp) = _val; \ @@ -167,13 +168,13 @@ char *enum_to_name(enum_name_t *e, int val); /** * Convert a enum string back to its enum value, integer pointer variant. * - * This variant takes integer pointer only, use enum_from_name() to pass + * This variant takes an integer pointer, use enum_from_name() to pass * enum type pointers for the result. * * @param e enum names for this enum value * @param name name to get enum value for * @param val integer pointer receiving value - * @return TRUE if enum name found, FALSE otherwise + * @return TRUE if all names found, FALSE otherwise */ bool enum_from_name_as_int(enum_name_t *e, const char *name, int *val); @@ -188,6 +189,36 @@ bool enum_from_name_as_int(enum_name_t *e, const char *name, int *val); */ char *enum_flags_to_string(enum_name_t *e, u_int val, char *buf, size_t len); +/** + * Convert a string of flags separated by | to their combined value + * + * @param e enum names for this enum value + * @param str string to get enum value for + * @param valp variable sized pointer receiving value + * @return TRUE if all names found, FALSE otherwise + */ +#define enum_flags_from_string(e, str, valp) ({ \ + u_int _val; \ + bool _found = enum_flags_from_string_as_int(e, str, &_val); \ + if (_found) \ + { \ + *(valp) = _val; \ + } \ + _found; }) + +/** + * Convert a string of flags separated by | to their combined value. + * + * This variant takes an unsigned integer pointer, use enum_flags_from_names() + * to pass enum type pointers for the result. + * + * @param e enum names for this enum value + * @param str string to get enum value for + * @param val integer pointer receiving value + * @return TRUE if enum name found, FALSE otherwise + */ +bool enum_flags_from_string_as_int(enum_name_t *e, const char *str, u_int *val); + /** * printf hook function for enum_names_t. * diff --git a/src/libstrongswan/utils/leak_detective.c b/src/libstrongswan/utils/leak_detective.c index bbf7078dd..96d55f5cc 100644 --- a/src/libstrongswan/utils/leak_detective.c +++ b/src/libstrongswan/utils/leak_detective.c @@ -539,6 +539,7 @@ static char *whitelist[] = { "initgroups", "tzset", "_IO_file_doallocate", + "selinux_check_access", /* ignore dlopen, as we do not dlclose to get proper leak reports */ "dlopen", "dlerror", diff --git a/src/swanctl/commands/list_conns.c b/src/swanctl/commands/list_conns.c index f00fb54fd..e7a8db0ce 100644 --- a/src/swanctl/commands/list_conns.c +++ b/src/swanctl/commands/list_conns.c @@ -20,6 +20,7 @@ #include #include +#include "swanctl.h" #include "command.h" #include @@ -135,6 +136,7 @@ CALLBACK(children_sn, int, } printf("\n"); + print_label(" label: ", child->get(child, "label")); printf(" local: %s\n", child->get(child, "local-ts")); printf(" remote: %s\n", child->get(child, "remote-ts")); diff --git a/src/swanctl/commands/list_pols.c b/src/swanctl/commands/list_pols.c index f2ae22172..b238b224b 100644 --- a/src/swanctl/commands/list_pols.c +++ b/src/swanctl/commands/list_pols.c @@ -18,6 +18,7 @@ #include #include "command.h" +#include "swanctl.h" #include @@ -85,6 +86,7 @@ CALLBACK(policies, int, ret = vici_parse_cb(res, NULL, policy_values, policy_list, pol); printf("%s, %s\n", name, 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/src/swanctl/commands/list_sas.c b/src/swanctl/commands/list_sas.c index ca2436dd2..0a7005ce2 100644 --- a/src/swanctl/commands/list_sas.c +++ b/src/swanctl/commands/list_sas.c @@ -43,6 +43,7 @@ #include #include "command.h" +#include "swanctl.h" #include @@ -81,7 +82,6 @@ CALLBACK(sa_values, int, return 0; } - CALLBACK(sa_list, int, hashtable_t *sa, vici_res_t *res, char *name, void *value, int len) { @@ -227,6 +227,7 @@ CALLBACK(child_sas, int, } printf("\n"); + print_label(" label ", child->get(child, "label")); printf(" local %s\n", child->get(child, "local-ts")); printf(" remote %s\n", child->get(child, "remote-ts")); } @@ -372,8 +373,8 @@ static int list_sas(vici_conn_t *conn) vici_res_t *res; bool noblock = FALSE; command_format_options_t format = COMMAND_FORMAT_NONE; - char *arg, *ike = NULL; - int ike_id = 0, ret; + char *arg, *ike = NULL, *child = NULL; + int ike_id = 0, child_id = 0, ret; while (TRUE) { @@ -387,6 +388,12 @@ static int list_sas(vici_conn_t *conn) case 'I': ike_id = atoi(arg); continue; + case 'c': + child = arg; + continue; + case 'C': + child_id = atoi(arg); + continue; case 'n': noblock = TRUE; continue; @@ -418,6 +425,14 @@ static int list_sas(vici_conn_t *conn) { vici_add_key_valuef(req, "ike-id", "%d", ike_id); } + if (child) + { + vici_add_key_valuef(req, "child", "%s", child); + } + if (child_id) + { + vici_add_key_valuef(req, "child-id", "%d", child_id); + } if (noblock) { vici_add_key_valuef(req, "noblock", "yes"); @@ -489,11 +504,14 @@ static void __attribute__ ((constructor))reg() { command_register((command_t) { list_sas, 'l', "list-sas", "list currently active IKE_SAs", - {"[--raw|--pretty]"}, + {"[--ike |--ike-id ] [--child |--child-id ]", + "[--raw|--pretty]"}, { {"help", 'h', 0, "show usage information"}, {"ike", 'i', 1, "filter IKE_SAs by name"}, {"ike-id", 'I', 1, "filter IKE_SAs by unique identifier"}, + {"child", 'c', 1, "filter CHILD_SAs by name"}, + {"child-id", 'C', 1, "filter CHILD_SAs by unique identifier"}, {"noblock", 'n', 0, "don't wait for IKE_SAs in use"}, {"raw", 'r', 0, "dump raw response message"}, {"pretty", 'P', 0, "dump raw response message in pretty print"}, diff --git a/src/swanctl/commands/terminate.c b/src/swanctl/commands/terminate.c index 2309843b2..21ec1c8fe 100644 --- a/src/swanctl/commands/terminate.c +++ b/src/swanctl/commands/terminate.c @@ -156,7 +156,7 @@ static void __attribute__ ((constructor))reg() {"help", 'h', 0, "show usage information"}, {"child", 'c', 1, "terminate by CHILD_SA name"}, {"ike", 'i', 1, "terminate by IKE_SA name"}, - {"child-id", 'C', 1, "terminate by CHILD_SA reqid"}, + {"child-id", 'C', 1, "terminate by CHILD_SA unique identifier"}, {"ike-id", 'I', 1, "terminate by IKE_SA unique identifier"}, {"force", 'f', 0, "terminate IKE_SA without waiting, unless timeout is set"}, {"timeout", 't', 1, "timeout in seconds before detaching"}, diff --git a/src/swanctl/swanctl.c b/src/swanctl/swanctl.c index cfc82f9d7..dc27646bd 100644 --- a/src/swanctl/swanctl.c +++ b/src/swanctl/swanctl.c @@ -22,6 +22,7 @@ #include #include +#include /* * Described in header @@ -59,6 +60,27 @@ settings_t *load_swanctl_conf(char *file) return cfg; } +/* + * Described in header + */ +void print_label(const char *prefix, const char *value) +{ + sec_label_t *label; + chunk_t encoding; + + if (value) + { + encoding = chunk_from_hex(chunk_from_str((char*)value), NULL); + label = sec_label_from_encoding(encoding); + if (label) + { + printf("%s%s\n", prefix, label->get_string(label)); + label->destroy(label); + } + chunk_free(&encoding); + } +} + /** * Cleanup library atexit() */ diff --git a/src/swanctl/swanctl.h b/src/swanctl/swanctl.h index 70f70d179..c7f7a6c43 100644 --- a/src/swanctl/swanctl.h +++ b/src/swanctl/swanctl.h @@ -111,4 +111,13 @@ extern char *swanctl_dir; */ settings_t *load_swanctl_conf(char *file); +/** + * Print the given security label (hex-encoded) on a separate line with the + * given prefix. + * + * @param prefix prefix to print before the string-version of the label + * @param value hex-encoded security label + */ +void print_label(const char *prefix, const char *value); + #endif /** SWANCTL_H_ @}*/ diff --git a/src/swanctl/swanctl.opt b/src/swanctl/swanctl.opt index 0ae9d4579..553831eca 100644 --- a/src/swanctl/swanctl.opt +++ b/src/swanctl/swanctl.opt @@ -885,7 +885,8 @@ connections..children..dpd_action = clear Action to perform for this CHILD_SA on DPD timeout. The default _clear_ closes the CHILD_SA and does not take further action. _trap_ installs a trap policy, which will catch matching traffic and tries to re-negotiate - the tunnel on-demand. _restart_ immediately tries to re-negotiate the + the tunnel on-demand (note that this is redundant if **start_action** + includes _trap_). _restart_ immediately tries to re-negotiate the CHILD_SA under a fresh IKE_SA. connections..children..ipcomp = no @@ -1010,6 +1011,34 @@ connections..children..if_id_out = 0 The daemon will not install routes for CHILD_SAs that have this option set. +connections..children..label + Optional security label (e.g. SELinux context), IKEv2 only. Refer to + **label_mode** for details on how labels are processed. + +connections..children..label_mode = system + Security label mode (_system_, _simple_ or _selinux_), IKEv2 only. + + Defines the mode in which the configured security label is used. The default + value of _system_ selects _selinux_ if strongSwan was built with SELinux + support and SELinux is enabled by the kernel, otherwise, _simple_ will be + selected. + + If set to _simple_, the label will be used as is as an additional + identifier/selector on the IKEv2 level when negotiating CHILD_SAs and + selecting configs, labels are not installed in the kernel and received + labels have to match exactly. + + If set to _selinux_, which is only allowed if SELinux is usable on the + system, the configured label is expected to be a generic context (e.g. + _system_u:object_r:ipsec_spd_t:s0_) for which flows, whose context match it + via association:polmatch, will trigger an acquire if no SA exists yet for + the flow's specific context. The configured label is installed on (trap) + policies, so this should generally be combined with _trap_ in + **start_action**. However, if the connection is initiated directly, + without acquire, a childless IKE_SA is established and appropriate trap + policies are installed on both ends. Labels received from peers are accepted + if they match the configured label via association:polmatch. + connections..children..tfc_padding = 0 Traffic Flow Confidentiality padding. @@ -1075,19 +1104,22 @@ connections..children..start_action = none The value _trap_ installs a trap policy, which triggers the tunnel as soon as matching traffic has been detected. The value _start_ initiates - the connection actively. + the connection actively. These two modes can be combined with _trap|start_, + to immediately initiate a connection for which trap policies have been + installed. When unloading or replacing a CHILD_SA configuration having a **start_action** different from _none_, the inverse action is performed. Configurations with _start_ get closed, while such with _trap_ get - uninstalled. + uninstalled (both happens for connections with _trap|start_). connections..children..close_action = none Action to perform after a CHILD_SA gets closed (_none_, _trap_, _start_). Action to perform after a CHILD_SA gets closed by the peer. The default of _none_ does not take any action, _trap_ installs a trap policy for the - CHILD_SA. _start_ tries to re-create the CHILD_SA. + CHILD_SA (note that this is redundant if **start_action** includes _trap_). + _start_ tries to immediately re-create the CHILD_SA. **close_action** does not provide any guarantee that the CHILD_SA is kept alive. It acts on explicit close messages only, but not on negotiation diff --git a/testing/do-tests b/testing/do-tests index ace12b86b..1e6a83a37 100755 --- a/testing/do-tests +++ b/testing/do-tests @@ -406,12 +406,16 @@ do echo -e "\nPRE-TEST\n" >> $CONSOLE_LOG 2>&1 eval `awk -F "::" '{ - if ($1 !~ /^#.*/ && $2 != "") - { - printf("echo \"$(print_time)%s# %s\"; ", $1, $2) - printf("ssh \044SSHCONF root@\044ipv4_%s \"%s\"; ", $1, $2) - printf("echo;\n") - } + if ($0 ~ /^#.*/) + { + printf("echo \"%s\"; ", $0); + } + else if ($2 != "") + { + printf("echo \"$(print_time)%s# %s\"; ", $1, $2) + printf("ssh \044SSHCONF root@\044ipv4_%s \"%s\"; ", $1, $2) + printf("echo;\n") + } }' $TESTDIR/pretest.dat` >> $CONSOLE_LOG 2>&1 @@ -443,7 +447,12 @@ do command=$2 pattern=$3 hit=$4 - if (host ~ /^#.*/ || command == "") + if ($0 ~ /^#.*/) + { + printf("echo \"%s\"; ", $0); + next + } + else if (command == "") { next } @@ -766,12 +775,16 @@ do echo -e "\nPOST-TEST\n" >> $CONSOLE_LOG 2>&1 eval `awk -F "::" '{ - if ($1 !~ /^#.*/ && $2 != "") - { - printf("echo \"$(print_time)%s# %s\"; ", $1, $2) - printf("ssh \044SSHCONF root@\044ipv4_%s \"%s\"; ", $1, $2) - printf("echo;\n") - } + if ($0 ~ /^#.*/) + { + printf("echo \"%s\"; ", $0); + } + else if ($2 != "") + { + printf("echo \"$(print_time)%s# %s\"; ", $1, $2) + printf("ssh \044SSHCONF root@\044ipv4_%s \"%s\"; ", $1, $2) + printf("echo;\n") + } }' $TESTDIR/posttest.dat` >> $CONSOLE_LOG 2>&1 ########################################################################## diff --git a/testing/tests/ikev2/net2net-dscp/description.txt b/testing/tests/ikev2/net2net-dscp/description.txt new file mode 100644 index 000000000..0108bcb14 --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/description.txt @@ -0,0 +1,11 @@ +In order to support Differentiated Services (DiffServ), two parallel IPsec +connections between the subnets behind the gateways moon and sun are +set up. Using XFRM marks one IPsec SA is designated for Best Effort (BE) +traffic and the second SA for Expedited Forwarding (EF) traffic. To guarantee +that the CHILD_SA with the correct mark is selected on the responder side, labels +are used and negotiated via IKEv2. The authentication is based on X.509 certificates. +

+Upon the successful establishment of the IPsec tunnel, the updown script automatically +inserts iptables-based firewall rules that let pass the tunneled traffic +In order to test both tunnel and firewall, client alice behind gateway moon +pings client bob located behind gateway sun. diff --git a/testing/tests/ikev2/net2net-dscp/evaltest.dat b/testing/tests/ikev2/net2net-dscp/evaltest.dat new file mode 100644 index 000000000..313891ef9 --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/evaltest.dat @@ -0,0 +1,10 @@ +moon::swanctl --list-sas --raw 2> /dev/null::dscp.*version=2 state=ESTABLISHED local-host=PH_IP_MOON local-port=500 local-id=moon.strongswan.org remote-host=PH_IP_SUN remote-port=500 remote-id=sun.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-be.*state=INSTALLED mode=TUNNEL label=647363702d626500.*ESP.*mark-in=0000000a mark-out=0000000a encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.1.0.0/16] remote-ts=\[10.2.0.0/16]::YES +moon::swanctl --list-sas --raw 2> /dev/null::dscp.*version=2 state=ESTABLISHED local-host=PH_IP_MOON local-port=500 local-id=moon.strongswan.org remote-host=PH_IP_SUN remote-port=500 remote-id=sun.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-ef.*state=INSTALLED mode=TUNNEL label=647363702d656600.*ESP.*mark-in=00000014 mark-out=00000014 encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.1.0.0/16] remote-ts=\[10.2.0.0/16]::YES +sun:: swanctl --list-sas --raw 2> /dev/null::dscp.*version=2 state=ESTABLISHED local-host=PH_IP_SUN local-port=500 local-id=sun.strongswan.org remote-host=PH_IP_MOON remote-port=500 remote-id=moon.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-be.*state=INSTALLED mode=TUNNEL label=647363702d626500.*ESP.*mark-in=0000000a mark-out=0000000a encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.2.0.0/16] remote-ts=\[10.1.0.0/16]::YES +sun:: swanctl --list-sas --raw 2> /dev/null::dscp.*version=2 state=ESTABLISHED local-host=PH_IP_SUN local-port=500 local-id=sun.strongswan.org remote-host=PH_IP_MOON remote-port=500 remote-id=moon.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-ef.*state=INSTALLED mode=TUNNEL label=647363702d656600.*ESP.*mark-in=00000014 mark-out=00000014 encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.2.0.0/16] remote-ts=\[10.1.0.0/16]::YES +alice::ping -c 1 PH_IP_BOB::64 bytes from PH_IP_BOB: icmp_.eq=1::YES +venus::ping -c 1 PH_IP_BOB::64 bytes from PH_IP_BOB: icmp_.eq=1::YES +moon::swanctl --list-sas --child net-be --raw 2> /dev/null::child-sas.*net-be.*packets-in=1.*packets-out=1::YES +moon::swanctl --list-sas --child net-ef --raw 2> /dev/null::child-sas.*net-ef.*packets-in=1.*packets-out=1::YES +sun::tcpdump::IP moon.strongswan.org > sun.strongswan.org: ESP::YES +sun::tcpdump::IP sun.strongswan.org > moon.strongswan.org: ESP::YES diff --git a/testing/tests/ikev2/net2net-dscp/hosts/moon/etc/strongswan.conf b/testing/tests/ikev2/net2net-dscp/hosts/moon/etc/strongswan.conf new file mode 100644 index 000000000..4fe92b6ec --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/hosts/moon/etc/strongswan.conf @@ -0,0 +1,14 @@ +# /etc/strongswan.conf - strongSwan configuration file + +swanctl { + load = pem pkcs1 x509 revocation constraints pubkey openssl random +} + +charon-systemd { + load = random nonce aes sha1 sha2 hmac pem pkcs1 x509 revocation curve25519 gmp curl kernel-netlink socket-default updown vici + syslog { + daemon { + knl = 2 + } + } +} diff --git a/testing/tests/ikev2/net2net-dscp/hosts/moon/etc/swanctl/swanctl.conf b/testing/tests/ikev2/net2net-dscp/hosts/moon/etc/swanctl/swanctl.conf new file mode 100755 index 000000000..d6c2b3003 --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/hosts/moon/etc/swanctl/swanctl.conf @@ -0,0 +1,38 @@ +connections { + + dscp { + local_addrs = PH_IP_MOON + remote_addrs = PH_IP_SUN + + local { + auth = pubkey + certs = moonCert.pem + id = moon.strongswan.org + } + remote { + auth = pubkey + id = sun.strongswan.org + } + children { + net-be { + local_ts = 10.1.0.0/16 + remote_ts = 10.2.0.0/16 + + mark_in = 10 + mark_out = 10 + label = dscp-be + + updown = /usr/local/libexec/ipsec/_updown iptables + esp_proposals = aes128gcm128-x25519 + } + net-ef : connections.dscp.children.net-be { + mark_in = 20 + mark_out = 20 + label = dscp-ef + } + } + version = 2 + mobike = no + proposals = aes128-sha256-x25519 + } +} diff --git a/testing/tests/ikev2/net2net-dscp/hosts/sun/etc/strongswan.conf b/testing/tests/ikev2/net2net-dscp/hosts/sun/etc/strongswan.conf new file mode 100644 index 000000000..f7a83f3a4 --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/hosts/sun/etc/strongswan.conf @@ -0,0 +1,15 @@ +# /etc/strongswan.conf - strongSwan configuration file + +swanctl { + load = pem pkcs1 x509 revocation constraints pubkey openssl random +} + +charon-systemd { + load = random nonce aes sha1 sha2 hmac pem pkcs1 x509 revocation curve25519 gmp curl kernel-netlink socket-default updown vici + multiple_authentication = no + syslog { + daemon { + knl = 2 + } + } +} diff --git a/testing/tests/ikev2/net2net-dscp/hosts/sun/etc/swanctl/swanctl.conf b/testing/tests/ikev2/net2net-dscp/hosts/sun/etc/swanctl/swanctl.conf new file mode 100755 index 000000000..6e69f2a3c --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/hosts/sun/etc/swanctl/swanctl.conf @@ -0,0 +1,38 @@ +connections { + + dscp { + local_addrs = PH_IP_SUN + remote_addrs = PH_IP_MOON + + local { + auth = pubkey + certs = sunCert.pem + id = sun.strongswan.org + } + remote { + auth = pubkey + id = moon.strongswan.org + } + children { + net-be { + local_ts = 10.2.0.0/16 + remote_ts = 10.1.0.0/16 + + mark_in = 10 + mark_out = 10 + label = dscp-be + + updown = /usr/local/libexec/ipsec/_updown iptables + esp_proposals = aes128gcm128-x25519 + } + net-ef : connections.dscp.children.net-be { + mark_in = 20 + mark_out = 20 + label = dscp-ef + } + } + version = 2 + mobike = no + proposals = aes128-sha256-x25519 + } +} diff --git a/testing/tests/ikev2/net2net-dscp/posttest.dat b/testing/tests/ikev2/net2net-dscp/posttest.dat new file mode 100644 index 000000000..8c9a5f9ba --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/posttest.dat @@ -0,0 +1,8 @@ +moon::systemctl stop strongswan +sun::systemctl stop strongswan +moon::iptables-restore < /etc/iptables.flush +sun::iptables-restore < /etc/iptables.flush +alice::iptables -t mangle -F OUTPUT +venus::iptables -t mangle -F OUTPUT +bob::iptables -t mangle -F OUTPUT + diff --git a/testing/tests/ikev2/net2net-dscp/pretest.dat b/testing/tests/ikev2/net2net-dscp/pretest.dat new file mode 100644 index 000000000..2d5d7ba37 --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/pretest.dat @@ -0,0 +1,16 @@ +moon::iptables-restore < /etc/iptables.rules +sun::iptables-restore < /etc/iptables.rules +alice::iptables -t mangle -A OUTPUT -p icmp -j DSCP --set-dscp-class BE +venus::iptables -t mangle -A OUTPUT -p icmp -j DSCP --set-dscp-class EF +moon::iptables -t mangle -A PREROUTING -m dscp --dscp-class BE -j MARK --set-mark 10 +moon::iptables -t mangle -A PREROUTING -m dscp --dscp-class EF -j MARK --set-mark 20 +bob::iptables -t mangle -A OUTPUT -d PH_IP_ALICE -p icmp -j DSCP --set-dscp-class BE +bob::iptables -t mangle -A OUTPUT -d PH_IP_VENUS -p icmp -j DSCP --set-dscp-class EF +sun::iptables -t mangle -A PREROUTING -m dscp --dscp-class BE -j MARK --set-mark 10 +sun::iptables -t mangle -A PREROUTING -m dscp --dscp-class EF -j MARK --set-mark 20 +sun::systemctl start strongswan +moon::systemctl start strongswan +sun::expect-connection dscp +moon::expect-connection dscp +moon::swanctl --initiate --child net-be +moon::swanctl --initiate --child net-ef diff --git a/testing/tests/ikev2/net2net-dscp/test.conf b/testing/tests/ikev2/net2net-dscp/test.conf new file mode 100644 index 000000000..7f82c02f1 --- /dev/null +++ b/testing/tests/ikev2/net2net-dscp/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="sun" + +# Guest instances on which IPsec is started +# Used for IPsec logging purposes +# +IPSECHOSTS="moon sun" + +# charon controlled by swanctl +# +SWANCTL=1