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
56 * Format string we use for Called/Calling-Station-Id for a host
61 * Disable accounting unless IKE_SA has at least one virtual IP
67 * Singleton instance of accounting
69 static private_eap_radius_accounting_t
*singleton
= NULL
;
72 * Acct-Terminate-Cause
75 ACCT_CAUSE_USER_REQUEST
= 1,
76 ACCT_CAUSE_LOST_CARRIER
= 2,
77 ACCT_CAUSE_LOST_SERVICE
= 3,
78 ACCT_CAUSE_IDLE_TIMEOUT
= 4,
79 ACCT_CAUSE_SESSION_TIMEOUT
= 5,
80 ACCT_CAUSE_ADMIN_RESET
= 6,
81 ACCT_CAUSE_ADMIN_REBOOT
= 7,
82 ACCT_CAUSE_PORT_ERROR
= 8,
83 ACCT_CAUSE_NAS_ERROR
= 9,
84 ACCT_CAUSE_NAS_REQUEST
= 10,
85 ACCT_CAUSE_NAS_REBOOT
= 11,
86 ACCT_CAUSE_PORT_UNNEEDED
= 12,
87 ACCT_CAUSE_PORT_PREEMPTED
= 13,
88 ACCT_CAUSE_PORT_SUSPENDED
= 14,
89 ACCT_CAUSE_SERVICE_UNAVAILABLE
= 15,
90 ACCT_CAUSE_CALLBACK
= 16,
91 ACCT_CAUSE_USER_ERROR
= 17,
92 ACCT_CAUSE_HOST_REQUEST
= 18,
93 } radius_acct_terminate_cause_t
;
96 * Hashtable entry with usage stats
99 /** IKE_SA identifier this entry is stored under */
101 /** RADIUS accounting session ID */
103 /** number of sent/received octets/packets */
108 /** session creation time */
110 /** terminate cause */
111 radius_acct_terminate_cause_t cause
;
112 /* interim interval and timestamp of last update */
117 /** did we send Accounting-Start */
124 static void destroy_entry(entry_t
*this)
126 this->id
->destroy(this->id
);
131 * Accounting message status types
134 ACCT_STATUS_START
= 1,
135 ACCT_STATUS_STOP
= 2,
136 ACCT_STATUS_INTERIM_UPDATE
= 3,
137 ACCT_STATUS_ACCOUNTING_ON
= 7,
138 ACCT_STATUS_ACCOUNTING_OFF
= 8,
139 } radius_acct_status_t
;
142 * Hashtable hash function
144 static u_int
hash(ike_sa_id_t
*key
)
146 return key
->get_responder_spi(key
);
150 * Hashtable equals function
152 static bool equals(ike_sa_id_t
*a
, ike_sa_id_t
*b
)
154 return a
->equals(a
, b
);
158 * Update usage counter when a CHILD_SA rekeys/goes down
160 static void update_usage(private_eap_radius_accounting_t
*this,
161 ike_sa_t
*ike_sa
, child_sa_t
*child_sa
)
163 u_int64_t bytes_in
, bytes_out
, packets_in
, packets_out
;
166 child_sa
->get_usestats(child_sa
, FALSE
, NULL
, &bytes_out
, &packets_out
);
167 child_sa
->get_usestats(child_sa
, TRUE
, NULL
, &bytes_in
, &packets_in
);
169 this->mutex
->lock(this->mutex
);
170 entry
= this->sessions
->get(this->sessions
, ike_sa
->get_id(ike_sa
));
173 entry
->bytes
.sent
+= bytes_out
;
174 entry
->bytes
.received
+= bytes_in
;
175 entry
->packets
.sent
+= packets_out
;
176 entry
->packets
.received
+= packets_in
;
178 this->mutex
->unlock(this->mutex
);
182 * Send a RADIUS message, wait for response
184 static bool send_message(private_eap_radius_accounting_t
*this,
185 radius_message_t
*request
)
187 radius_message_t
*response
;
188 radius_client_t
*client
;
191 client
= eap_radius_create_client();
194 response
= client
->request(client
, request
);
197 ack
= response
->get_code(response
) == RMC_ACCOUNTING_RESPONSE
;
198 response
->destroy(response
);
200 client
->destroy(client
);
206 * Add common IKE_SA parameters to RADIUS account message
208 static void add_ike_sa_parameters(private_eap_radius_accounting_t
*this,
209 radius_message_t
*message
, ike_sa_t
*ike_sa
)
211 enumerator_t
*enumerator
;
213 char buf
[MAX_RADIUS_ATTRIBUTE_SIZE
+ 1];
217 /* virtual NAS-Port-Type */
219 message
->add(message
, RAT_NAS_PORT_TYPE
, chunk_from_thing(value
));
220 /* framed ServiceType */
222 message
->add(message
, RAT_SERVICE_TYPE
, chunk_from_thing(value
));
224 value
= htonl(ike_sa
->get_unique_id(ike_sa
));
225 message
->add(message
, RAT_NAS_PORT
, chunk_from_thing(value
));
226 message
->add(message
, RAT_NAS_PORT_ID
,
227 chunk_from_str(ike_sa
->get_name(ike_sa
)));
229 host
= ike_sa
->get_my_host(ike_sa
);
230 data
= host
->get_address(host
);
231 switch (host
->get_family(host
))
234 message
->add(message
, RAT_NAS_IP_ADDRESS
, data
);
237 message
->add(message
, RAT_NAS_IPV6_ADDRESS
, data
);
241 snprintf(buf
, sizeof(buf
), this->station_id_fmt
, host
);
242 message
->add(message
, RAT_CALLED_STATION_ID
, chunk_from_str(buf
));
243 host
= ike_sa
->get_other_host(ike_sa
);
244 snprintf(buf
, sizeof(buf
), this->station_id_fmt
, host
);
245 message
->add(message
, RAT_CALLING_STATION_ID
, chunk_from_str(buf
));
247 snprintf(buf
, sizeof(buf
), "%Y", ike_sa
->get_other_eap_id(ike_sa
));
248 message
->add(message
, RAT_USER_NAME
, chunk_from_str(buf
));
250 enumerator
= ike_sa
->create_virtual_ip_enumerator(ike_sa
, FALSE
);
251 while (enumerator
->enumerate(enumerator
, &vip
))
253 switch (vip
->get_family(vip
))
256 message
->add(message
, RAT_FRAMED_IP_ADDRESS
,
257 vip
->get_address(vip
));
260 /* we currently assign /128 prefixes, only (reserved, length) */
261 data
= chunk_from_chars(0, 128);
262 data
= chunk_cata("cc", data
, vip
->get_address(vip
));
263 message
->add(message
, RAT_FRAMED_IPV6_PREFIX
, data
);
269 enumerator
->destroy(enumerator
);
273 * Get an existing or create a new entry from the locked session table
275 static entry_t
* get_or_create_entry(private_eap_radius_accounting_t
*this,
282 entry
= this->sessions
->get(this->sessions
, ike_sa
->get_id(ike_sa
));
285 now
= time_monotonic(NULL
);
286 id
= ike_sa
->get_id(ike_sa
);
294 /* default terminate cause, if none other catched */
295 .cause
= ACCT_CAUSE_USER_REQUEST
,
297 snprintf(entry
->sid
, sizeof(entry
->sid
), "%u-%u",
298 this->prefix
, ike_sa
->get_unique_id(ike_sa
));
299 this->sessions
->put(this->sessions
, entry
->id
, entry
);
304 /* forward declaration */
305 static void schedule_interim(private_eap_radius_accounting_t
*this,
309 * Data passed to send_interim() using callback job
312 /** reference to radius accounting */
313 private_eap_radius_accounting_t
*this;
314 /** IKE_SA identifier to send interim update to */
319 * Clean up interim data
321 void destroy_interim_data(interim_data_t
*this)
323 this->id
->destroy(this->id
);
328 * Send an interim update for entry of given IKE_SA identifier
330 static job_requeue_t
send_interim(interim_data_t
*data
)
332 private_eap_radius_accounting_t
*this = data
->this;
333 u_int64_t bytes_in
= 0, bytes_out
= 0, packets_in
= 0, packets_out
= 0;
334 u_int64_t bytes
, packets
;
335 radius_message_t
*message
= NULL
;
336 enumerator_t
*enumerator
;
337 child_sa_t
*child_sa
;
342 ike_sa
= charon
->ike_sa_manager
->checkout(charon
->ike_sa_manager
, data
->id
);
345 return JOB_REQUEUE_NONE
;
347 enumerator
= ike_sa
->create_child_sa_enumerator(ike_sa
);
348 while (enumerator
->enumerate(enumerator
, &child_sa
))
350 child_sa
->get_usestats(child_sa
, FALSE
, NULL
, &bytes
, &packets
);
352 packets_out
+= packets
;
353 child_sa
->get_usestats(child_sa
, TRUE
, NULL
, &bytes
, &packets
);
355 packets_in
+= packets
;
357 enumerator
->destroy(enumerator
);
358 charon
->ike_sa_manager
->checkin(charon
->ike_sa_manager
, ike_sa
);
360 /* avoid any races by returning IKE_SA before acquiring lock */
362 this->mutex
->lock(this->mutex
);
363 entry
= this->sessions
->get(this->sessions
, data
->id
);
366 entry
->interim
.last
= time_monotonic(NULL
);
368 bytes_in
+= entry
->bytes
.received
;
369 bytes_out
+= entry
->bytes
.sent
;
370 packets_in
+= entry
->packets
.received
;
371 packets_out
+= entry
->packets
.sent
;
373 message
= radius_message_create(RMC_ACCOUNTING_REQUEST
);
374 value
= htonl(ACCT_STATUS_INTERIM_UPDATE
);
375 message
->add(message
, RAT_ACCT_STATUS_TYPE
, chunk_from_thing(value
));
376 message
->add(message
, RAT_ACCT_SESSION_ID
,
377 chunk_create(entry
->sid
, strlen(entry
->sid
)));
378 add_ike_sa_parameters(this, message
, ike_sa
);
380 value
= htonl(bytes_out
);
381 message
->add(message
, RAT_ACCT_OUTPUT_OCTETS
, chunk_from_thing(value
));
382 value
= htonl(bytes_out
>> 32);
385 message
->add(message
, RAT_ACCT_OUTPUT_GIGAWORDS
,
386 chunk_from_thing(value
));
388 value
= htonl(packets_out
);
389 message
->add(message
, RAT_ACCT_OUTPUT_PACKETS
, chunk_from_thing(value
));
391 value
= htonl(bytes_in
);
392 message
->add(message
, RAT_ACCT_INPUT_OCTETS
, chunk_from_thing(value
));
393 value
= htonl(bytes_in
>> 32);
396 message
->add(message
, RAT_ACCT_INPUT_GIGAWORDS
,
397 chunk_from_thing(value
));
399 value
= htonl(packets_in
);
400 message
->add(message
, RAT_ACCT_INPUT_PACKETS
, chunk_from_thing(value
));
402 value
= htonl(entry
->interim
.last
- entry
->created
);
403 message
->add(message
, RAT_ACCT_SESSION_TIME
, chunk_from_thing(value
));
405 schedule_interim(this, entry
);
407 this->mutex
->unlock(this->mutex
);
411 if (!send_message(this, message
))
413 if (lib
->settings
->get_bool(lib
->settings
,
414 "%s.plugins.eap-radius.accounting_close_on_timeout",
417 eap_radius_handle_timeout(data
->id
);
420 message
->destroy(message
);
422 return JOB_REQUEUE_NONE
;
426 * Schedule interim update for given entry
428 static void schedule_interim(private_eap_radius_accounting_t
*this,
431 if (entry
->interim
.interval
)
433 interim_data_t
*data
;
435 .tv_sec
= entry
->interim
.last
+ entry
->interim
.interval
,
440 .id
= entry
->id
->clone(entry
->id
),
442 lib
->scheduler
->schedule_job_tv(lib
->scheduler
,
443 (job_t
*)callback_job_create_with_prio(
444 (callback_job_cb_t
)send_interim
,
445 data
, (void*)destroy_interim_data
,
446 (callback_job_cancel_t
)return_false
, JOB_PRIO_CRITICAL
), tv
);
451 * Check if an IKE_SA has assigned a virtual IP (to peer)
453 static bool has_vip(ike_sa_t
*ike_sa
)
455 enumerator_t
*enumerator
;
459 enumerator
= ike_sa
->create_virtual_ip_enumerator(ike_sa
, FALSE
);
460 found
= enumerator
->enumerate(enumerator
, &host
);
461 enumerator
->destroy(enumerator
);
467 * Send an accounting start message
469 static void send_start(private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
)
471 radius_message_t
*message
;
475 if (this->acct_req_vip
&& !has_vip(ike_sa
))
480 this->mutex
->lock(this->mutex
);
482 entry
= get_or_create_entry(this, ike_sa
);
483 entry
->start_sent
= TRUE
;
485 message
= radius_message_create(RMC_ACCOUNTING_REQUEST
);
486 value
= htonl(ACCT_STATUS_START
);
487 message
->add(message
, RAT_ACCT_STATUS_TYPE
, chunk_from_thing(value
));
488 message
->add(message
, RAT_ACCT_SESSION_ID
,
489 chunk_create(entry
->sid
, strlen(entry
->sid
)));
491 if (!entry
->interim
.interval
)
493 entry
->interim
.interval
= lib
->settings
->get_time(lib
->settings
,
494 "%s.plugins.eap-radius.accounting_interval", 0, lib
->ns
);
495 if (entry
->interim
.interval
)
497 DBG1(DBG_CFG
, "scheduling RADIUS Interim-Updates every %us",
498 entry
->interim
.interval
);
501 schedule_interim(this, entry
);
502 this->mutex
->unlock(this->mutex
);
504 add_ike_sa_parameters(this, message
, ike_sa
);
505 if (!send_message(this, message
))
507 eap_radius_handle_timeout(ike_sa
->get_id(ike_sa
));
509 message
->destroy(message
);
513 * Send an account stop message
515 static void send_stop(private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
)
517 radius_message_t
*message
;
521 this->mutex
->lock(this->mutex
);
522 entry
= this->sessions
->remove(this->sessions
, ike_sa
->get_id(ike_sa
));
523 this->mutex
->unlock(this->mutex
);
526 if (!entry
->start_sent
)
527 { /* we tried to authenticate this peer, but never sent a start */
528 destroy_entry(entry
);
531 message
= radius_message_create(RMC_ACCOUNTING_REQUEST
);
532 value
= htonl(ACCT_STATUS_STOP
);
533 message
->add(message
, RAT_ACCT_STATUS_TYPE
, chunk_from_thing(value
));
534 message
->add(message
, RAT_ACCT_SESSION_ID
,
535 chunk_create(entry
->sid
, strlen(entry
->sid
)));
536 add_ike_sa_parameters(this, message
, ike_sa
);
538 value
= htonl(entry
->bytes
.sent
);
539 message
->add(message
, RAT_ACCT_OUTPUT_OCTETS
, chunk_from_thing(value
));
540 value
= htonl(entry
->bytes
.sent
>> 32);
543 message
->add(message
, RAT_ACCT_OUTPUT_GIGAWORDS
,
544 chunk_from_thing(value
));
546 value
= htonl(entry
->packets
.sent
);
547 message
->add(message
, RAT_ACCT_OUTPUT_PACKETS
, chunk_from_thing(value
));
549 value
= htonl(entry
->bytes
.received
);
550 message
->add(message
, RAT_ACCT_INPUT_OCTETS
, chunk_from_thing(value
));
551 value
= htonl(entry
->bytes
.received
>> 32);
554 message
->add(message
, RAT_ACCT_INPUT_GIGAWORDS
,
555 chunk_from_thing(value
));
557 value
= htonl(entry
->packets
.received
);
558 message
->add(message
, RAT_ACCT_INPUT_PACKETS
, chunk_from_thing(value
));
560 value
= htonl(time_monotonic(NULL
) - entry
->created
);
561 message
->add(message
, RAT_ACCT_SESSION_TIME
, chunk_from_thing(value
));
564 value
= htonl(entry
->cause
);
565 message
->add(message
, RAT_ACCT_TERMINATE_CAUSE
, chunk_from_thing(value
));
567 if (!send_message(this, message
))
569 eap_radius_handle_timeout(NULL
);
571 message
->destroy(message
);
572 destroy_entry(entry
);
576 METHOD(listener_t
, alert
, bool,
577 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
, alert_t alert
,
580 radius_acct_terminate_cause_t cause
;
585 case ALERT_IKE_SA_EXPIRED
:
586 cause
= ACCT_CAUSE_SESSION_TIMEOUT
;
588 case ALERT_RETRANSMIT_SEND_TIMEOUT
:
589 cause
= ACCT_CAUSE_LOST_SERVICE
;
594 this->mutex
->lock(this->mutex
);
595 entry
= this->sessions
->get(this->sessions
, ike_sa
->get_id(ike_sa
));
598 entry
->cause
= cause
;
600 this->mutex
->unlock(this->mutex
);
604 METHOD(listener_t
, ike_updown
, bool,
605 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
, bool up
)
609 enumerator_t
*enumerator
;
610 child_sa_t
*child_sa
;
612 /* update usage for all children just before sending stop */
613 enumerator
= ike_sa
->create_child_sa_enumerator(ike_sa
);
614 while (enumerator
->enumerate(enumerator
, &child_sa
))
616 update_usage(this, ike_sa
, child_sa
);
618 enumerator
->destroy(enumerator
);
620 send_stop(this, ike_sa
);
625 METHOD(listener_t
, message_hook
, bool,
626 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
,
627 message_t
*message
, bool incoming
, bool plain
)
629 /* start accounting here, virtual IP now is set */
630 if (plain
&& ike_sa
->get_state(ike_sa
) == IKE_ESTABLISHED
&&
631 !incoming
&& !message
->get_request(message
))
633 if (ike_sa
->get_version(ike_sa
) == IKEV1
&&
634 message
->get_exchange_type(message
) == TRANSACTION
)
636 send_start(this, ike_sa
);
638 if (ike_sa
->get_version(ike_sa
) == IKEV2
&&
639 message
->get_exchange_type(message
) == IKE_AUTH
)
641 send_start(this, ike_sa
);
647 METHOD(listener_t
, ike_rekey
, bool,
648 private_eap_radius_accounting_t
*this, ike_sa_t
*old
, ike_sa_t
*new)
652 this->mutex
->lock(this->mutex
);
653 entry
= this->sessions
->remove(this->sessions
, old
->get_id(old
));
656 /* update IKE_SA identifier */
657 entry
->id
->destroy(entry
->id
);
658 entry
->id
= new->get_id(new);
659 entry
->id
= entry
->id
->clone(entry
->id
);
660 /* fire new interim update job, old gets invalid */
661 schedule_interim(this, entry
);
663 entry
= this->sessions
->put(this->sessions
, entry
->id
, entry
);
666 destroy_entry(entry
);
669 this->mutex
->unlock(this->mutex
);
674 METHOD(listener_t
, child_rekey
, bool,
675 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
,
676 child_sa_t
*old
, child_sa_t
*new)
678 update_usage(this, ike_sa
, old
);
683 METHOD(listener_t
, child_updown
, bool,
684 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
,
685 child_sa_t
*child_sa
, bool up
)
687 if (!up
&& ike_sa
->get_state(ike_sa
) == IKE_ESTABLISHED
)
689 update_usage(this, ike_sa
, child_sa
);
694 METHOD(eap_radius_accounting_t
, destroy
, void,
695 private_eap_radius_accounting_t
*this)
697 charon
->bus
->remove_listener(charon
->bus
, &this->public.listener
);
699 this->mutex
->destroy(this->mutex
);
700 this->sessions
->destroy(this->sessions
);
707 eap_radius_accounting_t
*eap_radius_accounting_create()
709 private_eap_radius_accounting_t
*this;
715 .ike_updown
= _ike_updown
,
716 .ike_rekey
= _ike_rekey
,
717 .message
= _message_hook
,
718 .child_updown
= _child_updown
,
719 .child_rekey
= _child_rekey
,
723 /* use system time as Session ID prefix */
724 .prefix
= (u_int32_t
)time(NULL
),
725 .sessions
= hashtable_create((hashtable_hash_t
)hash
,
726 (hashtable_equals_t
)equals
, 32),
727 .mutex
= mutex_create(MUTEX_TYPE_DEFAULT
),
729 if (lib
->settings
->get_bool(lib
->settings
,
730 "%s.plugins.eap-radius.station_id_with_port", TRUE
, lib
->ns
))
732 this->station_id_fmt
= "%#H";
736 this->station_id_fmt
= "%H";
738 if (lib
->settings
->get_bool(lib
->settings
,
739 "%s.plugins.eap-radius.accounting", FALSE
, lib
->ns
))
742 charon
->bus
->add_listener(charon
->bus
, &this->public.listener
);
744 this->acct_req_vip
= lib
->settings
->get_bool(lib
->settings
,
745 "%s.plugins.eap-radius.accounting_requires_vip",
748 return &this->public;
754 void eap_radius_accounting_start_interim(ike_sa_t
*ike_sa
, u_int32_t interval
)
760 DBG1(DBG_CFG
, "scheduling RADIUS Interim-Updates every %us", interval
);
761 singleton
->mutex
->lock(singleton
->mutex
);
762 entry
= get_or_create_entry(singleton
, ike_sa
);
763 entry
->interim
.interval
= interval
;
764 singleton
->mutex
->unlock(singleton
->mutex
);