2 * Copyright (C) 2012 Martin Willi
3 * Copyright (C) 2012 revosec AG
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 #include "eap_radius_accounting.h"
17 #include "eap_radius_plugin.h"
21 #include <radius_message.h>
22 #include <radius_client.h>
24 #include <collections/hashtable.h>
25 #include <threading/mutex.h>
26 #include <processing/jobs/callback_job.h>
28 typedef struct private_eap_radius_accounting_t private_eap_radius_accounting_t
;
31 * Private data of an eap_radius_accounting_t object.
33 struct private_eap_radius_accounting_t
{
36 * Public eap_radius_accounting_t interface.
38 eap_radius_accounting_t
public;
41 * Hashtable with sessions, ike_sa_id_t => entry_t
43 hashtable_t
*sessions
;
46 * Mutex to lock sessions
57 * Singleton instance of accounting
59 static private_eap_radius_accounting_t
*singleton
= NULL
;
62 * Acct-Terminate-Cause
65 ACCT_CAUSE_USER_REQUEST
= 1,
66 ACCT_CAUSE_LOST_CARRIER
= 2,
67 ACCT_CAUSE_LOST_SERVICE
= 3,
68 ACCT_CAUSE_IDLE_TIMEOUT
= 4,
69 ACCT_CAUSE_SESSION_TIMEOUT
= 5,
70 ACCT_CAUSE_ADMIN_RESET
= 6,
71 ACCT_CAUSE_ADMIN_REBOOT
= 7,
72 ACCT_CAUSE_PORT_ERROR
= 8,
73 ACCT_CAUSE_NAS_ERROR
= 9,
74 ACCT_CAUSE_NAS_REQUEST
= 10,
75 ACCT_CAUSE_NAS_REBOOT
= 11,
76 ACCT_CAUSE_PORT_UNNEEDED
= 12,
77 ACCT_CAUSE_PORT_PREEMPTED
= 13,
78 ACCT_CAUSE_PORT_SUSPENDED
= 14,
79 ACCT_CAUSE_SERVICE_UNAVAILABLE
= 15,
80 ACCT_CAUSE_CALLBACK
= 16,
81 ACCT_CAUSE_USER_ERROR
= 17,
82 ACCT_CAUSE_HOST_REQUEST
= 18,
83 } radius_acct_terminate_cause_t
;
86 * Hashtable entry with usage stats
89 /** IKE_SA identifier this entry is stored under */
91 /** RADIUS accounting session ID */
93 /** number of sent/received octets/packets */
98 /** session creation time */
100 /** terminate cause */
101 radius_acct_terminate_cause_t cause
;
102 /* interim interval and timestamp of last update */
107 /** did we send Accounting-Start */
114 static void destroy_entry(entry_t
*this)
116 this->id
->destroy(this->id
);
121 * Accounting message status types
124 ACCT_STATUS_START
= 1,
125 ACCT_STATUS_STOP
= 2,
126 ACCT_STATUS_INTERIM_UPDATE
= 3,
127 ACCT_STATUS_ACCOUNTING_ON
= 7,
128 ACCT_STATUS_ACCOUNTING_OFF
= 8,
129 } radius_acct_status_t
;
132 * Hashtable hash function
134 static u_int
hash(ike_sa_id_t
*key
)
136 return key
->get_responder_spi(key
);
140 * Hashtable equals function
142 static bool equals(ike_sa_id_t
*a
, ike_sa_id_t
*b
)
144 return a
->equals(a
, b
);
148 * Update usage counter when a CHILD_SA rekeys/goes down
150 static void update_usage(private_eap_radius_accounting_t
*this,
151 ike_sa_t
*ike_sa
, child_sa_t
*child_sa
)
153 u_int64_t bytes_in
, bytes_out
, packets_in
, packets_out
;
156 child_sa
->get_usestats(child_sa
, FALSE
, NULL
, &bytes_out
, &packets_out
);
157 child_sa
->get_usestats(child_sa
, TRUE
, NULL
, &bytes_in
, &packets_in
);
159 this->mutex
->lock(this->mutex
);
160 entry
= this->sessions
->get(this->sessions
, ike_sa
->get_id(ike_sa
));
163 entry
->bytes
.sent
+= bytes_out
;
164 entry
->bytes
.received
+= bytes_in
;
165 entry
->packets
.sent
+= packets_out
;
166 entry
->packets
.received
+= packets_in
;
168 this->mutex
->unlock(this->mutex
);
172 * Send a RADIUS message, wait for response
174 static bool send_message(private_eap_radius_accounting_t
*this,
175 radius_message_t
*request
)
177 radius_message_t
*response
;
178 radius_client_t
*client
;
181 client
= eap_radius_create_client();
184 response
= client
->request(client
, request
);
187 ack
= response
->get_code(response
) == RMC_ACCOUNTING_RESPONSE
;
188 response
->destroy(response
);
190 client
->destroy(client
);
196 * Add common IKE_SA parameters to RADIUS account message
198 static void add_ike_sa_parameters(radius_message_t
*message
, ike_sa_t
*ike_sa
)
200 enumerator_t
*enumerator
;
206 /* virtual NAS-Port-Type */
208 message
->add(message
, RAT_NAS_PORT_TYPE
, chunk_from_thing(value
));
209 /* framed ServiceType */
211 message
->add(message
, RAT_SERVICE_TYPE
, chunk_from_thing(value
));
213 value
= htonl(ike_sa
->get_unique_id(ike_sa
));
214 message
->add(message
, RAT_NAS_PORT
, chunk_from_thing(value
));
215 message
->add(message
, RAT_NAS_PORT_ID
,
216 chunk_from_str(ike_sa
->get_name(ike_sa
)));
218 host
= ike_sa
->get_my_host(ike_sa
);
219 data
= host
->get_address(host
);
220 switch (host
->get_family(host
))
223 message
->add(message
, RAT_NAS_IP_ADDRESS
, data
);
226 message
->add(message
, RAT_NAS_IPV6_ADDRESS
, data
);
230 snprintf(buf
, sizeof(buf
), "%#H", host
);
231 message
->add(message
, RAT_CALLED_STATION_ID
, chunk_from_str(buf
));
232 host
= ike_sa
->get_other_host(ike_sa
);
233 snprintf(buf
, sizeof(buf
), "%#H", host
);
234 message
->add(message
, RAT_CALLING_STATION_ID
, chunk_from_str(buf
));
236 snprintf(buf
, sizeof(buf
), "%Y", ike_sa
->get_other_eap_id(ike_sa
));
237 message
->add(message
, RAT_USER_NAME
, chunk_from_str(buf
));
239 enumerator
= ike_sa
->create_virtual_ip_enumerator(ike_sa
, FALSE
);
240 while (enumerator
->enumerate(enumerator
, &vip
))
242 switch (vip
->get_family(vip
))
245 message
->add(message
, RAT_FRAMED_IP_ADDRESS
,
246 vip
->get_address(vip
));
249 /* we currently assign /128 prefixes, only (reserved, length) */
250 data
= chunk_from_chars(0, 128);
251 data
= chunk_cata("cc", data
, vip
->get_address(vip
));
252 message
->add(message
, RAT_FRAMED_IPV6_PREFIX
, data
);
258 enumerator
->destroy(enumerator
);
262 * Get an existing or create a new entry from the locked session table
264 static entry_t
* get_or_create_entry(private_eap_radius_accounting_t
*this,
271 entry
= this->sessions
->get(this->sessions
, ike_sa
->get_id(ike_sa
));
274 now
= time_monotonic(NULL
);
275 id
= ike_sa
->get_id(ike_sa
);
283 /* default terminate cause, if none other catched */
284 .cause
= ACCT_CAUSE_USER_REQUEST
,
286 snprintf(entry
->sid
, sizeof(entry
->sid
), "%u-%u",
287 this->prefix
, ike_sa
->get_unique_id(ike_sa
));
288 this->sessions
->put(this->sessions
, entry
->id
, entry
);
293 /* forward declaration */
294 static void schedule_interim(private_eap_radius_accounting_t
*this,
298 * Data passed to send_interim() using callback job
301 /** reference to radius accounting */
302 private_eap_radius_accounting_t
*this;
303 /** IKE_SA identifier to send interim update to */
308 * Clean up interim data
310 void destroy_interim_data(interim_data_t
*this)
312 this->id
->destroy(this->id
);
317 * Send an interim update for entry of given IKE_SA identifier
319 static job_requeue_t
send_interim(interim_data_t
*data
)
321 private_eap_radius_accounting_t
*this = data
->this;
322 u_int64_t bytes_in
= 0, bytes_out
= 0, packets_in
= 0, packets_out
= 0;
323 u_int64_t bytes
, packets
;
324 radius_message_t
*message
= NULL
;
325 enumerator_t
*enumerator
;
326 child_sa_t
*child_sa
;
331 ike_sa
= charon
->ike_sa_manager
->checkout(charon
->ike_sa_manager
, data
->id
);
334 return JOB_REQUEUE_NONE
;
336 enumerator
= ike_sa
->create_child_sa_enumerator(ike_sa
);
337 while (enumerator
->enumerate(enumerator
, &child_sa
))
339 child_sa
->get_usestats(child_sa
, FALSE
, NULL
, &bytes
, &packets
);
341 packets_out
+= packets
;
342 child_sa
->get_usestats(child_sa
, TRUE
, NULL
, &bytes
, &packets
);
344 packets_in
+= packets
;
346 enumerator
->destroy(enumerator
);
347 charon
->ike_sa_manager
->checkin(charon
->ike_sa_manager
, ike_sa
);
349 /* avoid any races by returning IKE_SA before acquiring lock */
351 this->mutex
->lock(this->mutex
);
352 entry
= this->sessions
->get(this->sessions
, data
->id
);
355 entry
->interim
.last
= time_monotonic(NULL
);
357 bytes_in
+= entry
->bytes
.received
;
358 bytes_out
+= entry
->bytes
.sent
;
359 packets_in
+= entry
->packets
.received
;
360 packets_out
+= entry
->packets
.sent
;
362 message
= radius_message_create(RMC_ACCOUNTING_REQUEST
);
363 value
= htonl(ACCT_STATUS_INTERIM_UPDATE
);
364 message
->add(message
, RAT_ACCT_STATUS_TYPE
, chunk_from_thing(value
));
365 message
->add(message
, RAT_ACCT_SESSION_ID
,
366 chunk_create(entry
->sid
, strlen(entry
->sid
)));
367 add_ike_sa_parameters(message
, ike_sa
);
369 value
= htonl(bytes_out
);
370 message
->add(message
, RAT_ACCT_OUTPUT_OCTETS
, chunk_from_thing(value
));
371 value
= htonl(bytes_out
>> 32);
374 message
->add(message
, RAT_ACCT_OUTPUT_GIGAWORDS
,
375 chunk_from_thing(value
));
377 value
= htonl(packets_out
);
378 message
->add(message
, RAT_ACCT_OUTPUT_PACKETS
, chunk_from_thing(value
));
380 value
= htonl(bytes_in
);
381 message
->add(message
, RAT_ACCT_INPUT_OCTETS
, chunk_from_thing(value
));
382 value
= htonl(bytes_in
>> 32);
385 message
->add(message
, RAT_ACCT_INPUT_GIGAWORDS
,
386 chunk_from_thing(value
));
388 value
= htonl(packets_in
);
389 message
->add(message
, RAT_ACCT_INPUT_PACKETS
, chunk_from_thing(value
));
391 value
= htonl(entry
->interim
.last
- entry
->created
);
392 message
->add(message
, RAT_ACCT_SESSION_TIME
, chunk_from_thing(value
));
394 schedule_interim(this, entry
);
396 this->mutex
->unlock(this->mutex
);
400 if (!send_message(this, message
))
402 eap_radius_handle_timeout(data
->id
);
404 message
->destroy(message
);
406 return JOB_REQUEUE_NONE
;
410 * Schedule interim update for given entry
412 static void schedule_interim(private_eap_radius_accounting_t
*this,
415 if (entry
->interim
.interval
)
417 interim_data_t
*data
;
419 .tv_sec
= entry
->interim
.last
+ entry
->interim
.interval
,
424 .id
= entry
->id
->clone(entry
->id
),
426 lib
->scheduler
->schedule_job_tv(lib
->scheduler
,
427 (job_t
*)callback_job_create_with_prio(
428 (callback_job_cb_t
)send_interim
,
429 data
, (void*)destroy_interim_data
,
430 (callback_job_cancel_t
)return_false
, JOB_PRIO_CRITICAL
), tv
);
435 * Send an accounting start message
437 static void send_start(private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
)
439 radius_message_t
*message
;
443 this->mutex
->lock(this->mutex
);
445 entry
= get_or_create_entry(this, ike_sa
);
446 entry
->start_sent
= TRUE
;
448 message
= radius_message_create(RMC_ACCOUNTING_REQUEST
);
449 value
= htonl(ACCT_STATUS_START
);
450 message
->add(message
, RAT_ACCT_STATUS_TYPE
, chunk_from_thing(value
));
451 message
->add(message
, RAT_ACCT_SESSION_ID
,
452 chunk_create(entry
->sid
, strlen(entry
->sid
)));
454 schedule_interim(this, entry
);
455 this->mutex
->unlock(this->mutex
);
457 add_ike_sa_parameters(message
, ike_sa
);
458 if (!send_message(this, message
))
460 eap_radius_handle_timeout(ike_sa
->get_id(ike_sa
));
462 message
->destroy(message
);
466 * Send an account stop message
468 static void send_stop(private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
)
470 radius_message_t
*message
;
474 this->mutex
->lock(this->mutex
);
475 entry
= this->sessions
->remove(this->sessions
, ike_sa
->get_id(ike_sa
));
476 this->mutex
->unlock(this->mutex
);
479 if (!entry
->start_sent
)
480 { /* we tried to authenticate this peer, but never sent a start */
481 destroy_entry(entry
);
484 message
= radius_message_create(RMC_ACCOUNTING_REQUEST
);
485 value
= htonl(ACCT_STATUS_STOP
);
486 message
->add(message
, RAT_ACCT_STATUS_TYPE
, chunk_from_thing(value
));
487 message
->add(message
, RAT_ACCT_SESSION_ID
,
488 chunk_create(entry
->sid
, strlen(entry
->sid
)));
489 add_ike_sa_parameters(message
, ike_sa
);
491 value
= htonl(entry
->bytes
.sent
);
492 message
->add(message
, RAT_ACCT_OUTPUT_OCTETS
, chunk_from_thing(value
));
493 value
= htonl(entry
->bytes
.sent
>> 32);
496 message
->add(message
, RAT_ACCT_OUTPUT_GIGAWORDS
,
497 chunk_from_thing(value
));
499 value
= htonl(entry
->packets
.sent
);
500 message
->add(message
, RAT_ACCT_OUTPUT_PACKETS
, chunk_from_thing(value
));
502 value
= htonl(entry
->bytes
.received
);
503 message
->add(message
, RAT_ACCT_INPUT_OCTETS
, chunk_from_thing(value
));
504 value
= htonl(entry
->bytes
.received
>> 32);
507 message
->add(message
, RAT_ACCT_INPUT_GIGAWORDS
,
508 chunk_from_thing(value
));
510 value
= htonl(entry
->packets
.received
);
511 message
->add(message
, RAT_ACCT_INPUT_PACKETS
, chunk_from_thing(value
));
513 value
= htonl(time_monotonic(NULL
) - entry
->created
);
514 message
->add(message
, RAT_ACCT_SESSION_TIME
, chunk_from_thing(value
));
517 value
= htonl(entry
->cause
);
518 message
->add(message
, RAT_ACCT_TERMINATE_CAUSE
, chunk_from_thing(value
));
520 if (!send_message(this, message
))
522 eap_radius_handle_timeout(NULL
);
524 message
->destroy(message
);
525 destroy_entry(entry
);
529 METHOD(listener_t
, alert
, bool,
530 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
, alert_t alert
,
533 radius_acct_terminate_cause_t cause
;
538 case ALERT_IKE_SA_EXPIRED
:
539 cause
= ACCT_CAUSE_SESSION_TIMEOUT
;
541 case ALERT_RETRANSMIT_SEND_TIMEOUT
:
542 cause
= ACCT_CAUSE_LOST_SERVICE
;
547 this->mutex
->lock(this->mutex
);
548 entry
= this->sessions
->get(this->sessions
, ike_sa
->get_id(ike_sa
));
551 entry
->cause
= cause
;
553 this->mutex
->unlock(this->mutex
);
557 METHOD(listener_t
, ike_updown
, bool,
558 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
, bool up
)
562 enumerator_t
*enumerator
;
563 child_sa_t
*child_sa
;
565 /* update usage for all children just before sending stop */
566 enumerator
= ike_sa
->create_child_sa_enumerator(ike_sa
);
567 while (enumerator
->enumerate(enumerator
, &child_sa
))
569 update_usage(this, ike_sa
, child_sa
);
571 enumerator
->destroy(enumerator
);
573 send_stop(this, ike_sa
);
578 METHOD(listener_t
, message_hook
, bool,
579 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
,
580 message_t
*message
, bool incoming
, bool plain
)
582 /* start accounting here, virtual IP now is set */
583 if (plain
&& ike_sa
->get_state(ike_sa
) == IKE_ESTABLISHED
&&
584 !incoming
&& !message
->get_request(message
))
586 if (ike_sa
->get_version(ike_sa
) == IKEV1
&&
587 message
->get_exchange_type(message
) == TRANSACTION
)
589 send_start(this, ike_sa
);
591 if (ike_sa
->get_version(ike_sa
) == IKEV2
&&
592 message
->get_exchange_type(message
) == IKE_AUTH
)
594 send_start(this, ike_sa
);
600 METHOD(listener_t
, ike_rekey
, bool,
601 private_eap_radius_accounting_t
*this, ike_sa_t
*old
, ike_sa_t
*new)
605 this->mutex
->lock(this->mutex
);
606 entry
= this->sessions
->remove(this->sessions
, old
->get_id(old
));
609 /* update IKE_SA identifier */
610 entry
->id
->destroy(entry
->id
);
611 entry
->id
= new->get_id(new);
612 entry
->id
= entry
->id
->clone(entry
->id
);
613 /* fire new interim update job, old gets invalid */
614 schedule_interim(this, entry
);
616 entry
= this->sessions
->put(this->sessions
, entry
->id
, entry
);
619 destroy_entry(entry
);
622 this->mutex
->unlock(this->mutex
);
627 METHOD(listener_t
, child_rekey
, bool,
628 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
,
629 child_sa_t
*old
, child_sa_t
*new)
631 update_usage(this, ike_sa
, old
);
636 METHOD(listener_t
, child_updown
, bool,
637 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
,
638 child_sa_t
*child_sa
, bool up
)
640 if (!up
&& ike_sa
->get_state(ike_sa
) == IKE_ESTABLISHED
)
642 update_usage(this, ike_sa
, child_sa
);
647 METHOD(eap_radius_accounting_t
, destroy
, void,
648 private_eap_radius_accounting_t
*this)
650 charon
->bus
->remove_listener(charon
->bus
, &this->public.listener
);
652 this->mutex
->destroy(this->mutex
);
653 this->sessions
->destroy(this->sessions
);
660 eap_radius_accounting_t
*eap_radius_accounting_create()
662 private_eap_radius_accounting_t
*this;
668 .ike_updown
= _ike_updown
,
669 .ike_rekey
= _ike_rekey
,
670 .message
= _message_hook
,
671 .child_updown
= _child_updown
,
672 .child_rekey
= _child_rekey
,
676 /* use system time as Session ID prefix */
677 .prefix
= (u_int32_t
)time(NULL
),
678 .sessions
= hashtable_create((hashtable_hash_t
)hash
,
679 (hashtable_equals_t
)equals
, 32),
680 .mutex
= mutex_create(MUTEX_TYPE_DEFAULT
),
683 if (lib
->settings
->get_bool(lib
->settings
,
684 "%s.plugins.eap-radius.accounting", FALSE
, charon
->name
))
687 charon
->bus
->add_listener(charon
->bus
, &this->public.listener
);
689 return &this->public;
695 void eap_radius_accounting_start_interim(ike_sa_t
*ike_sa
, u_int32_t interval
)
701 DBG1(DBG_CFG
, "scheduling RADIUS Interim-Updates every %us", interval
);
702 singleton
->mutex
->lock(singleton
->mutex
);
703 entry
= get_or_create_entry(singleton
, ike_sa
);
704 entry
->interim
.interval
= interval
;
705 singleton
->mutex
->unlock(singleton
->mutex
);