Besides being clearer, this fixes issues with GCC 15. The latter uses C23 by default, which changes the meaning of function declarations without parameters such as bool return false(); Instead of "this function takes an unknown number of arguments", this now equals (void), that is, "this function takes no arguments". So we run into incompatible pointer type warnings all over when using such functions. They could be cast to (void*) but this seems the cleaner solution for this use case.
530 lines
11 KiB
C
530 lines
11 KiB
C
/*
|
|
* Copyright (C) 2008 Martin Willi
|
|
*
|
|
* Copyright (C) secunet Security Networks 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 "ha_segments.h"
|
|
|
|
#include <threading/mutex.h>
|
|
#include <threading/condvar.h>
|
|
#include <collections/linked_list.h>
|
|
#include <threading/thread.h>
|
|
#include <processing/jobs/callback_job.h>
|
|
|
|
#define DEFAULT_HEARTBEAT_DELAY 1000
|
|
#define DEFAULT_HEARTBEAT_TIMEOUT 2100
|
|
|
|
typedef struct private_ha_segments_t private_ha_segments_t;
|
|
|
|
/**
|
|
* Private data of an ha_segments_t object.
|
|
*/
|
|
struct private_ha_segments_t {
|
|
|
|
/**
|
|
* Public ha_segments_t interface.
|
|
*/
|
|
ha_segments_t public;
|
|
|
|
/**
|
|
* communication socket
|
|
*/
|
|
ha_socket_t *socket;
|
|
|
|
/**
|
|
* Sync tunnel, if any
|
|
*/
|
|
ha_tunnel_t *tunnel;
|
|
|
|
/**
|
|
* Interface to control segments at kernel level
|
|
*/
|
|
ha_kernel_t *kernel;
|
|
|
|
/**
|
|
* Mutex to lock segment manipulation
|
|
*/
|
|
mutex_t *mutex;
|
|
|
|
/**
|
|
* Condvar to wait for heartbeats
|
|
*/
|
|
condvar_t *condvar;
|
|
|
|
/**
|
|
* Total number of ClusterIP segments
|
|
*/
|
|
u_int count;
|
|
|
|
/**
|
|
* mask of active segments
|
|
*/
|
|
segment_mask_t active;
|
|
|
|
/**
|
|
* Node number
|
|
*/
|
|
u_int node;
|
|
|
|
/**
|
|
* Are we checking for heartbeats?
|
|
*/
|
|
bool heartbeat_active;
|
|
|
|
/**
|
|
* Interval we send heartbeats
|
|
*/
|
|
int heartbeat_delay;
|
|
|
|
/**
|
|
* Timeout for heartbeats received from other node
|
|
*/
|
|
int heartbeat_timeout;
|
|
|
|
/**
|
|
* Interval to check for autobalance, 0 to disable
|
|
*/
|
|
int autobalance;
|
|
};
|
|
|
|
/**
|
|
* Log currently active segments
|
|
*/
|
|
static void log_segments(private_ha_segments_t *this, bool activated,
|
|
u_int segment)
|
|
{
|
|
char buf[64] = "none", *pos = buf;
|
|
int i;
|
|
bool first = TRUE;
|
|
|
|
for (i = 1; i <= this->count; i++)
|
|
{
|
|
if (this->active & SEGMENTS_BIT(i))
|
|
{
|
|
if (first)
|
|
{
|
|
first = FALSE;
|
|
}
|
|
else
|
|
{
|
|
pos += snprintf(pos, buf + sizeof(buf) - pos, ",");
|
|
}
|
|
pos += snprintf(pos, buf + sizeof(buf) - pos, "%d", i);
|
|
}
|
|
}
|
|
DBG1(DBG_CFG, "HA segment %d %sactivated, now active: %s",
|
|
segment, activated ? "" : "de", buf);
|
|
}
|
|
|
|
/**
|
|
* Enable/Disable a specific segment
|
|
*/
|
|
static void enable_disable(private_ha_segments_t *this, u_int segment,
|
|
bool enable, bool notify)
|
|
{
|
|
ike_sa_t *ike_sa;
|
|
enumerator_t *enumerator;
|
|
ike_sa_state_t old, new, cur;
|
|
ha_message_t *message = NULL;
|
|
ha_message_type_t type;
|
|
array_t *to_destroy;
|
|
uint32_t unique_id;
|
|
bool changes = FALSE;
|
|
|
|
if (segment > this->count)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (enable)
|
|
{
|
|
old = IKE_PASSIVE;
|
|
new = IKE_ESTABLISHED;
|
|
type = HA_SEGMENT_TAKE;
|
|
if (!(this->active & SEGMENTS_BIT(segment)))
|
|
{
|
|
this->active |= SEGMENTS_BIT(segment);
|
|
this->kernel->activate(this->kernel, segment);
|
|
changes = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
old = IKE_ESTABLISHED;
|
|
new = IKE_PASSIVE;
|
|
type = HA_SEGMENT_DROP;
|
|
if (this->active & SEGMENTS_BIT(segment))
|
|
{
|
|
this->active &= ~SEGMENTS_BIT(segment);
|
|
this->kernel->deactivate(this->kernel, segment);
|
|
changes = TRUE;
|
|
}
|
|
}
|
|
|
|
if (changes)
|
|
{
|
|
to_destroy = array_create(sizeof(u_int), 0);
|
|
enumerator = charon->ike_sa_manager->create_enumerator(
|
|
charon->ike_sa_manager, TRUE);
|
|
while (enumerator->enumerate(enumerator, &ike_sa))
|
|
{
|
|
cur = ike_sa->get_state(ike_sa);
|
|
if (cur != old && cur != IKE_CONNECTING)
|
|
{
|
|
continue;
|
|
}
|
|
if (this->tunnel && this->tunnel->is_sa(this->tunnel, ike_sa))
|
|
{
|
|
continue;
|
|
}
|
|
if (this->kernel->get_segment(this->kernel,
|
|
ike_sa->get_other_host(ike_sa)) == segment)
|
|
{
|
|
if (cur == IKE_CONNECTING)
|
|
{
|
|
unique_id = ike_sa->get_unique_id(ike_sa);
|
|
array_insert(to_destroy, ARRAY_TAIL, &unique_id);
|
|
}
|
|
else
|
|
{
|
|
ike_sa->set_state(ike_sa, new);
|
|
}
|
|
}
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
log_segments(this, enable, segment);
|
|
|
|
while (array_remove(to_destroy, ARRAY_HEAD, &unique_id))
|
|
{
|
|
ike_sa = charon->ike_sa_manager->checkout_by_id(charon->ike_sa_manager,
|
|
unique_id);
|
|
if (ike_sa)
|
|
{
|
|
DBG1(DBG_IKE, "destroying incomplete IKE_SA %s[%d] after "
|
|
"%sactivating HA segment %d", ike_sa->get_name(ike_sa),
|
|
unique_id, segment, enable ? "" : "de");
|
|
charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
|
|
ike_sa);
|
|
}
|
|
}
|
|
array_destroy(to_destroy);
|
|
}
|
|
|
|
if (notify)
|
|
{
|
|
message = ha_message_create(type);
|
|
message->add_attribute(message, HA_SEGMENT, segment);
|
|
this->socket->push(this->socket, message);
|
|
message->destroy(message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable/Disable all or a specific segment, do locking
|
|
*/
|
|
static void enable_disable_all(private_ha_segments_t *this, u_int segment,
|
|
bool enable, bool notify)
|
|
{
|
|
int i;
|
|
|
|
this->mutex->lock(this->mutex);
|
|
if (segment == 0)
|
|
{
|
|
for (i = 1; i <= this->count; i++)
|
|
{
|
|
enable_disable(this, i, enable, notify);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
enable_disable(this, segment, enable, notify);
|
|
}
|
|
this->mutex->unlock(this->mutex);
|
|
}
|
|
|
|
METHOD(ha_segments_t, activate, void,
|
|
private_ha_segments_t *this, u_int segment, bool notify)
|
|
{
|
|
enable_disable_all(this, segment, TRUE, notify);
|
|
}
|
|
|
|
METHOD(ha_segments_t, deactivate, void,
|
|
private_ha_segments_t *this, u_int segment, bool notify)
|
|
{
|
|
enable_disable_all(this, segment, FALSE, notify);
|
|
}
|
|
|
|
METHOD(listener_t, alert_hook, bool,
|
|
private_ha_segments_t *this, ike_sa_t *ike_sa, alert_t alert, va_list args)
|
|
{
|
|
if (alert == ALERT_SHUTDOWN_SIGNAL)
|
|
{
|
|
if (this->heartbeat_active)
|
|
{
|
|
DBG1(DBG_CFG, "HA heartbeat active, dropping all segments");
|
|
deactivate(this, 0, TRUE);
|
|
}
|
|
else
|
|
{
|
|
DBG1(DBG_CFG, "no HA heartbeat active, closing IKE_SAs");
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Monitor heartbeat activity of remote node
|
|
*/
|
|
static job_requeue_t watchdog(private_ha_segments_t *this)
|
|
{
|
|
bool timeout, oldstate;
|
|
|
|
this->mutex->lock(this->mutex);
|
|
thread_cleanup_push((void*)this->mutex->unlock, this->mutex);
|
|
oldstate = thread_cancelability(TRUE);
|
|
timeout = this->condvar->timed_wait(this->condvar, this->mutex,
|
|
this->heartbeat_timeout);
|
|
thread_cancelability(oldstate);
|
|
thread_cleanup_pop(TRUE);
|
|
if (timeout)
|
|
{
|
|
DBG1(DBG_CFG, "no heartbeat received, taking all segments");
|
|
activate(this, 0, TRUE);
|
|
/* disable heartbeat detection util we get one */
|
|
this->heartbeat_active = FALSE;
|
|
return JOB_REQUEUE_NONE;
|
|
}
|
|
return JOB_REQUEUE_DIRECT;
|
|
}
|
|
|
|
/**
|
|
* Start the heartbeat detection thread
|
|
*/
|
|
static void start_watchdog(private_ha_segments_t *this)
|
|
{
|
|
this->heartbeat_active = TRUE;
|
|
lib->processor->queue_job(lib->processor,
|
|
(job_t*)callback_job_create_with_prio((callback_job_cb_t)watchdog, this,
|
|
NULL, callback_job_cancel_thread, JOB_PRIO_CRITICAL));
|
|
}
|
|
|
|
METHOD(ha_segments_t, handle_status, void,
|
|
private_ha_segments_t *this, segment_mask_t mask)
|
|
{
|
|
segment_mask_t missing, twice;
|
|
int i;
|
|
|
|
this->mutex->lock(this->mutex);
|
|
|
|
missing = ~(this->active | mask);
|
|
twice = this->active & mask;
|
|
|
|
for (i = 1; i <= this->count; i++)
|
|
{
|
|
if (missing & SEGMENTS_BIT(i))
|
|
{
|
|
if (this->node == i % 2)
|
|
{
|
|
DBG1(DBG_CFG, "HA segment %d was not handled, taking", i);
|
|
enable_disable(this, i, TRUE, TRUE);
|
|
}
|
|
else
|
|
{
|
|
DBG1(DBG_CFG, "HA segment %d was not handled, dropping", i);
|
|
enable_disable(this, i, FALSE, TRUE);
|
|
}
|
|
}
|
|
if (twice & SEGMENTS_BIT(i))
|
|
{
|
|
if (this->node == i % 2)
|
|
{
|
|
DBG1(DBG_CFG, "HA segment %d was handled twice, taking", i);
|
|
enable_disable(this, i, TRUE, TRUE);
|
|
}
|
|
else
|
|
{
|
|
DBG1(DBG_CFG, "HA segment %d was handled twice, dropping", i);
|
|
enable_disable(this, i, FALSE, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
this->condvar->signal(this->condvar);
|
|
this->mutex->unlock(this->mutex);
|
|
|
|
if (!this->heartbeat_active)
|
|
{
|
|
DBG1(DBG_CFG, "received heartbeat, reenabling watchdog");
|
|
start_watchdog(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send a status message with our active segments
|
|
*/
|
|
static job_requeue_t send_status(private_ha_segments_t *this)
|
|
{
|
|
ha_message_t *message;
|
|
int i;
|
|
|
|
message = ha_message_create(HA_STATUS);
|
|
|
|
this->mutex->lock(this->mutex);
|
|
for (i = 1; i <= this->count; i++)
|
|
{
|
|
if (this->active & SEGMENTS_BIT(i))
|
|
{
|
|
message->add_attribute(message, HA_SEGMENT, i);
|
|
}
|
|
}
|
|
this->mutex->unlock(this->mutex);
|
|
|
|
this->socket->push(this->socket, message);
|
|
message->destroy(message);
|
|
|
|
/* schedule next invocation */
|
|
return JOB_RESCHEDULE_MS(this->heartbeat_delay);
|
|
}
|
|
|
|
/**
|
|
* Start the heartbeat sending task
|
|
*/
|
|
static void start_heartbeat(private_ha_segments_t *this)
|
|
{
|
|
lib->processor->queue_job(lib->processor,
|
|
(job_t*)callback_job_create_with_prio((callback_job_cb_t)send_status,
|
|
this, NULL, callback_job_cancel_thread, JOB_PRIO_CRITICAL));
|
|
}
|
|
|
|
/**
|
|
* Take a segment if we are handling less than half of segments
|
|
*/
|
|
static job_requeue_t autobalance(private_ha_segments_t *this)
|
|
{
|
|
int i, active = 0;
|
|
|
|
this->mutex->lock(this->mutex);
|
|
|
|
for (i = 1; i <= this->count; i++)
|
|
{
|
|
if (this->active & SEGMENTS_BIT(i))
|
|
{
|
|
active++;
|
|
}
|
|
}
|
|
if (active < this->count / 2)
|
|
{
|
|
for (i = 1; i <= this->count; i++)
|
|
{
|
|
if (!(this->active & SEGMENTS_BIT(i)))
|
|
{
|
|
DBG1(DBG_CFG, "autobalancing HA (%d/%d active), taking %d",
|
|
active, this->count, i);
|
|
enable_disable(this, i, TRUE, TRUE);
|
|
/* we claim only one in each interval */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
this->mutex->unlock(this->mutex);
|
|
|
|
return JOB_RESCHEDULE(this->autobalance);
|
|
}
|
|
|
|
/**
|
|
* Schedule autobalancing
|
|
*/
|
|
static void start_autobalance(private_ha_segments_t *this)
|
|
{
|
|
DBG1(DBG_CFG, "scheduling HA autobalance every %ds", this->autobalance);
|
|
lib->scheduler->schedule_job(lib->scheduler,
|
|
(job_t*)callback_job_create_with_prio((callback_job_cb_t)autobalance,
|
|
this, NULL, callback_job_cancel_thread, JOB_PRIO_CRITICAL),
|
|
this->autobalance);
|
|
}
|
|
|
|
METHOD(ha_segments_t, is_active, bool,
|
|
private_ha_segments_t *this, u_int segment)
|
|
{
|
|
return (this->active & SEGMENTS_BIT(segment)) != 0;
|
|
}
|
|
|
|
METHOD(ha_segments_t, count, u_int,
|
|
private_ha_segments_t *this)
|
|
{
|
|
return this->count;
|
|
}
|
|
|
|
METHOD(ha_segments_t, destroy, void,
|
|
private_ha_segments_t *this)
|
|
{
|
|
this->mutex->destroy(this->mutex);
|
|
this->condvar->destroy(this->condvar);
|
|
free(this);
|
|
}
|
|
|
|
/**
|
|
* See header
|
|
*/
|
|
ha_segments_t *ha_segments_create(ha_socket_t *socket, ha_kernel_t *kernel,
|
|
ha_tunnel_t *tunnel, u_int count, u_int node,
|
|
bool monitor)
|
|
{
|
|
private_ha_segments_t *this;
|
|
|
|
INIT(this,
|
|
.public = {
|
|
.listener = {
|
|
.alert = _alert_hook,
|
|
},
|
|
.activate = _activate,
|
|
.deactivate = _deactivate,
|
|
.handle_status = _handle_status,
|
|
.is_active = _is_active,
|
|
.count = _count,
|
|
.destroy = _destroy,
|
|
},
|
|
.socket = socket,
|
|
.tunnel = tunnel,
|
|
.kernel = kernel,
|
|
.count = count,
|
|
.node = node,
|
|
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
|
|
.condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
|
|
.heartbeat_delay = lib->settings->get_int(lib->settings,
|
|
"%s.plugins.ha.heartbeat_delay", DEFAULT_HEARTBEAT_DELAY,
|
|
lib->ns),
|
|
.heartbeat_timeout = lib->settings->get_int(lib->settings,
|
|
"%s.plugins.ha.heartbeat_timeout", DEFAULT_HEARTBEAT_TIMEOUT,
|
|
lib->ns),
|
|
.autobalance = lib->settings->get_int(lib->settings,
|
|
"%s.plugins.ha.autobalance", 0, lib->ns),
|
|
);
|
|
|
|
if (monitor)
|
|
{
|
|
DBG1(DBG_CFG, "starting HA heartbeat, delay %dms, timeout %dms",
|
|
this->heartbeat_delay, this->heartbeat_timeout);
|
|
start_heartbeat(this);
|
|
start_watchdog(this);
|
|
}
|
|
if (this->autobalance)
|
|
{
|
|
start_autobalance(this);
|
|
}
|
|
|
|
return &this->public;
|
|
}
|