| /* |
| * Copyright (C) 2012 Martin Willi |
| * Copyright (C) 2012 revosec AG |
| * |
| * 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 "load_tester_control.h" |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| #include <errno.h> |
| |
| #include <daemon.h> |
| #include <collections/hashtable.h> |
| #include <threading/thread.h> |
| #include <threading/mutex.h> |
| #include <threading/condvar.h> |
| #include <processing/jobs/callback_job.h> |
| |
| typedef struct private_load_tester_control_t private_load_tester_control_t; |
| typedef struct init_listener_t init_listener_t; |
| |
| /** |
| * Private data of an load_tester_control_t object. |
| */ |
| struct private_load_tester_control_t { |
| |
| /** |
| * Public load_tester_control_t interface. |
| */ |
| load_tester_control_t public; |
| |
| /** |
| * Load tester control stream service |
| */ |
| stream_service_t *service; |
| }; |
| |
| /** |
| * Listener to follow initiation progress |
| */ |
| struct init_listener_t { |
| |
| /** |
| * implements listener_t |
| */ |
| listener_t listener; |
| |
| /** |
| * Output stream to log to |
| */ |
| FILE *stream; |
| |
| /** |
| * IKE_SAs we have started to initiate |
| */ |
| hashtable_t *initiated; |
| |
| /** |
| * IKE_SAs we have completed to initiate (success or failure) |
| */ |
| hashtable_t *completed; |
| |
| /** |
| * Mutex to lock IKE_SA tables |
| */ |
| mutex_t *mutex; |
| |
| /** |
| * Condvar to wait for completion |
| */ |
| condvar_t *condvar; |
| }; |
| |
| /** |
| * Hashtable hash function |
| */ |
| static u_int hash(uintptr_t id) |
| { |
| return id; |
| } |
| |
| /** |
| * Hashtable hash function |
| */ |
| static bool equals(uintptr_t a, uintptr_t b) |
| { |
| return a == b; |
| } |
| |
| METHOD(listener_t, alert, bool, |
| init_listener_t *this, ike_sa_t *ike_sa, alert_t alert, va_list args) |
| { |
| if (alert == ALERT_RETRANSMIT_SEND) |
| { |
| uintptr_t id; |
| bool match = FALSE; |
| |
| id = ike_sa->get_unique_id(ike_sa); |
| this->mutex->lock(this->mutex); |
| if (this->initiated->get(this->initiated, (void*)id)) |
| { |
| match = TRUE; |
| } |
| this->mutex->unlock(this->mutex); |
| |
| if (match) |
| { |
| fprintf(this->stream, "*"); |
| fflush(this->stream); |
| } |
| } |
| return TRUE; |
| } |
| |
| METHOD(listener_t, ike_state_change, bool, |
| init_listener_t *this, ike_sa_t *ike_sa, ike_sa_state_t state) |
| { |
| if (state == IKE_ESTABLISHED || state == IKE_DESTROYING) |
| { |
| uintptr_t id; |
| bool match = FALSE; |
| |
| id = ike_sa->get_unique_id(ike_sa); |
| this->mutex->lock(this->mutex); |
| if (this->initiated->get(this->initiated, (void*)id)) |
| { |
| match = !this->completed->put(this->completed, (void*)id, (void*)id); |
| } |
| this->mutex->unlock(this->mutex); |
| |
| if (match) |
| { |
| this->condvar->signal(this->condvar); |
| fprintf(this->stream, state == IKE_ESTABLISHED ? "+" : "-"); |
| fflush(this->stream); |
| } |
| } |
| return TRUE; |
| } |
| |
| /** |
| * Logging callback function used during initiate |
| */ |
| static bool initiate_cb(init_listener_t *this, debug_t group, level_t level, |
| ike_sa_t *ike_sa, const char *message) |
| { |
| uintptr_t id; |
| |
| if (ike_sa) |
| { |
| id = ike_sa->get_unique_id(ike_sa); |
| this->mutex->lock(this->mutex); |
| this->initiated->put(this->initiated, (void*)id, (void*)id); |
| this->mutex->unlock(this->mutex); |
| |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| /** |
| * Accept connections, initiate load-test, write progress to stream |
| */ |
| static bool on_accept(private_load_tester_control_t *this, stream_t *io) |
| { |
| init_listener_t *listener; |
| enumerator_t *enumerator; |
| peer_cfg_t *peer_cfg; |
| child_cfg_t *child_cfg; |
| u_int i, count, failed = 0, delay = 0; |
| char buf[16] = ""; |
| FILE *stream; |
| |
| stream = io->get_file(io); |
| if (!stream) |
| { |
| return FALSE; |
| } |
| fflush(stream); |
| if (fgets(buf, sizeof(buf), stream) == NULL) |
| { |
| fclose(stream); |
| return FALSE; |
| } |
| if (sscanf(buf, "%u %u", &count, &delay) < 1) |
| { |
| fclose(stream); |
| return FALSE; |
| } |
| |
| INIT(listener, |
| .listener = { |
| .ike_state_change = _ike_state_change, |
| .alert = _alert, |
| }, |
| .stream = stream, |
| .initiated = hashtable_create((void*)hash, (void*)equals, count), |
| .completed = hashtable_create((void*)hash, (void*)equals, count), |
| .mutex = mutex_create(MUTEX_TYPE_DEFAULT), |
| .condvar = condvar_create(CONDVAR_TYPE_DEFAULT), |
| ); |
| |
| charon->bus->add_listener(charon->bus, &listener->listener); |
| |
| for (i = 0; i < count; i++) |
| { |
| peer_cfg = charon->backends->get_peer_cfg_by_name(charon->backends, |
| "load-test"); |
| if (!peer_cfg) |
| { |
| failed++; |
| fprintf(stream, "!"); |
| continue; |
| } |
| enumerator = peer_cfg->create_child_cfg_enumerator(peer_cfg); |
| if (!enumerator->enumerate(enumerator, &child_cfg)) |
| { |
| enumerator->destroy(enumerator); |
| peer_cfg->destroy(peer_cfg); |
| failed++; |
| fprintf(stream, "!"); |
| continue; |
| } |
| enumerator->destroy(enumerator); |
| |
| switch (charon->controller->initiate(charon->controller, |
| peer_cfg, child_cfg->get_ref(child_cfg), |
| (void*)initiate_cb, listener, 0, FALSE)) |
| { |
| case NEED_MORE: |
| /* Callback returns FALSE once it got track of this IKE_SA. |
| * FALL */ |
| case SUCCESS: |
| fprintf(stream, "."); |
| break; |
| default: |
| fprintf(stream, "!"); |
| break; |
| } |
| if (delay) |
| { |
| usleep(delay * 1000); |
| } |
| fflush(stream); |
| } |
| |
| listener->mutex->lock(listener->mutex); |
| while (listener->completed->get_count(listener->completed) < count - failed) |
| { |
| listener->condvar->wait(listener->condvar, listener->mutex); |
| } |
| listener->mutex->unlock(listener->mutex); |
| |
| charon->bus->remove_listener(charon->bus, &listener->listener); |
| |
| listener->initiated->destroy(listener->initiated); |
| listener->completed->destroy(listener->completed); |
| listener->mutex->destroy(listener->mutex); |
| listener->condvar->destroy(listener->condvar); |
| free(listener); |
| |
| fprintf(stream, "\n"); |
| fclose(stream); |
| |
| return FALSE; |
| } |
| |
| METHOD(load_tester_control_t, destroy, void, |
| private_load_tester_control_t *this) |
| { |
| DESTROY_IF(this->service); |
| free(this); |
| } |
| |
| /** |
| * See header |
| */ |
| load_tester_control_t *load_tester_control_create() |
| { |
| private_load_tester_control_t *this; |
| char *uri; |
| |
| INIT(this, |
| .public = { |
| .destroy = _destroy, |
| }, |
| ); |
| |
| uri = lib->settings->get_str(lib->settings, |
| "%s.plugins.load-tester.socket", "unix://" LOAD_TESTER_SOCKET, |
| lib->ns); |
| this->service = lib->streams->create_service(lib->streams, uri, 10); |
| if (this->service) |
| { |
| this->service->on_accept(this->service, (stream_service_cb_t)on_accept, |
| this, JOB_PRIO_CRITICAL, 0); |
| } |
| else |
| { |
| DBG1(DBG_CFG, "creating load-tester control socket failed"); |
| } |
| return &this->public; |
| } |