From 7fbe79bce7a92c7ce5aac8eacdf418680e3ae198 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 24 Mar 2015 17:38:49 +0100 Subject: [PATCH 1/6] 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. --- .../eap_radius/eap_radius_accounting.c | 108 +++++++++++++++++- 1 file changed, 102 insertions(+), 6 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..80d01d456 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; } From 8dbef6dac8f2b2f40d898361c4b8f4ab9c94e16b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 25 Mar 2015 16:47:24 +0100 Subject: [PATCH 2/6] eap-radius: Remove cache entries for expired SAs during ike/child_rekey --- .../eap_radius/eap_radius_accounting.c | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/libcharon/plugins/eap_radius/eap_radius_accounting.c b/src/libcharon/plugins/eap_radius/eap_radius_accounting.c index 80d01d456..b486ba4dc 100644 --- a/src/libcharon/plugins/eap_radius/eap_radius_accounting.c +++ b/src/libcharon/plugins/eap_radius/eap_radius_accounting.c @@ -225,6 +225,48 @@ static void update_usage(private_eap_radius_accounting_t *this, this->mutex->unlock(this->mutex); } +/** + * 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 */ + 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); + array_destroy_function(sas, (void*)free, NULL); +} + /** * Send a RADIUS message, wait for response */ @@ -757,6 +799,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) { @@ -772,7 +816,16 @@ 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; } From 072d9dc3c66961090025b3802d48ab078c2d1852 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 25 Mar 2015 18:05:57 +0100 Subject: [PATCH 3/6] bus: Add new hook called when IKEv1 CHILD_SAs are migrated to a new IKE_SA The interface is currently not very nice, but if we ever were able to safely checkout multiple SAs concurrently we could add something similar to ike_rekey() and call that when we detect a reauthentication. --- src/libcharon/bus/bus.c | 34 +++++++++++++++++++++++++- src/libcharon/bus/bus.h | 10 +++++++- src/libcharon/bus/listeners/listener.h | 17 ++++++++++++- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/libcharon/bus/bus.c b/src/libcharon/bus/bus.c index 7938f46cc..53ded6be7 100644 --- a/src/libcharon/bus/bus.c +++ b/src/libcharon/bus/bus.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2014 Tobias Brunner + * Copyright (C) 2011-2015 Tobias Brunner * Copyright (C) 2006 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -687,6 +687,37 @@ METHOD(bus_t, child_rekey, void, this->mutex->unlock(this->mutex); } +METHOD(bus_t, children_migrate, void, + private_bus_t *this, ike_sa_id_t *new, u_int32_t unique) +{ + enumerator_t *enumerator; + ike_sa_t *ike_sa; + entry_t *entry; + bool keep; + + ike_sa = this->thread_sa->get(this->thread_sa); + + this->mutex->lock(this->mutex); + enumerator = this->listeners->create_enumerator(this->listeners); + while (enumerator->enumerate(enumerator, &entry)) + { + if (entry->calling || !entry->listener->children_migrate) + { + continue; + } + entry->calling++; + keep = entry->listener->children_migrate(entry->listener, ike_sa, new, + unique); + entry->calling--; + if (!keep) + { + unregister_listener(this, entry, enumerator); + } + } + enumerator->destroy(enumerator); + this->mutex->unlock(this->mutex); +} + METHOD(bus_t, ike_updown, void, private_bus_t *this, ike_sa_t *ike_sa, bool up) { @@ -1038,6 +1069,7 @@ bus_t *bus_create() .ike_reestablish_post = _ike_reestablish_post, .child_updown = _child_updown, .child_rekey = _child_rekey, + .children_migrate = _children_migrate, .authorize = _authorize, .narrow = _narrow, .assign_vips = _assign_vips, diff --git a/src/libcharon/bus/bus.h b/src/libcharon/bus/bus.h index 47b8820d3..b6757b140 100644 --- a/src/libcharon/bus/bus.h +++ b/src/libcharon/bus/bus.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2014 Tobias Brunner + * Copyright (C) 2012-2015 Tobias Brunner * Copyright (C) 2006-2009 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -426,6 +426,14 @@ struct bus_t { */ void (*child_rekey)(bus_t *this, child_sa_t *old, child_sa_t *new); + /** + * CHILD_SA migration hook. + * + * @param new ID of new SA when called for the old, NULL otherwise + * @param uniue unique ID of new SA when called for the old, 0 otherwise + */ + void (*children_migrate)(bus_t *this, ike_sa_id_t *new, u_int32_t unique); + /** * Virtual IP assignment hook. * diff --git a/src/libcharon/bus/listeners/listener.h b/src/libcharon/bus/listeners/listener.h index 3447d8f99..c7a8d8d1e 100644 --- a/src/libcharon/bus/listeners/listener.h +++ b/src/libcharon/bus/listeners/listener.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2014 Tobias Brunner + * Copyright (C) 2011-2015 Tobias Brunner * Copyright (C) 2009 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -187,6 +187,21 @@ struct listener_t { bool (*child_rekey)(listener_t *this, ike_sa_t *ike_sa, child_sa_t *old, child_sa_t *new); + /** + * Hook called when CHILD_SAs get migrated from one IKE_SA to another during + * IKEv1 reauthentication. + * + * This is called twice, once for the old IKE_SA before the CHILD_SAs are + * removed, and once for the new IKE_SA just after they got added. + * + * @param ike_sa new or old IKE_SA + * @param new ID of new SA when called for the old, NULL otherwise + * @param unique unique ID of new SA when called for the old, 0 otherwise + * @return TRUE to stay registered, FALSE to unregister + */ + bool (*children_migrate)(listener_t *this, ike_sa_t *ike_sa, + ike_sa_id_t *new, u_int32_t unique); + /** * Hook called to invoke additional authorization rules. * From 6a9a69ae58c68eeb821e5762631afe6a9b8c624b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 25 Mar 2015 18:11:22 +0100 Subject: [PATCH 4/6] ikev1: Trigger children_migrate event if CHILD_SAs are adopted --- src/libcharon/processing/jobs/adopt_children_job.c | 4 ++++ src/libcharon/sa/ike_sa_manager.c | 4 +++- src/libcharon/sa/ikev1/task_manager_v1.c | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/libcharon/processing/jobs/adopt_children_job.c b/src/libcharon/processing/jobs/adopt_children_job.c index c8a9c17de..9ad4b7304 100644 --- a/src/libcharon/processing/jobs/adopt_children_job.c +++ b/src/libcharon/processing/jobs/adopt_children_job.c @@ -64,11 +64,13 @@ METHOD(job_t, execute, job_requeue_t, ike_sa_id_t *id; ike_sa_t *ike_sa; child_sa_t *child_sa; + u_int32_t unique; ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, this->id); if (ike_sa) { /* get what we need from new SA */ + unique = ike_sa->get_unique_id(ike_sa); me = ike_sa->get_my_host(ike_sa); me = me->clone(me); other = ike_sa->get_other_host(ike_sa); @@ -106,6 +108,7 @@ METHOD(job_t, execute, job_requeue_t, other_id->equals(other_id, ike_sa->get_other_id(ike_sa)) && cfg->equals(cfg, ike_sa->get_peer_cfg(ike_sa))) { + charon->bus->children_migrate(charon->bus, this->id, unique); subenum = ike_sa->create_child_sa_enumerator(ike_sa); while (subenum->enumerate(subenum, &child_sa)) { @@ -176,6 +179,7 @@ METHOD(job_t, execute, job_requeue_t, } charon->bus->assign_vips(charon->bus, ike_sa, TRUE); } + charon->bus->children_migrate(charon->bus, NULL, 0); charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); } } diff --git a/src/libcharon/sa/ike_sa_manager.c b/src/libcharon/sa/ike_sa_manager.c index 13fc74ff7..938f7848f 100644 --- a/src/libcharon/sa/ike_sa_manager.c +++ b/src/libcharon/sa/ike_sa_manager.c @@ -1737,7 +1737,8 @@ static void adopt_children_and_vips(ike_sa_t *old, ike_sa_t *new) host_t *vip; int chcount = 0, vipcount = 0; - + charon->bus->children_migrate(charon->bus, new->get_id(new), + new->get_unique_id(new)); enumerator = old->create_child_sa_enumerator(old); while (enumerator->enumerate(enumerator, &child_sa)) { @@ -1760,6 +1761,7 @@ static void adopt_children_and_vips(ike_sa_t *old, ike_sa_t *new) /* ...trigger the analogous event on the new SA */ charon->bus->set_sa(charon->bus, new); charon->bus->assign_vips(charon->bus, new, TRUE); + charon->bus->children_migrate(charon->bus, NULL, 0); charon->bus->set_sa(charon->bus, old); if (chcount || vipcount) diff --git a/src/libcharon/sa/ikev1/task_manager_v1.c b/src/libcharon/sa/ikev1/task_manager_v1.c index cb22bf606..ed547c4c2 100644 --- a/src/libcharon/sa/ikev1/task_manager_v1.c +++ b/src/libcharon/sa/ikev1/task_manager_v1.c @@ -1475,6 +1475,8 @@ METHOD(task_manager_t, queue_ike_reauth, void, } enumerator->destroy(enumerator); + charon->bus->children_migrate(charon->bus, new->get_id(new), + new->get_unique_id(new)); enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa); while (enumerator->enumerate(enumerator, &child_sa)) { @@ -1482,6 +1484,9 @@ METHOD(task_manager_t, queue_ike_reauth, void, new->add_child_sa(new, child_sa); } enumerator->destroy(enumerator); + charon->bus->set_sa(charon->bus, new); + charon->bus->children_migrate(charon->bus, NULL, 0); + charon->bus->set_sa(charon->bus, this->ike_sa); if (!new->get_child_count(new)) { /* check if a Quick Mode task is queued (UNITY_LOAD_BALANCE case) */ From 2b51124026dde366449ef5dffd42abe073d3584b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 25 Mar 2015 18:20:01 +0100 Subject: [PATCH 5/6] eap-radius: Keep track of stats for SAs migrated during IKEv1 reauthentication --- .../eap_radius/eap_radius_accounting.c | 321 +++++++++++++----- 1 file changed, 231 insertions(+), 90 deletions(-) diff --git a/src/libcharon/plugins/eap_radius/eap_radius_accounting.c b/src/libcharon/plugins/eap_radius/eap_radius_accounting.c index b486ba4dc..cef19305c 100644 --- a/src/libcharon/plugins/eap_radius/eap_radius_accounting.c +++ b/src/libcharon/plugins/eap_radius/eap_radius_accounting.c @@ -97,18 +97,61 @@ typedef enum { } radius_acct_terminate_cause_t; /** - * Usage stats for a cached SAs + * 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 */ - struct { - u_int64_t sent; - u_int64_t received; - } bytes, packets; + 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 */ @@ -118,12 +161,11 @@ typedef struct { /** RADIUS accounting session ID */ char sid[24]; /** number of sent/received octets/packets for expired SAs */ - struct { - u_int64_t sent; - u_int64_t received; - } bytes, packets; + 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 */ @@ -143,6 +185,7 @@ 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); } @@ -191,40 +234,89 @@ 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; - 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); + 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) { - 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; + 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 */ @@ -255,15 +347,24 @@ static void cleanup_sas(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, { /* 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; + 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); } @@ -362,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), @@ -383,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; @@ -419,11 +517,9 @@ 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; @@ -435,28 +531,7 @@ static job_requeue_t send_interim(interim_data_t *data) { 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; - } - 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 */ @@ -474,27 +549,38 @@ static job_requeue_t send_interim(interim_data_t *data) { /* 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; + sa->usage = found->usage; } else { - /* SA is gone, add its latest stats to the total for this IKE_SA + /* SA is gone, add its last 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; + add_usage(&entry->usage, sa->usage); 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; - packets_out += entry->packets.sent; + 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); @@ -503,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); @@ -606,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); @@ -660,10 +747,14 @@ static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa) 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; + 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); @@ -674,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); @@ -829,6 +920,54 @@ METHOD(listener_t, child_rekey, bool, 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; +} + METHOD(listener_t, child_updown, bool, private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, bool up) @@ -866,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, }, @@ -908,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); } From 240ad7abf5894bcdcb3756a9f5dae4154de9beae Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 27 Mar 2015 14:49:38 +0100 Subject: [PATCH 6/6] libipsec: Insert SAs first, so latest SA with the same reqid gets used This was useful for testing purposes of RADIUS accounting, but OS kernels generally will use the latest SA, so we do the same. --- src/libipsec/ipsec_sa_mgr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libipsec/ipsec_sa_mgr.c b/src/libipsec/ipsec_sa_mgr.c index 07ffa9e4f..9d461f2c1 100644 --- a/src/libipsec/ipsec_sa_mgr.c +++ b/src/libipsec/ipsec_sa_mgr.c @@ -482,7 +482,7 @@ METHOD(ipsec_sa_mgr_t, add_sa, status_t, entry = create_entry(sa_new); schedule_expiration(this, entry); - this->sas->insert_last(this->sas, entry); + this->sas->insert_first(this->sas, entry); this->mutex->unlock(this->mutex); return SUCCESS;