From: Tobias Brunner Date: Wed, 25 Mar 2015 17:20:01 +0000 (+0100) Subject: eap-radius: Keep track of stats for SAs migrated during IKEv1 reauthentication X-Git-Tag: 5.3.1rc1~14^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2b51124026dde366449ef5dffd42abe073d3584b;p=thirdparty%2Fstrongswan.git eap-radius: Keep track of stats for SAs migrated during IKEv1 reauthentication --- diff --git a/src/libcharon/plugins/eap_radius/eap_radius_accounting.c b/src/libcharon/plugins/eap_radius/eap_radius_accounting.c index b486ba4dca..cef19305c5 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 { - /** 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; + +/** + * 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 */ @@ -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,38 +234,87 @@ 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) + 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) { - INIT(sa, - .id = lookup.id, - ); - array_insert_create(&entry->cached, ARRAY_TAIL, sa); - array_sort(entry->cached, sa_sort, NULL); + add_usage(total, usage); } - 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); + enumerator->destroy(enumerator); + return stats; } /** @@ -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); }