/* * Copyright (C) 2015-2018 Tobias Brunner * HSR Hochschule fuer Technik Rapperswil * * Copyright (C) 2012 Martin Willi * Copyright (C) 2012 revosec 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 . * * 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 "eap_radius_accounting.h" #include "eap_radius_provider.h" #include "eap_radius_plugin.h" #include #include #include #include #include #include #include #include typedef struct private_eap_radius_accounting_t private_eap_radius_accounting_t; /** * Private data of an eap_radius_accounting_t object. */ struct private_eap_radius_accounting_t { /** * Public eap_radius_accounting_t interface. */ eap_radius_accounting_t public; /** * Hashtable with sessions, ike_sa_id_t => entry_t */ hashtable_t *sessions; /** * Mutex to lock sessions */ mutex_t *mutex; /** * Session ID prefix */ uint32_t prefix; /** * Format string we use for Called/Calling-Station-Id for a host */ char *station_id_fmt; /** * Disable accounting unless IKE_SA has at least one virtual IP */ bool acct_req_vip; }; /** * Singleton instance of accounting */ static private_eap_radius_accounting_t *singleton = NULL; /** * Acct-Terminate-Cause */ typedef enum { ACCT_CAUSE_USER_REQUEST = 1, ACCT_CAUSE_LOST_CARRIER = 2, ACCT_CAUSE_LOST_SERVICE = 3, ACCT_CAUSE_IDLE_TIMEOUT = 4, ACCT_CAUSE_SESSION_TIMEOUT = 5, ACCT_CAUSE_ADMIN_RESET = 6, ACCT_CAUSE_ADMIN_REBOOT = 7, ACCT_CAUSE_PORT_ERROR = 8, ACCT_CAUSE_NAS_ERROR = 9, ACCT_CAUSE_NAS_REQUEST = 10, ACCT_CAUSE_NAS_REBOOT = 11, ACCT_CAUSE_PORT_UNNEEDED = 12, ACCT_CAUSE_PORT_PREEMPTED = 13, ACCT_CAUSE_PORT_SUSPENDED = 14, ACCT_CAUSE_SERVICE_UNAVAILABLE = 15, ACCT_CAUSE_CALLBACK = 16, ACCT_CAUSE_USER_ERROR = 17, ACCT_CAUSE_HOST_REQUEST = 18, } radius_acct_terminate_cause_t; /** * Usage stats for bytes and packets */ typedef struct { struct { uint64_t sent; uint64_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 */ uint32_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 { /** IKE_SA identifier this entry is stored under */ ike_sa_id_t *id; /** RADIUS accounting session ID */ char sid[24]; /** cached Class attributes */ array_t *class_attrs; /** 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 */ radius_acct_terminate_cause_t cause; /* interim interval and timestamp of last update */ struct { uint32_t interval; time_t last; } interim; /** did we send Accounting-Start */ bool start_sent; } entry_t; /** * Destroy an entry_t */ static void destroy_entry(entry_t *this) { array_destroy_function(this->cached, (void*)free, NULL); array_destroy_function(this->migrated, (void*)free, NULL); array_destroy_function(this->class_attrs, (void*)chunk_free, NULL); this->id->destroy(this->id); free(this); } /** * Accounting message status types */ typedef enum { ACCT_STATUS_START = 1, ACCT_STATUS_STOP = 2, ACCT_STATUS_INTERIM_UPDATE = 3, ACCT_STATUS_ACCOUNTING_ON = 7, ACCT_STATUS_ACCOUNTING_OFF = 8, } radius_acct_status_t; /** * Hashtable hash function */ static u_int hash(ike_sa_id_t *key) { return key->get_responder_spi(key); } /** * Hashtable equals function */ 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 or create usage counters of a cached SA */ static void update_sa(entry_t *entry, uint32_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) { usage_t usage; entry_t *entry; 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) { 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, radius_message_t *request) { radius_message_t *response; radius_client_t *client; bool ack = FALSE; client = eap_radius_create_client(); if (client) { response = client->request(client, request); if (response) { ack = response->get_code(response) == RMC_ACCOUNTING_RESPONSE; response->destroy(response); } client->destroy(client); } return ack; } /** * Add common IKE_SA parameters to RADIUS account message */ static void add_ike_sa_parameters(private_eap_radius_accounting_t *this, radius_message_t *message, ike_sa_t *ike_sa) { enumerator_t *enumerator; host_t *vip, *host; char buf[MAX_RADIUS_ATTRIBUTE_SIZE + 1]; chunk_t data; uint32_t value; /* virtual NAS-Port-Type */ value = htonl(5); message->add(message, RAT_NAS_PORT_TYPE, chunk_from_thing(value)); /* framed ServiceType */ value = htonl(2); message->add(message, RAT_SERVICE_TYPE, chunk_from_thing(value)); value = htonl(ike_sa->get_unique_id(ike_sa)); message->add(message, RAT_NAS_PORT, chunk_from_thing(value)); message->add(message, RAT_NAS_PORT_ID, chunk_from_str(ike_sa->get_name(ike_sa))); host = ike_sa->get_my_host(ike_sa); data = host->get_address(host); switch (host->get_family(host)) { case AF_INET: message->add(message, RAT_NAS_IP_ADDRESS, data); break; case AF_INET6: message->add(message, RAT_NAS_IPV6_ADDRESS, data); default: break; } snprintf(buf, sizeof(buf), this->station_id_fmt, host); message->add(message, RAT_CALLED_STATION_ID, chunk_from_str(buf)); host = ike_sa->get_other_host(ike_sa); snprintf(buf, sizeof(buf), this->station_id_fmt, host); message->add(message, RAT_CALLING_STATION_ID, chunk_from_str(buf)); snprintf(buf, sizeof(buf), "%Y", ike_sa->get_other_eap_id(ike_sa)); message->add(message, RAT_USER_NAME, chunk_from_str(buf)); enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, FALSE); while (enumerator->enumerate(enumerator, &vip)) { switch (vip->get_family(vip)) { case AF_INET: message->add(message, RAT_FRAMED_IP_ADDRESS, vip->get_address(vip)); break; case AF_INET6: message->add(message, RAT_FRAMED_IPV6_ADDRESS, vip->get_address(vip)); break; default: break; } } enumerator->destroy(enumerator); } /** * Add any unclaimed IP addresses to the message */ static void add_unclaimed_ips(radius_message_t *message, ike_sa_t *ike_sa) { eap_radius_provider_t *provider; enumerator_t *enumerator; host_t *vip; provider = eap_radius_provider_get(); enumerator = provider->clear_unclaimed(provider, ike_sa->get_unique_id(ike_sa)); while (enumerator->enumerate(enumerator, &vip)) { switch (vip->get_family(vip)) { case AF_INET: message->add(message, RAT_FRAMED_IP_ADDRESS, vip->get_address(vip)); break; case AF_INET6: message->add(message, RAT_FRAMED_IPV6_ADDRESS, vip->get_address(vip)); break; default: break; } } enumerator->destroy(enumerator); } /** * Add the Class attributes received in the Access-Accept message to the * RADIUS accounting message */ static void add_class_attributes(radius_message_t *message, entry_t *entry) { enumerator_t *enumerator; chunk_t *cls; enumerator = array_create_enumerator(entry->class_attrs); while (enumerator->enumerate(enumerator, &cls)) { message->add(message, RAT_CLASS, *cls); } enumerator->destroy(enumerator); } /** * 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_id_t *id, uint32_t unique) { entry_t *entry; time_t now; entry = this->sessions->get(this->sessions, id); if (!entry) { now = time_monotonic(NULL); INIT(entry, .id = id->clone(id), .created = now, .interim = { .last = now, }, /* default terminate cause, if none other caught */ .cause = ACCT_CAUSE_USER_REQUEST, ); snprintf(entry->sid, sizeof(entry->sid), "%u-%u", this->prefix, unique); this->sessions->put(this->sessions, entry->id, entry); } return entry; } /* forward declaration */ static void schedule_interim(private_eap_radius_accounting_t *this, entry_t *entry); /** * Data passed to send_interim() using callback job */ typedef struct { /** reference to radius accounting */ private_eap_radius_accounting_t *this; /** IKE_SA identifier to send interim update to */ ike_sa_id_t *id; } interim_data_t; /** * Clean up interim data */ void destroy_interim_data(interim_data_t *this) { this->id->destroy(this->id); free(this); } /** * Send an interim update for entry of given IKE_SA identifier */ static job_requeue_t send_interim(interim_data_t *data) { private_eap_radius_accounting_t *this = data->this; usage_t usage; radius_message_t *message = NULL; enumerator_t *enumerator; ike_sa_t *ike_sa; entry_t *entry; uint32_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 = 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 */ this->mutex->lock(this->mutex); entry = this->sessions->get(this->sessions, data->id); if (entry) { 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->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); message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value)); message->add(message, RAT_ACCT_SESSION_ID, chunk_create(entry->sid, strlen(entry->sid))); add_class_attributes(message, entry); add_ike_sa_parameters(this, message, ike_sa); value = htonl(usage.bytes.sent); message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value)); value = htonl(usage.bytes.sent >> 32); if (value) { message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS, chunk_from_thing(value)); } value = htonl(usage.packets.sent); message->add(message, RAT_ACCT_OUTPUT_PACKETS, chunk_from_thing(value)); value = htonl(usage.bytes.received); message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value)); value = htonl(usage.bytes.received >> 32); if (value) { message->add(message, RAT_ACCT_INPUT_GIGAWORDS, chunk_from_thing(value)); } value = htonl(usage.packets.received); message->add(message, RAT_ACCT_INPUT_PACKETS, chunk_from_thing(value)); value = htonl(entry->interim.last - entry->created); message->add(message, RAT_ACCT_SESSION_TIME, chunk_from_thing(value)); schedule_interim(this, entry); } this->mutex->unlock(this->mutex); array_destroy_function(stats, (void*)free, NULL); if (message) { if (!send_message(this, message)) { if (lib->settings->get_bool(lib->settings, "%s.plugins.eap-radius.accounting_close_on_timeout", TRUE, lib->ns)) { eap_radius_handle_timeout(data->id); } } message->destroy(message); } return JOB_REQUEUE_NONE; } /** * Schedule interim update for given entry */ static void schedule_interim(private_eap_radius_accounting_t *this, entry_t *entry) { if (entry->interim.interval) { interim_data_t *data; timeval_t tv = { .tv_sec = entry->interim.last + entry->interim.interval, }; INIT(data, .this = this, .id = entry->id->clone(entry->id), ); lib->scheduler->schedule_job_tv(lib->scheduler, (job_t*)callback_job_create_with_prio( (callback_job_cb_t)send_interim, data, (void*)destroy_interim_data, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL), tv); } } /** * Check if an IKE_SA has assigned a virtual IP (to peer) */ static bool has_vip(ike_sa_t *ike_sa) { enumerator_t *enumerator; host_t *host; bool found; enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, FALSE); found = enumerator->enumerate(enumerator, &host); enumerator->destroy(enumerator); return found; } /** * Send an accounting start message */ static void send_start(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa) { radius_message_t *message; entry_t *entry; uint32_t value; if (this->acct_req_vip && !has_vip(ike_sa)) { return; } this->mutex->lock(this->mutex); entry = get_or_create_entry(this, ike_sa->get_id(ike_sa), ike_sa->get_unique_id(ike_sa)); if (entry->start_sent) { this->mutex->unlock(this->mutex); return; } entry->start_sent = TRUE; message = radius_message_create(RMC_ACCOUNTING_REQUEST); value = htonl(ACCT_STATUS_START); message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value)); message->add(message, RAT_ACCT_SESSION_ID, chunk_create(entry->sid, strlen(entry->sid))); add_class_attributes(message, entry); if (!entry->interim.interval) { entry->interim.interval = lib->settings->get_time(lib->settings, "%s.plugins.eap-radius.accounting_interval", 0, lib->ns); if (entry->interim.interval) { DBG1(DBG_CFG, "scheduling RADIUS Interim-Updates every %us", entry->interim.interval); } } schedule_interim(this, entry); this->mutex->unlock(this->mutex); add_ike_sa_parameters(this, message, ike_sa); if (!send_message(this, message)) { eap_radius_handle_timeout(ike_sa->get_id(ike_sa)); } message->destroy(message); } /** * Send an account stop message */ 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; uint32_t value; this->mutex->lock(this->mutex); entry = this->sessions->remove(this->sessions, ike_sa->get_id(ike_sa)); this->mutex->unlock(this->mutex); if (entry) { if (!entry->start_sent) { /* we tried to authenticate this peer, but never sent a start */ 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)); message->add(message, RAT_ACCT_SESSION_ID, chunk_create(entry->sid, strlen(entry->sid))); add_class_attributes(message, entry); add_ike_sa_parameters(this, message, ike_sa); add_unclaimed_ips(message, ike_sa); value = htonl(entry->usage.bytes.sent); message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value)); value = htonl(entry->usage.bytes.sent >> 32); if (value) { message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS, chunk_from_thing(value)); } value = htonl(entry->usage.packets.sent); message->add(message, RAT_ACCT_OUTPUT_PACKETS, chunk_from_thing(value)); value = htonl(entry->usage.bytes.received); message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value)); value = htonl(entry->usage.bytes.received >> 32); if (value) { message->add(message, RAT_ACCT_INPUT_GIGAWORDS, chunk_from_thing(value)); } value = htonl(entry->usage.packets.received); message->add(message, RAT_ACCT_INPUT_PACKETS, chunk_from_thing(value)); value = htonl(time_monotonic(NULL) - entry->created); message->add(message, RAT_ACCT_SESSION_TIME, chunk_from_thing(value)); value = htonl(entry->cause); message->add(message, RAT_ACCT_TERMINATE_CAUSE, chunk_from_thing(value)); if (!send_message(this, message)) { eap_radius_handle_timeout(NULL); } message->destroy(message); destroy_entry(entry); } } METHOD(listener_t, alert, bool, private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, alert_t alert, va_list args) { radius_acct_terminate_cause_t cause; entry_t *entry; switch (alert) { case ALERT_IKE_SA_EXPIRED: cause = ACCT_CAUSE_SESSION_TIMEOUT; break; case ALERT_RETRANSMIT_SEND_TIMEOUT: cause = ACCT_CAUSE_LOST_SERVICE; break; default: return TRUE; } this->mutex->lock(this->mutex); entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa)); if (entry) { entry->cause = cause; } this->mutex->unlock(this->mutex); return TRUE; } METHOD(listener_t, ike_updown, bool, private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, bool up) { if (!up) { enumerator_t *enumerator; child_sa_t *child_sa; /* update usage for all children just before sending stop */ enumerator = ike_sa->create_child_sa_enumerator(ike_sa); while (enumerator->enumerate(enumerator, &child_sa)) { update_usage(this, ike_sa, child_sa); } enumerator->destroy(enumerator); send_stop(this, ike_sa); } return TRUE; } METHOD(listener_t, message_hook, bool, private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, message_t *message, bool incoming, bool plain) { /* start accounting here, virtual IP now is set */ if (plain && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED && !incoming && !message->get_request(message)) { if (ike_sa->get_version(ike_sa) == IKEV2 && message->get_exchange_type(message) == IKE_AUTH) { send_start(this, ike_sa); } } return TRUE; } METHOD(listener_t, assign_vips, bool, private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, bool assign) { /* start accounting as soon as the virtual IP is set */ if (assign && ike_sa->get_version(ike_sa) == IKEV1) { send_start(this, ike_sa); } return TRUE; } METHOD(listener_t, ike_rekey, bool, private_eap_radius_accounting_t *this, ike_sa_t *old, ike_sa_t *new) { entry_t *entry; this->mutex->lock(this->mutex); entry = this->sessions->remove(this->sessions, old->get_id(old)); if (entry) { /* update IKE_SA identifier */ entry->id->destroy(entry->id); entry->id = new->get_id(new); entry->id = entry->id->clone(entry->id); /* 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) { destroy_entry(entry); } } this->mutex->unlock(this->mutex); return TRUE; } 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, uint32_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) { if (!up && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED) { update_usage(this, ike_sa, child_sa); } return TRUE; } METHOD(eap_radius_accounting_t, destroy, void, private_eap_radius_accounting_t *this) { charon->bus->remove_listener(charon->bus, &this->public.listener); singleton = NULL; this->mutex->destroy(this->mutex); this->sessions->destroy(this->sessions); free(this); } /** * See header */ eap_radius_accounting_t *eap_radius_accounting_create() { private_eap_radius_accounting_t *this; INIT(this, .public = { .listener = { .alert = _alert, .ike_updown = _ike_updown, .ike_rekey = _ike_rekey, .message = _message_hook, .assign_vips = _assign_vips, .child_updown = _child_updown, .child_rekey = _child_rekey, .children_migrate = _children_migrate, }, .destroy = _destroy, }, /* use system time as Session ID prefix */ .prefix = (uint32_t)time(NULL), .sessions = hashtable_create((hashtable_hash_t)hash, (hashtable_equals_t)equals, 32), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), ); if (lib->settings->get_bool(lib->settings, "%s.plugins.eap-radius.station_id_with_port", TRUE, lib->ns)) { this->station_id_fmt = "%#H"; } else { this->station_id_fmt = "%H"; } if (lib->settings->get_bool(lib->settings, "%s.plugins.eap-radius.accounting", FALSE, lib->ns)) { singleton = this; charon->bus->add_listener(charon->bus, &this->public.listener); } this->acct_req_vip = lib->settings->get_bool(lib->settings, "%s.plugins.eap-radius.accounting_requires_vip", FALSE, lib->ns); return &this->public; } /* * Described in header */ char *eap_radius_accounting_session_id(ike_sa_t *ike_sa) { entry_t *entry; char *sid = NULL; if (singleton) { singleton->mutex->lock(singleton->mutex); entry = get_or_create_entry(singleton, ike_sa->get_id(ike_sa), ike_sa->get_unique_id(ike_sa)); sid = strdup(entry->sid); singleton->mutex->unlock(singleton->mutex); } return sid; } /* * Described in header */ void eap_radius_accounting_start_interim(ike_sa_t *ike_sa, uint32_t interval) { if (singleton) { entry_t *entry; DBG1(DBG_CFG, "scheduling RADIUS Interim-Updates every %us", interval); singleton->mutex->lock(singleton->mutex); 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); } } /* * Described in header */ void eap_radius_accounting_add_class(ike_sa_t *ike_sa, chunk_t cls) { if (singleton) { entry_t *entry; chunk_t clone; DBG2(DBG_CFG, "cache RADIUS Class attribute %B", &cls); singleton->mutex->lock(singleton->mutex); entry = get_or_create_entry(singleton, ike_sa->get_id(ike_sa), ike_sa->get_unique_id(ike_sa)); clone = chunk_clone(cls); array_insert_create_value(&entry->class_attrs, sizeof(chunk_t), ARRAY_TAIL, &clone); singleton->mutex->unlock(singleton->mutex); } }