strongswan/src/libcharon/config/child_cfg.c

705 lines
16 KiB
C

/*
* Copyright (C) 2008-2019 Tobias Brunner
* Copyright (C) 2016 Andreas Steffen
* Copyright (C) 2005-2007 Martin Willi
* 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.
*/
#include "child_cfg.h"
#include <stdint.h>
#include <daemon.h>
ENUM(action_names, ACTION_NONE, ACTION_RESTART,
"clear",
"hold",
"restart",
);
/** Default replay window size, if not set using charon.replay_window */
#define DEFAULT_REPLAY_WINDOW 32
typedef struct private_child_cfg_t private_child_cfg_t;
/**
* Private data of an child_cfg_t object
*/
struct private_child_cfg_t {
/**
* Public part
*/
child_cfg_t public;
/**
* Number of references hold by others to this child_cfg
*/
refcount_t refcount;
/**
* Name of the child_cfg, used to query it
*/
char *name;
/**
* Options
*/
child_cfg_option_t options;
/**
* list for all proposals
*/
linked_list_t *proposals;
/**
* list for traffic selectors for my site
*/
linked_list_t *my_ts;
/**
* list for traffic selectors for others site
*/
linked_list_t *other_ts;
/**
* updown script
*/
char *updown;
/**
* Mode to propose for a initiated CHILD: tunnel/transport
*/
ipsec_mode_t mode;
/**
* action to take to start CHILD_SA
*/
action_t start_action;
/**
* action to take on DPD
*/
action_t dpd_action;
/**
* action to take on CHILD_SA close
*/
action_t close_action;
/**
* CHILD_SA lifetime config
*/
lifetime_cfg_t lifetime;
/**
* Inactivity timeout
*/
uint32_t inactivity;
/**
* Reqid to install CHILD_SA with
*/
uint32_t reqid;
/**
* Optional interface ID to use for inbound CHILD_SA
*/
uint32_t if_id_in;
/**
* Optional interface ID to use for outbound CHILD_SA
*/
uint32_t if_id_out;
/**
* Optional mark to install inbound CHILD_SA with
*/
mark_t mark_in;
/**
* Optional mark to install outbound CHILD_SA with
*/
mark_t mark_out;
/**
* Optional mark to set to packets after inbound processing
*/
mark_t set_mark_in;
/**
* Optional mark to set to packets after outbound processing
*/
mark_t set_mark_out;
/**
* Traffic Flow Confidentiality padding, if enabled
*/
uint32_t tfc;
/**
* Optional manually-set IPsec policy priorities
*/
uint32_t manual_prio;
/**
* Optional restriction of IPsec policy to a given network interface
*/
char *interface;
/**
* anti-replay window size
*/
uint32_t replay_window;
/**
* HW offload mode
*/
hw_offload_t hw_offload;
/**
* DS header field copy mode
*/
dscp_copy_t copy_dscp;
};
METHOD(child_cfg_t, get_name, char*,
private_child_cfg_t *this)
{
return this->name;
}
METHOD(child_cfg_t, has_option, bool,
private_child_cfg_t *this, child_cfg_option_t option)
{
return this->options & option;
}
METHOD(child_cfg_t, add_proposal, void,
private_child_cfg_t *this, proposal_t *proposal)
{
if (proposal)
{
this->proposals->insert_last(this->proposals, proposal);
}
}
CALLBACK(match_proposal, bool,
proposal_t *item, va_list args)
{
proposal_t *proposal;
VA_ARGS_VGET(args, proposal);
return item->equals(item, proposal);
}
METHOD(child_cfg_t, get_proposals, linked_list_t*,
private_child_cfg_t *this, bool strip_dh)
{
enumerator_t *enumerator;
proposal_t *current;
proposal_selection_flag_t flags = 0;
linked_list_t *proposals = linked_list_create();
if (strip_dh)
{
flags |= PROPOSAL_SKIP_DH;
}
enumerator = this->proposals->create_enumerator(this->proposals);
while (enumerator->enumerate(enumerator, &current))
{
current = current->clone(current, flags);
if (proposals->find_first(proposals, match_proposal, NULL, current))
{
current->destroy(current);
continue;
}
proposals->insert_last(proposals, current);
}
enumerator->destroy(enumerator);
DBG2(DBG_CFG, "configured proposals: %#P", proposals);
return proposals;
}
METHOD(child_cfg_t, select_proposal, proposal_t*,
private_child_cfg_t*this, linked_list_t *proposals,
proposal_selection_flag_t flags)
{
return proposal_select(this->proposals, proposals, flags);
}
METHOD(child_cfg_t, add_traffic_selector, void,
private_child_cfg_t *this, bool local, traffic_selector_t *ts)
{
if (local)
{
this->my_ts->insert_last(this->my_ts, ts);
}
else
{
this->other_ts->insert_last(this->other_ts, ts);
}
}
METHOD(child_cfg_t, get_traffic_selectors, linked_list_t*,
private_child_cfg_t *this, bool local, linked_list_t *supplied,
linked_list_t *hosts, bool log)
{
enumerator_t *e1, *e2;
traffic_selector_t *ts1, *ts2, *selected;
linked_list_t *result, *derived;
host_t *host;
result = linked_list_create();
derived = linked_list_create();
if (local)
{
e1 = this->my_ts->create_enumerator(this->my_ts);
}
else
{
e1 = this->other_ts->create_enumerator(this->other_ts);
}
/* in a first step, replace "dynamic" TS with the host list */
while (e1->enumerate(e1, &ts1))
{
if (hosts && hosts->get_count(hosts))
{ /* set hosts if TS is dynamic or as initiator in transport mode */
bool dynamic = ts1->is_dynamic(ts1),
proxy_mode = has_option(this, OPT_PROXY_MODE);
if (dynamic || (this->mode == MODE_TRANSPORT && !proxy_mode &&
!supplied))
{
e2 = hosts->create_enumerator(hosts);
while (e2->enumerate(e2, &host))
{
ts2 = ts1->clone(ts1);
if (dynamic || !host->is_anyaddr(host))
{ /* don't make regular TS larger than they were */
ts2->set_address(ts2, host);
}
derived->insert_last(derived, ts2);
}
e2->destroy(e2);
continue;
}
}
derived->insert_last(derived, ts1->clone(ts1));
}
e1->destroy(e1);
if (log)
{
DBG2(DBG_CFG, "%s traffic selectors for %s:",
supplied ? "selecting" : "proposing", local ? "us" : "other");
}
if (!supplied)
{
while (derived->remove_first(derived, (void**)&ts1) == SUCCESS)
{
if (log)
{
DBG2(DBG_CFG, " %R", ts1);
}
result->insert_last(result, ts1);
}
derived->destroy(derived);
}
else
{
e1 = derived->create_enumerator(derived);
e2 = supplied->create_enumerator(supplied);
/* enumerate all configured/derived selectors */
while (e1->enumerate(e1, &ts1))
{
/* enumerate all supplied traffic selectors */
while (e2->enumerate(e2, &ts2))
{
selected = ts1->get_subset(ts1, ts2);
if (selected)
{
if (log)
{
DBG2(DBG_CFG, " config: %R, received: %R => match: %R",
ts1, ts2, selected);
}
result->insert_last(result, selected);
}
else if (log)
{
DBG2(DBG_CFG, " config: %R, received: %R => no match",
ts1, ts2);
}
}
supplied->reset_enumerator(supplied, e2);
}
e1->destroy(e1);
e2->destroy(e2);
/* check if we/peer did any narrowing, raise alert */
e1 = derived->create_enumerator(derived);
e2 = result->create_enumerator(result);
while (e1->enumerate(e1, &ts1))
{
if (!e2->enumerate(e2, &ts2) || !ts1->equals(ts1, ts2))
{
charon->bus->alert(charon->bus, ALERT_TS_NARROWED,
local, result, this);
break;
}
}
e1->destroy(e1);
e2->destroy(e2);
derived->destroy_offset(derived, offsetof(traffic_selector_t, destroy));
}
/* remove any redundant traffic selectors in the list */
e1 = result->create_enumerator(result);
e2 = result->create_enumerator(result);
while (e1->enumerate(e1, &ts1))
{
while (e2->enumerate(e2, &ts2))
{
if (ts1 != ts2)
{
if (ts2->is_contained_in(ts2, ts1))
{
result->remove_at(result, e2);
ts2->destroy(ts2);
result->reset_enumerator(result, e1);
break;
}
if (ts1->is_contained_in(ts1, ts2))
{
result->remove_at(result, e1);
ts1->destroy(ts1);
break;
}
}
}
result->reset_enumerator(result, e2);
}
e1->destroy(e1);
e2->destroy(e2);
return result;
}
METHOD(child_cfg_t, get_updown, char*,
private_child_cfg_t *this)
{
return this->updown;
}
/**
* Applies jitter to the rekey value. Returns the new rekey value.
* Note: The distribution of random values is not perfect, but it
* should get the job done.
*/
static uint64_t apply_jitter(uint64_t rekey, uint64_t jitter)
{
if (jitter == 0)
{
return rekey;
}
jitter = (jitter == UINT64_MAX) ? jitter : jitter + 1;
return rekey - jitter * (random() / (RAND_MAX + 1.0));
}
#define APPLY_JITTER(l) l.rekey = apply_jitter(l.rekey, l.jitter)
METHOD(child_cfg_t, get_lifetime, lifetime_cfg_t*,
private_child_cfg_t *this, bool jitter)
{
lifetime_cfg_t *lft = malloc_thing(lifetime_cfg_t);
memcpy(lft, &this->lifetime, sizeof(lifetime_cfg_t));
if (!jitter)
{
lft->time.jitter = lft->bytes.jitter = lft->packets.jitter = 0;
}
APPLY_JITTER(lft->time);
APPLY_JITTER(lft->bytes);
APPLY_JITTER(lft->packets);
return lft;
}
METHOD(child_cfg_t, get_mode, ipsec_mode_t,
private_child_cfg_t *this)
{
return this->mode;
}
METHOD(child_cfg_t, get_start_action, action_t,
private_child_cfg_t *this)
{
return this->start_action;
}
METHOD(child_cfg_t, get_hw_offload, hw_offload_t,
private_child_cfg_t *this)
{
return this->hw_offload;
}
METHOD(child_cfg_t, get_copy_dscp, dscp_copy_t,
private_child_cfg_t *this)
{
return this->copy_dscp;
}
METHOD(child_cfg_t, get_dpd_action, action_t,
private_child_cfg_t *this)
{
return this->dpd_action;
}
METHOD(child_cfg_t, get_close_action, action_t,
private_child_cfg_t *this)
{
return this->close_action;
}
METHOD(child_cfg_t, get_dh_group, diffie_hellman_group_t,
private_child_cfg_t *this)
{
enumerator_t *enumerator;
proposal_t *proposal;
uint16_t dh_group = MODP_NONE;
enumerator = this->proposals->create_enumerator(this->proposals);
while (enumerator->enumerate(enumerator, &proposal))
{
if (proposal->get_algorithm(proposal, DIFFIE_HELLMAN_GROUP, &dh_group, NULL))
{
break;
}
}
enumerator->destroy(enumerator);
return dh_group;
}
METHOD(child_cfg_t, get_inactivity, uint32_t,
private_child_cfg_t *this)
{
return this->inactivity;
}
METHOD(child_cfg_t, get_reqid, uint32_t,
private_child_cfg_t *this)
{
return this->reqid;
}
METHOD(child_cfg_t, get_if_id, uint32_t,
private_child_cfg_t *this, bool inbound)
{
return inbound ? this->if_id_in : this->if_id_out;
}
METHOD(child_cfg_t, get_mark, mark_t,
private_child_cfg_t *this, bool inbound)
{
return inbound ? this->mark_in : this->mark_out;
}
METHOD(child_cfg_t, get_set_mark, mark_t,
private_child_cfg_t *this, bool inbound)
{
return inbound ? this->set_mark_in : this->set_mark_out;
}
METHOD(child_cfg_t, get_tfc, uint32_t,
private_child_cfg_t *this)
{
return this->tfc;
}
METHOD(child_cfg_t, get_manual_prio, uint32_t,
private_child_cfg_t *this)
{
return this->manual_prio;
}
METHOD(child_cfg_t, get_interface, char*,
private_child_cfg_t *this)
{
return this->interface;
}
METHOD(child_cfg_t, get_replay_window, uint32_t,
private_child_cfg_t *this)
{
return this->replay_window;
}
METHOD(child_cfg_t, set_replay_window, void,
private_child_cfg_t *this, uint32_t replay_window)
{
this->replay_window = replay_window;
}
#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); })
METHOD(child_cfg_t, equals, bool,
private_child_cfg_t *this, child_cfg_t *other_pub)
{
private_child_cfg_t *other = (private_child_cfg_t*)other_pub;
if (this == other)
{
return TRUE;
}
if (this->public.equals != other->public.equals)
{
return FALSE;
}
if (!this->proposals->equals_offset(this->proposals, other->proposals,
offsetof(proposal_t, equals)))
{
return FALSE;
}
if (!this->my_ts->equals_offset(this->my_ts, other->my_ts,
offsetof(traffic_selector_t, equals)))
{
return FALSE;
}
if (!this->other_ts->equals_offset(this->other_ts, other->other_ts,
offsetof(traffic_selector_t, equals)))
{
return FALSE;
}
return this->options == other->options &&
this->mode == other->mode &&
this->start_action == other->start_action &&
this->dpd_action == other->dpd_action &&
this->close_action == other->close_action &&
LIFETIME_EQUALS(this->lifetime, other->lifetime) &&
this->inactivity == other->inactivity &&
this->reqid == other->reqid &&
this->if_id_in == other->if_id_in &&
this->if_id_out == other->if_id_out &&
this->mark_in.value == other->mark_in.value &&
this->mark_in.mask == other->mark_in.mask &&
this->mark_out.value == other->mark_out.value &&
this->mark_out.mask == other->mark_out.mask &&
this->set_mark_in.value == other->set_mark_in.value &&
this->set_mark_in.mask == other->set_mark_in.mask &&
this->set_mark_out.value == other->set_mark_out.value &&
this->set_mark_out.mask == other->set_mark_out.mask &&
this->tfc == other->tfc &&
this->manual_prio == other->manual_prio &&
this->replay_window == other->replay_window &&
this->hw_offload == other->hw_offload &&
this->copy_dscp == other->copy_dscp &&
streq(this->updown, other->updown) &&
streq(this->interface, other->interface);
}
METHOD(child_cfg_t, get_ref, child_cfg_t*,
private_child_cfg_t *this)
{
ref_get(&this->refcount);
return &this->public;
}
METHOD(child_cfg_t, destroy, void,
private_child_cfg_t *this)
{
if (ref_put(&this->refcount))
{
this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy));
this->my_ts->destroy_offset(this->my_ts, offsetof(traffic_selector_t, destroy));
this->other_ts->destroy_offset(this->other_ts, offsetof(traffic_selector_t, destroy));
free(this->updown);
free(this->interface);
free(this->name);
free(this);
}
}
/*
* Described in header-file
*/
child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data)
{
private_child_cfg_t *this;
INIT(this,
.public = {
.get_name = _get_name,
.add_traffic_selector = _add_traffic_selector,
.get_traffic_selectors = _get_traffic_selectors,
.add_proposal = _add_proposal,
.get_proposals = _get_proposals,
.select_proposal = _select_proposal,
.get_updown = _get_updown,
.get_mode = _get_mode,
.get_start_action = _get_start_action,
.get_dpd_action = _get_dpd_action,
.get_close_action = _get_close_action,
.get_lifetime = _get_lifetime,
.get_dh_group = _get_dh_group,
.get_inactivity = _get_inactivity,
.get_reqid = _get_reqid,
.get_if_id = _get_if_id,
.get_mark = _get_mark,
.get_set_mark = _get_set_mark,
.get_tfc = _get_tfc,
.get_manual_prio = _get_manual_prio,
.get_interface = _get_interface,
.get_replay_window = _get_replay_window,
.set_replay_window = _set_replay_window,
.has_option = _has_option,
.equals = _equals,
.get_ref = _get_ref,
.destroy = _destroy,
.get_hw_offload = _get_hw_offload,
.get_copy_dscp = _get_copy_dscp,
},
.name = strdup(name),
.options = data->options,
.updown = strdupnull(data->updown),
.reqid = data->reqid,
.if_id_in = data->if_id_in,
.if_id_out = data->if_id_out,
.mode = data->mode,
.start_action = data->start_action,
.dpd_action = data->dpd_action,
.close_action = data->close_action,
.mark_in = data->mark_in,
.mark_out = data->mark_out,
.set_mark_in = data->set_mark_in,
.set_mark_out = data->set_mark_out,
.lifetime = data->lifetime,
.inactivity = data->inactivity,
.tfc = data->tfc,
.manual_prio = data->priority,
.interface = strdupnull(data->interface),
.refcount = 1,
.proposals = linked_list_create(),
.my_ts = linked_list_create(),
.other_ts = linked_list_create(),
.replay_window = lib->settings->get_int(lib->settings,
"%s.replay_window", DEFAULT_REPLAY_WINDOW, lib->ns),
.hw_offload = data->hw_offload,
.copy_dscp = data->copy_dscp,
);
return &this->public;
}