summaryrefslogtreecommitdiff
path: root/src/libcharon/plugins/eap_radius/eap_radius_accounting.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcharon/plugins/eap_radius/eap_radius_accounting.c')
-rw-r--r--src/libcharon/plugins/eap_radius/eap_radius_accounting.c390
1 files changed, 340 insertions, 50 deletions
diff --git a/src/libcharon/plugins/eap_radius/eap_radius_accounting.c b/src/libcharon/plugins/eap_radius/eap_radius_accounting.c
index ac4ecfc86..cef19305c 100644
--- a/src/libcharon/plugins/eap_radius/eap_radius_accounting.c
+++ b/src/libcharon/plugins/eap_radius/eap_radius_accounting.c
@@ -1,4 +1,7 @@
/*
+ * Copyright (C) 2015 Tobias Brunner
+ * Hochschule fuer Technik Rapperswil
+ *
* Copyright (C) 2012 Martin Willi
* Copyright (C) 2012 revosec AG
*
@@ -21,6 +24,7 @@
#include <radius_message.h>
#include <radius_client.h>
#include <daemon.h>
+#include <collections/array.h>
#include <collections/hashtable.h>
#include <threading/mutex.h>
#include <processing/jobs/callback_job.h>
@@ -93,6 +97,62 @@ typedef enum {
} radius_acct_terminate_cause_t;
/**
+ * Usage stats for bytes and packets
+ */
+typedef struct {
+ struct {
+ u_int64_t sent;
+ u_int64_t received;
+ } bytes, packets;
+} usage_t;
+
+/**
+ * Add usage stats (modifies a)
+ */
+static inline void add_usage(usage_t *a, usage_t b)
+{
+ a->bytes.sent += b.bytes.sent;
+ a->bytes.received += b.bytes.received;
+ a->packets.sent += b.packets.sent;
+ a->packets.received += b.packets.received;
+}
+
+/**
+ * Subtract usage stats (modifies a)
+ */
+static inline void sub_usage(usage_t *a, usage_t b)
+{
+ a->bytes.sent -= b.bytes.sent;
+ a->bytes.received -= b.bytes.received;
+ a->packets.sent -= b.packets.sent;
+ a->packets.received -= b.packets.received;
+}
+
+/**
+ * Usage stats for a cached/migrated SAs
+ */
+typedef struct {
+ /** unique CHILD_SA identifier */
+ u_int32_t id;
+ /** usage stats for this SA */
+ usage_t usage;
+} sa_entry_t;
+
+/**
+ * Clone an sa_entry_t
+ */
+static sa_entry_t *clone_sa(sa_entry_t *sa)
+{
+ sa_entry_t *this;
+
+ INIT(this,
+ .id = sa->id,
+ .usage = sa->usage,
+ );
+ return this;
+}
+
+/**
* Hashtable entry with usage stats
*/
typedef struct {
@@ -100,11 +160,12 @@ typedef struct {
ike_sa_id_t *id;
/** RADIUS accounting session ID */
char sid[24];
- /** number of sent/received octets/packets */
- struct {
- u_int64_t sent;
- u_int64_t received;
- } bytes, packets;
+ /** number of sent/received octets/packets for expired SAs */
+ usage_t usage;
+ /** list of cached SAs, sa_entry_t (sorted by their unique ID) */
+ array_t *cached;
+ /** list of migrated SAs, sa_entry_t (sorted by their unique ID) */
+ array_t *migrated;
/** session creation time */
time_t created;
/** terminate cause */
@@ -123,6 +184,8 @@ typedef struct {
*/
static void destroy_entry(entry_t *this)
{
+ array_destroy_function(this->cached, (void*)free, NULL);
+ array_destroy_function(this->migrated, (void*)free, NULL);
this->id->destroy(this->id);
free(this);
}
@@ -155,30 +218,157 @@ static bool equals(ike_sa_id_t *a, ike_sa_id_t *b)
}
/**
+ * Sort cached SAs
+ */
+static int sa_sort(const void *a, const void *b, void *user)
+{
+ const sa_entry_t *ra = a, *rb = b;
+ return ra->id - rb->id;
+}
+
+/**
+ * Find a cached SA
+ */
+static int sa_find(const void *a, const void *b)
+{
+ return sa_sort(a, b, NULL);
+}
+
+/**
+ * Update or create usage counters of a cached SA
+ */
+static void update_sa(entry_t *entry, u_int32_t id, usage_t usage)
+{
+ sa_entry_t *sa, lookup;
+
+ lookup.id = id;
+ if (array_bsearch(entry->cached, &lookup, sa_find, &sa) == -1)
+ {
+ INIT(sa,
+ .id = id,
+ );
+ array_insert_create(&entry->cached, ARRAY_TAIL, sa);
+ array_sort(entry->cached, sa_sort, NULL);
+ }
+ sa->usage = usage;
+}
+
+/**
* Update usage counter when a CHILD_SA rekeys/goes down
*/
static void update_usage(private_eap_radius_accounting_t *this,
ike_sa_t *ike_sa, child_sa_t *child_sa)
{
- u_int64_t bytes_in, bytes_out, packets_in, packets_out;
+ usage_t usage;
entry_t *entry;
- child_sa->get_usestats(child_sa, FALSE, NULL, &bytes_out, &packets_out);
- child_sa->get_usestats(child_sa, TRUE, NULL, &bytes_in, &packets_in);
+ child_sa->get_usestats(child_sa, TRUE, NULL, &usage.bytes.received,
+ &usage.packets.received);
+ child_sa->get_usestats(child_sa, FALSE, NULL, &usage.bytes.sent,
+ &usage.packets.sent);
this->mutex->lock(this->mutex);
entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
if (entry)
{
- entry->bytes.sent += bytes_out;
- entry->bytes.received += bytes_in;
- entry->packets.sent += packets_out;
- entry->packets.received += packets_in;
+ update_sa(entry, child_sa->get_unique_id(child_sa), usage);
}
this->mutex->unlock(this->mutex);
}
/**
+ * Collect usage stats for all CHILD_SAs of the given IKE_SA, optionally returns
+ * the total number of bytes and packets
+ */
+static array_t *collect_stats(ike_sa_t *ike_sa, usage_t *total)
+{
+ enumerator_t *enumerator;
+ child_sa_t *child_sa;
+ array_t *stats;
+ sa_entry_t *sa;
+ usage_t usage;
+
+ if (total)
+ {
+ *total = (usage_t){};
+ }
+
+ stats = array_create(0, 0);
+ enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
+ while (enumerator->enumerate(enumerator, &child_sa))
+ {
+ INIT(sa,
+ .id = child_sa->get_unique_id(child_sa),
+ );
+ array_insert(stats, ARRAY_TAIL, sa);
+ array_sort(stats, sa_sort, NULL);
+
+ child_sa->get_usestats(child_sa, TRUE, NULL, &usage.bytes.received,
+ &usage.packets.received);
+ child_sa->get_usestats(child_sa, FALSE, NULL, &usage.bytes.sent,
+ &usage.packets.sent);
+ sa->usage = usage;
+ if (total)
+ {
+ add_usage(total, usage);
+ }
+ }
+ enumerator->destroy(enumerator);
+ return stats;
+}
+
+/**
+ * Cleanup cached SAs
+ */
+static void cleanup_sas(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
+ entry_t *entry)
+{
+ enumerator_t *enumerator;
+ child_sa_t *child_sa;
+ sa_entry_t *sa, *found;
+ array_t *sas;
+
+ sas = array_create(0, 0);
+ enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
+ while (enumerator->enumerate(enumerator, &child_sa))
+ {
+ INIT(sa,
+ .id = child_sa->get_unique_id(child_sa),
+ );
+ array_insert(sas, ARRAY_TAIL, sa);
+ array_sort(sas, sa_sort, NULL);
+ }
+ enumerator->destroy(enumerator);
+
+ enumerator = array_create_enumerator(entry->cached);
+ while (enumerator->enumerate(enumerator, &sa))
+ {
+ if (array_bsearch(sas, sa, sa_find, &found) == -1)
+ {
+ /* SA is gone, add its latest stats to the total for this IKE_SA
+ * and remove the cache entry */
+ add_usage(&entry->usage, sa->usage);
+ array_remove_at(entry->cached, enumerator);
+ free(sa);
+ }
+ }
+ enumerator->destroy(enumerator);
+ enumerator = array_create_enumerator(entry->migrated);
+ while (enumerator->enumerate(enumerator, &sa))
+ {
+ if (array_bsearch(sas, sa, sa_find, &found) == -1)
+ {
+ /* SA is gone, subtract stats from the total for this IKE_SA */
+ sub_usage(&entry->usage, sa->usage);
+ array_remove_at(entry->migrated, enumerator);
+ free(sa);
+ }
+ }
+ enumerator->destroy(enumerator);
+ array_destroy_function(sas, (void*)free, NULL);
+}
+
+/**
* Send a RADIUS message, wait for response
*/
static bool send_message(private_eap_radius_accounting_t *this,
@@ -273,17 +463,15 @@ static void add_ike_sa_parameters(private_eap_radius_accounting_t *this,
* Get an existing or create a new entry from the locked session table
*/
static entry_t* get_or_create_entry(private_eap_radius_accounting_t *this,
- ike_sa_t *ike_sa)
+ ike_sa_id_t *id, u_int32_t unique)
{
- ike_sa_id_t *id;
entry_t *entry;
time_t now;
- entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
+ entry = this->sessions->get(this->sessions, id);
if (!entry)
{
now = time_monotonic(NULL);
- id = ike_sa->get_id(ike_sa);
INIT(entry,
.id = id->clone(id),
@@ -294,8 +482,7 @@ static entry_t* get_or_create_entry(private_eap_radius_accounting_t *this,
/* default terminate cause, if none other catched */
.cause = ACCT_CAUSE_USER_REQUEST,
);
- snprintf(entry->sid, sizeof(entry->sid), "%u-%u",
- this->prefix, ike_sa->get_unique_id(ike_sa));
+ snprintf(entry->sid, sizeof(entry->sid), "%u-%u", this->prefix, unique);
this->sessions->put(this->sessions, entry->id, entry);
}
return entry;
@@ -330,31 +517,21 @@ void destroy_interim_data(interim_data_t *this)
static job_requeue_t send_interim(interim_data_t *data)
{
private_eap_radius_accounting_t *this = data->this;
- u_int64_t bytes_in = 0, bytes_out = 0, packets_in = 0, packets_out = 0;
- u_int64_t bytes, packets;
+ usage_t usage;
radius_message_t *message = NULL;
enumerator_t *enumerator;
- child_sa_t *child_sa;
ike_sa_t *ike_sa;
entry_t *entry;
u_int32_t value;
+ array_t *stats;
+ sa_entry_t *sa, *found;
ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, data->id);
if (!ike_sa)
{
return JOB_REQUEUE_NONE;
}
- enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
- while (enumerator->enumerate(enumerator, &child_sa))
- {
- child_sa->get_usestats(child_sa, FALSE, NULL, &bytes, &packets);
- bytes_out += bytes;
- packets_out += packets;
- child_sa->get_usestats(child_sa, TRUE, NULL, &bytes, &packets);
- bytes_in += bytes;
- packets_in += packets;
- }
- enumerator->destroy(enumerator);
+ stats = collect_stats(ike_sa, &usage);
charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
/* avoid any races by returning IKE_SA before acquiring lock */
@@ -365,10 +542,45 @@ static job_requeue_t send_interim(interim_data_t *data)
{
entry->interim.last = time_monotonic(NULL);
- bytes_in += entry->bytes.received;
- bytes_out += entry->bytes.sent;
- packets_in += entry->packets.received;
- packets_out += entry->packets.sent;
+ enumerator = array_create_enumerator(entry->cached);
+ while (enumerator->enumerate(enumerator, &sa))
+ {
+ if (array_bsearch(stats, sa, sa_find, &found) != -1)
+ {
+ /* SA is still around, update stats (e.g. for IKEv1 where
+ * SA might get used even after rekeying) */
+ sa->usage = found->usage;
+ }
+ else
+ {
+ /* SA is gone, add its last stats to the total for this IKE_SA
+ * and remove the cache entry */
+ add_usage(&entry->usage, sa->usage);
+ array_remove_at(entry->cached, enumerator);
+ free(sa);
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ enumerator = array_create_enumerator(entry->migrated);
+ while (enumerator->enumerate(enumerator, &sa))
+ {
+ if (array_bsearch(stats, sa, sa_find, &found) != -1)
+ {
+ /* SA is still around, but we have to compensate */
+ sub_usage(&usage, sa->usage);
+ }
+ else
+ {
+ /* SA is gone, subtract stats from the total for this IKE_SA */
+ sub_usage(&entry->usage, sa->usage);
+ array_remove_at(entry->migrated, enumerator);
+ free(sa);
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ add_usage(&usage, entry->usage);
message = radius_message_create(RMC_ACCOUNTING_REQUEST);
value = htonl(ACCT_STATUS_INTERIM_UPDATE);
@@ -377,26 +589,26 @@ static job_requeue_t send_interim(interim_data_t *data)
chunk_create(entry->sid, strlen(entry->sid)));
add_ike_sa_parameters(this, message, ike_sa);
- value = htonl(bytes_out);
+ value = htonl(usage.bytes.sent);
message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
- value = htonl(bytes_out >> 32);
+ value = htonl(usage.bytes.sent >> 32);
if (value)
{
message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
chunk_from_thing(value));
}
- value = htonl(packets_out);
+ value = htonl(usage.packets.sent);
message->add(message, RAT_ACCT_OUTPUT_PACKETS, chunk_from_thing(value));
- value = htonl(bytes_in);
+ value = htonl(usage.bytes.received);
message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
- value = htonl(bytes_in >> 32);
+ value = htonl(usage.bytes.received >> 32);
if (value)
{
message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
chunk_from_thing(value));
}
- value = htonl(packets_in);
+ value = htonl(usage.packets.received);
message->add(message, RAT_ACCT_INPUT_PACKETS, chunk_from_thing(value));
value = htonl(entry->interim.last - entry->created);
@@ -405,6 +617,7 @@ static job_requeue_t send_interim(interim_data_t *data)
schedule_interim(this, entry);
}
this->mutex->unlock(this->mutex);
+ array_destroy_function(stats, (void*)free, NULL);
if (message)
{
@@ -479,7 +692,8 @@ static void send_start(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
this->mutex->lock(this->mutex);
- entry = get_or_create_entry(this, ike_sa);
+ entry = get_or_create_entry(this, ike_sa->get_id(ike_sa),
+ ike_sa->get_unique_id(ike_sa));
entry->start_sent = TRUE;
message = radius_message_create(RMC_ACCOUNTING_REQUEST);
@@ -515,7 +729,9 @@ static void send_start(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
{
radius_message_t *message;
+ enumerator_t *enumerator;
entry_t *entry;
+ sa_entry_t *sa;
u_int32_t value;
this->mutex->lock(this->mutex);
@@ -528,6 +744,20 @@ static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
destroy_entry(entry);
return;
}
+ enumerator = array_create_enumerator(entry->cached);
+ while (enumerator->enumerate(enumerator, &sa))
+ {
+ add_usage(&entry->usage, sa->usage);
+ }
+ enumerator->destroy(enumerator);
+
+ enumerator = array_create_enumerator(entry->migrated);
+ while (enumerator->enumerate(enumerator, &sa))
+ {
+ sub_usage(&entry->usage, sa->usage);
+ }
+ enumerator->destroy(enumerator);
+
message = radius_message_create(RMC_ACCOUNTING_REQUEST);
value = htonl(ACCT_STATUS_STOP);
message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
@@ -535,26 +765,26 @@ static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
chunk_create(entry->sid, strlen(entry->sid)));
add_ike_sa_parameters(this, message, ike_sa);
- value = htonl(entry->bytes.sent);
+ value = htonl(entry->usage.bytes.sent);
message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
- value = htonl(entry->bytes.sent >> 32);
+ value = htonl(entry->usage.bytes.sent >> 32);
if (value)
{
message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
chunk_from_thing(value));
}
- value = htonl(entry->packets.sent);
+ value = htonl(entry->usage.packets.sent);
message->add(message, RAT_ACCT_OUTPUT_PACKETS, chunk_from_thing(value));
- value = htonl(entry->bytes.received);
+ value = htonl(entry->usage.bytes.received);
message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
- value = htonl(entry->bytes.received >> 32);
+ value = htonl(entry->usage.bytes.received >> 32);
if (value)
{
message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
chunk_from_thing(value));
}
- value = htonl(entry->packets.received);
+ value = htonl(entry->usage.packets.received);
message->add(message, RAT_ACCT_INPUT_PACKETS, chunk_from_thing(value));
value = htonl(time_monotonic(NULL) - entry->created);
@@ -660,6 +890,8 @@ METHOD(listener_t, ike_rekey, bool,
/* fire new interim update job, old gets invalid */
schedule_interim(this, entry);
+ cleanup_sas(this, new, entry);
+
entry = this->sessions->put(this->sessions, entry->id, entry);
if (entry)
{
@@ -675,8 +907,64 @@ METHOD(listener_t, child_rekey, bool,
private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
child_sa_t *old, child_sa_t *new)
{
+ entry_t *entry;
+
update_usage(this, ike_sa, old);
+ this->mutex->lock(this->mutex);
+ entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
+ if (entry)
+ {
+ cleanup_sas(this, ike_sa, entry);
+ }
+ this->mutex->unlock(this->mutex);
+ return TRUE;
+}
+
+METHOD(listener_t, children_migrate, bool,
+ private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, ike_sa_id_t *new,
+ u_int32_t unique)
+{
+ enumerator_t *enumerator;
+ sa_entry_t *sa, *sa_new, *cached;
+ entry_t *entry_old, *entry_new;
+ array_t *stats;
+ if (!new)
+ {
+ return TRUE;
+ }
+ stats = collect_stats(ike_sa, NULL);
+ this->mutex->lock(this->mutex);
+ entry_old = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
+ if (entry_old)
+ {
+ entry_new = get_or_create_entry(this, new, unique);
+ enumerator = array_create_enumerator(stats);
+ while (enumerator->enumerate(enumerator, &sa))
+ {
+ /* if the SA was already rekeyed/cached we cache it too on the new
+ * SA to track it properly until it's finally gone */
+ if (array_bsearch(entry_old->cached, sa, sa_find, &cached) != -1)
+ {
+ sa_new = clone_sa(sa);
+ array_insert_create(&entry_new->cached, ARRAY_TAIL, sa_new);
+ array_sort(entry_new->cached, sa_sort, NULL);
+ }
+ /* if the SA was used, we store it to compensate on the new SA */
+ if (sa->usage.bytes.sent || sa->usage.bytes.received ||
+ sa->usage.packets.sent || sa->usage.packets.received)
+ {
+ sa_new = clone_sa(sa);
+ array_insert_create(&entry_new->migrated, ARRAY_TAIL, sa_new);
+ array_sort(entry_new->migrated, sa_sort, NULL);
+ /* store/update latest stats on old SA to report in Stop */
+ update_sa(entry_old, sa->id, sa->usage);
+ }
+ }
+ enumerator->destroy(enumerator);
+ }
+ this->mutex->unlock(this->mutex);
+ array_destroy_function(stats, (void*)free, NULL);
return TRUE;
}
@@ -717,6 +1005,7 @@ eap_radius_accounting_t *eap_radius_accounting_create()
.message = _message_hook,
.child_updown = _child_updown,
.child_rekey = _child_rekey,
+ .children_migrate = _children_migrate,
},
.destroy = _destroy,
},
@@ -759,7 +1048,8 @@ void eap_radius_accounting_start_interim(ike_sa_t *ike_sa, u_int32_t interval)
DBG1(DBG_CFG, "scheduling RADIUS Interim-Updates every %us", interval);
singleton->mutex->lock(singleton->mutex);
- entry = get_or_create_entry(singleton, ike_sa);
+ entry = get_or_create_entry(singleton, ike_sa->get_id(ike_sa),
+ ike_sa->get_unique_id(ike_sa));
entry->interim.interval = interval;
singleton->mutex->unlock(singleton->mutex);
}