Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Max child SA limit per configuration #1856

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/libcharon/config/child_cfg.c
Expand Up @@ -183,6 +183,11 @@ struct private_child_cfg_t {
* DS header field copy mode
*/
dscp_copy_t copy_dscp;

/**
* Maximum number of child SA pairs to allow on this child_cfg
*/
uint32_t max_child_sas;
};

METHOD(child_cfg_t, get_name, char*,
Expand Down Expand Up @@ -660,6 +665,12 @@ METHOD(child_cfg_t, set_replay_window, void,
this->replay_window = replay_window;
}

METHOD(child_cfg_t, get_max_child_sas, uint32_t,
private_child_cfg_t *this)
{
return this->max_child_sas;
}

#define LT_PART_EQUALS(a, b) ({ a.life == b.life && a.rekey == b.rekey && a.jitter == b.jitter; })
#define LIFETIME_EQUALS(a, b) ({ LT_PART_EQUALS(a.time, b.time) && LT_PART_EQUALS(a.bytes, b.bytes) && LT_PART_EQUALS(a.packets, b.packets); })

Expand Down Expand Up @@ -717,7 +728,8 @@ METHOD(child_cfg_t, equals, bool,
streq(this->updown, other->updown) &&
streq(this->interface, other->interface) &&
sec_labels_equal(this->label, other->label) &&
this->label_mode == other->label_mode;
this->label_mode == other->label_mode &&
this->max_child_sas == other->max_child_sas;
}

METHOD(child_cfg_t, get_ref, child_cfg_t*,
Expand Down Expand Up @@ -784,6 +796,7 @@ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data)
.destroy = _destroy,
.get_hw_offload = _get_hw_offload,
.get_copy_dscp = _get_copy_dscp,
.get_max_child_sas = _get_max_child_sas,
},
.name = strdup(name),
.options = data->options,
Expand Down Expand Up @@ -815,6 +828,7 @@ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data)
"%s.replay_window", DEFAULT_REPLAY_WINDOW, lib->ns),
.hw_offload = data->hw_offload,
.copy_dscp = data->copy_dscp,
.max_child_sas = data->max_child_sas,
);

return &this->public;
Expand Down
9 changes: 9 additions & 0 deletions src/libcharon/config/child_cfg.h
Expand Up @@ -323,6 +323,13 @@ struct child_cfg_t {
*/
void (*set_replay_window)(child_cfg_t *this, uint32_t window);

/**
* Get maximum child SA pairs allowed for this child_cfg
*
* @return max_child_sas
*/
uint32_t (*get_max_child_sas)(child_cfg_t *this);

/**
* Check if an option flag is set.
*
Expand Down Expand Up @@ -436,6 +443,8 @@ struct child_cfg_create_t {
hw_offload_t hw_offload;
/** How to handle the DS header field in tunnel mode */
dscp_copy_t copy_dscp;
/** Maximum number of child SA pairs to allow for this child_cfg */
uint32_t max_child_sas;
};

/**
Expand Down
2 changes: 2 additions & 0 deletions src/libcharon/plugins/vici/vici_config.c
Expand Up @@ -576,6 +576,7 @@ static void log_child_data(child_data_t *data, char *name)
DBG2(DBG_CFG, " copy_df = %u", !has_opt(OPT_NO_COPY_DF));
DBG2(DBG_CFG, " copy_ecn = %u", !has_opt(OPT_NO_COPY_ECN));
DBG2(DBG_CFG, " copy_dscp = %N", dscp_copy_names, cfg->copy_dscp);
DBG2(DBG_CFG, " max_child_sas = %u", cfg->max_child_sas);
}

/**
Expand Down Expand Up @@ -1833,6 +1834,7 @@ CALLBACK(child_kv, bool,
{ "if_id_out", parse_if_id, &child->cfg.if_id_out },
{ "label", parse_label, &child->cfg.label },
{ "label_mode", parse_label_mode, &child->cfg.label_mode },
{ "max_child_sas", parse_uint32, &child->cfg.max_child_sas },
};

return parse_rules(rules, countof(rules), name, value,
Expand Down
58 changes: 56 additions & 2 deletions src/libcharon/sa/ikev2/tasks/child_create.c
Expand Up @@ -1124,6 +1124,52 @@ static bool check_for_duplicate(private_child_create_t *this)
return found;
}

/**
* Check how many CHILD_SAs are already established for the given
* IKE SA using the same child cfg policy
*/
static bool check_for_max_child_sa_reached(private_child_create_t *this)
{
enumerator_t *enumerator;
child_sa_t *child_sa;
uint32_t child_sa_cnt = 0, limit;
child_cfg_t *cfg = this->child_sa->get_config(this->child_sa);

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 &&
streq(this->child_sa->get_name(this->child_sa),
child_sa->get_name(child_sa)))
{
child_sa_cnt++;
}
}
enumerator->destroy(enumerator);
/* Increment child SA count to include the one being established */
child_sa_cnt++;
limit = cfg->get_max_child_sas(cfg);
if (limit && (child_sa_cnt > limit))
{
DBG1(DBG_IKE, "blocking CHILD_SA %s{%d} max_child_sas limit: %d "
"exceeded by expected CHILD_SA cnt: %d",
this->child_sa->get_name(this->child_sa),
this->child_sa->get_unique_id(this->child_sa),
limit,
child_sa_cnt);
return TRUE;
}
else
{
DBG1(DBG_IKE, "checked max_child_sas limit (%d/%d), allowing CHILD_SA %s{%d} ",
child_sa_cnt,
limit,
this->child_sa->get_name(this->child_sa),
this->child_sa->get_unique_id(this->child_sa));
return FALSE;
}
}

/**
* Check if this is an attempt to create an SA with generic label and should
* be aborted.
Expand Down Expand Up @@ -1363,9 +1409,10 @@ METHOD(task_t, process_r, status_t,
static void handle_child_sa_failure(private_child_create_t *this,
message_t *message)
{
bool is_first;
bool is_first, is_rekey;

is_first = message->get_exchange_type(message) == IKE_AUTH;
is_rekey = this->rekey;
if (is_first &&
lib->settings->get_bool(lib->settings,
"%s.close_ike_on_child_failure", FALSE, lib->ns))
Expand All @@ -1381,7 +1428,7 @@ static void handle_child_sa_failure(private_child_create_t *this,
{
DBG1(DBG_IKE, "failed to establish CHILD_SA, keeping IKE_SA");
charon->bus->alert(charon->bus, ALERT_KEEP_ON_CHILD_SA_FAILURE,
is_first);
is_first, is_rekey);
}
}

Expand Down Expand Up @@ -1535,6 +1582,13 @@ METHOD(task_t, build_r, status_t,
enumerator_t *enumerator;
bool no_dh = TRUE, ike_auth = FALSE;

if (!this->rekey && check_for_max_child_sa_reached(this))
{
message->add_notify(message, FALSE, NO_ADDITIONAL_SAS, chunk_empty);
handle_child_sa_failure(this, message);
return SUCCESS;
}

switch (message->get_exchange_type(message))
{
case IKE_SA_INIT:
Expand Down
10 changes: 10 additions & 0 deletions src/swanctl/swanctl.opt
Expand Up @@ -1152,6 +1152,16 @@ connections.<conn>.children.<child>.close_action = none
alive. It acts on explicit close messages only, but not on negotiation
failures. Use trap policies to reliably re-create failed CHILD_SAs.

connections.<conn>.children.<child>.max_child_sas = 0
Maximum number of CHILD_SA pairs to allow for this child_cfg

Sets the maximum number of CHILD_SA pairs to allow for this child_cfg. If
configured, during CHILD_SA negotiation of new CHILD_SAs this configuration
will be compared against the existing number of child SAs already negotiated
and allowed via this child_cfg and will reject any additional child SAs with
a NO_ADDITIONAL_SAS notify. Default 0 will mean no limit on the number of
CHILD_SA pairs for the child_cfg.

secrets { # }
Section defining secrets for IKE/EAP/XAuth authentication and private
key decryption.
Expand Down