/* * Copyright (C) 2006-2019 Tobias Brunner * Copyright (C) 2016 Andreas Steffen * Copyright (C) 2005-2008 Martin Willi * Copyright (C) 2006 Daniel Roethlisberger * Copyright (C) 2005 Jan Hutter * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #define _GNU_SOURCE #include "child_sa.h" #include #include #include #include #include ENUM(child_sa_state_names, CHILD_CREATED, CHILD_DESTROYING, "CREATED", "ROUTED", "INSTALLING", "INSTALLED", "UPDATING", "REKEYING", "REKEYED", "RETRYING", "DELETING", "DELETED", "DESTROYING", ); ENUM_FLAGS(child_sa_outbound_state_names, CHILD_OUTBOUND_REGISTERED, CHILD_OUTBOUND_POLICIES, "REGISTERED", "SA", "POLICIES", ); typedef struct private_child_sa_t private_child_sa_t; /** * Private data of a child_sa_t object. */ struct private_child_sa_t { /** * Public interface of child_sa_t. */ child_sa_t public; /** * address of us */ host_t *my_addr; /** * address of remote */ host_t *other_addr; /** * our actually used SPI, 0 if unused */ uint32_t my_spi; /** * others used SPI, 0 if unused */ uint32_t other_spi; /** * our Compression Parameter Index (CPI) used, 0 if unused */ uint16_t my_cpi; /** * others Compression Parameter Index (CPI) used, 0 if unused */ uint16_t other_cpi; /** * Array for local traffic selectors */ array_t *my_ts; /** * Array for remote traffic selectors */ array_t *other_ts; /** * Outbound encryption key cached during a rekeying */ chunk_t encr_r; /** * Outbound integrity key cached during a rekeying */ chunk_t integ_r; /** * Whether the outbound SA has only been registered yet during a rekeying */ child_sa_outbound_state_t outbound_state; /** * Whether the peer supports TFCv3 */ bool tfcv3; /** * The outbound SPI of the CHILD_SA that replaced this one during a rekeying */ uint32_t rekey_spi; /** * Protocol used to protect this SA, ESP|AH */ protocol_id_t protocol; /** * reqid used for this child_sa */ uint32_t reqid; /** * Did we allocate/confirm and must release the reqid? */ bool reqid_allocated; /** * Is the reqid statically configured */ bool static_reqid; /** * Unique CHILD_SA identifier */ uint32_t unique_id; /** * Whether FWD policies in the outbound direction should be installed */ bool policies_fwd_out; /** * Inbound interface ID */ uint32_t if_id_in; /** * Outbound interface ID */ uint32_t if_id_out; /** * inbound mark used for this child_sa */ mark_t mark_in; /** * outbound mark used for this child_sa */ mark_t mark_out; /** * absolute time when rekeying is scheduled */ time_t rekey_time; /** * absolute time when the SA expires */ time_t expire_time; /** * absolute time when SA has been installed */ time_t install_time; /** * state of the CHILD_SA */ child_sa_state_t state; /** * TRUE if this CHILD_SA is used to install trap policies */ bool trap; /** * Specifies if UDP encapsulation is enabled (NAT traversal) */ bool encap; /** * Specifies the IPComp transform used (IPCOMP_NONE if disabled) */ ipcomp_transform_t ipcomp; /** * mode this SA uses, tunnel/transport */ ipsec_mode_t mode; /** * Action to enforce if peer closes the CHILD_SA */ action_t close_action; /** * Action to enforce if peer is considered dead */ action_t dpd_action; /** * selected proposal */ proposal_t *proposal; /** * config used to create this child */ child_cfg_t *config; /** * time of last use in seconds (inbound) */ time_t my_usetime; /** * time of last use in seconds (outbound) */ time_t other_usetime; /** * last number of inbound bytes */ uint64_t my_usebytes; /** * last number of outbound bytes */ uint64_t other_usebytes; /** * last number of inbound packets */ uint64_t my_usepackets; /** * last number of outbound bytes */ uint64_t other_usepackets; }; /** * Convert an IKEv2 specific protocol identifier to the IP protocol identifier */ static inline uint8_t proto_ike2ip(protocol_id_t protocol) { switch (protocol) { case PROTO_ESP: return IPPROTO_ESP; case PROTO_AH: return IPPROTO_AH; default: return protocol; } } /** * Returns the mark to use on the inbound SA */ static inline mark_t mark_in_sa(private_child_sa_t *this) { if (this->config->has_option(this->config, OPT_MARK_IN_SA)) { return this->mark_in; } return (mark_t){}; } METHOD(child_sa_t, get_name, char*, private_child_sa_t *this) { return this->config->get_name(this->config); } METHOD(child_sa_t, get_reqid, uint32_t, private_child_sa_t *this) { return this->reqid; } METHOD(child_sa_t, get_unique_id, uint32_t, private_child_sa_t *this) { return this->unique_id; } METHOD(child_sa_t, get_config, child_cfg_t*, private_child_sa_t *this) { return this->config; } METHOD(child_sa_t, set_state, void, private_child_sa_t *this, child_sa_state_t state) { if (this->state != state) { DBG2(DBG_CHD, "CHILD_SA %s{%d} state change: %N => %N", get_name(this), this->unique_id, child_sa_state_names, this->state, child_sa_state_names, state); charon->bus->child_state_change(charon->bus, &this->public, state); this->state = state; } } METHOD(child_sa_t, get_state, child_sa_state_t, private_child_sa_t *this) { return this->state; } METHOD(child_sa_t, get_outbound_state, child_sa_outbound_state_t, private_child_sa_t *this) { return this->outbound_state; } METHOD(child_sa_t, get_spi, uint32_t, private_child_sa_t *this, bool inbound) { return inbound ? this->my_spi : this->other_spi; } METHOD(child_sa_t, get_cpi, uint16_t, private_child_sa_t *this, bool inbound) { return inbound ? this->my_cpi : this->other_cpi; } METHOD(child_sa_t, get_protocol, protocol_id_t, private_child_sa_t *this) { return this->protocol; } METHOD(child_sa_t, set_protocol, void, private_child_sa_t *this, protocol_id_t protocol) { this->protocol = protocol; } METHOD(child_sa_t, get_mode, ipsec_mode_t, private_child_sa_t *this) { return this->mode; } METHOD(child_sa_t, set_mode, void, private_child_sa_t *this, ipsec_mode_t mode) { this->mode = mode; } METHOD(child_sa_t, has_encap, bool, private_child_sa_t *this) { return this->encap; } METHOD(child_sa_t, get_ipcomp, ipcomp_transform_t, private_child_sa_t *this) { return this->ipcomp; } METHOD(child_sa_t, set_ipcomp, void, private_child_sa_t *this, ipcomp_transform_t ipcomp) { this->ipcomp = ipcomp; } METHOD(child_sa_t, set_close_action, void, private_child_sa_t *this, action_t action) { this->close_action = action; } METHOD(child_sa_t, get_close_action, action_t, private_child_sa_t *this) { return this->close_action; } METHOD(child_sa_t, set_dpd_action, void, private_child_sa_t *this, action_t action) { this->dpd_action = action; } METHOD(child_sa_t, get_dpd_action, action_t, private_child_sa_t *this) { return this->dpd_action; } METHOD(child_sa_t, get_proposal, proposal_t*, private_child_sa_t *this) { return this->proposal; } METHOD(child_sa_t, set_proposal, void, private_child_sa_t *this, proposal_t *proposal) { this->proposal = proposal->clone(proposal, 0); } METHOD(child_sa_t, create_ts_enumerator, enumerator_t*, private_child_sa_t *this, bool local) { if (local) { return array_create_enumerator(this->my_ts); } return array_create_enumerator(this->other_ts); } typedef struct policy_enumerator_t policy_enumerator_t; /** * Private policy enumerator */ struct policy_enumerator_t { /** implements enumerator_t */ enumerator_t public; /** enumerator over own TS */ enumerator_t *mine; /** enumerator over others TS */ enumerator_t *other; /** array of others TS, to recreate enumerator */ array_t *array; /** currently enumerating TS for "me" side */ traffic_selector_t *ts; }; METHOD(enumerator_t, policy_enumerate, bool, policy_enumerator_t *this, va_list args) { traffic_selector_t *other_ts, **my_out, **other_out; VA_ARGS_VGET(args, my_out, other_out); while (this->ts || this->mine->enumerate(this->mine, &this->ts)) { if (!this->other->enumerate(this->other, &other_ts)) { /* end of others list, restart with new of mine */ this->other->destroy(this->other); this->other = array_create_enumerator(this->array); this->ts = NULL; continue; } if (this->ts->get_type(this->ts) != other_ts->get_type(other_ts)) { /* family mismatch */ continue; } if (this->ts->get_protocol(this->ts) && other_ts->get_protocol(other_ts) && this->ts->get_protocol(this->ts) != other_ts->get_protocol(other_ts)) { /* protocol mismatch */ continue; } if (my_out) { *my_out = this->ts; } if (other_out) { *other_out = other_ts; } return TRUE; } return FALSE; } METHOD(enumerator_t, policy_destroy, void, policy_enumerator_t *this) { this->mine->destroy(this->mine); this->other->destroy(this->other); free(this); } METHOD(child_sa_t, create_policy_enumerator, enumerator_t*, private_child_sa_t *this) { policy_enumerator_t *e; INIT(e, .public = { .enumerate = enumerator_enumerate_default, .venumerate = _policy_enumerate, .destroy = _policy_destroy, }, .mine = array_create_enumerator(this->my_ts), .other = array_create_enumerator(this->other_ts), .array = this->other_ts, .ts = NULL, ); return &e->public; } /** * update the cached usebytes * returns SUCCESS if the usebytes have changed, FAILED if not or no SPIs * are available, and NOT_SUPPORTED if the kernel interface does not support * querying the usebytes. */ static status_t update_usebytes(private_child_sa_t *this, bool inbound) { status_t status = FAILED; uint64_t bytes, packets; time_t time; if (inbound) { if (this->my_spi) { kernel_ipsec_sa_id_t id = { .src = this->other_addr, .dst = this->my_addr, .spi = this->my_spi, .proto = proto_ike2ip(this->protocol), .mark = mark_in_sa(this), .if_id = this->if_id_in, }; kernel_ipsec_query_sa_t query = {}; status = charon->kernel->query_sa(charon->kernel, &id, &query, &bytes, &packets, &time); if (status == SUCCESS) { if (bytes > this->my_usebytes) { this->my_usebytes = bytes; this->my_usepackets = packets; if (time) { this->my_usetime = time; } } else { status = FAILED; } } } } else { if (this->other_spi && (this->outbound_state & CHILD_OUTBOUND_SA)) { kernel_ipsec_sa_id_t id = { .src = this->my_addr, .dst = this->other_addr, .spi = this->other_spi, .proto = proto_ike2ip(this->protocol), .mark = this->mark_out, .if_id = this->if_id_out, }; kernel_ipsec_query_sa_t query = {}; status = charon->kernel->query_sa(charon->kernel, &id, &query, &bytes, &packets, &time); if (status == SUCCESS) { if (bytes > this->other_usebytes) { this->other_usebytes = bytes; this->other_usepackets = packets; if (time) { this->other_usetime = time; } } else { status = FAILED; } } } } return status; } /** * updates the cached usetime */ static bool update_usetime(private_child_sa_t *this, bool inbound) { enumerator_t *enumerator; traffic_selector_t *my_ts, *other_ts; time_t last_use = 0; enumerator = create_policy_enumerator(this); while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) { time_t in, out, fwd; if (inbound) { kernel_ipsec_policy_id_t id = { .dir = POLICY_IN, .src_ts = other_ts, .dst_ts = my_ts, .mark = this->mark_in, .if_id = this->if_id_in, }; kernel_ipsec_query_policy_t query = {}; if (charon->kernel->query_policy(charon->kernel, &id, &query, &in) == SUCCESS) { last_use = max(last_use, in); } if (this->mode != MODE_TRANSPORT) { id.dir = POLICY_FWD; if (charon->kernel->query_policy(charon->kernel, &id, &query, &fwd) == SUCCESS) { last_use = max(last_use, fwd); } } } else { kernel_ipsec_policy_id_t id = { .dir = POLICY_OUT, .src_ts = my_ts, .dst_ts = other_ts, .mark = this->mark_out, .if_id = this->if_id_out, .interface = this->config->get_interface(this->config), }; kernel_ipsec_query_policy_t query = {}; if (charon->kernel->query_policy(charon->kernel, &id, &query, &out) == SUCCESS) { last_use = max(last_use, out); } } } enumerator->destroy(enumerator); if (last_use == 0) { return FALSE; } if (inbound) { this->my_usetime = last_use; } else { this->other_usetime = last_use; } return TRUE; } METHOD(child_sa_t, get_usestats, void, private_child_sa_t *this, bool inbound, time_t *time, uint64_t *bytes, uint64_t *packets) { if ((!bytes && !packets) || update_usebytes(this, inbound) != FAILED) { /* there was traffic since last update or the kernel interface * does not support querying the number of usebytes. */ if (time) { if (!update_usetime(this, inbound) && !bytes && !packets) { /* if policy query did not yield a usetime, query SAs instead */ update_usebytes(this, inbound); } } } if (time) { *time = inbound ? this->my_usetime : this->other_usetime; } if (bytes) { *bytes = inbound ? this->my_usebytes : this->other_usebytes; } if (packets) { *packets = inbound ? this->my_usepackets : this->other_usepackets; } } METHOD(child_sa_t, get_mark, mark_t, private_child_sa_t *this, bool inbound) { return inbound ? this->mark_in : this->mark_out; } METHOD(child_sa_t, get_if_id, uint32_t, private_child_sa_t *this, bool inbound) { return inbound ? this->if_id_in : this->if_id_out; } METHOD(child_sa_t, get_lifetime, time_t, private_child_sa_t *this, bool hard) { return hard ? this->expire_time : this->rekey_time; } METHOD(child_sa_t, get_installtime, time_t, private_child_sa_t *this) { return this->install_time; } METHOD(child_sa_t, alloc_spi, uint32_t, private_child_sa_t *this, protocol_id_t protocol) { if (charon->kernel->get_spi(charon->kernel, this->other_addr, this->my_addr, proto_ike2ip(protocol), &this->my_spi) == SUCCESS) { /* if we allocate a SPI, but then are unable to establish the SA, we * need to know the protocol family to delete the partial SA */ this->protocol = protocol; return this->my_spi; } return 0; } METHOD(child_sa_t, alloc_cpi, uint16_t, private_child_sa_t *this) { if (charon->kernel->get_cpi(charon->kernel, this->other_addr, this->my_addr, &this->my_cpi) == SUCCESS) { return this->my_cpi; } return 0; } /** * Install the given SA in the kernel */ static status_t install_internal(private_child_sa_t *this, chunk_t encr, chunk_t integ, uint32_t spi, uint16_t cpi, bool initiator, bool inbound, bool tfcv3) { uint16_t enc_alg = ENCR_UNDEFINED, int_alg = AUTH_UNDEFINED, size; uint16_t esn = NO_EXT_SEQ_NUMBERS; linked_list_t *my_ts, *other_ts, *src_ts, *dst_ts; time_t now; kernel_ipsec_sa_id_t id; kernel_ipsec_add_sa_t sa; lifetime_cfg_t *lifetime; uint32_t tfc = 0; host_t *src, *dst; status_t status; bool update = FALSE; /* BEET requires the bound address from the traffic selectors */ my_ts = linked_list_create_from_enumerator( array_create_enumerator(this->my_ts)); other_ts = linked_list_create_from_enumerator( array_create_enumerator(this->other_ts)); /* now we have to decide which spi to use. Use self allocated, if "in", * or the one in the proposal, if not "in" (others). Additionally, * source and dest host switch depending on the role */ if (inbound) { dst = this->my_addr; src = this->other_addr; if (this->my_spi == spi) { /* alloc_spi has been called, do an SA update */ update = TRUE; } this->my_spi = spi; this->my_cpi = cpi; dst_ts = my_ts; src_ts = other_ts; } else { src = this->my_addr; dst = this->other_addr; this->other_spi = spi; this->other_cpi = cpi; src_ts = my_ts; dst_ts = other_ts; if (tfcv3) { tfc = this->config->get_tfc(this->config); } this->outbound_state |= CHILD_OUTBOUND_SA; } DBG2(DBG_CHD, "adding %s %N SA", inbound ? "inbound" : "outbound", protocol_id_names, this->protocol); /* send SA down to the kernel */ DBG2(DBG_CHD, " SPI 0x%.8x, src %H dst %H", ntohl(spi), src, dst); this->proposal->get_algorithm(this->proposal, ENCRYPTION_ALGORITHM, &enc_alg, &size); this->proposal->get_algorithm(this->proposal, INTEGRITY_ALGORITHM, &int_alg, &size); this->proposal->get_algorithm(this->proposal, EXTENDED_SEQUENCE_NUMBERS, &esn, NULL); if (int_alg == AUTH_HMAC_SHA2_256_128 && this->config->has_option(this->config, OPT_SHA256_96)) { DBG2(DBG_CHD, " using %N with 96-bit truncation", integrity_algorithm_names, int_alg); int_alg = AUTH_HMAC_SHA2_256_96; } if (!this->reqid_allocated && !this->static_reqid) { 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); if (status != SUCCESS) { my_ts->destroy(my_ts); other_ts->destroy(other_ts); return status; } this->reqid_allocated = TRUE; } lifetime = this->config->get_lifetime(this->config, TRUE); now = time_monotonic(NULL); if (lifetime->time.rekey) { if (this->rekey_time) { this->rekey_time = min(this->rekey_time, now + lifetime->time.rekey); } else { this->rekey_time = now + lifetime->time.rekey; } } if (lifetime->time.life) { this->expire_time = now + lifetime->time.life; } if (!lifetime->time.jitter && !inbound) { /* avoid triggering multiple rekey events */ lifetime->time.rekey = 0; } id = (kernel_ipsec_sa_id_t){ .src = src, .dst = dst, .spi = spi, .proto = proto_ike2ip(this->protocol), .mark = inbound ? mark_in_sa(this) : this->mark_out, .if_id = inbound ? this->if_id_in : this->if_id_out, }; sa = (kernel_ipsec_add_sa_t){ .reqid = this->reqid, .mode = this->mode, .src_ts = src_ts, .dst_ts = dst_ts, .interface = inbound ? NULL : this->config->get_interface(this->config), .lifetime = lifetime, .enc_alg = enc_alg, .enc_key = encr, .int_alg = int_alg, .int_key = integ, .replay_window = this->config->get_replay_window(this->config), .tfc = tfc, .ipcomp = this->ipcomp, .cpi = cpi, .encap = this->encap, .hw_offload = this->config->get_hw_offload(this->config), .mark = this->config->get_set_mark(this->config, inbound), .esn = esn, .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), .initiator = initiator, .inbound = inbound, .update = update, }; if (sa.mark.value == MARK_SAME) { sa.mark.value = inbound ? this->mark_in.value : this->mark_out.value; } status = charon->kernel->add_sa(charon->kernel, &id, &sa); my_ts->destroy(my_ts); other_ts->destroy(other_ts); free(lifetime); return status; } METHOD(child_sa_t, install, status_t, private_child_sa_t *this, chunk_t encr, chunk_t integ, uint32_t spi, uint16_t cpi, bool initiator, bool inbound, bool tfcv3) { return install_internal(this, encr, integ, spi, cpi, initiator, inbound, tfcv3); } /** * Check kernel interface if policy updates are required */ static bool require_policy_update() { kernel_feature_t f; f = charon->kernel->get_features(charon->kernel); return !(f & KERNEL_NO_POLICY_UPDATES); } /** * Prepare SA config to install/delete policies */ static void prepare_sa_cfg(private_child_sa_t *this, ipsec_sa_cfg_t *my_sa, ipsec_sa_cfg_t *other_sa) { enumerator_t *enumerator; *my_sa = (ipsec_sa_cfg_t){ .mode = this->mode, .reqid = this->reqid, .ipcomp = { .transform = this->ipcomp, }, }; *other_sa = *my_sa; my_sa->ipcomp.cpi = this->my_cpi; other_sa->ipcomp.cpi = this->other_cpi; if (this->protocol == PROTO_ESP) { my_sa->esp.use = TRUE; my_sa->esp.spi = this->my_spi; other_sa->esp.use = TRUE; other_sa->esp.spi = this->other_spi; } else { my_sa->ah.use = TRUE; my_sa->ah.spi = this->my_spi; other_sa->ah.use = TRUE; other_sa->ah.spi = this->other_spi; } enumerator = create_policy_enumerator(this); while (enumerator->enumerate(enumerator, NULL, NULL)) { my_sa->policy_count++; other_sa->policy_count++; } enumerator->destroy(enumerator); } /** * Install inbound policies: in, fwd */ static status_t install_policies_inbound(private_child_sa_t *this, host_t *my_addr, host_t *other_addr, traffic_selector_t *my_ts, traffic_selector_t *other_ts, ipsec_sa_cfg_t *my_sa, ipsec_sa_cfg_t *other_sa, policy_type_t type, policy_priority_t priority, uint32_t manual_prio) { kernel_ipsec_policy_id_t in_id = { .dir = POLICY_IN, .src_ts = other_ts, .dst_ts = my_ts, .mark = this->mark_in, .if_id = this->if_id_in, }; kernel_ipsec_manage_policy_t in_policy = { .type = type, .prio = priority, .manual_prio = manual_prio, .src = other_addr, .dst = my_addr, .sa = my_sa, }; status_t status = SUCCESS; status |= charon->kernel->add_policy(charon->kernel, &in_id, &in_policy); if (this->mode != MODE_TRANSPORT) { in_id.dir = POLICY_FWD; status |= charon->kernel->add_policy(charon->kernel, &in_id, &in_policy); } return status; } /** * Install outbound policies: out, [fwd] */ static status_t install_policies_outbound(private_child_sa_t *this, host_t *my_addr, host_t *other_addr, traffic_selector_t *my_ts, traffic_selector_t *other_ts, ipsec_sa_cfg_t *my_sa, ipsec_sa_cfg_t *other_sa, policy_type_t type, policy_priority_t priority, uint32_t manual_prio) { kernel_ipsec_policy_id_t out_id = { .dir = POLICY_OUT, .src_ts = my_ts, .dst_ts = other_ts, .mark = this->mark_out, .if_id = this->if_id_out, .interface = this->config->get_interface(this->config), }; kernel_ipsec_manage_policy_t out_policy = { .type = type, .prio = priority, .manual_prio = manual_prio, .src = my_addr, .dst = other_addr, .sa = other_sa, }; status_t status = SUCCESS; status |= charon->kernel->add_policy(charon->kernel, &out_id, &out_policy); if (this->mode != MODE_TRANSPORT && this->policies_fwd_out) { /* install an "outbound" FWD policy in case there is a drop policy * matching outbound forwarded traffic, to allow another tunnel to use * the reversed subnets and do the same we don't set a reqid (this also * allows the kernel backend to distinguish between the two types of * FWD policies). To avoid problems with symmetrically overlapping * policies of two SAs we install them with reduced priority. As they * basically act as bypass policies for drop policies we use a higher * priority than is used for them. */ out_id.dir = POLICY_FWD; other_sa->reqid = 0; if (priority == POLICY_PRIORITY_DEFAULT) { out_policy.prio = POLICY_PRIORITY_ROUTED; } status |= charon->kernel->add_policy(charon->kernel, &out_id, &out_policy); /* reset the reqid for any other further policies */ other_sa->reqid = this->reqid; } return status; } /** * Install all policies */ static status_t install_policies_internal(private_child_sa_t *this, host_t *my_addr, host_t *other_addr, traffic_selector_t *my_ts, traffic_selector_t *other_ts, ipsec_sa_cfg_t *my_sa, ipsec_sa_cfg_t *other_sa, policy_type_t type, policy_priority_t priority, uint32_t manual_prio, bool outbound) { status_t status = SUCCESS; status |= install_policies_inbound(this, my_addr, other_addr, my_ts, other_ts, my_sa, other_sa, type, priority, manual_prio); if (outbound) { status |= install_policies_outbound(this, my_addr, other_addr, my_ts, other_ts, my_sa, other_sa, type, priority, manual_prio); } return status; } /** * Delete inbound policies: in, fwd */ static void del_policies_inbound(private_child_sa_t *this, host_t *my_addr, host_t *other_addr, traffic_selector_t *my_ts, traffic_selector_t *other_ts, ipsec_sa_cfg_t *my_sa, ipsec_sa_cfg_t *other_sa, policy_type_t type, policy_priority_t priority, uint32_t manual_prio) { kernel_ipsec_policy_id_t in_id = { .dir = POLICY_IN, .src_ts = other_ts, .dst_ts = my_ts, .mark = this->mark_in, .if_id = this->if_id_in, }; kernel_ipsec_manage_policy_t in_policy = { .type = type, .prio = priority, .manual_prio = manual_prio, .src = other_addr, .dst = my_addr, .sa = my_sa, }; charon->kernel->del_policy(charon->kernel, &in_id, &in_policy); if (this->mode != MODE_TRANSPORT) { in_id.dir = POLICY_FWD; charon->kernel->del_policy(charon->kernel, &in_id, &in_policy); } } /** * Delete outbound policies: out, [fwd] */ static void del_policies_outbound(private_child_sa_t *this, host_t *my_addr, host_t *other_addr, traffic_selector_t *my_ts, traffic_selector_t *other_ts, ipsec_sa_cfg_t *my_sa, ipsec_sa_cfg_t *other_sa, policy_type_t type, policy_priority_t priority, uint32_t manual_prio) { kernel_ipsec_policy_id_t out_id = { .dir = POLICY_OUT, .src_ts = my_ts, .dst_ts = other_ts, .mark = this->mark_out, .if_id = this->if_id_out, .interface = this->config->get_interface(this->config), }; kernel_ipsec_manage_policy_t out_policy = { .type = type, .prio = priority, .manual_prio = manual_prio, .src = my_addr, .dst = other_addr, .sa = other_sa, }; charon->kernel->del_policy(charon->kernel, &out_id, &out_policy); if (this->mode != MODE_TRANSPORT && this->policies_fwd_out) { out_id.dir = POLICY_FWD; other_sa->reqid = 0; if (priority == POLICY_PRIORITY_DEFAULT) { out_policy.prio = POLICY_PRIORITY_ROUTED; } charon->kernel->del_policy(charon->kernel, &out_id, &out_policy); other_sa->reqid = this->reqid; } } /** * Delete in- and outbound policies */ static void del_policies_internal(private_child_sa_t *this, host_t *my_addr, host_t *other_addr, traffic_selector_t *my_ts, traffic_selector_t *other_ts, ipsec_sa_cfg_t *my_sa, ipsec_sa_cfg_t *other_sa, policy_type_t type, policy_priority_t priority, uint32_t manual_prio, bool outbound) { if (outbound) { del_policies_outbound(this, my_addr, other_addr, my_ts, other_ts, my_sa, other_sa, type, priority, manual_prio); } del_policies_inbound(this, my_addr, other_addr, my_ts, other_ts, my_sa, other_sa, type, priority, manual_prio); } METHOD(child_sa_t, set_policies, void, private_child_sa_t *this, linked_list_t *my_ts_list, linked_list_t *other_ts_list) { enumerator_t *enumerator; traffic_selector_t *my_ts, *other_ts; if (array_count(this->my_ts)) { array_destroy_offset(this->my_ts, offsetof(traffic_selector_t, destroy)); this->my_ts = array_create(0, 0); } enumerator = my_ts_list->create_enumerator(my_ts_list); while (enumerator->enumerate(enumerator, &my_ts)) { array_insert(this->my_ts, ARRAY_TAIL, my_ts->clone(my_ts)); } enumerator->destroy(enumerator); array_sort(this->my_ts, (void*)traffic_selector_cmp, NULL); if (array_count(this->other_ts)) { array_destroy_offset(this->other_ts, offsetof(traffic_selector_t, destroy)); this->other_ts = array_create(0, 0); } enumerator = other_ts_list->create_enumerator(other_ts_list); while (enumerator->enumerate(enumerator, &other_ts)) { array_insert(this->other_ts, ARRAY_TAIL, other_ts->clone(other_ts)); } enumerator->destroy(enumerator); array_sort(this->other_ts, (void*)traffic_selector_cmp, NULL); } 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); if (status != SUCCESS) { return status; } this->reqid_allocated = TRUE; } if (!(this->outbound_state & CHILD_OUTBOUND_REGISTERED)) { install_outbound = TRUE; this->outbound_state |= CHILD_OUTBOUND_POLICIES; } if (!this->config->has_option(this->config, OPT_NO_POLICIES)) { policy_priority_t priority; ipsec_sa_cfg_t my_sa, other_sa; uint32_t manual_prio; prepare_sa_cfg(this, &my_sa, &other_sa); manual_prio = this->config->get_manual_prio(this->config); /* if we're not in state CHILD_INSTALLING (i.e. if there is no SAD * entry) we install a trap policy */ this->trap = this->state == CHILD_CREATED; priority = this->trap ? POLICY_PRIORITY_ROUTED : POLICY_PRIORITY_DEFAULT; /* enumerate pairs of traffic selectors */ enumerator = create_policy_enumerator(this); while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) { status |= install_policies_internal(this, this->my_addr, this->other_addr, my_ts, other_ts, &my_sa, &other_sa, POLICY_IPSEC, priority, manual_prio, install_outbound); if (status != SUCCESS) { break; } } enumerator->destroy(enumerator); } if (status == SUCCESS && this->trap) { set_state(this, CHILD_ROUTED); } return status; } 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) { status = install_internal(this, encr, integ, spi, cpi, FALSE, FALSE, tfcv3); } else { DBG2(DBG_CHD, "registering outbound %N SA", protocol_id_names, this->protocol); DBG2(DBG_CHD, " SPI 0x%.8x, src %H dst %H", ntohl(spi), this->my_addr, this->other_addr); this->other_spi = spi; this->other_cpi = cpi; this->encr_r = chunk_clone(encr); this->integ_r = chunk_clone(integ); this->tfcv3 = tfcv3; status = SUCCESS; } this->outbound_state |= CHILD_OUTBOUND_REGISTERED; return status; } METHOD(child_sa_t, install_outbound, status_t, private_child_sa_t *this) { enumerator_t *enumerator; traffic_selector_t *my_ts, *other_ts; status_t status = SUCCESS; if (!(this->outbound_state & CHILD_OUTBOUND_SA)) { status = install_internal(this, this->encr_r, this->integ_r, this->other_spi, this->other_cpi, FALSE, FALSE, this->tfcv3); chunk_clear(&this->encr_r); chunk_clear(&this->integ_r); } this->outbound_state &= ~CHILD_OUTBOUND_REGISTERED; if (status != SUCCESS) { return status; } if (!this->config->has_option(this->config, OPT_NO_POLICIES) && !(this->outbound_state & CHILD_OUTBOUND_POLICIES)) { ipsec_sa_cfg_t my_sa, other_sa; uint32_t manual_prio; prepare_sa_cfg(this, &my_sa, &other_sa); manual_prio = this->config->get_manual_prio(this->config); enumerator = create_policy_enumerator(this); while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) { status |= install_policies_outbound(this, this->my_addr, this->other_addr, my_ts, other_ts, &my_sa, &other_sa, POLICY_IPSEC, POLICY_PRIORITY_DEFAULT, manual_prio); if (status != SUCCESS) { break; } } enumerator->destroy(enumerator); } this->outbound_state |= CHILD_OUTBOUND_POLICIES; return status; } METHOD(child_sa_t, remove_outbound, void, private_child_sa_t *this) { enumerator_t *enumerator; traffic_selector_t *my_ts, *other_ts; if (!(this->outbound_state & CHILD_OUTBOUND_SA)) { if (this->outbound_state & CHILD_OUTBOUND_REGISTERED) { chunk_clear(&this->encr_r); chunk_clear(&this->integ_r); this->outbound_state = CHILD_OUTBOUND_NONE; } return; } if (!this->config->has_option(this->config, OPT_NO_POLICIES) && (this->outbound_state & CHILD_OUTBOUND_POLICIES)) { ipsec_sa_cfg_t my_sa, other_sa; uint32_t manual_prio; prepare_sa_cfg(this, &my_sa, &other_sa); manual_prio = this->config->get_manual_prio(this->config); enumerator = create_policy_enumerator(this); while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) { del_policies_outbound(this, this->my_addr, this->other_addr, my_ts, other_ts, &my_sa, &other_sa, POLICY_IPSEC, POLICY_PRIORITY_DEFAULT, manual_prio); } enumerator->destroy(enumerator); } kernel_ipsec_sa_id_t id = { .src = this->my_addr, .dst = this->other_addr, .spi = this->other_spi, .proto = proto_ike2ip(this->protocol), .mark = this->mark_out, .if_id = this->if_id_out, }; kernel_ipsec_del_sa_t sa = { .cpi = this->other_cpi, }; charon->kernel->del_sa(charon->kernel, &id, &sa); this->outbound_state = CHILD_OUTBOUND_NONE; } METHOD(child_sa_t, set_rekey_spi, void, private_child_sa_t *this, uint32_t spi) { this->rekey_spi = spi; } METHOD(child_sa_t, get_rekey_spi, uint32_t, private_child_sa_t *this) { return this->rekey_spi; } CALLBACK(reinstall_vip, void, host_t *vip, va_list args) { host_t *me; char *iface; VA_ARGS_VGET(args, me); if (charon->kernel->get_interface(charon->kernel, me, &iface)) { charon->kernel->del_ip(charon->kernel, vip, -1, TRUE); charon->kernel->add_ip(charon->kernel, vip, -1, iface); free(iface); } } /** * 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) { /* update our (initiator) SA */ if (this->my_spi) { kernel_ipsec_sa_id_t id = { .src = this->other_addr, .dst = this->my_addr, .spi = this->my_spi, .proto = proto_ike2ip(this->protocol), .mark = mark_in_sa(this), .if_id = this->if_id_in, }; kernel_ipsec_update_sa_t sa = { .cpi = this->ipcomp != IPCOMP_NONE ? this->my_cpi : 0, .new_src = other, .new_dst = me, .encap = this->encap, .new_encap = encap, }; if (charon->kernel->update_sa(charon->kernel, &id, &sa) == NOT_SUPPORTED) { return NOT_SUPPORTED; } } /* update his (responder) SA */ if (this->other_spi && (this->outbound_state & CHILD_OUTBOUND_SA)) { kernel_ipsec_sa_id_t id = { .src = this->my_addr, .dst = this->other_addr, .spi = this->other_spi, .proto = proto_ike2ip(this->protocol), .mark = this->mark_out, .if_id = this->if_id_out, }; kernel_ipsec_update_sa_t sa = { .cpi = this->ipcomp != IPCOMP_NONE ? this->other_cpi : 0, .new_src = me, .new_dst = other, .encap = this->encap, .new_encap = encap, }; if (charon->kernel->update_sa(charon->kernel, &id, &sa) == NOT_SUPPORTED) { return NOT_SUPPORTED; } } /* we currently ignore the actual return values above */ return SUCCESS; } METHOD(child_sa_t, update, status_t, private_child_sa_t *this, host_t *me, host_t *other, linked_list_t *vips, bool encap) { child_sa_state_t old; bool transport_proxy_mode; /* anything changed at all? */ if (me->equals(me, this->my_addr) && other->equals(other, this->other_addr) && this->encap == encap) { return SUCCESS; } old = this->state; set_state(this, CHILD_UPDATING); transport_proxy_mode = this->mode == MODE_TRANSPORT && this->config->has_option(this->config, OPT_PROXY_MODE); if (!this->config->has_option(this->config, OPT_NO_POLICIES) && require_policy_update()) { ipsec_sa_cfg_t my_sa, other_sa; enumerator_t *enumerator; traffic_selector_t *my_ts, *other_ts; uint32_t manual_prio; 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); enumerator = create_policy_enumerator(this); while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) { /* install drop policy to avoid traffic leaks, acquires etc. */ if (outbound) { install_policies_outbound(this, this->my_addr, this->other_addr, my_ts, other_ts, &my_sa, &other_sa, POLICY_DROP, POLICY_PRIORITY_DEFAULT, manual_prio); } /* 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); } enumerator->destroy(enumerator); /* update the IPsec SAs */ state = update_sas(this, me, other, encap); 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 */ 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); } /* 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, POLICY_PRIORITY_DEFAULT, manual_prio); } DESTROY_IF(old_my_ts); DESTROY_IF(old_other_ts); } enumerator->destroy(enumerator); if (state == NOT_SUPPORTED) { set_state(this, old); return NOT_SUPPORTED; } } else if (!transport_proxy_mode) { if (update_sas(this, me, other, encap) == NOT_SUPPORTED) { set_state(this, old); return NOT_SUPPORTED; } } if (!transport_proxy_mode) { /* apply hosts */ if (!me->equals(me, this->my_addr)) { this->my_addr->destroy(this->my_addr); this->my_addr = me->clone(me); } if (!other->equals(other, this->other_addr)) { this->other_addr->destroy(this->other_addr); this->other_addr = other->clone(other); } } this->encap = encap; set_state(this, old); return SUCCESS; } METHOD(child_sa_t, destroy, void, private_child_sa_t *this) { enumerator_t *enumerator; traffic_selector_t *my_ts, *other_ts; policy_priority_t priority; priority = this->trap ? POLICY_PRIORITY_ROUTED : POLICY_PRIORITY_DEFAULT; set_state(this, CHILD_DESTROYING); if (!this->config->has_option(this->config, OPT_NO_POLICIES)) { ipsec_sa_cfg_t my_sa, other_sa; uint32_t manual_prio; bool del_outbound; prepare_sa_cfg(this, &my_sa, &other_sa); manual_prio = this->config->get_manual_prio(this->config); del_outbound = (this->outbound_state & CHILD_OUTBOUND_POLICIES) || this->trap; /* delete all policies in the kernel */ enumerator = create_policy_enumerator(this); while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) { del_policies_internal(this, this->my_addr, this->other_addr, my_ts, other_ts, &my_sa, &other_sa, POLICY_IPSEC, priority, manual_prio, del_outbound); } enumerator->destroy(enumerator); } /* delete SAs in the kernel, if they are set up */ if (this->my_spi) { kernel_ipsec_sa_id_t id = { .src = this->other_addr, .dst = this->my_addr, .spi = this->my_spi, .proto = proto_ike2ip(this->protocol), .mark = mark_in_sa(this), .if_id = this->if_id_in, }; kernel_ipsec_del_sa_t sa = { .cpi = this->my_cpi, }; charon->kernel->del_sa(charon->kernel, &id, &sa); } if (this->other_spi && (this->outbound_state & CHILD_OUTBOUND_SA)) { kernel_ipsec_sa_id_t id = { .src = this->my_addr, .dst = this->other_addr, .spi = this->other_spi, .proto = proto_ike2ip(this->protocol), .mark = this->mark_out, .if_id = this->if_id_out, }; kernel_ipsec_del_sa_t sa = { .cpi = this->other_cpi, }; charon->kernel->del_sa(charon->kernel, &id, &sa); } if (this->reqid_allocated) { if (charon->kernel->release_reqid(charon->kernel, this->reqid, this->mark_in, this->mark_out, this->if_id_in, this->if_id_out) != SUCCESS) { DBG1(DBG_CHD, "releasing reqid %u failed", this->reqid); } } array_destroy_offset(this->my_ts, offsetof(traffic_selector_t, destroy)); array_destroy_offset(this->other_ts, offsetof(traffic_selector_t, destroy)); this->my_addr->destroy(this->my_addr); this->other_addr->destroy(this->other_addr); DESTROY_IF(this->proposal); this->config->destroy(this->config); chunk_clear(&this->encr_r); chunk_clear(&this->integ_r); free(this); } /** * Get proxy address for one side, if any */ static host_t* get_proxy_addr(child_cfg_t *config, host_t *ike, bool local) { host_t *host = NULL; uint8_t mask; enumerator_t *enumerator; linked_list_t *ts_list, *list; traffic_selector_t *ts; list = linked_list_create_with_items(ike, NULL); ts_list = config->get_traffic_selectors(config, local, NULL, list, FALSE); list->destroy(list); enumerator = ts_list->create_enumerator(ts_list); while (enumerator->enumerate(enumerator, &ts)) { if (ts->is_host(ts, NULL) && ts->to_subnet(ts, &host, &mask)) { DBG1(DBG_CHD, "%s address: %H is a transport mode proxy for %H", local ? "my" : "other", ike, host); break; } } enumerator->destroy(enumerator); ts_list->destroy_offset(ts_list, offsetof(traffic_selector_t, destroy)); if (!host) { host = ike->clone(ike); } return host; } /* * Described in header */ child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, child_sa_create_t *data) { private_child_sa_t *this; static refcount_t unique_id = 0, unique_mark = 0; INIT(this, .public = { .get_name = _get_name, .get_reqid = _get_reqid, .get_unique_id = _get_unique_id, .get_config = _get_config, .get_state = _get_state, .set_state = _set_state, .get_outbound_state = _get_outbound_state, .get_spi = _get_spi, .get_cpi = _get_cpi, .get_protocol = _get_protocol, .set_protocol = _set_protocol, .get_mode = _get_mode, .set_mode = _set_mode, .get_proposal = _get_proposal, .set_proposal = _set_proposal, .get_lifetime = _get_lifetime, .get_installtime = _get_installtime, .get_usestats = _get_usestats, .get_mark = _get_mark, .get_if_id = _get_if_id, .has_encap = _has_encap, .get_ipcomp = _get_ipcomp, .set_ipcomp = _set_ipcomp, .get_close_action = _get_close_action, .set_close_action = _set_close_action, .get_dpd_action = _get_dpd_action, .set_dpd_action = _set_dpd_action, .alloc_spi = _alloc_spi, .alloc_cpi = _alloc_cpi, .install = _install, .register_outbound = _register_outbound, .install_outbound = _install_outbound, .remove_outbound = _remove_outbound, .set_rekey_spi = _set_rekey_spi, .get_rekey_spi = _get_rekey_spi, .update = _update, .set_policies = _set_policies, .install_policies = _install_policies, .create_ts_enumerator = _create_ts_enumerator, .create_policy_enumerator = _create_policy_enumerator, .destroy = _destroy, }, .encap = data->encap, .ipcomp = IPCOMP_NONE, .state = CHILD_CREATED, .my_ts = array_create(0, 0), .other_ts = array_create(0, 0), .protocol = PROTO_NONE, .mode = MODE_TUNNEL, .close_action = config->get_close_action(config), .dpd_action = config->get_dpd_action(config), .reqid = config->get_reqid(config), .unique_id = ref_get(&unique_id), .mark_in = config->get_mark(config, TRUE), .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, .install_time = time_monotonic(NULL), .policies_fwd_out = config->has_option(config, OPT_FWD_OUT_POLICIES), ); this->config = config; config->get_ref(config); if (data->mark_in) { this->mark_in.value = data->mark_in; } if (data->mark_out) { this->mark_out.value = data->mark_out; } if (data->if_id_in) { this->if_id_in = data->if_id_in; } if (data->if_id_out) { this->if_id_out = data->if_id_out; } allocate_unique_if_ids(&this->if_id_in, &this->if_id_out); if (MARK_IS_UNIQUE(this->mark_in.value) || MARK_IS_UNIQUE(this->mark_out.value)) { refcount_t mark = 0; bool unique_dir = this->mark_in.value == MARK_UNIQUE_DIR || this->mark_out.value == MARK_UNIQUE_DIR; if (!unique_dir) { mark = ref_get(&unique_mark); } if (MARK_IS_UNIQUE(this->mark_in.value)) { this->mark_in.value = unique_dir ? ref_get(&unique_mark) : mark; } if (MARK_IS_UNIQUE(this->mark_out.value)) { this->mark_out.value = unique_dir ? ref_get(&unique_mark) : mark; } } if (!this->reqid) { /* reuse old reqid if we are rekeying an existing CHILD_SA and when * initiating a trap policy. While the reqid cache would find the same * reqid for our selectors, this does not work in a special case: If an * SA is triggered by a trap policy, but the negotiated TS get * narrowed, we still must reuse the same reqid to successfully * replace the temporary SA on the kernel level. Rekeying such an SA * requires an explicit reqid, as the cache currently knows the original * selectors only for that reqid. */ this->reqid = data->reqid; } else { this->static_reqid = TRUE; } /* MIPv6 proxy transport mode sets SA endpoints to TS hosts */ if (config->get_mode(config) == MODE_TRANSPORT && config->has_option(config, OPT_PROXY_MODE)) { this->mode = MODE_TRANSPORT; this->my_addr = get_proxy_addr(config, me, TRUE); this->other_addr = get_proxy_addr(config, other, FALSE); } else { this->my_addr = me->clone(me); this->other_addr = other->clone(other); } return &this->public; }