/*
 * Copyright (C) 2006-2025 Tobias Brunner
 * Copyright (C) 2016 Andreas Steffen
 * Copyright (C) 2005-2008 Martin Willi
 * Copyright (C) 2006 Daniel Roethlisberger
 * Copyright (C) 2005 Jan Hutter
 *
 * Copyright (C) secunet Security Networks AG
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <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,
	"NONE",
	"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 registered outbound SA was created as initiator
	 */
	bool initiator;

	/**
	 * 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 "other" CHILD_SA involved in a passive rekeying (either replacing
	 * this one, or being replaced by it)
	 */
	child_sa_t *rekey_sa;

	/**
	 * 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;

	/**
	 * Optional sequence number associated with triggering acquire
	 */
	uint32_t seq;

	/**
	 * 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;

	/**
	 * CPU ID to use for the outbound SA
	 */
	uint32_t cpu;

	/**
	 * Whether to enable per-CPU feature for this SA
	 */
	bool per_cpu;

	/**
	 * inbound mark used for this child_sa
	 */
	mark_t mark_in;

	/**
	 * outbound mark used for this child_sa
	 */
	mark_t mark_out;

	/**
	 * Security label
	 */
	sec_label_t *label;

	/**
	 * 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;

	/**
	 * Disable fragmenting packets across IP-TFS packets
	 */
	bool iptfs_dont_frag;

	/**
	 * 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 packets
	 */
	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){};
}

/**
 * Possible uses for security labels
 */
typedef enum {
	LABEL_USE_REQID,
	LABEL_USE_POLICY,
	LABEL_USE_SA,
} label_use_t;

/**
 * Returns the security label for either policies, SAs or reqids.
 */
static inline sec_label_t *label_for(private_child_sa_t *this, label_use_t use)
{
	/* For SELinux we use the configured label for policies and reqid but the
	 * negotiated one for the SAs. That's because the label on the policies is
	 * usually a generic one that matches specific labels on flows, which will
	 * trigger an acquire if no matching SA with that label exists yet.
	 * When that SA is later installed, we want to avoid having to install
	 * policies in the kernel that will never get used, so we use the configured
	 * label again.
	 * Note that while the labels don't have to be equal, they are both either
	 * NULL or defined.
	 */
	if (this->config->get_label_mode(this->config) == SEC_LABEL_MODE_SELINUX)
	{
		switch (use)
		{
			case LABEL_USE_REQID:
			case LABEL_USE_POLICY:
				return this->config->get_label(this->config);
			case LABEL_USE_SA:
				return this->label;
		}
	}
	/* for the simple label mode we don't pass labels to the kernel, so we don't
	 * use it to acquire unique reqids either */
	return NULL;
}

METHOD(child_sa_t, get_name, char*,
	   private_child_sa_t *this)
{
	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_reqid_ref, uint32_t,
	   private_child_sa_t *this)
{
	if ((this->reqid_allocated || (!this->static_reqid && this->reqid)) &&
		charon->kernel->ref_reqid(charon->kernel, this->reqid) == SUCCESS)
	{
		return this->reqid;
	}
	return 0;
}

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{%u} 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, set_iptfs_dont_fragment, void,
	private_child_sa_t *this)
{
	this->iptfs_dont_frag = TRUE;
}

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);
}

/**
 * Create an enumerator over two lists of traffic selectors, returning all the
 * pairs of traffic selectors from the first and second list.
 */
static enumerator_t *create_policy_enumerator_internal(array_t *my_ts,
													   array_t *other_ts)
{
	policy_enumerator_t *e;

	INIT(e,
		.public = {
			.enumerate = enumerator_enumerate_default,
			.venumerate = _policy_enumerate,
			.destroy = _policy_destroy,
		},
		.mine = array_create_enumerator(my_ts),
		.other = array_create_enumerator(other_ts),
		.array = other_ts,
		.ts = NULL,
	);

	return &e->public;
}

METHOD(child_sa_t, create_policy_enumerator, enumerator_t*,
	   private_child_sa_t *this)
{
	return create_policy_enumerator_internal(this->my_ts, this->other_ts);
}

/**
 * update the cached usebytes
 * returns SUCCESS if the usebytes have changed, FAILED if not or no SPIs
 * 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->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->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,
				.label = label_for(this, LABEL_USE_POLICY),
			};
			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),
				.label = label_for(this, LABEL_USE_POLICY),
			};
			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)
{
	status_t status = NOT_SUPPORTED;
	bool sa_use_time;

	sa_use_time = charon->kernel->get_features(charon->kernel) & KERNEL_SA_USE_TIME;

	if (bytes || packets || sa_use_time)
	{
		status = update_usebytes(this, inbound);
	}
	if (time && !sa_use_time && status != FAILED)
	{	/* query policies only if last use time is not available from SAs and
		 * there was either traffic or querying the SA wasn't supported */
		update_usetime(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_label, sec_label_t*,
	private_child_sa_t *this)
{
	return this->label ?: this->config->get_label(this->config);
}

METHOD(child_sa_t, get_acquire_seq, uint32_t,
	private_child_sa_t *this)
{
	return this->seq;
}

METHOD(child_sa_t, set_acquire_seq, void,
	private_child_sa_t *this, uint32_t seq)
{
	this->seq = seq;
}

METHOD(child_sa_t, get_cpu, uint32_t,
	private_child_sa_t *this)
{
	return this->cpu;
}

METHOD(child_sa_t, use_per_cpu, bool,
	private_child_sa_t *this)
{
	return this->per_cpu;
}

METHOD(child_sa_t, set_per_cpu, void,
	private_child_sa_t *this, bool per_cpu)
{
	if (!per_cpu)
	{
		this->cpu = CPU_ID_MAX;
	}
	this->per_cpu = per_cpu;
}

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;
}

/**
 * Allocate a reqid for the given local and remote traffic selector lists.
 * On success, release the previously allocated reqid.
 */
static status_t alloc_reqid_lists(private_child_sa_t *this,
								  linked_list_t *my_ts, linked_list_t *other_ts,
								  uint32_t *reqid)
{
	uint32_t existing_reqid = *reqid;
	status_t status;

	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, label_for(this, LABEL_USE_REQID),
							reqid);

	if (status == SUCCESS && existing_reqid)
	{
		if (charon->kernel->release_reqid(charon->kernel,
										  existing_reqid) != SUCCESS)
		{
			DBG1(DBG_CHD, "releasing previous reqid %u failed", existing_reqid);
		}
	}
	return status;
}

/**
 * Allocate a reqid for the given local and remote traffic selectors.
 */
static status_t alloc_reqid(private_child_sa_t *this, array_t *my_ts,
							array_t *other_ts, uint32_t *reqid)
{
	linked_list_t *my_ts_list, *other_ts_list;
	status_t status;

	my_ts_list = linked_list_create_from_enumerator(array_create_enumerator(my_ts));
	other_ts_list = linked_list_create_from_enumerator(array_create_enumerator(other_ts));
	status = alloc_reqid_lists(this, my_ts_list, other_ts_list, reqid);
	my_ts_list->destroy(my_ts_list);
	other_ts_list->destroy(other_ts_list);
	return status;
}

/**
 * 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;

		if (this->per_cpu && this->encap)
		{
			src = src->clone(src);
			/* accept inbound traffic from any port as we don't know if the
			 * peer uses random ports or not */
			src->set_port(src, 0);
		}
	}
	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);
		}
		if (this->per_cpu && this->encap &&
			this->config->has_option(this->config, OPT_PER_CPU_SAS_ENCAP))
		{
			src = src->clone(src);
			/* use a random source port between 49152 and 65535. doesn't matter
			 * if it's free or not as we don't receive traffic on it */
			src->set_port(src, 0xc000 | (random() & 0xffff));
		}
	}

	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 = alloc_reqid_lists(this, my_ts, other_ts, &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,
		.seq = this->seq,
		.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,
		.cpu = this->per_cpu ? this->cpu : CPU_ID_MAX,
		.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),
		.iptfs_dont_frag = this->iptfs_dont_frag,
		.forward_icmp = this->config->has_option(this->config, OPT_FORWARD_ICMP),
		.label = label_for(this, LABEL_USE_SA),
		.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);

	if (src != this->my_addr && src != this->other_addr)
	{
		src->destroy(src);
	}
	my_ts->destroy(my_ts);
	other_ts->destroy(other_ts);
	free(lifetime);

	if (status == SUCCESS)
	{
		if (inbound)
		{
			this->inbound_installed = TRUE;
		}
		else
		{
			this->outbound_state |= CHILD_OUTBOUND_SA;
		}
	}
	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 whether to install policies for this CHILD_SA
 */
static bool require_policies(private_child_sa_t *this)
{
	return !this->config->has_option(this->config, OPT_NO_POLICIES);
}

/**
 * Check if policy updates are required
 */
static bool require_policy_update(private_child_sa_t *this)
{
	kernel_feature_t f;

	if (!require_policies(this))
	{
		return FALSE;
	}

	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,
		.label = label_for(this, LABEL_USE_POLICY),
	};
	kernel_ipsec_manage_policy_t in_policy = {
		.type = type,
		.prio = priority,
		.manual_prio = manual_prio,
		.hw_offload = this->config->get_hw_offload(this->config),
		.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),
		.label = label_for(this, LABEL_USE_POLICY),
	};
	kernel_ipsec_manage_policy_t out_policy = {
		.type = type,
		.prio = priority,
		.manual_prio = manual_prio,
		.hw_offload = this->config->get_hw_offload(this->config),
		.src = my_addr,
		.dst = other_addr,
		.pcpu_acquires = this->per_cpu,
		.forward_icmp = this->config->has_option(this->config, OPT_FORWARD_ICMP),
		.sa = other_sa,
	};
	uint32_t reqid = other_sa->reqid;
	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 = 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,
		.label = label_for(this, LABEL_USE_POLICY),
	};
	kernel_ipsec_manage_policy_t in_policy = {
		.type = type,
		.prio = priority,
		.manual_prio = manual_prio,
		.hw_offload = this->config->get_hw_offload(this->config),
		.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),
		.label = label_for(this, LABEL_USE_POLICY),
	};
	kernel_ipsec_manage_policy_t out_policy = {
		.type = type,
		.prio = priority,
		.manual_prio = manual_prio,
		.hw_offload = this->config->get_hw_offload(this->config),
		.src = my_addr,
		.dst = other_addr,
		.pcpu_acquires = this->per_cpu,
		.forward_icmp = this->config->has_option(this->config, OPT_FORWARD_ICMP),
		.sa = other_sa,
	};
	uint32_t reqid = other_sa->reqid;

	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 = 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;
	traffic_selector_t *my_ts, *other_ts;
	status_t status = SUCCESS;
	bool install_outbound = FALSE;

	if (!this->reqid_allocated && !this->static_reqid)
	{
		status = alloc_reqid(this, this->my_ts, this->other_ts, &this->reqid);
		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 (require_policies(this))
	{
		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;
}

/**
 * Check if we can install the outbound SA immediately.
 *
 * If the kernel supports installing SPIs with policies, we can do so as it
 * will only be used once we update the policies.
 *
 * However, if we use labels with SELinux, we can't as we don't set SPIs
 * on the policy in order to match SAs with other labels that match the generic
 * label that's used on the policies. The same is the case for per-CPU SAs.
 */
static bool install_outbound_immediately(private_child_sa_t *this)
{
	if (charon->kernel->get_features(charon->kernel) & KERNEL_POLICY_SPI &&
		!this->per_cpu)
	{
		if (this->config->get_label_mode(this->config) == SEC_LABEL_MODE_SELINUX)
		{
			return !this->label;
		}
		return TRUE;
	}
	return FALSE;
}

METHOD(child_sa_t, register_outbound, status_t,
	private_child_sa_t *this, chunk_t encr, chunk_t integ, uint32_t spi,
	uint16_t cpi, bool initiator, bool tfcv3)
{
	status_t status;

	if (install_outbound_immediately(this))
	{
		status = install_internal(this, encr, integ, spi, cpi, initiator, 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->initiator = initiator;
		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,
								  this->initiator, 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 (require_policies(this) &&
		!(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 (require_policies(this) &&
		(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_sa, void,
	private_child_sa_t *this, child_sa_t *sa)
{
	this->rekey_sa = sa;
}

METHOD(child_sa_t, get_rekey_sa, child_sa_t*,
	private_child_sa_t *this)
{
	return this->rekey_sa;
}

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 bool update_sas(private_child_sa_t *this, host_t *me, host_t *other,
					   bool encap, uint32_t reqid, array_t *new_my_ts,
					   array_t *new_other_ts)
{
	linked_list_t *my_ts, *other_ts;
	bool success = FALSE, inbound_updated = FALSE;

	/* BEET requires the bound address from the traffic selectors */
	my_ts = linked_list_create_from_enumerator(
									array_create_enumerator(new_my_ts));
	other_ts = linked_list_create_from_enumerator(
									array_create_enumerator(new_other_ts));

	/* update our (initiator) SA */
	if (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 = {
			.mode = this->mode,
			.cpi = this->ipcomp != IPCOMP_NONE ? this->my_cpi : 0,
			.new_src = other,
			.new_dst = me,
			.encap = this->encap,
			.new_encap = encap,
			.old_reqid = this->reqid,
			.new_reqid = reqid ?: this->reqid,
			.hw_offload = this->config->get_hw_offload(this->config),
			.src_ts = other_ts,
			.dst_ts = my_ts,
			.inbound = TRUE,
		};
		if (charon->kernel->update_sa(charon->kernel, &id,
									  &sa) != SUCCESS)
		{
			goto failed;
		}
		inbound_updated = TRUE;
	}

	/* update his (responder) SA */
	if (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 = {
			.mode = this->mode,
			.cpi = this->ipcomp != IPCOMP_NONE ? this->other_cpi : 0,
			.new_src = me,
			.new_dst = other,
			.encap = this->encap,
			.new_encap = encap,
			.old_reqid = this->reqid,
			.new_reqid = reqid ?: this->reqid,
			.hw_offload = this->config->get_hw_offload(this->config),
			.src_ts = my_ts,
			.dst_ts = other_ts,
			.interface = this->config->get_interface(this->config),
		};
		if (charon->kernel->update_sa(charon->kernel, &id,
									  &sa) != SUCCESS)
		{
			if (inbound_updated)
			{
				/* restore the already migrated inbound SA as the rekey fallback
				 * is otherwise not able to delete it using the old addresses */
				kernel_ipsec_sa_id_t restore_id = {
					.src = other,
					.dst = me,
					.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 restore_sa = {
					.mode = this->mode,
					.cpi = this->ipcomp != IPCOMP_NONE ? this->my_cpi : 0,
					.new_src = this->other_addr,
					.new_dst = this->my_addr,
					.encap = encap,
					.new_encap = this->encap,
					.old_reqid = reqid ?: this->reqid,
					.new_reqid = this->reqid,
					.hw_offload = this->config->get_hw_offload(this->config),
					.inbound = TRUE,
				};
				linked_list_t *restore_my_ts, *restore_other_ts;

				/* we need to use the original TS */
				restore_my_ts = linked_list_create_from_enumerator(
									array_create_enumerator(this->my_ts));
				restore_other_ts = linked_list_create_from_enumerator(
									array_create_enumerator(this->other_ts));
				restore_sa.src_ts = restore_other_ts;
				restore_sa.dst_ts = restore_my_ts;

				if (charon->kernel->update_sa(charon->kernel, &restore_id,
											  &restore_sa) != SUCCESS)
				{
					/* delete the migrated inbound SA if we can't restore it
					 * to avoid it's left in the kernel after the rekeying */
					kernel_ipsec_del_sa_t del = {
						.cpi = this->ipcomp != IPCOMP_NONE ? this->my_cpi : 0,
					};
					DBG1(DBG_CHD, "failed to restore inbound SA with SPI "
						 "0x%.8x, deleting it", ntohl(this->my_spi));
					charon->kernel->del_sa(charon->kernel, &restore_id, &del);
				}
				else
				{
					DBG1(DBG_CHD, "restored inbound SA with SPI 0x%.8x to "
						 "previous addresses after failure to update outbound "
						 "SA", ntohl(this->my_spi));
				}
				restore_my_ts->destroy(restore_my_ts);
				restore_other_ts->destroy(restore_other_ts);
			}
			goto failed;
		}
	}
	success = TRUE;

failed:
	my_ts->destroy(my_ts);
	other_ts->destroy(other_ts);
	return success;
}

/**
 * Fill the second list with copies of the given traffic selectors updating
 * dynamic traffic selectors based on the given addresses.
 */
static void update_ts(host_t *old_host, host_t *new_host, array_t *old_list,
					  array_t *new_list)
{
	enumerator_t *enumerator;
	traffic_selector_t *old_ts, *new_ts;

	enumerator = array_create_enumerator(old_list);
	while (enumerator->enumerate(enumerator, &old_ts))
	{
		new_ts = old_ts->clone(old_ts);
		if (new_ts->is_host(new_ts, old_host))
		{
			new_ts->set_address(new_ts, new_host);
		}
		array_insert(new_list, ARRAY_TAIL, new_ts);
	}
	enumerator->destroy(enumerator);
	array_sort(new_list, (void*)traffic_selector_cmp, NULL);
}

METHOD(child_sa_t, update, status_t,
	private_child_sa_t *this, host_t *me, host_t *other, linked_list_t *vips,
	bool encap)
{
	array_t *new_my_ts = NULL, *new_other_ts = NULL;
	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 (require_policy_update(this) && 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;
		policy_priority_t priority;
		uint32_t manual_prio, new_reqid = 0;
		bool outbound, sas_updated;

		prepare_sa_cfg(this, &my_sa, &other_sa);
		manual_prio = this->config->get_manual_prio(this->config);
		priority = this->trap ? POLICY_PRIORITY_ROUTED
							  : POLICY_PRIORITY_DEFAULT;
		outbound = (this->outbound_state & CHILD_OUTBOUND_POLICIES) || this->trap;

		enumerator = create_policy_enumerator(this);
		while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
		{
			if (!new_my_ts && !me->ip_equals(me, this->my_addr) &&
				my_ts->is_host(my_ts, this->my_addr))
			{
				new_my_ts = array_create(0, 0);
			}
			if (!new_other_ts && !other->ip_equals(other, this->other_addr) &&
				other_ts->is_host(other_ts, this->other_addr))
			{
				new_other_ts = array_create(0, 0);
			}
			/* install drop policy to avoid traffic leaks, acquires etc. */
			if (outbound)
			{
				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,
						priority, manual_prio, outbound);
		}
		enumerator->destroy(enumerator);

		if (new_my_ts)
		{
			update_ts(this->my_addr, me, this->my_ts, new_my_ts);
		}
		if (new_other_ts)
		{
			update_ts(this->other_addr, other, this->other_ts, new_other_ts);
		}
		if (this->reqid_allocated && (new_my_ts || new_other_ts))
		{
			/* if we allocated a reqid with the previous TS, we have to get a
			 * new one that matches the updated TS */
			if (alloc_reqid(this, new_my_ts ?: this->my_ts,
							new_other_ts ?: this->other_ts, &new_reqid) != SUCCESS)
			{
				DBG1(DBG_CHD, "allocating new reqid for updated SA failed");
			}
		}

		/* update the IPsec SAs */
		sas_updated = update_sas(this, me, other, encap, new_reqid,
								 new_my_ts ?: this->my_ts,
								 new_other_ts ?: this->other_ts);

		/* install new/updated policies only if we were able to update the
		 * SAs, otherwise we reinstall the old policies further below */
		if (sas_updated)
		{
			/* we reinstall the virtual IP to handle interface roaming
			 * correctly */
			if (vips)
			{
				vips->invoke_function(vips, reinstall_vip, me);
			}
			if (new_reqid)
			{
				my_sa.reqid = other_sa.reqid = new_reqid;
			}
			enumerator = create_policy_enumerator_internal(
												new_my_ts ?: this->my_ts,
												new_other_ts ?: this->other_ts);
			while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
			{
				install_policies_internal(this, me, other, my_ts, other_ts,
										  &my_sa, &other_sa, POLICY_IPSEC,
										  priority, manual_prio, outbound);
			}
			enumerator->destroy(enumerator);
			if (new_reqid)
			{
				my_sa.reqid = other_sa.reqid = this->reqid;
			}
		}

		enumerator = create_policy_enumerator(this);
		while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
		{
			/* reinstall the previous policies if we can't update the SAs */
			if (!sas_updated)
			{
				install_policies_internal(this, this->my_addr, this->other_addr,
						my_ts, other_ts, &my_sa, &other_sa, POLICY_IPSEC,
						priority, manual_prio, outbound);
			}
			/* remove the drop policy */
			if (outbound)
			{
				del_policies_outbound(this, this->my_addr, this->other_addr,
						my_ts, other_ts, &my_sa, &other_sa, POLICY_DROP,
						POLICY_PRIORITY_DEFAULT, manual_prio);
			}
		}
		enumerator->destroy(enumerator);

		if (!sas_updated)
		{
			if (new_reqid &&
				charon->kernel->release_reqid(charon->kernel,
											  new_reqid) != SUCCESS)
			{
				DBG1(DBG_CHD, "releasing reqid %u failed", new_reqid);
			}
			array_destroy_offset(new_my_ts,
								 offsetof(traffic_selector_t, destroy));
			array_destroy_offset(new_other_ts,
								 offsetof(traffic_selector_t, destroy));
			set_state(this, old);
			return NOT_SUPPORTED;
		}

		if (new_reqid)
		{
			if (charon->kernel->release_reqid(charon->kernel,
											  this->reqid) != SUCCESS)
			{
				DBG1(DBG_CHD, "releasing reqid %u failed", this->reqid);
			}
			DBG1(DBG_CHD, "replaced reqid %u with reqid %u for updated "
				 "CHILD_SA %s{%u}", this->reqid, new_reqid, get_name(this),
				 this->unique_id);
			this->reqid = new_reqid;
		}
	}
	else if (!transport_proxy_mode)
	{
		if (!me->ip_equals(me, this->my_addr))
		{
			new_my_ts = array_create(0, 0);
			update_ts(this->my_addr, me, this->my_ts, new_my_ts);
		}
		if (!other->ip_equals(other, this->other_addr))
		{
			new_other_ts = array_create(0, 0);
			update_ts(this->other_addr, other, this->other_ts, new_other_ts);
		}
		if (!update_sas(this, me, other, encap, 0,
						new_my_ts ?: this->my_ts,
						new_other_ts ?: this->other_ts))
		{
			array_destroy_offset(new_my_ts,
								 offsetof(traffic_selector_t, destroy));
			array_destroy_offset(new_other_ts,
								 offsetof(traffic_selector_t, destroy));
			set_state(this, old);
			return NOT_SUPPORTED;
		}
	}

	if (new_my_ts)
	{
		array_destroy_offset(this->my_ts,
							 offsetof(traffic_selector_t, destroy));
		this->my_ts = new_my_ts;
	}
	if (new_other_ts)
	{
		array_destroy_offset(this->other_ts,
							 offsetof(traffic_selector_t, destroy));
		this->other_ts = new_other_ts;
	}

	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 (require_policies(this))
	{
		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->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 || (!this->static_reqid && this->reqid))
	{
		if (charon->kernel->release_reqid(charon->kernel,
										  this->reqid) != 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);
	DESTROY_IF(this->label);
	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, list);
	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;

	INIT(this,
		.public = {
			.get_name = _get_name,
			.get_reqid = _get_reqid,
			.get_reqid_ref = _get_reqid_ref,
			.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,
			.get_label = _get_label,
			.get_cpu = _get_cpu,
			.set_per_cpu = _set_per_cpu,
			.use_per_cpu = _use_per_cpu,
			.get_acquire_seq = _get_acquire_seq,
			.set_acquire_seq = _set_acquire_seq,
			.has_encap = _has_encap,
			.get_ipcomp = _get_ipcomp,
			.set_ipcomp = _set_ipcomp,
			.set_iptfs_dont_fragment = _set_iptfs_dont_fragment,
			.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_sa = _set_rekey_sa,
			.get_rekey_sa = _get_rekey_sa,
			.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_nonzero(&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,
		.label = data->label ? data->label->clone(data->label) : NULL,
		.cpu = data->cpu,
		.per_cpu = data->per_cpu,
		.seq = data->seq,
		.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);
	allocate_unique_marks(&this->mark_in.value, &this->mark_out.value);

	if (!this->reqid)
	{
		/* reuse old reqid if we are rekeying an existing CHILD_SA and when
		 * initiating a trap policy. the reqid cache will generally find the
		 * same reqid for our selectors. but this does not work in a special
		 * case: if the IPsec stack does not use sequence numbers for acquires,
		 * an SA is triggered by a trap policy and the negotiated TS get
		 * narrowed, we still must reuse the same reqid to successfully replace
		 * the temporary SA on the kernel level. however, if sequence numbers
		 * are supported, the reqid will later get updated in case of narrowing
		 * when alloc_reqid() is called */
		if (data->reqid &&
			charon->kernel->ref_reqid(charon->kernel, data->reqid) == SUCCESS)
		{
			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;
}

/**
 * Check if the given traffic selector is contained in any of the traffic
 * selectors in the given list.
 */
static bool is_ts_match(traffic_selector_t *to_check, array_t *list)
{
	traffic_selector_t *ts;
	int i;

	for (i = 0; i < array_count(list); i++)
	{
		array_get(list, i, &ts);
		if (to_check->is_contained_in(to_check, ts))
		{
			return TRUE;
		}
	}
	return FALSE;
}

/**
 * Check if all given traffic selectors are contained in any of the traffic
 * selectors in the given list.
 */
static bool is_ts_list_match(traffic_selector_list_t *to_check, array_t *list)
{
	enumerator_t *enumerator;
	traffic_selector_t *ts;
	bool matched = TRUE;

	enumerator = to_check->create_enumerator(to_check);
	while (enumerator->enumerate(enumerator, &ts))
	{
		if (!is_ts_match(ts, list))
		{
			matched = FALSE;
			break;
		}
	}
	enumerator->destroy(enumerator);
	return matched;
}

/*
 * Described in header
 */
bool child_sa_ts_match(child_sa_t *child, traffic_selector_t *src,
					   traffic_selector_t *dst)
{
	private_child_sa_t *this = (private_child_sa_t*)child;

	return src && dst &&
		   is_ts_match(src, this->my_ts) &&
		   is_ts_match(dst, this->other_ts);
}

/*
 * Described in header
 */
bool child_sa_ts_lists_match(child_sa_t *child, traffic_selector_list_t *src,
							 traffic_selector_list_t *dst)
{
	private_child_sa_t *this = (private_child_sa_t*)child;

	return src && dst &&
		   is_ts_list_match(src, this->my_ts) &&
		   is_ts_list_match(dst, this->other_ts);
}
