diff options
Diffstat (limited to 'src/libcharon/control/controller.c')
-rw-r--r-- | src/libcharon/control/controller.c | 437 |
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; } |