/*
- * Copyright (C) 2011-2012 Tobias Brunner
+ * Copyright (C) 2011-2017 Tobias Brunner
* Copyright (C) 2009 Martin Willi
- * Hochschule fuer Technik Rapperswil
+ * 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
#include "trap_manager.h"
-#include <hydra.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 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 instanciated CHILD_SA */
+ /** ref to instantiated CHILD_SA (i.e the trap policy) */
child_sa_t *child_sa;
- /** TRUE if an acquire is pending */
- bool pending;
+ /** 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;
-} entry_t;
+ /** 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 *entry)
+static void destroy_entry(entry_t *this)
{
- entry->child_sa->destroy(entry->child_sa);
- entry->peer_cfg->destroy(entry->peer_cfg);
- free(entry);
+ this->child_sa->destroy(this->child_sa);
+ this->peer_cfg->destroy(this->peer_cfg);
+ free(this->name);
+ free(this);
}
-METHOD(trap_manager_t, install, u_int32_t,
+/**
+ * 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;
linked_list_t *my_ts, *other_ts, *list;
enumerator_t *enumerator;
status_t status;
- u_int32_t reqid = 0;
+ 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 = host_create_from_dns(ike_cfg->get_other_addr(ike_cfg, NULL),
- 0, ike_cfg->get_other_port(ike_cfg));
- if (!other || other->is_anyaddr(other))
+ other = ike_cfg->resolve_other(ike_cfg, AF_UNSPEC);
+ if (other && other->is_anyaddr(other) &&
+ child->get_mode(child) == MODE_TRANSPORT)
{
- DESTROY_IF(other);
- DBG1(DBG_CFG, "installing trap failed, remote address unknown");
- return 0;
+ /* allow wildcard for Transport Mode SAs */
+ me = host_create_any(other->get_family(other));
+ wildcard = TRUE;
}
- me = host_create_from_dns(ike_cfg->get_my_addr(ike_cfg, NULL),
- other->get_family(other), ike_cfg->get_my_port(ike_cfg));
- if (!me || me->is_anyaddr(me))
+ else if (other && other->is_anyaddr(other))
{
- DESTROY_IF(me);
- me = hydra->kernel_interface->get_source_addr(
- hydra->kernel_interface, other, NULL);
+ 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)
{
- DBG1(DBG_CFG, "installing trap failed, local address unknown");
- other->destroy(other);
- return 0;
+ 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->child_sa->get_name(entry->child_sa),
- child->get_name(child)))
+ if (streq(entry->name, child->get_name(child)) &&
+ streq(entry->peer_cfg->get_name(entry->peer_cfg),
+ peer->get_name(peer)))
{
- this->traps->remove_at(this->traps, enumerator);
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);
- this->lock->unlock(this->lock);
if (found)
- { /* config might have changed so update everything */
- DBG1(DBG_CFG, "updating already routed CHILD_SA '%s'",
- child->get_name(child));
- reqid = found->child_sa->get_reqid(found->child_sa);
+ {
+ 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 = child_sa_create(me, other, child, reqid, FALSE);
+ 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);
+ 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);
+ other_ts = child->get_traffic_selectors(child, FALSE, NULL, list, FALSE);
list->destroy_offset(list, offsetof(host_t, destroy));
- /* while we don't know the finally negotiated protocol (ESP|AH), we
- * could iterate all proposals for a best guess (TODO). But as we
- * support ESP only for now, we set it here. */
- child_sa->set_protocol(child_sa, PROTO_ESP);
+ /* 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));
- status = child_sa->add_policies(child_sa, my_ts, other_ts);
+ 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");
- reqid = 0;
- /* hold off destroying the CHILD_SA until we released the lock */
+ 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
{
- INIT(entry,
- .child_sa = child_sa,
- .peer_cfg = peer->get_ref(peer),
- );
this->lock->write_lock(this->lock);
- this->traps->insert_last(this->traps, entry);
+ entry->child_sa = child_sa;
this->lock->unlock(this->lock);
- reqid = child_sa->get_reqid(child_sa);
- }
-
- if (status != SUCCESS)
- {
- child_sa->destroy(child_sa);
+ result = TRUE;
}
if (found)
{
destroy_entry(found);
}
- return reqid;
+ 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, u_int32_t reqid)
+ 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 (entry->child_sa->get_reqid(entry->child_sa) == reqid)
+ 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;
if (!found)
{
- DBG1(DBG_CFG, "trap %d not found to uninstall", reqid);
return FALSE;
}
-
destroy_entry(found);
return TRUE;
}
-/**
- * convert enumerated entries to peer_cfg, child_sa
- */
-static bool trap_filter(rwlock_t *lock, entry_t **entry, peer_cfg_t **peer_cfg,
- void *none, child_sa_t **child_sa)
+CALLBACK(trap_filter, bool,
+ rwlock_t *lock, enumerator_t *orig, va_list args)
{
- if (peer_cfg)
- {
- *peer_cfg = (*entry)->peer_cfg;
- }
- if (child_sa)
+ 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))
{
- *child_sa = (*entry)->child_sa;
+ 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 TRUE;
+ return FALSE;
}
METHOD(trap_manager_t, create_enumerator, enumerator_t*,
{
this->lock->read_lock(this->lock);
return enumerator_create_filter(this->traps->create_enumerator(this->traps),
- (void*)trap_filter, this->lock,
+ trap_filter, this->lock,
(void*)this->lock->unlock);
}
-METHOD(trap_manager_t, find_reqid, u_int32_t,
- private_trap_manager_t *this, child_cfg_t *child)
-{
- enumerator_t *enumerator;
- child_cfg_t *current;
- entry_t *entry;
- u_int32_t reqid = 0;
-
- this->lock->read_lock(this->lock);
- enumerator = this->traps->create_enumerator(this->traps);
- while (enumerator->enumerate(enumerator, &entry))
- {
- current = entry->child_sa->get_config(entry->child_sa);
- if (streq(current->get_name(current), child->get_name(child)))
- {
- reqid = entry->child_sa->get_reqid(entry->child_sa);
- break;
- }
- }
- enumerator->destroy(enumerator);
- this->lock->unlock(this->lock);
-
- return reqid;
-}
-
METHOD(trap_manager_t, acquire, void,
- private_trap_manager_t *this, u_int32_t reqid,
+ 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->get_reqid(entry->child_sa) == reqid)
+ if (entry->child_sa &&
+ entry->child_sa->get_reqid(entry->child_sa) == reqid)
{
found = entry;
break;
if (!found)
{
- DBG1(DBG_CFG, "trap not found, unable to acquire reqid %d",reqid);
+ DBG1(DBG_CFG, "trap not found, unable to acquire reqid %d", reqid);
this->lock->unlock(this->lock);
return;
}
- if (!cas_bool(&found->pending, FALSE, TRUE))
+ 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);
peer = found->peer_cfg->get_ref(found->peer_cfg);
child = found->child_sa->get_config(found->child_sa);
child = child->get_ref(child);
- reqid = found->child_sa->get_reqid(found->child_sa);
/* don't hold the lock while checking out the IKE_SA */
this->lock->unlock(this->lock);
- ike_sa = charon->ike_sa_manager->checkout_by_config(
+ if (wildcard)
+ { /* the peer config would match IKE_SAs with other peers */
+ ike_sa = charon->ike_sa_manager->checkout_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);
+ }
if (ike_sa)
{
if (ike_sa->get_peer_cfg(ike_sa) == NULL)
{
ike_sa->set_peer_cfg(ike_sa, peer);
}
- if (ike_sa->get_version(ike_sa) == IKEV1)
+ 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)
{
- /* make sure the entry is still there */
- this->lock->read_lock(this->lock);
- if (this->traps->find_first(this->traps, NULL,
- (void**)&found) == SUCCESS)
- {
- found->ike_sa = ike_sa;
- }
- this->lock->unlock(this->lock);
charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
}
else
{
- ike_sa->destroy(ike_sa);
+ 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);
+ }
peer->destroy(peer);
}
child_sa_t *child_sa)
{
enumerator_t *enumerator;
- entry_t *entry;
+ acquire_t *acquire;
- this->lock->read_lock(this->lock);
- enumerator = this->traps->create_enumerator(this->traps);
- while (enumerator->enumerate(enumerator, &entry))
+ this->mutex->lock(this->mutex);
+ enumerator = this->acquires->create_enumerator(this->acquires);
+ while (enumerator->enumerate(enumerator, &acquire))
{
- if (entry->ike_sa != ike_sa)
+ if (!acquire->ike_sa || acquire->ike_sa != ike_sa)
{
continue;
}
- if (child_sa && child_sa->get_reqid(child_sa) !=
- entry->child_sa->get_reqid(entry->child_sa))
+ if (child_sa)
{
- continue;
+ 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;
+ }
}
- entry->ike_sa = NULL;
- entry->pending = FALSE;
+ this->acquires->remove_at(this->acquires, enumerator);
+ destroy_acquire(acquire);
}
enumerator->destroy(enumerator);
- this->lock->unlock(this->lock);
+ this->mutex->unlock(this->mutex);
}
METHOD(listener_t, ike_state_change, bool,
METHOD(trap_manager_t, flush, void,
private_trap_manager_t *this)
{
- linked_list_t *traps;
- /* since destroying the CHILD_SA results in events which require a read
- * lock we cannot destroy the list while holding the write lock */
this->lock->write_lock(this->lock);
- traps = this->traps;
+ 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);
- traps->destroy_function(traps, (void*)destroy_entry);
}
METHOD(trap_manager_t, destroy, void,
{
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);
}
.install = _install,
.uninstall = _uninstall,
.create_enumerator = _create_enumerator,
- .find_reqid = _find_reqid,
.acquire = _acquire,
.flush = _flush,
.destroy = _destroy,
},
},
.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);