child-create: Add support to negotiate per-CPU SAs

This commit is contained in:
Tobias Brunner 2021-05-19 10:59:03 +02:00
parent 3a8bb93761
commit bdf882d3af
2 changed files with 199 additions and 0 deletions

View File

@ -16,12 +16,15 @@
* for more details.
*/
#include <unistd.h>
#include "child_create.h"
#include <daemon.h>
#include <sa/ikev2/keymat_v2.h>
#include <crypto/key_exchange.h>
#include <credentials/certificates/x509.h>
#include <collections/hashtable.h>
#include <encoding/payloads/sa_payload.h>
#include <encoding/payloads/ke_payload.h>
#include <encoding/payloads/ts_payload.h>
@ -37,6 +40,9 @@
typedef struct private_child_create_t private_child_create_t;
/** Assumed minimum CPU ID, used when searching for a CPU without SA */
#define CPU_ID_MIN 0
/**
* Private members of a child_create_t task.
*/
@ -127,6 +133,16 @@ struct private_child_create_t {
*/
traffic_selector_list_t *other_ts;
/**
* Resource info sent by the peer
*/
chunk_t resource_info;
/**
* Whether we received resource info (might be empty)
*/
bool resource_info_seen;
/**
* Key exchanges to perform
*/
@ -265,6 +281,7 @@ static void schedule_delayed_retry(private_child_create_t *this)
task->use_marks(task, this->child.mark_in, this->child.mark_out);
task->use_if_ids(task, this->child.if_id_in, this->child.if_id_out);
task->use_label(task, this->child.label);
task->use_per_cpu(task, this->child.per_cpu, this->child.cpu);
/* clone these directly as we don't have a public method */
if (this->my_ts && this->other_ts)
@ -873,6 +890,83 @@ static bool select_proposal(private_child_create_t *this, bool no_ke)
return TRUE;
}
/**
* Returns the number of CPUs that are configured on the system or only those
* that are currently online (if we can distinguish this).
*/
static uint32_t get_system_cpus(bool online)
{
long cpus;
#ifdef WIN32
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
cpus = sysinfo.dwNumberOfProcessors;
#else /* WIN32 */
cpus = sysconf(online ? _SC_NPROCESSORS_ONLN : _SC_NPROCESSORS_CONF);
if (cpus < 0)
{
DBG1(DBG_IKE, "failed to determine number of CPUs for this system");
/* assume we have at least one CPU */
cpus = 1;
}
#endif /* WIN32 */
return cpus;
}
/**
* Finds an unused CPU ID to be assigned to a responder's CHILD_SA, or return
* CPU_ID_MAX if we don't have a fallback yet.
*/
static uint32_t get_cpu(private_child_create_t *this)
{
enumerator_t *enumerator;
hashtable_t *used;
child_sa_t *child_sa;
bool found_fallback = FALSE;
uint32_t cpu;
used = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 32);
enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa);
while (enumerator->enumerate(enumerator, (void**)&child_sa))
{
if (child_sa->get_state(child_sa) == CHILD_INSTALLED &&
this->config->equals(this->config, child_sa->get_config(child_sa)))
{
cpu = child_sa->get_cpu(child_sa);
if (cpu != CPU_ID_MAX)
{
used->put(used, (void*)(uintptr_t)cpu, child_sa);
}
else
{
found_fallback = TRUE;
}
}
}
enumerator->destroy(enumerator);
if (!found_fallback)
{
cpu = CPU_ID_MAX;
}
else
{
cpu = get_system_cpus(TRUE) + CPU_ID_MIN - 1;
for (; cpu > CPU_ID_MIN; cpu--)
{
if (!used->get(used, (void*)(uintptr_t)cpu))
{
break;
}
}
}
used->destroy(used);
return cpu;
}
/**
* Add a KE payload if a key exchange is used. As responder we might already
* have stored the object in the list of completed exchanges.
@ -989,6 +1083,21 @@ static bool build_payloads(private_child_create_t *this, message_t *message)
message->add_notify(message, FALSE, ESP_TFC_PADDING_NOT_SUPPORTED,
chunk_empty);
}
if (!this->rekey && this->child.per_cpu)
{
chunk_t cpu_id = chunk_empty;
uint32_t cpu;
/* always send the notify, but possibly without CPU ID */
if (this->child.cpu != CPU_ID_MAX)
{
cpu = htonl(this->child.cpu);
cpu_id = chunk_from_thing(cpu);
}
message->add_notify(message, FALSE, SA_RESOURCE_INFO,
cpu_id);
}
return TRUE;
}
@ -1063,6 +1172,11 @@ static void handle_notify(private_child_create_t *this, notify_payload_t *notify
notify_type_names, notify->get_notify_type(notify));
this->tfcv3 = FALSE;
break;
case SA_RESOURCE_INFO:
chunk_free(&this->resource_info);
this->resource_info = chunk_clone(notify->get_notification_data(notify));
this->resource_info_seen = TRUE;
break;
default:
break;
}
@ -1384,6 +1498,7 @@ static bool child_sa_equals(private_child_create_t *this, child_sa_t *a,
a->get_mark(a, FALSE).value == b->get_mark(b, FALSE).value &&
a->get_if_id(a, TRUE) == b->get_if_id(b, TRUE) &&
a->get_if_id(a, FALSE) == b->get_if_id(b, FALSE) &&
a->get_cpu(a) == b->get_cpu(b) &&
sec_labels_equal(a->get_label(a), b->get_label(b)) &&
reqid_and_ts_equals(this, a, b);
}
@ -1593,6 +1708,11 @@ METHOD(task_t, build_i, status_t,
DBG2(DBG_CFG, "proposing security label '%s'",
this->child.label->get_string(this->child.label));
}
if (!this->rekey)
{
this->child.per_cpu = this->config->has_option(this->config,
OPT_PER_CPU_SAS);
}
this->proposals = this->config->get_proposals(this->config, no_ke);
this->mode = this->config->get_mode(this->config);
@ -1956,6 +2076,48 @@ static bool select_label(private_child_create_t *this)
return TRUE;
}
/**
* Handle per-resource information.
*/
static void handle_per_resource(private_child_create_t *this)
{
if (!this->resource_info_seen)
{
DBG1(DBG_IKE, "peer didn't send %N notify, creating regular SA",
notify_type_names, SA_RESOURCE_INFO);
this->child.per_cpu = FALSE;
this->child.cpu = CPU_ID_MAX;
return;
}
/* as responder, assign a CPU ID (or CPU_ID_MAX if we don't have a
* fallback SA yet) */
if (!this->initiator)
{
this->child.per_cpu = TRUE;
this->child.cpu = get_cpu(this);
}
if (this->child.cpu != CPU_ID_MAX && this->resource_info.len)
{
DBG1(DBG_IKE, "creating per-resource SA as one of several (our ID %u, "
"peer's ID %+B)", this->child.cpu, &this->resource_info);
}
else if (this->child.cpu != CPU_ID_MAX)
{
DBG1(DBG_IKE, "creating per-resource SA as one of several (our ID %u)",
this->child.cpu);
}
else if (this->resource_info.len)
{
DBG1(DBG_IKE, "creating SA as one of several per-resource SAs (peer's "
"ID %+B)", &this->resource_info);
}
else
{
DBG1(DBG_IKE, "creating SA as one of several per-resource SAs");
}
}
/**
* Called when a key exchange is done, returns TRUE once all are done.
*/
@ -2201,6 +2363,15 @@ METHOD(task_t, build_r, status_t,
this->child.if_id_in_def = this->ike_sa->get_if_id(this->ike_sa, TRUE);
this->child.if_id_out_def = this->ike_sa->get_if_id(this->ike_sa, FALSE);
this->child.encap = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY);
if (!this->rekey && this->config->has_option(this->config, OPT_PER_CPU_SAS))
{
handle_per_resource(this);
/* if it's a per-resource SA, we could check if we already have
* "too many" and reject this one with TS_MAX_QUEUE, but for now we just
* keep it */
}
this->child_sa = child_sa_create(this->ike_sa->get_my_host(this->ike_sa),
this->ike_sa->get_other_host(this->ike_sa),
this->config, &this->child);
@ -2550,6 +2721,13 @@ METHOD(task_t, process_i, status_t,
return delete_failed_sa(this);
}
if (!this->rekey && this->config->has_option(this->config, OPT_PER_CPU_SAS))
{
handle_per_resource(this);
/* the peer might not have returned the notify, update the flag */
this->child_sa->set_per_cpu(this->child_sa, this->child.per_cpu);
}
if (key_exchange_done_and_install_i(this, message, ike_auth) == NEED_MORE)
{
/* if the installation failed, we delete the failed SA, i.e. build() was
@ -2602,6 +2780,13 @@ METHOD(child_create_t, use_label, void,
this->child.label = label ? label->clone(label) : NULL;
}
METHOD(child_create_t, use_per_cpu, void,
private_child_create_t *this, bool per_cpu, uint32_t cpu)
{
this->child.per_cpu = per_cpu ? per_cpu : cpu != CPU_ID_MAX;
this->child.cpu = cpu;
}
/**
* Prepare traffic selectors for reuse when recreating a CHILD_SA.
*/
@ -2736,6 +2921,7 @@ METHOD(task_t, migrate, void,
chunk_free(&this->my_nonce);
chunk_free(&this->other_nonce);
chunk_free(&this->link);
chunk_free(&this->resource_info);
DESTROY_OFFSET_IF(this->tsr, offsetof(traffic_selector_t, destroy));
DESTROY_OFFSET_IF(this->tsi, offsetof(traffic_selector_t, destroy));
DESTROY_OFFSET_IF(this->labels_i, offsetof(sec_label_t, destroy));
@ -2767,6 +2953,7 @@ METHOD(task_t, migrate, void,
this->ipcomp_received = IPCOMP_NONE;
this->other_cpi = 0;
this->established = FALSE;
this->resource_info_seen = FALSE;
this->public.task.build = _build_i;
this->public.task.process = _process_i;
}
@ -2777,6 +2964,7 @@ METHOD(task_t, destroy, void,
chunk_free(&this->my_nonce);
chunk_free(&this->other_nonce);
chunk_free(&this->link);
chunk_free(&this->resource_info);
DESTROY_OFFSET_IF(this->tsr, offsetof(traffic_selector_t, destroy));
DESTROY_OFFSET_IF(this->tsi, offsetof(traffic_selector_t, destroy));
DESTROY_OFFSET_IF(this->labels_i, offsetof(sec_label_t, destroy));
@ -2824,6 +3012,7 @@ child_create_t *child_create_create(ike_sa_t *ike_sa,
.use_marks = _use_marks,
.use_if_ids = _use_if_ids,
.use_label = _use_label,
.use_per_cpu = _use_per_cpu,
.recreate_sa = _recreate_sa,
.abort = _abort_,
.task = {

View File

@ -80,6 +80,16 @@ struct child_create_t {
*/
void (*use_label)(child_create_t *this, sec_label_t *label);
/**
* Enable per-CPU feature, optionally with a specific CPU ID for the
* negotiated CHILD_SA.
*
* @param per_cpu TRUE to enable per-CPU feature (automatically set if
* cpu is not CPU_ID_MAX)
* @param cpu CPU ID
*/
void (*use_per_cpu)(child_create_t *this, bool per_cpu, uint32_t cpu);
/**
* Use data from the given old SA (e.g. KE method and traffic selectors)
* when rekeying/recreating it.