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
;
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 eap_radius_handle_timeout(data
->id
);
415 message
->destroy(message
);
417 return JOB_REQUEUE_NONE
;
421 * Schedule interim update for given entry
423 static void schedule_interim(private_eap_radius_accounting_t
*this,
426 if (entry
->interim
.interval
)
428 interim_data_t
*data
;
430 .tv_sec
= entry
->interim
.last
+ entry
->interim
.interval
,
435 .id
= entry
->id
->clone(entry
->id
),
437 lib
->scheduler
->schedule_job_tv(lib
->scheduler
,
438 (job_t
*)callback_job_create_with_prio(
439 (callback_job_cb_t
)send_interim
,
440 data
, (void*)destroy_interim_data
,
441 (callback_job_cancel_t
)return_false
, JOB_PRIO_CRITICAL
), tv
);
446 * Check if an IKE_SA has assigned a virtual IP (to peer)
448 static bool has_vip(ike_sa_t
*ike_sa
)
450 enumerator_t
*enumerator
;
454 enumerator
= ike_sa
->create_virtual_ip_enumerator(ike_sa
, FALSE
);
455 found
= enumerator
->enumerate(enumerator
, &host
);
456 enumerator
->destroy(enumerator
);
462 * Send an accounting start message
464 static void send_start(private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
)
466 radius_message_t
*message
;
470 if (this->acct_req_vip
&& !has_vip(ike_sa
))
475 this->mutex
->lock(this->mutex
);
477 entry
= get_or_create_entry(this, ike_sa
);
478 entry
->start_sent
= TRUE
;
480 message
= radius_message_create(RMC_ACCOUNTING_REQUEST
);
481 value
= htonl(ACCT_STATUS_START
);
482 message
->add(message
, RAT_ACCT_STATUS_TYPE
, chunk_from_thing(value
));
483 message
->add(message
, RAT_ACCT_SESSION_ID
,
484 chunk_create(entry
->sid
, strlen(entry
->sid
)));
486 schedule_interim(this, entry
);
487 this->mutex
->unlock(this->mutex
);
489 add_ike_sa_parameters(this, message
, ike_sa
);
490 if (!send_message(this, message
))
492 eap_radius_handle_timeout(ike_sa
->get_id(ike_sa
));
494 message
->destroy(message
);
498 * Send an account stop message
500 static void send_stop(private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
)
502 radius_message_t
*message
;
506 this->mutex
->lock(this->mutex
);
507 entry
= this->sessions
->remove(this->sessions
, ike_sa
->get_id(ike_sa
));
508 this->mutex
->unlock(this->mutex
);
511 if (!entry
->start_sent
)
512 { /* we tried to authenticate this peer, but never sent a start */
513 destroy_entry(entry
);
516 message
= radius_message_create(RMC_ACCOUNTING_REQUEST
);
517 value
= htonl(ACCT_STATUS_STOP
);
518 message
->add(message
, RAT_ACCT_STATUS_TYPE
, chunk_from_thing(value
));
519 message
->add(message
, RAT_ACCT_SESSION_ID
,
520 chunk_create(entry
->sid
, strlen(entry
->sid
)));
521 add_ike_sa_parameters(this, message
, ike_sa
);
523 value
= htonl(entry
->bytes
.sent
);
524 message
->add(message
, RAT_ACCT_OUTPUT_OCTETS
, chunk_from_thing(value
));
525 value
= htonl(entry
->bytes
.sent
>> 32);
528 message
->add(message
, RAT_ACCT_OUTPUT_GIGAWORDS
,
529 chunk_from_thing(value
));
531 value
= htonl(entry
->packets
.sent
);
532 message
->add(message
, RAT_ACCT_OUTPUT_PACKETS
, chunk_from_thing(value
));
534 value
= htonl(entry
->bytes
.received
);
535 message
->add(message
, RAT_ACCT_INPUT_OCTETS
, chunk_from_thing(value
));
536 value
= htonl(entry
->bytes
.received
>> 32);
539 message
->add(message
, RAT_ACCT_INPUT_GIGAWORDS
,
540 chunk_from_thing(value
));
542 value
= htonl(entry
->packets
.received
);
543 message
->add(message
, RAT_ACCT_INPUT_PACKETS
, chunk_from_thing(value
));
545 value
= htonl(time_monotonic(NULL
) - entry
->created
);
546 message
->add(message
, RAT_ACCT_SESSION_TIME
, chunk_from_thing(value
));
549 value
= htonl(entry
->cause
);
550 message
->add(message
, RAT_ACCT_TERMINATE_CAUSE
, chunk_from_thing(value
));
552 if (!send_message(this, message
))
554 eap_radius_handle_timeout(NULL
);
556 message
->destroy(message
);
557 destroy_entry(entry
);
561 METHOD(listener_t
, alert
, bool,
562 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
, alert_t alert
,
565 radius_acct_terminate_cause_t cause
;
570 case ALERT_IKE_SA_EXPIRED
:
571 cause
= ACCT_CAUSE_SESSION_TIMEOUT
;
573 case ALERT_RETRANSMIT_SEND_TIMEOUT
:
574 cause
= ACCT_CAUSE_LOST_SERVICE
;
579 this->mutex
->lock(this->mutex
);
580 entry
= this->sessions
->get(this->sessions
, ike_sa
->get_id(ike_sa
));
583 entry
->cause
= cause
;
585 this->mutex
->unlock(this->mutex
);
589 METHOD(listener_t
, ike_updown
, bool,
590 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
, bool up
)
594 enumerator_t
*enumerator
;
595 child_sa_t
*child_sa
;
597 /* update usage for all children just before sending stop */
598 enumerator
= ike_sa
->create_child_sa_enumerator(ike_sa
);
599 while (enumerator
->enumerate(enumerator
, &child_sa
))
601 update_usage(this, ike_sa
, child_sa
);
603 enumerator
->destroy(enumerator
);
605 send_stop(this, ike_sa
);
610 METHOD(listener_t
, message_hook
, bool,
611 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
,
612 message_t
*message
, bool incoming
, bool plain
)
614 /* start accounting here, virtual IP now is set */
615 if (plain
&& ike_sa
->get_state(ike_sa
) == IKE_ESTABLISHED
&&
616 !incoming
&& !message
->get_request(message
))
618 if (ike_sa
->get_version(ike_sa
) == IKEV1
&&
619 message
->get_exchange_type(message
) == TRANSACTION
)
621 send_start(this, ike_sa
);
623 if (ike_sa
->get_version(ike_sa
) == IKEV2
&&
624 message
->get_exchange_type(message
) == IKE_AUTH
)
626 send_start(this, ike_sa
);
632 METHOD(listener_t
, ike_rekey
, bool,
633 private_eap_radius_accounting_t
*this, ike_sa_t
*old
, ike_sa_t
*new)
637 this->mutex
->lock(this->mutex
);
638 entry
= this->sessions
->remove(this->sessions
, old
->get_id(old
));
641 /* update IKE_SA identifier */
642 entry
->id
->destroy(entry
->id
);
643 entry
->id
= new->get_id(new);
644 entry
->id
= entry
->id
->clone(entry
->id
);
645 /* fire new interim update job, old gets invalid */
646 schedule_interim(this, entry
);
648 entry
= this->sessions
->put(this->sessions
, entry
->id
, entry
);
651 destroy_entry(entry
);
654 this->mutex
->unlock(this->mutex
);
659 METHOD(listener_t
, child_rekey
, bool,
660 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
,
661 child_sa_t
*old
, child_sa_t
*new)
663 update_usage(this, ike_sa
, old
);
668 METHOD(listener_t
, child_updown
, bool,
669 private_eap_radius_accounting_t
*this, ike_sa_t
*ike_sa
,
670 child_sa_t
*child_sa
, bool up
)
672 if (!up
&& ike_sa
->get_state(ike_sa
) == IKE_ESTABLISHED
)
674 update_usage(this, ike_sa
, child_sa
);
679 METHOD(eap_radius_accounting_t
, destroy
, void,
680 private_eap_radius_accounting_t
*this)
682 charon
->bus
->remove_listener(charon
->bus
, &this->public.listener
);
684 this->mutex
->destroy(this->mutex
);
685 this->sessions
->destroy(this->sessions
);
692 eap_radius_accounting_t
*eap_radius_accounting_create()
694 private_eap_radius_accounting_t
*this;
700 .ike_updown
= _ike_updown
,
701 .ike_rekey
= _ike_rekey
,
702 .message
= _message_hook
,
703 .child_updown
= _child_updown
,
704 .child_rekey
= _child_rekey
,
708 /* use system time as Session ID prefix */
709 .prefix
= (u_int32_t
)time(NULL
),
710 .sessions
= hashtable_create((hashtable_hash_t
)hash
,
711 (hashtable_equals_t
)equals
, 32),
712 .mutex
= mutex_create(MUTEX_TYPE_DEFAULT
),
714 if (lib
->settings
->get_bool(lib
->settings
,
715 "%s.plugins.eap-radius.station_id_with_port", TRUE
, lib
->ns
))
717 this->station_id_fmt
= "%#H";
721 this->station_id_fmt
= "%H";
723 if (lib
->settings
->get_bool(lib
->settings
,
724 "%s.plugins.eap-radius.accounting", FALSE
, lib
->ns
))
727 charon
->bus
->add_listener(charon
->bus
, &this->public.listener
);
729 this->acct_req_vip
= lib
->settings
->get_bool(lib
->settings
,
730 "%s.plugins.eap-radius.accounting_requires_vip",
733 return &this->public;
739 void eap_radius_accounting_start_interim(ike_sa_t
*ike_sa
, u_int32_t interval
)
745 DBG1(DBG_CFG
, "scheduling RADIUS Interim-Updates every %us", interval
);
746 singleton
->mutex
->lock(singleton
->mutex
);
747 entry
= get_or_create_entry(singleton
, ike_sa
);
748 entry
->interim
.interval
= interval
;
749 singleton
->mutex
->unlock(singleton
->mutex
);