| /* |
| * 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 <http://www.fsf.org/copyleft/gpl.txt>. |
| * |
| * 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 <stdio.h> |
| #include <string.h> |
| #include <time.h> |
| |
| #include <daemon.h> |
| #include <collections/array.h> |
| |
| 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 inbound SA has been installed |
| */ |
| bool inbound_installed; |
| |
| /** |
| * 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 && this->inbound_installed) |
| { |
| 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; |
| this->inbound_installed = TRUE; |
| } |
| 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 && this->inbound_installed) |
| { |
| 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() && array_count(this->my_ts) && |
| array_count(this->other_ts)) |
| { |
| 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 */ |
| 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); |
| } |
| /* 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, inbound is always deleted |
| * to remove allocated SPIs */ |
| 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; |
| } |