summaryrefslogtreecommitdiff
path: root/src/libcharon/control/controller.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcharon/control/controller.c')
-rw-r--r--src/libcharon/control/controller.c437
1 files changed, 308 insertions, 129 deletions
diff --git a/src/libcharon/control/controller.c b/src/libcharon/control/controller.c
index 0f247962b..77d73dba9 100644
--- a/src/libcharon/control/controller.c
+++ b/src/libcharon/control/controller.c
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2011-2012 Tobias Brunner
* Copyright (C) 2007-2011 Martin Willi
* Copyright (C) 2011 revosec AG
* Hochschule fuer Technik Rapperswil
@@ -23,10 +24,13 @@
#include <daemon.h>
#include <library.h>
-
+#include <threading/thread.h>
+#include <threading/spinlock.h>
+#include <threading/semaphore.h>
typedef struct private_controller_t private_controller_t;
typedef struct interface_listener_t interface_listener_t;
+typedef struct interface_logger_t interface_logger_t;
/**
* Private data of an stroke_t object.
@@ -40,19 +44,18 @@ struct private_controller_t {
};
/**
- * helper struct to map listener callbacks to interface callbacks
+ * helper struct for the logger interface
*/
-struct interface_listener_t {
-
+struct interface_logger_t {
/**
- * public bus listener interface
+ * public logger interface
*/
- listener_t public;
+ logger_t public;
/**
- * status of the operation, return to method callers
+ * reference to the listener
*/
- status_t status;
+ interface_listener_t *listener;
/**
* interface callback (listener gets redirected to here)
@@ -63,6 +66,27 @@ struct interface_listener_t {
* user parameter to pass to callback
*/
void *param;
+};
+
+/**
+ * helper struct to map listener callbacks to interface callbacks
+ */
+struct interface_listener_t {
+
+ /**
+ * public bus listener interface
+ */
+ listener_t public;
+
+ /**
+ * logger interface
+ */
+ interface_logger_t logger;
+
+ /**
+ * status of the operation, return to method callers
+ */
+ status_t status;
/**
* child configuration, used for initiate
@@ -80,14 +104,19 @@ struct interface_listener_t {
ike_sa_t *ike_sa;
/**
- * CHILD_SA to handle
+ * unique ID, used for various methods
*/
- child_sa_t *child_sa;
+ u_int32_t id;
/**
- * unique ID, used for various methods
+ * semaphore to implement wait_for_listener()
*/
- u_int32_t id;
+ semaphore_t *done;
+
+ /**
+ * spinlock to update the IKE_SA handle properly
+ */
+ spinlock_t *lock;
};
@@ -107,20 +136,103 @@ struct interface_job_t {
* associated listener
*/
interface_listener_t listener;
+
+ /**
+ * the job is reference counted as the thread executing a job as well as
+ * the thread waiting in wait_for_listener() require it but either of them
+ * could be done first
+ */
+ refcount_t refcount;
};
-METHOD(listener_t, listener_log, bool,
- interface_listener_t *this, debug_t group, level_t level, int thread,
- ike_sa_t *ike_sa, char* format, va_list args)
+/**
+ * This function wakes a thread that is waiting in wait_for_listener(),
+ * either from a listener or from a job.
+ */
+static inline bool listener_done(interface_listener_t *listener)
{
- if (this->ike_sa == ike_sa)
+ if (listener->done)
{
- if (!this->callback(this->param, group, level, ike_sa, format, args))
+ listener->done->post(listener->done);
+ }
+ return FALSE;
+}
+
+/**
+ * thread_cleanup_t handler to unregister a listener.
+ */
+static void listener_unregister(interface_listener_t *listener)
+{
+ charon->bus->remove_listener(charon->bus, &listener->public);
+ charon->bus->remove_logger(charon->bus, &listener->logger.public);
+}
+
+/**
+ * Registers the listener, executes the job and then waits synchronously until
+ * the listener is done or the timeout occurred.
+ *
+ * @note Use 'return listener_done(listener)' to properly unregister a listener
+ *
+ * @param listener listener to register
+ * @param job job to execute asynchronously when registered, or NULL
+ * @param timeout max timeout in ms to listen for events, 0 to disable
+ * @return TRUE if timed out
+ */
+static bool wait_for_listener(interface_job_t *job, u_int timeout)
+{
+ interface_listener_t *listener = &job->listener;
+ bool old, timed_out = FALSE;
+
+ /* avoid that the job is destroyed too early */
+ ref_get(&job->refcount);
+
+ listener->done = semaphore_create(0);
+
+ charon->bus->add_logger(charon->bus, &listener->logger.public);
+ charon->bus->add_listener(charon->bus, &listener->public);
+ lib->processor->queue_job(lib->processor, &job->public);
+
+ thread_cleanup_push((thread_cleanup_t)listener_unregister, listener);
+ old = thread_cancelability(TRUE);
+ if (timeout)
+ {
+ timed_out = listener->done->timed_wait(listener->done, timeout);
+ }
+ else
+ {
+ listener->done->wait(listener->done);
+ }
+ thread_cancelability(old);
+ thread_cleanup_pop(TRUE);
+ return timed_out;
+}
+
+METHOD(logger_t, listener_log, void,
+ interface_logger_t *this, debug_t group, level_t level, int thread,
+ ike_sa_t *ike_sa, const char *message)
+{
+ ike_sa_t *target;
+
+ this->listener->lock->lock(this->listener->lock);
+ target = this->listener->ike_sa;
+ this->listener->lock->unlock(this->listener->lock);
+
+ if (target == ike_sa)
+ {
+ if (!this->callback(this->param, group, level, ike_sa, message))
{
- return FALSE;
+ this->listener->status = NEED_MORE;
+ listener_done(this->listener);
}
}
- return TRUE;
+}
+
+METHOD(logger_t, listener_get_level, level_t,
+ interface_logger_t *this, debug_t group)
+{
+ /* in order to allow callback listeners to decide what they want to log
+ * we request any log message, but only if we actually want logging */
+ return this->callback == controller_cb_empty ? LEVEL_SILENT : LEVEL_PRIVATE;
}
METHOD(job_t, get_priority_medium, job_priority_t,
@@ -132,7 +244,13 @@ METHOD(job_t, get_priority_medium, job_priority_t,
METHOD(listener_t, ike_state_change, bool,
interface_listener_t *this, ike_sa_t *ike_sa, ike_sa_state_t state)
{
- if (this->ike_sa == ike_sa)
+ ike_sa_t *target;
+
+ this->lock->lock(this->lock);
+ target = this->ike_sa;
+ this->lock->unlock(this->lock);
+
+ if (target == ike_sa)
{
switch (state)
{
@@ -144,7 +262,7 @@ METHOD(listener_t, ike_state_change, bool,
if (peer_cfg->is_mediation(peer_cfg))
{
this->status = SUCCESS;
- return FALSE;
+ return listener_done(this);
}
break;
}
@@ -154,7 +272,7 @@ METHOD(listener_t, ike_state_change, bool,
{ /* proper termination */
this->status = SUCCESS;
}
- return FALSE;
+ return listener_done(this);
default:
break;
}
@@ -166,13 +284,19 @@ METHOD(listener_t, child_state_change, bool,
interface_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
child_sa_state_t state)
{
- if (this->ike_sa == ike_sa)
+ ike_sa_t *target;
+
+ this->lock->lock(this->lock);
+ target = this->ike_sa;
+ this->lock->unlock(this->lock);
+
+ if (target == ike_sa)
{
switch (state)
{
case CHILD_INSTALLED:
this->status = SUCCESS;
- return FALSE;
+ return listener_done(this);
case CHILD_DESTROYING:
switch (child_sa->get_state(child_sa))
{
@@ -183,7 +307,7 @@ METHOD(listener_t, child_state_change, bool,
default:
break;
}
- return FALSE;
+ return listener_done(this);
default:
break;
}
@@ -191,13 +315,14 @@ METHOD(listener_t, child_state_change, bool,
return TRUE;
}
-METHOD(job_t, recheckin, void,
- interface_job_t *job)
+METHOD(job_t, destroy_job, void,
+ interface_job_t *this)
{
- if (job->listener.ike_sa)
+ if (ref_put(&this->refcount))
{
- charon->ike_sa_manager->checkin(charon->ike_sa_manager,
- job->listener.ike_sa);
+ this->listener.lock->destroy(this->listener.lock);
+ DESTROY_IF(this->listener.done);
+ free(this);
}
}
@@ -208,7 +333,7 @@ METHOD(controller_t, create_ike_sa_enumerator, enumerator_t*,
wait);
}
-METHOD(job_t, initiate_execute, void,
+METHOD(job_t, initiate_execute, job_requeue_t,
interface_job_t *job)
{
ike_sa_t *ike_sa;
@@ -217,7 +342,18 @@ METHOD(job_t, initiate_execute, void,
ike_sa = charon->ike_sa_manager->checkout_by_config(charon->ike_sa_manager,
peer_cfg);
+ if (!ike_sa)
+ {
+ listener->child_cfg->destroy(listener->child_cfg);
+ peer_cfg->destroy(peer_cfg);
+ listener->status = FAILED;
+ /* release listener */
+ listener_done(listener);
+ return JOB_REQUEUE_NONE;
+ }
+ listener->lock->lock(listener->lock);
listener->ike_sa = ike_sa;
+ listener->lock->unlock(listener->lock);
if (ike_sa->get_peer_cfg(ike_sa) == NULL)
{
@@ -227,228 +363,271 @@ METHOD(job_t, initiate_execute, void,
if (ike_sa->initiate(ike_sa, listener->child_cfg, 0, NULL, NULL) == SUCCESS)
{
- charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
listener->status = SUCCESS;
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
}
else
{
+ listener->status = FAILED;
charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
ike_sa);
- listener->status = FAILED;
}
+ return JOB_REQUEUE_NONE;
}
METHOD(controller_t, initiate, status_t,
private_controller_t *this, peer_cfg_t *peer_cfg, child_cfg_t *child_cfg,
controller_cb_t callback, void *param, u_int timeout)
{
- interface_job_t job = {
+ interface_job_t *job;
+ status_t status;
+
+ INIT(job,
.listener = {
.public = {
- .log = _listener_log,
.ike_state_change = _ike_state_change,
.child_state_change = _child_state_change,
},
- .callback = callback,
- .param = param,
+ .logger = {
+ .public = {
+ .log = _listener_log,
+ .get_level = _listener_get_level,
+ },
+ .callback = callback,
+ .param = param,
+ },
.status = FAILED,
.child_cfg = child_cfg,
.peer_cfg = peer_cfg,
+ .lock = spinlock_create(),
},
.public = {
.execute = _initiate_execute,
.get_priority = _get_priority_medium,
- .destroy = _recheckin,
+ .destroy = _destroy_job,
},
- };
+ .refcount = 1,
+ );
+ job->listener.logger.listener = &job->listener;
+
if (callback == NULL)
{
- initiate_execute(&job);
+ initiate_execute(job);
}
else
{
- if (charon->bus->listen(charon->bus, &job.listener.public, &job.public,
- timeout))
+ if (wait_for_listener(job, timeout))
{
- job.listener.status = OUT_OF_RES;
+ job->listener.status = OUT_OF_RES;
}
}
- return job.listener.status;
+ status = job->listener.status;
+ destroy_job(job);
+ return status;
}
-METHOD(job_t, terminate_ike_execute, void,
+METHOD(job_t, terminate_ike_execute, job_requeue_t,
interface_job_t *job)
{
interface_listener_t *listener = &job->listener;
- ike_sa_t *ike_sa = listener->ike_sa;
+ u_int32_t unique_id = listener->id;
+ ike_sa_t *ike_sa;
- charon->bus->set_sa(charon->bus, ike_sa);
+ ike_sa = charon->ike_sa_manager->checkout_by_id(charon->ike_sa_manager,
+ unique_id, FALSE);
+ if (!ike_sa)
+ {
+ DBG1(DBG_IKE, "unable to terminate IKE_SA: ID %d not found", unique_id);
+ listener->status = NOT_FOUND;
+ /* release listener */
+ listener_done(listener);
+ return JOB_REQUEUE_NONE;
+ }
+ listener->lock->lock(listener->lock);
+ listener->ike_sa = ike_sa;
+ listener->lock->unlock(listener->lock);
if (ike_sa->delete(ike_sa) != DESTROY_ME)
- {
- charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
- /* delete failed */
+ { /* delete failed */
listener->status = FAILED;
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
}
else
{
+ listener->status = SUCCESS;
charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
ike_sa);
- listener->status = SUCCESS;
}
+ return JOB_REQUEUE_NONE;
}
METHOD(controller_t, terminate_ike, status_t,
controller_t *this, u_int32_t unique_id,
controller_cb_t callback, void *param, u_int timeout)
{
- ike_sa_t *ike_sa;
- interface_job_t job = {
+ interface_job_t *job;
+ status_t status;
+
+ INIT(job,
.listener = {
.public = {
- .log = _listener_log,
.ike_state_change = _ike_state_change,
.child_state_change = _child_state_change,
},
- .callback = callback,
- .param = param,
+ .logger = {
+ .public = {
+ .log = _listener_log,
+ .get_level = _listener_get_level,
+ },
+ .callback = callback,
+ .param = param,
+ },
.status = FAILED,
.id = unique_id,
+ .lock = spinlock_create(),
},
.public = {
.execute = _terminate_ike_execute,
.get_priority = _get_priority_medium,
- .destroy = _recheckin,
+ .destroy = _destroy_job,
},
- };
-
- ike_sa = charon->ike_sa_manager->checkout_by_id(charon->ike_sa_manager,
- unique_id, FALSE);
- if (ike_sa == NULL)
- {
- DBG1(DBG_IKE, "unable to terminate IKE_SA: ID %d not found", unique_id);
- return NOT_FOUND;
- }
- job.listener.ike_sa = ike_sa;
+ .refcount = 1,
+ );
+ job->listener.logger.listener = &job->listener;
if (callback == NULL)
{
- terminate_ike_execute(&job);
+ terminate_ike_execute(job);
}
else
{
- if (charon->bus->listen(charon->bus, &job.listener.public, &job.public,
- timeout))
+ if (wait_for_listener(job, timeout))
{
- job.listener.status = OUT_OF_RES;
+ job->listener.status = OUT_OF_RES;
}
- /* checkin of the ike_sa happened in the thread that executed the job */
- charon->bus->set_sa(charon->bus, NULL);
}
- return job.listener.status;
+ status = job->listener.status;
+ destroy_job(job);
+ return status;
}
-METHOD(job_t, terminate_child_execute, void,
+METHOD(job_t, terminate_child_execute, job_requeue_t,
interface_job_t *job)
{
interface_listener_t *listener = &job->listener;
- ike_sa_t *ike_sa = listener->ike_sa;
- child_sa_t *child_sa = listener->child_sa;
+ u_int32_t reqid = listener->id;
+ enumerator_t *enumerator;
+ child_sa_t *child_sa;
+ ike_sa_t *ike_sa;
- charon->bus->set_sa(charon->bus, ike_sa);
- if (ike_sa->delete_child_sa(ike_sa, child_sa->get_protocol(child_sa),
- child_sa->get_spi(child_sa, TRUE)) != DESTROY_ME)
+ ike_sa = charon->ike_sa_manager->checkout_by_id(charon->ike_sa_manager,
+ reqid, TRUE);
+ if (!ike_sa)
+ {
+ DBG1(DBG_IKE, "unable to terminate, CHILD_SA with ID %d not found",
+ reqid);
+ listener->status = NOT_FOUND;
+ /* release listener */
+ listener_done(listener);
+ return JOB_REQUEUE_NONE;
+ }
+ listener->lock->lock(listener->lock);
+ listener->ike_sa = ike_sa;
+ listener->lock->unlock(listener->lock);
+
+ enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
+ while (enumerator->enumerate(enumerator, (void**)&child_sa))
{
+ if (child_sa->get_state(child_sa) != CHILD_ROUTED &&
+ child_sa->get_reqid(child_sa) == reqid)
+ {
+ break;
+ }
+ child_sa = NULL;
+ }
+ enumerator->destroy(enumerator);
+
+ if (!child_sa)
+ {
+ DBG1(DBG_IKE, "unable to terminate, established "
+ "CHILD_SA with ID %d not found", reqid);
charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
+ listener->status = NOT_FOUND;
+ /* release listener */
+ listener_done(listener);
+ return JOB_REQUEUE_NONE;
+ }
+
+ if (ike_sa->delete_child_sa(ike_sa, child_sa->get_protocol(child_sa),
+ child_sa->get_spi(child_sa, TRUE), FALSE) != DESTROY_ME)
+ {
listener->status = SUCCESS;
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
}
else
{
+ listener->status = FAILED;
charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
ike_sa);
- listener->status = FAILED;
}
+ return JOB_REQUEUE_NONE;
}
METHOD(controller_t, terminate_child, status_t,
controller_t *this, u_int32_t reqid,
controller_cb_t callback, void *param, u_int timeout)
{
- ike_sa_t *ike_sa;
- child_sa_t *child_sa;
- enumerator_t *enumerator;
- interface_job_t job = {
+ interface_job_t *job;
+ status_t status;
+
+ INIT(job,
.listener = {
.public = {
- .log = _listener_log,
.ike_state_change = _ike_state_change,
.child_state_change = _child_state_change,
},
- .callback = callback,
- .param = param,
+ .logger = {
+ .public = {
+ .log = _listener_log,
+ .get_level = _listener_get_level,
+ },
+ .callback = callback,
+ .param = param,
+ },
.status = FAILED,
.id = reqid,
+ .lock = spinlock_create(),
},
.public = {
.execute = _terminate_child_execute,
.get_priority = _get_priority_medium,
- .destroy = _recheckin,
+ .destroy = _destroy_job,
},
- };
-
- ike_sa = charon->ike_sa_manager->checkout_by_id(charon->ike_sa_manager,
- reqid, TRUE);
- if (ike_sa == NULL)
- {
- DBG1(DBG_IKE, "unable to terminate, CHILD_SA with ID %d not found",
- reqid);
- return NOT_FOUND;
- }
- job.listener.ike_sa = ike_sa;
-
- enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
- while (enumerator->enumerate(enumerator, (void**)&child_sa))
- {
- if (child_sa->get_state(child_sa) != CHILD_ROUTED &&
- child_sa->get_reqid(child_sa) == reqid)
- {
- break;
- }
- child_sa = NULL;
- }
- enumerator->destroy(enumerator);
-
- if (child_sa == NULL)
- {
- DBG1(DBG_IKE, "unable to terminate, established "
- "CHILD_SA with ID %d not found", reqid);
- charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
- return NOT_FOUND;
- }
- job.listener.child_sa = child_sa;
+ .refcount = 1,
+ );
+ job->listener.logger.listener = &job->listener;
if (callback == NULL)
{
- terminate_child_execute(&job);
+ terminate_child_execute(job);
}
else
{
- if (charon->bus->listen(charon->bus, &job.listener.public, &job.public,
- timeout))
+ if (wait_for_listener(job, timeout))
{
- job.listener.status = OUT_OF_RES;
+ job->listener.status = OUT_OF_RES;
}
- /* checkin of the ike_sa happened in the thread that executed the job */
- charon->bus->set_sa(charon->bus, NULL);
}
- return job.listener.status;
+ status = job->listener.status;
+ destroy_job(job);
+ return status;
}
/**
* See header
*/
bool controller_cb_empty(void *param, debug_t group, level_t level,
- ike_sa_t *ike_sa, char *format, va_list args)
+ ike_sa_t *ike_sa, const char *message)
{
return TRUE;
}