Files
strongswan-ext/src/libcharon/sa/trap_manager.c
T
Tobias Brunner bde5bd47bd ike-sa-manager: Rename checkout_new() to create_new()
We don't actually check that SA out (i.e. it's not registered with the
manager).  That was originally different but had to be changed with
86993d6b90 to avoid that SAs created for rekeying don't block other
threads on the manager.
2021-02-12 15:49:08 +01:00

700 lines
17 KiB
C

/*
* Copyright (C) 2011-2017 Tobias Brunner
* Copyright (C) 2009 Martin Willi
* 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 "trap_manager.h"
#include <daemon.h>
#include <threading/mutex.h>
#include <threading/rwlock.h>
#include <threading/rwlock_condvar.h>
#include <collections/linked_list.h>
#define INSTALL_DISABLED ((u_int)~0)
typedef struct private_trap_manager_t private_trap_manager_t;
typedef struct trap_listener_t trap_listener_t;
/**
* listener to track acquires
*/
struct trap_listener_t {
/**
* Implements listener interface
*/
listener_t listener;
/**
* points to trap_manager
*/
private_trap_manager_t *traps;
};
/**
* Private data of an trap_manager_t object.
*/
struct private_trap_manager_t {
/**
* Public trap_manager_t interface.
*/
trap_manager_t public;
/**
* Installed traps, as entry_t
*/
linked_list_t *traps;
/**
* read write lock for traps list
*/
rwlock_t *lock;
/**
* listener to track acquiring IKE_SAs
*/
trap_listener_t listener;
/**
* list of acquires we currently handle
*/
linked_list_t *acquires;
/**
* mutex for list of acquires
*/
mutex_t *mutex;
/**
* number of threads currently installing trap policies, or INSTALL_DISABLED
*/
u_int installing;
/**
* condvar to signal trap policy installation
*/
rwlock_condvar_t *condvar;
/**
* Whether to ignore traffic selectors from acquires
*/
bool ignore_acquire_ts;
};
/**
* A installed trap entry
*/
typedef struct {
/** name of the trapped CHILD_SA */
char *name;
/** ref to peer_cfg to initiate */
peer_cfg_t *peer_cfg;
/** ref to instantiated CHILD_SA (i.e the trap policy) */
child_sa_t *child_sa;
/** TRUE in case of wildcard Transport Mode SA */
bool wildcard;
} entry_t;
/**
* A handled acquire
*/
typedef struct {
/** pending IKE_SA connecting upon acquire */
ike_sa_t *ike_sa;
/** reqid of pending trap policy */
uint32_t reqid;
/** destination address (wildcard case) */
host_t *dst;
} acquire_t;
/**
* actually uninstall and destroy an installed entry
*/
static void destroy_entry(entry_t *this)
{
this->child_sa->destroy(this->child_sa);
this->peer_cfg->destroy(this->peer_cfg);
free(this->name);
free(this);
}
/**
* destroy a cached acquire entry
*/
static void destroy_acquire(acquire_t *this)
{
DESTROY_IF(this->dst);
free(this);
}
CALLBACK(acquire_by_reqid, bool,
acquire_t *this, va_list args)
{
uint32_t reqid;
VA_ARGS_VGET(args, reqid);
return this->reqid == reqid;
}
CALLBACK(acquire_by_dst, bool,
acquire_t *this, va_list args)
{
host_t *dst;
VA_ARGS_VGET(args, dst);
return this->dst && this->dst->ip_equals(this->dst, dst);
}
/**
* Check if any remote TS are dynamic
*/
static bool dynamic_remote_ts(child_cfg_t *child)
{
enumerator_t *enumerator;
linked_list_t *other_ts;
traffic_selector_t *ts;
bool found = FALSE;
other_ts = child->get_traffic_selectors(child, FALSE, NULL, NULL, FALSE);
enumerator = other_ts->create_enumerator(other_ts);
while (enumerator->enumerate(enumerator, &ts))
{
if (ts->is_dynamic(ts))
{
found = TRUE;
break;
}
}
enumerator->destroy(enumerator);
other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy));
return found;
}
METHOD(trap_manager_t, install, bool,
private_trap_manager_t *this, peer_cfg_t *peer, child_cfg_t *child)
{
entry_t *entry, *found = NULL;
ike_cfg_t *ike_cfg;
child_sa_t *child_sa;
host_t *me, *other;
linked_list_t *my_ts, *other_ts, *list;
enumerator_t *enumerator;
status_t status;
linked_list_t *proposals;
proposal_t *proposal;
protocol_id_t proto = PROTO_ESP;
bool result = FALSE, wildcard = FALSE;
/* try to resolve addresses */
ike_cfg = peer->get_ike_cfg(peer);
other = ike_cfg->resolve_other(ike_cfg, AF_UNSPEC);
if (other && other->is_anyaddr(other) &&
child->get_mode(child) == MODE_TRANSPORT)
{
/* allow wildcard for Transport Mode SAs */
me = host_create_any(other->get_family(other));
wildcard = TRUE;
}
else if (other && other->is_anyaddr(other))
{
other->destroy(other);
DBG1(DBG_CFG, "installing trap failed, remote address unknown");
return FALSE;
}
else
{ /* depending on the traffic selectors we don't really need a remote
* host yet, but we might fail later if no IP can be resolved */
if (!other && dynamic_remote_ts(child))
{ /* with dynamic TS we do need a host, otherwise 0.0.0.0/0 is used,
* which is probably not what users expect*/
DBG1(DBG_CFG, "installing trap failed, remote address unknown with "
"dynamic traffic selector");
return FALSE;
}
me = ike_cfg->resolve_me(ike_cfg, other ? other->get_family(other)
: AF_UNSPEC);
if (!other)
{
other = host_create_any(me ? me->get_family(me) : AF_INET);
}
other->set_port(other, ike_cfg->get_other_port(ike_cfg));
if ((!me || me->is_anyaddr(me)) && !other->is_anyaddr(other))
{
DESTROY_IF(me);
me = charon->kernel->get_source_addr(charon->kernel, other, NULL);
}
if (!me)
{
me = host_create_any(other->get_family(other));
}
me->set_port(me, ike_cfg->get_my_port(ike_cfg));
}
this->lock->write_lock(this->lock);
if (this->installing == INSTALL_DISABLED)
{ /* flush() has been called */
this->lock->unlock(this->lock);
other->destroy(other);
me->destroy(me);
return FALSE;
}
enumerator = this->traps->create_enumerator(this->traps);
while (enumerator->enumerate(enumerator, &entry))
{
if (streq(entry->name, child->get_name(child)) &&
streq(entry->peer_cfg->get_name(entry->peer_cfg),
peer->get_name(peer)))
{
found = entry;
if (entry->child_sa)
{ /* replace it with an updated version, if already installed */
this->traps->remove_at(this->traps, enumerator);
}
break;
}
}
enumerator->destroy(enumerator);
if (found)
{
if (!found->child_sa)
{
DBG1(DBG_CFG, "CHILD_SA '%s' is already being routed", found->name);
this->lock->unlock(this->lock);
other->destroy(other);
me->destroy(me);
return FALSE;
}
/* config might have changed so update everything */
DBG1(DBG_CFG, "updating already routed CHILD_SA '%s'", found->name);
}
INIT(entry,
.name = strdup(child->get_name(child)),
.peer_cfg = peer->get_ref(peer),
.wildcard = wildcard,
);
this->traps->insert_first(this->traps, entry);
this->installing++;
/* don't hold lock while creating CHILD_SA and installing policies */
this->lock->unlock(this->lock);
/* create and route CHILD_SA */
child_sa_create_t child_data = {
/* TODO: no reason to allocate unique interface IDs, there is currently
* no event to use them upon trap installation and we'd also have to
* pass them in a later initiate() call */
.if_id_in_def = peer->get_if_id(peer, TRUE),
.if_id_out_def = peer->get_if_id(peer, FALSE),
};
child_sa = child_sa_create(me, other, child, &child_data);
list = linked_list_create_with_items(me, NULL);
my_ts = child->get_traffic_selectors(child, TRUE, NULL, list, FALSE);
list->destroy_offset(list, offsetof(host_t, destroy));
list = linked_list_create_with_items(other, NULL);
other_ts = child->get_traffic_selectors(child, FALSE, NULL, list, FALSE);
list->destroy_offset(list, offsetof(host_t, destroy));
/* We don't know the finally negotiated protocol (ESP|AH), we install
* the SA with the protocol of the first proposal */
proposals = child->get_proposals(child, TRUE);
if (proposals->get_first(proposals, (void**)&proposal) == SUCCESS)
{
proto = proposal->get_protocol(proposal);
}
proposals->destroy_offset(proposals, offsetof(proposal_t, destroy));
child_sa->set_protocol(child_sa, proto);
child_sa->set_mode(child_sa, child->get_mode(child));
child_sa->set_policies(child_sa, my_ts, other_ts);
status = child_sa->install_policies(child_sa);
my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy));
other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy));
if (status != SUCCESS)
{
DBG1(DBG_CFG, "installing trap failed");
this->lock->write_lock(this->lock);
this->traps->remove(this->traps, entry, NULL);
this->lock->unlock(this->lock);
entry->child_sa = child_sa;
destroy_entry(entry);
}
else
{
this->lock->write_lock(this->lock);
entry->child_sa = child_sa;
this->lock->unlock(this->lock);
result = TRUE;
}
if (found)
{
destroy_entry(found);
}
this->lock->write_lock(this->lock);
/* do this at the end, so entries created temporarily are also destroyed */
this->installing--;
this->condvar->signal(this->condvar);
this->lock->unlock(this->lock);
return result;
}
METHOD(trap_manager_t, uninstall, bool,
private_trap_manager_t *this, char *peer, char *child)
{
enumerator_t *enumerator;
entry_t *entry, *found = NULL;
this->lock->write_lock(this->lock);
while (this->installing)
{
this->condvar->wait(this->condvar, this->lock);
}
enumerator = this->traps->create_enumerator(this->traps);
while (enumerator->enumerate(enumerator, &entry))
{
if (streq(entry->name, child) &&
(!peer || streq(peer, entry->peer_cfg->get_name(entry->peer_cfg))))
{
this->traps->remove_at(this->traps, enumerator);
found = entry;
break;
}
}
enumerator->destroy(enumerator);
this->lock->unlock(this->lock);
if (!found)
{
return FALSE;
}
destroy_entry(found);
return TRUE;
}
CALLBACK(trap_filter, bool,
rwlock_t *lock, enumerator_t *orig, va_list args)
{
entry_t *entry;
peer_cfg_t **peer_cfg;
child_sa_t **child_sa;
VA_ARGS_VGET(args, peer_cfg, child_sa);
while (orig->enumerate(orig, &entry))
{
if (!entry->child_sa)
{ /* skip entries that are currently being installed */
continue;
}
if (peer_cfg)
{
*peer_cfg = entry->peer_cfg;
}
if (child_sa)
{
*child_sa = entry->child_sa;
}
return TRUE;
}
return FALSE;
}
METHOD(trap_manager_t, create_enumerator, enumerator_t*,
private_trap_manager_t *this)
{
this->lock->read_lock(this->lock);
return enumerator_create_filter(this->traps->create_enumerator(this->traps),
trap_filter, this->lock,
(void*)this->lock->unlock);
}
METHOD(trap_manager_t, acquire, void,
private_trap_manager_t *this, uint32_t reqid,
traffic_selector_t *src, traffic_selector_t *dst)
{
enumerator_t *enumerator;
entry_t *entry, *found = NULL;
acquire_t *acquire;
peer_cfg_t *peer;
child_cfg_t *child;
ike_sa_t *ike_sa;
host_t *host;
bool wildcard, ignore = FALSE;
this->lock->read_lock(this->lock);
enumerator = this->traps->create_enumerator(this->traps);
while (enumerator->enumerate(enumerator, &entry))
{
if (entry->child_sa &&
entry->child_sa->get_reqid(entry->child_sa) == reqid)
{
found = entry;
break;
}
}
enumerator->destroy(enumerator);
if (!found)
{
DBG1(DBG_CFG, "trap not found, unable to acquire reqid %d", reqid);
this->lock->unlock(this->lock);
return;
}
reqid = found->child_sa->get_reqid(found->child_sa);
wildcard = found->wildcard;
this->mutex->lock(this->mutex);
if (wildcard)
{ /* for wildcard acquires we check that we don't have a pending acquire
* with the same peer */
uint8_t mask;
dst->to_subnet(dst, &host, &mask);
if (this->acquires->find_first(this->acquires, acquire_by_dst,
(void**)&acquire, host))
{
host->destroy(host);
ignore = TRUE;
}
else
{
INIT(acquire,
.dst = host,
.reqid = reqid,
);
this->acquires->insert_last(this->acquires, acquire);
}
}
else
{
if (this->acquires->find_first(this->acquires, acquire_by_reqid,
(void**)&acquire, reqid))
{
ignore = TRUE;
}
else
{
INIT(acquire,
.reqid = reqid,
);
this->acquires->insert_last(this->acquires, acquire);
}
}
this->mutex->unlock(this->mutex);
if (ignore)
{
DBG1(DBG_CFG, "ignoring acquire, connection attempt pending");
this->lock->unlock(this->lock);
return;
}
peer = found->peer_cfg->get_ref(found->peer_cfg);
child = found->child_sa->get_config(found->child_sa);
child = child->get_ref(child);
/* don't hold the lock while checking out the IKE_SA */
this->lock->unlock(this->lock);
if (wildcard)
{ /* the peer config would match IKE_SAs with other peers */
ike_sa = charon->ike_sa_manager->create_new(charon->ike_sa_manager,
peer->get_ike_version(peer), TRUE);
if (ike_sa)
{
ike_cfg_t *ike_cfg;
uint16_t port;
uint8_t mask;
ike_sa->set_peer_cfg(ike_sa, peer);
ike_cfg = ike_sa->get_ike_cfg(ike_sa);
port = ike_cfg->get_other_port(ike_cfg);
dst->to_subnet(dst, &host, &mask);
host->set_port(host, port);
ike_sa->set_other_host(ike_sa, host);
port = ike_cfg->get_my_port(ike_cfg);
src->to_subnet(src, &host, &mask);
host->set_port(host, port);
ike_sa->set_my_host(ike_sa, host);
charon->bus->set_sa(charon->bus, ike_sa);
}
}
else
{
ike_sa = charon->ike_sa_manager->checkout_by_config(
charon->ike_sa_manager, peer);
}
peer->destroy(peer);
if (ike_sa)
{
if (this->ignore_acquire_ts || ike_sa->get_version(ike_sa) == IKEV1)
{ /* in IKEv1, don't prepend the acquiring packet TS, as we only
* have a single TS that we can establish in a Quick Mode. */
src = dst = NULL;
}
this->mutex->lock(this->mutex);
acquire->ike_sa = ike_sa;
this->mutex->unlock(this->mutex);
if (ike_sa->initiate(ike_sa, child, reqid, src, dst) != DESTROY_ME)
{
charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
}
else
{
charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
ike_sa);
}
}
else
{
this->mutex->lock(this->mutex);
this->acquires->remove(this->acquires, acquire, NULL);
this->mutex->unlock(this->mutex);
destroy_acquire(acquire);
child->destroy(child);
}
}
/**
* Complete the acquire, if successful or failed
*/
static void complete(private_trap_manager_t *this, ike_sa_t *ike_sa,
child_sa_t *child_sa)
{
enumerator_t *enumerator;
acquire_t *acquire;
this->mutex->lock(this->mutex);
enumerator = this->acquires->create_enumerator(this->acquires);
while (enumerator->enumerate(enumerator, &acquire))
{
if (!acquire->ike_sa || acquire->ike_sa != ike_sa)
{
continue;
}
if (child_sa)
{
if (acquire->dst)
{
/* since every wildcard acquire results in a separate IKE_SA
* there is no need to compare the destination address */
}
else if (child_sa->get_reqid(child_sa) != acquire->reqid)
{
continue;
}
}
this->acquires->remove_at(this->acquires, enumerator);
destroy_acquire(acquire);
}
enumerator->destroy(enumerator);
this->mutex->unlock(this->mutex);
}
METHOD(listener_t, ike_state_change, bool,
trap_listener_t *listener, ike_sa_t *ike_sa, ike_sa_state_t state)
{
switch (state)
{
case IKE_DESTROYING:
complete(listener->traps, ike_sa, NULL);
return TRUE;
default:
return TRUE;
}
}
METHOD(listener_t, child_state_change, bool,
trap_listener_t *listener, ike_sa_t *ike_sa, child_sa_t *child_sa,
child_sa_state_t state)
{
switch (state)
{
case CHILD_INSTALLED:
case CHILD_DESTROYING:
complete(listener->traps, ike_sa, child_sa);
return TRUE;
default:
return TRUE;
}
}
METHOD(trap_manager_t, flush, void,
private_trap_manager_t *this)
{
this->lock->write_lock(this->lock);
while (this->installing)
{
this->condvar->wait(this->condvar, this->lock);
}
this->traps->destroy_function(this->traps, (void*)destroy_entry);
this->traps = linked_list_create();
this->installing = INSTALL_DISABLED;
this->lock->unlock(this->lock);
}
METHOD(trap_manager_t, destroy, void,
private_trap_manager_t *this)
{
charon->bus->remove_listener(charon->bus, &this->listener.listener);
this->traps->destroy_function(this->traps, (void*)destroy_entry);
this->acquires->destroy_function(this->acquires, (void*)destroy_acquire);
this->condvar->destroy(this->condvar);
this->mutex->destroy(this->mutex);
this->lock->destroy(this->lock);
free(this);
}
/**
* See header
*/
trap_manager_t *trap_manager_create(void)
{
private_trap_manager_t *this;
INIT(this,
.public = {
.install = _install,
.uninstall = _uninstall,
.create_enumerator = _create_enumerator,
.acquire = _acquire,
.flush = _flush,
.destroy = _destroy,
},
.listener = {
.traps = this,
.listener = {
.ike_state_change = _ike_state_change,
.child_state_change = _child_state_change,
},
},
.traps = linked_list_create(),
.acquires = linked_list_create(),
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
.condvar = rwlock_condvar_create(),
.ignore_acquire_ts = lib->settings->get_bool(lib->settings,
"%s.ignore_acquire_ts", FALSE, lib->ns),
);
charon->bus->add_listener(charon->bus, &this->listener.listener);
return &this->public;
}