Add support for RADIUS Interim accounting updates
authorMartin Willi <martin@revosec.ch>
Thu, 14 Mar 2013 15:35:11 +0000 (16:35 +0100)
committerMartin Willi <martin@revosec.ch>
Thu, 14 Mar 2013 15:35:11 +0000 (16:35 +0100)
src/libcharon/plugins/eap_radius/eap_radius.c
src/libcharon/plugins/eap_radius/eap_radius_accounting.c
src/libcharon/plugins/eap_radius/eap_radius_accounting.h

index 7b3c152..59340df 100644 (file)
@@ -17,6 +17,7 @@
 #include "eap_radius_plugin.h"
 #include "eap_radius_forward.h"
 #include "eap_radius_provider.h"
+#include "eap_radius_accounting.h"
 
 #include <radius_message.h>
 #include <radius_client.h>
@@ -353,7 +354,7 @@ static void process_filter_id(private_eap_radius_t *this, radius_message_t *msg)
 }
 
 /**
- * Handle Session-Timeout attribte
+ * Handle Session-Timeout attribte and Interim updates
  */
 static void process_timeout(private_eap_radius_t *this, radius_message_t *msg)
 {
@@ -362,19 +363,23 @@ static void process_timeout(private_eap_radius_t *this, radius_message_t *msg)
        chunk_t data;
        int type;
 
-       enumerator = msg->create_enumerator(msg);
-       while (enumerator->enumerate(enumerator, &type, &data))
+       ike_sa = charon->bus->get_sa(charon->bus);
+       if (ike_sa)
        {
-               if (type == RAT_SESSION_TIMEOUT && data.len == 4)
+               enumerator = msg->create_enumerator(msg);
+               while (enumerator->enumerate(enumerator, &type, &data))
                {
-                       ike_sa = charon->bus->get_sa(charon->bus);
-                       if (ike_sa)
+                       if (type == RAT_SESSION_TIMEOUT && data.len == 4)
                        {
                                ike_sa->set_auth_lifetime(ike_sa, untoh32(data.ptr));
                        }
+                       else if (type == RAT_ACCT_INTERIM_INTERVAL && data.len == 4)
+                       {
+                               eap_radius_accounting_start_interim(ike_sa, untoh32(data.ptr));
+                       }
                }
+               enumerator->destroy(enumerator);
        }
-       enumerator->destroy(enumerator);
 }
 
 /**
index 7375937..dfec0bd 100644 (file)
@@ -23,6 +23,7 @@
 #include <daemon.h>
 #include <collections/hashtable.h>
 #include <threading/mutex.h>
+#include <processing/jobs/callback_job.h>
 
 typedef struct private_eap_radius_accounting_t private_eap_radius_accounting_t;
 
@@ -37,7 +38,7 @@ struct private_eap_radius_accounting_t {
        eap_radius_accounting_t public;
 
        /**
-        * Hashtable with sessions, IKE_SA unique id => entry_t
+        * Hashtable with sessions, ike_sa_id_t => entry_t
         */
        hashtable_t *sessions;
 
@@ -53,6 +54,11 @@ struct private_eap_radius_accounting_t {
 };
 
 /**
+ * Singleton instance of accounting
+ */
+static private_eap_radius_accounting_t *singleton = NULL;
+
+/**
  * Acct-Terminate-Cause
  */
 typedef enum {
@@ -80,6 +86,8 @@ typedef enum {
  * 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[16];
        /** number of sent/received octets/packets */
@@ -91,9 +99,25 @@ typedef struct {
        time_t created;
        /** terminate cause */
        radius_acct_terminate_cause_t cause;
+       /* interim interval and timestamp of last update */
+       struct {
+               u_int32_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)
+{
+       this->id->destroy(this->id);
+       free(this);
+}
+
+/**
  * Accounting message status types
  */
 typedef enum {
@@ -107,17 +131,17 @@ typedef enum {
 /**
  * Hashtable hash function
  */
-static u_int hash(uintptr_t key)
+static u_int hash(ike_sa_id_t *key)
 {
-       return key;
+       return key->get_responder_spi(key);
 }
 
 /**
  * Hashtable equals function
  */
-static bool equals(uintptr_t a, uintptr_t b)
+static bool equals(ike_sa_id_t *a, ike_sa_id_t *b)
 {
-       return a == b;
+       return a->equals(a, b);
 }
 
 /**
@@ -133,8 +157,7 @@ static void update_usage(private_eap_radius_accounting_t *this,
        child_sa->get_usestats(child_sa, TRUE, NULL, &bytes_in, &packets_in);
 
        this->mutex->lock(this->mutex);
-       entry = this->sessions->get(this->sessions,
-                                                               (void*)(uintptr_t)ike_sa->get_unique_id(ike_sa));
+       entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
        if (entry)
        {
                entry->bytes.sent += bytes_out;
@@ -236,40 +259,207 @@ static void add_ike_sa_parameters(radius_message_t *message, ike_sa_t *ike_sa)
 }
 
 /**
+ * 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;
+       entry_t *entry;
+       time_t now;
+
+       entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
+       if (!entry)
+       {
+               now = time_monotonic(NULL);
+               id = ike_sa->get_id(ike_sa);
+
+               INIT(entry,
+                       .id = id->clone(id),
+                       .created = now,
+                       .interim = {
+                               .last = now,
+                       },
+                       /* 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));
+               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;
+       u_int64_t bytes_in = 0, bytes_out = 0, packets_in = 0, packets_out = 0;
+       u_int64_t bytes, packets;
+       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;
+
+       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);
+       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);
+
+               bytes_in += entry->bytes.received;
+               bytes_out += entry->bytes.sent;
+               packets_in += entry->packets.received;
+               packets_out += entry->packets.sent;
+
+               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_ike_sa_parameters(message, ike_sa);
+
+               value = htonl(bytes_out);
+               message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
+               value = htonl(bytes_out >> 32);
+               if (value)
+               {
+                       message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
+                                                chunk_from_thing(value));
+               }
+               value = htonl(packets_out);
+               message->add(message, RAT_ACCT_OUTPUT_PACKETS, chunk_from_thing(value));
+
+               value = htonl(bytes_in);
+               message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
+               value = htonl(bytes_in >> 32);
+               if (value)
+               {
+                       message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
+                                                chunk_from_thing(value));
+               }
+               value = htonl(packets_in);
+               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);
+
+       if (message)
+       {
+               if (!send_message(this, message))
+               {
+                       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);
+       }
+}
+
+/**
  * 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;
-       u_int32_t id, value;
+       u_int32_t value;
 
-       id = ike_sa->get_unique_id(ike_sa);
-       INIT(entry,
-               .created = time_monotonic(NULL),
-               /* default terminate cause, if none other catched */
-               .cause = ACCT_CAUSE_USER_REQUEST,
-       );
-       snprintf(entry->sid, sizeof(entry->sid), "%u-%u", this->prefix, id);
+       this->mutex->lock(this->mutex);
+
+       entry = get_or_create_entry(this, ike_sa);
+       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)));
+
+       schedule_interim(this, entry);
+       this->mutex->unlock(this->mutex);
+
        add_ike_sa_parameters(message, ike_sa);
-       if (send_message(this, message))
-       {
-               this->mutex->lock(this->mutex);
-               entry = this->sessions->put(this->sessions, (void*)(uintptr_t)id, entry);
-               this->mutex->unlock(this->mutex);
-       }
-       else
+       if (!send_message(this, message))
        {
                eap_radius_handle_timeout(ike_sa->get_id(ike_sa));
        }
        message->destroy(message);
-       free(entry);
 }
 
 /**
@@ -283,10 +473,15 @@ static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
 
        id = ike_sa->get_unique_id(ike_sa);
        this->mutex->lock(this->mutex);
-       entry = this->sessions->remove(this->sessions, (void*)(uintptr_t)id);
+       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;
+               }
                message = radius_message_create(RMC_ACCOUNTING_REQUEST);
                value = htonl(ACCT_STATUS_STOP);
                message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
@@ -328,7 +523,7 @@ static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
                        eap_radius_handle_timeout(NULL);
                }
                message->destroy(message);
-               free(entry);
+               destroy_entry(entry);
        }
 }
 
@@ -351,8 +546,7 @@ METHOD(listener_t, alert, bool,
                        return TRUE;
        }
        this->mutex->lock(this->mutex);
-       entry = this->sessions->get(this->sessions,
-                                                               (void*)(uintptr_t)ike_sa->get_unique_id(ike_sa));
+       entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
        if (entry)
        {
                entry->cause = cause;
@@ -410,15 +604,20 @@ METHOD(listener_t, ike_rekey, bool,
        entry_t *entry;
 
        this->mutex->lock(this->mutex);
-       entry = this->sessions->remove(this->sessions,
-                                                       (void*)(uintptr_t)old->get_unique_id(old));
+       entry = this->sessions->remove(this->sessions, old->get_id(old));
        if (entry)
        {
-               entry = this->sessions->put(this->sessions,
-                                                       (void*)(uintptr_t)new->get_unique_id(new), 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);
+
+               entry = this->sessions->put(this->sessions, entry->id, entry);
                if (entry)
                {
-                       free(entry);
+                       destroy_entry(entry);
                }
        }
        this->mutex->unlock(this->mutex);
@@ -449,6 +648,7 @@ METHOD(listener_t, child_updown, bool,
 METHOD(eap_radius_accounting_t, destroy, void,
        private_eap_radius_accounting_t *this)
 {
+       singleton = NULL;
        this->mutex->destroy(this->mutex);
        this->sessions->destroy(this->sessions);
        free(this);
@@ -480,5 +680,23 @@ eap_radius_accounting_t *eap_radius_accounting_create()
                .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
        );
 
+       singleton = this;
        return &this->public;
 }
+
+/**
+ * See header
+ */
+void eap_radius_accounting_start_interim(ike_sa_t *ike_sa, u_int32_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);
+               entry->interim.interval = interval;
+               singleton->mutex->unlock(singleton->mutex);
+       }
+}
index 811a5bb..3e59cec 100644 (file)
@@ -46,4 +46,11 @@ struct eap_radius_accounting_t {
  */
 eap_radius_accounting_t *eap_radius_accounting_create();
 
+/**
+ * Schedule Accounting interim updates for the given IKE_SA unique identifer.
+ *
+ * @param                      IKE_SA unique identifier
+ */
+void eap_radius_accounting_start_interim(ike_sa_t *ike_sa, u_int32_t interval);
+
 #endif /** EAP_RADIUS_ACCOUNTING_H_ @}*/