From: Tobias Brunner Date: Tue, 24 Mar 2015 16:38:49 +0000 (+0100) Subject: eap-radius: Add cache for usage stats of expired/rekeyed SAs X-Git-Tag: 5.3.1rc1~14^2~5 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7fbe79bce7a92c7ce5aac8eacdf418680e3ae198;p=thirdparty%2Fstrongswan.git eap-radius: Add cache for usage stats of expired/rekeyed SAs There are several situations that the previous code didn't handle that well, for example, interim updates during rekeying (until the rekeyed SA was deleted the numbers were too high, then suddenly dropped afterwards), or rekeying for IKEv1 in general because rekeyed IPsec SAs stay installed until they expire (so if they were still around when the IKE_SA was terminated, the reported numbers in the Stop message were too high). If intermediate updates are not used the cache entries for rekeyed CHILD_SA will accumulate, we can't clean them up as we don't get child_updown() events for them. --- diff --git a/src/libcharon/plugins/eap_radius/eap_radius_accounting.c b/src/libcharon/plugins/eap_radius/eap_radius_accounting.c index ac4ecfc867..80d01d4567 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 #include #include +#include #include #include #include @@ -92,6 +96,19 @@ typedef enum { ACCT_CAUSE_HOST_REQUEST = 18, } radius_acct_terminate_cause_t; +/** + * Usage stats for a cached SAs + */ +typedef struct { + /** unique CHILD_SA identifier */ + u_int32_t id; + /** usage stats for this SA */ + struct { + u_int64_t sent; + u_int64_t received; + } bytes, packets; +} sa_entry_t; + /** * Hashtable entry with usage stats */ @@ -100,11 +117,13 @@ typedef struct { ike_sa_id_t *id; /** RADIUS accounting session ID */ char sid[24]; - /** number of sent/received octets/packets */ + /** number of sent/received octets/packets for expired SAs */ struct { u_int64_t sent; u_int64_t received; } bytes, packets; + /** list of cached SAs, sa_entry_t (sorted by their unique ID) */ + array_t *cached; /** session creation time */ time_t created; /** terminate cause */ @@ -123,6 +142,7 @@ typedef struct { */ static void destroy_entry(entry_t *this) { + array_destroy_function(this->cached, (void*)free, NULL); this->id->destroy(this->id); free(this); } @@ -154,6 +174,23 @@ static bool equals(ike_sa_id_t *a, ike_sa_id_t *b) return a->equals(a, 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 usage counter when a CHILD_SA rekeys/goes down */ @@ -162,6 +199,7 @@ static void update_usage(private_eap_radius_accounting_t *this, { u_int64_t bytes_in, bytes_out, packets_in, packets_out; entry_t *entry; + sa_entry_t *sa, lookup; child_sa->get_usestats(child_sa, FALSE, NULL, &bytes_out, &packets_out); child_sa->get_usestats(child_sa, TRUE, NULL, &bytes_in, &packets_in); @@ -170,10 +208,19 @@ static void update_usage(private_eap_radius_accounting_t *this, 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; + lookup.id = child_sa->get_unique_id(child_sa); + if (array_bsearch(entry->cached, &lookup, sa_find, &sa) == -1) + { + INIT(sa, + .id = lookup.id, + ); + array_insert_create(&entry->cached, ARRAY_TAIL, sa); + array_sort(entry->cached, sa_sort, NULL); + } + sa->bytes.sent = bytes_out; + sa->bytes.received = bytes_in; + sa->packets.sent = packets_out; + sa->packets.received = packets_in; } this->mutex->unlock(this->mutex); } @@ -338,19 +385,32 @@ static job_requeue_t send_interim(interim_data_t *data) 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; } + 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, FALSE, NULL, &bytes, &packets); + sa->bytes.sent = bytes; + sa->packets.sent = packets; bytes_out += bytes; packets_out += packets; child_sa->get_usestats(child_sa, TRUE, NULL, &bytes, &packets); + sa->bytes.received = bytes; + sa->packets.received = packets; bytes_in += bytes; packets_in += packets; } @@ -365,6 +425,30 @@ static job_requeue_t send_interim(interim_data_t *data) { entry->interim.last = time_monotonic(NULL); + 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->bytes = found->bytes; + sa->packets = found->packets; + } + else + { + /* SA is gone, add its latest stats to the total for this IKE_SA + * and remove the cache entry */ + entry->bytes.sent += sa->bytes.sent; + entry->bytes.received += sa->bytes.received; + entry->packets.sent += sa->packets.sent; + entry->packets.received += sa->packets.received; + array_remove_at(entry->cached, enumerator); + free(sa); + } + } + enumerator->destroy(enumerator); + bytes_in += entry->bytes.received; bytes_out += entry->bytes.sent; packets_in += entry->packets.received; @@ -405,6 +489,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) { @@ -515,7 +600,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 +615,16 @@ 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)) + { + entry->bytes.sent += sa->bytes.sent; + entry->bytes.received += sa->bytes.received; + entry->packets.sent += sa->packets.sent; + entry->packets.received += sa->packets.received; + } + 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)); @@ -676,7 +773,6 @@ METHOD(listener_t, child_rekey, bool, child_sa_t *old, child_sa_t *new) { update_usage(this, ike_sa, old); - return TRUE; }