Add support for RADIUS Interim accounting updates
[strongswan.git] / src / libcharon / plugins / eap_radius / eap_radius_accounting.c
1 /*
2 * Copyright (C) 2012 Martin Willi
3 * Copyright (C) 2012 revosec AG
4 *
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>.
9 *
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
13 * for more details.
14 */
15
16 #include "eap_radius_accounting.h"
17 #include "eap_radius_plugin.h"
18
19 #include <time.h>
20
21 #include <radius_message.h>
22 #include <radius_client.h>
23 #include <daemon.h>
24 #include <collections/hashtable.h>
25 #include <threading/mutex.h>
26 #include <processing/jobs/callback_job.h>
27
28 typedef struct private_eap_radius_accounting_t private_eap_radius_accounting_t;
29
30 /**
31 * Private data of an eap_radius_accounting_t object.
32 */
33 struct private_eap_radius_accounting_t {
34
35 /**
36 * Public eap_radius_accounting_t interface.
37 */
38 eap_radius_accounting_t public;
39
40 /**
41 * Hashtable with sessions, ike_sa_id_t => entry_t
42 */
43 hashtable_t *sessions;
44
45 /**
46 * Mutex to lock sessions
47 */
48 mutex_t *mutex;
49
50 /**
51 * Session ID prefix
52 */
53 u_int32_t prefix;
54 };
55
56 /**
57 * Singleton instance of accounting
58 */
59 static private_eap_radius_accounting_t *singleton = NULL;
60
61 /**
62 * Acct-Terminate-Cause
63 */
64 typedef enum {
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;
84
85 /**
86 * Hashtable entry with usage stats
87 */
88 typedef struct {
89 /** IKE_SA identifier this entry is stored under */
90 ike_sa_id_t *id;
91 /** RADIUS accounting session ID */
92 char sid[16];
93 /** number of sent/received octets/packets */
94 struct {
95 u_int64_t sent;
96 u_int64_t received;
97 } bytes, packets;
98 /** session creation time */
99 time_t created;
100 /** terminate cause */
101 radius_acct_terminate_cause_t cause;
102 /* interim interval and timestamp of last update */
103 struct {
104 u_int32_t interval;
105 time_t last;
106 } interim;
107 /** did we send Accounting-Start */
108 bool start_sent;
109 } entry_t;
110
111 /**
112 * Destroy an entry_t
113 */
114 static void destroy_entry(entry_t *this)
115 {
116 this->id->destroy(this->id);
117 free(this);
118 }
119
120 /**
121 * Accounting message status types
122 */
123 typedef enum {
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;
130
131 /**
132 * Hashtable hash function
133 */
134 static u_int hash(ike_sa_id_t *key)
135 {
136 return key->get_responder_spi(key);
137 }
138
139 /**
140 * Hashtable equals function
141 */
142 static bool equals(ike_sa_id_t *a, ike_sa_id_t *b)
143 {
144 return a->equals(a, b);
145 }
146
147 /**
148 * Update usage counter when a CHILD_SA rekeys/goes down
149 */
150 static void update_usage(private_eap_radius_accounting_t *this,
151 ike_sa_t *ike_sa, child_sa_t *child_sa)
152 {
153 u_int64_t bytes_in, bytes_out, packets_in, packets_out;
154 entry_t *entry;
155
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);
158
159 this->mutex->lock(this->mutex);
160 entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
161 if (entry)
162 {
163 entry->bytes.sent += bytes_out;
164 entry->bytes.received += bytes_in;
165 entry->packets.sent += packets_out;
166 entry->packets.received += packets_in;
167 }
168 this->mutex->unlock(this->mutex);
169 }
170
171 /**
172 * Send a RADIUS message, wait for response
173 */
174 static bool send_message(private_eap_radius_accounting_t *this,
175 radius_message_t *request)
176 {
177 radius_message_t *response;
178 radius_client_t *client;
179 bool ack = FALSE;
180
181 client = eap_radius_create_client();
182 if (client)
183 {
184 response = client->request(client, request);
185 if (response)
186 {
187 ack = response->get_code(response) == RMC_ACCOUNTING_RESPONSE;
188 response->destroy(response);
189 }
190 client->destroy(client);
191 }
192 return ack;
193 }
194
195 /**
196 * Add common IKE_SA parameters to RADIUS account message
197 */
198 static void add_ike_sa_parameters(radius_message_t *message, ike_sa_t *ike_sa)
199 {
200 enumerator_t *enumerator;
201 host_t *vip, *host;
202 char buf[64];
203 chunk_t data;
204 u_int32_t value;
205
206 /* virtual NAS-Port-Type */
207 value = htonl(5);
208 message->add(message, RAT_NAS_PORT_TYPE, chunk_from_thing(value));
209 /* framed ServiceType */
210 value = htonl(2);
211 message->add(message, RAT_SERVICE_TYPE, chunk_from_thing(value));
212
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)));
217
218 host = ike_sa->get_my_host(ike_sa);
219 data = host->get_address(host);
220 switch (host->get_family(host))
221 {
222 case AF_INET:
223 message->add(message, RAT_NAS_IP_ADDRESS, data);
224 break;
225 case AF_INET6:
226 message->add(message, RAT_NAS_IPV6_ADDRESS, data);
227 default:
228 break;
229 }
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));
235
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));
238
239 enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, FALSE);
240 while (enumerator->enumerate(enumerator, &vip))
241 {
242 switch (vip->get_family(vip))
243 {
244 case AF_INET:
245 message->add(message, RAT_FRAMED_IP_ADDRESS,
246 vip->get_address(vip));
247 break;
248 case AF_INET6:
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);
253 break;
254 default:
255 break;
256 }
257 }
258 enumerator->destroy(enumerator);
259 }
260
261 /**
262 * Get an existing or create a new entry from the locked session table
263 */
264 static entry_t* get_or_create_entry(private_eap_radius_accounting_t *this,
265 ike_sa_t *ike_sa)
266 {
267 ike_sa_id_t *id;
268 entry_t *entry;
269 time_t now;
270
271 entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
272 if (!entry)
273 {
274 now = time_monotonic(NULL);
275 id = ike_sa->get_id(ike_sa);
276
277 INIT(entry,
278 .id = id->clone(id),
279 .created = now,
280 .interim = {
281 .last = now,
282 },
283 /* default terminate cause, if none other catched */
284 .cause = ACCT_CAUSE_USER_REQUEST,
285 );
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);
289 }
290 return entry;
291 }
292
293 /* forward declaration */
294 static void schedule_interim(private_eap_radius_accounting_t *this,
295 entry_t *entry);
296
297 /**
298 * Data passed to send_interim() using callback job
299 */
300 typedef struct {
301 /** reference to radius accounting */
302 private_eap_radius_accounting_t *this;
303 /** IKE_SA identifier to send interim update to */
304 ike_sa_id_t *id;
305 } interim_data_t;
306
307 /**
308 * Clean up interim data
309 */
310 void destroy_interim_data(interim_data_t *this)
311 {
312 this->id->destroy(this->id);
313 free(this);
314 }
315
316 /**
317 * Send an interim update for entry of given IKE_SA identifier
318 */
319 static job_requeue_t send_interim(interim_data_t *data)
320 {
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;
327 ike_sa_t *ike_sa;
328 entry_t *entry;
329 u_int32_t value;
330
331 ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, data->id);
332 if (!ike_sa)
333 {
334 return JOB_REQUEUE_NONE;
335 }
336 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
337 while (enumerator->enumerate(enumerator, &child_sa))
338 {
339 child_sa->get_usestats(child_sa, FALSE, NULL, &bytes, &packets);
340 bytes_out += bytes;
341 packets_out += packets;
342 child_sa->get_usestats(child_sa, TRUE, NULL, &bytes, &packets);
343 bytes_in += bytes;
344 packets_in += packets;
345 }
346 enumerator->destroy(enumerator);
347 charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
348
349 /* avoid any races by returning IKE_SA before acquiring lock */
350
351 this->mutex->lock(this->mutex);
352 entry = this->sessions->get(this->sessions, data->id);
353 if (entry)
354 {
355 entry->interim.last = time_monotonic(NULL);
356
357 bytes_in += entry->bytes.received;
358 bytes_out += entry->bytes.sent;
359 packets_in += entry->packets.received;
360 packets_out += entry->packets.sent;
361
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);
368
369 value = htonl(bytes_out);
370 message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
371 value = htonl(bytes_out >> 32);
372 if (value)
373 {
374 message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
375 chunk_from_thing(value));
376 }
377 value = htonl(packets_out);
378 message->add(message, RAT_ACCT_OUTPUT_PACKETS, chunk_from_thing(value));
379
380 value = htonl(bytes_in);
381 message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
382 value = htonl(bytes_in >> 32);
383 if (value)
384 {
385 message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
386 chunk_from_thing(value));
387 }
388 value = htonl(packets_in);
389 message->add(message, RAT_ACCT_INPUT_PACKETS, chunk_from_thing(value));
390
391 value = htonl(entry->interim.last - entry->created);
392 message->add(message, RAT_ACCT_SESSION_TIME, chunk_from_thing(value));
393
394 schedule_interim(this, entry);
395 }
396 this->mutex->unlock(this->mutex);
397
398 if (message)
399 {
400 if (!send_message(this, message))
401 {
402 eap_radius_handle_timeout(data->id);
403 }
404 message->destroy(message);
405 }
406 return JOB_REQUEUE_NONE;
407 }
408
409 /**
410 * Schedule interim update for given entry
411 */
412 static void schedule_interim(private_eap_radius_accounting_t *this,
413 entry_t *entry)
414 {
415 if (entry->interim.interval)
416 {
417 interim_data_t *data;
418 timeval_t tv = {
419 .tv_sec = entry->interim.last + entry->interim.interval,
420 };
421
422 INIT(data,
423 .this = this,
424 .id = entry->id->clone(entry->id),
425 );
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);
431 }
432 }
433
434 /**
435 * Send an accounting start message
436 */
437 static void send_start(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
438 {
439 radius_message_t *message;
440 entry_t *entry;
441 u_int32_t value;
442
443 this->mutex->lock(this->mutex);
444
445 entry = get_or_create_entry(this, ike_sa);
446 entry->start_sent = TRUE;
447
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)));
453
454 schedule_interim(this, entry);
455 this->mutex->unlock(this->mutex);
456
457 add_ike_sa_parameters(message, ike_sa);
458 if (!send_message(this, message))
459 {
460 eap_radius_handle_timeout(ike_sa->get_id(ike_sa));
461 }
462 message->destroy(message);
463 }
464
465 /**
466 * Send an account stop message
467 */
468 static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
469 {
470 radius_message_t *message;
471 entry_t *entry;
472 u_int32_t id, value;
473
474 id = ike_sa->get_unique_id(ike_sa);
475 this->mutex->lock(this->mutex);
476 entry = this->sessions->remove(this->sessions, ike_sa->get_id(ike_sa));
477 this->mutex->unlock(this->mutex);
478 if (entry)
479 {
480 if (!entry->start_sent)
481 { /* we tried to authenticate this peer, but never sent a start */
482 destroy_entry(entry);
483 return;
484 }
485 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
486 value = htonl(ACCT_STATUS_STOP);
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)));
490 add_ike_sa_parameters(message, ike_sa);
491
492 value = htonl(entry->bytes.sent);
493 message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
494 value = htonl(entry->bytes.sent >> 32);
495 if (value)
496 {
497 message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
498 chunk_from_thing(value));
499 }
500 value = htonl(entry->packets.sent);
501 message->add(message, RAT_ACCT_OUTPUT_PACKETS, chunk_from_thing(value));
502
503 value = htonl(entry->bytes.received);
504 message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
505 value = htonl(entry->bytes.received >> 32);
506 if (value)
507 {
508 message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
509 chunk_from_thing(value));
510 }
511 value = htonl(entry->packets.received);
512 message->add(message, RAT_ACCT_INPUT_PACKETS, chunk_from_thing(value));
513
514 value = htonl(time_monotonic(NULL) - entry->created);
515 message->add(message, RAT_ACCT_SESSION_TIME, chunk_from_thing(value));
516
517
518 value = htonl(entry->cause);
519 message->add(message, RAT_ACCT_TERMINATE_CAUSE, chunk_from_thing(value));
520
521 if (!send_message(this, message))
522 {
523 eap_radius_handle_timeout(NULL);
524 }
525 message->destroy(message);
526 destroy_entry(entry);
527 }
528 }
529
530 METHOD(listener_t, alert, bool,
531 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, alert_t alert,
532 va_list args)
533 {
534 radius_acct_terminate_cause_t cause;
535 entry_t *entry;
536
537 switch (alert)
538 {
539 case ALERT_IKE_SA_EXPIRED:
540 cause = ACCT_CAUSE_SESSION_TIMEOUT;
541 break;
542 case ALERT_RETRANSMIT_SEND_TIMEOUT:
543 cause = ACCT_CAUSE_LOST_SERVICE;
544 break;
545 default:
546 return TRUE;
547 }
548 this->mutex->lock(this->mutex);
549 entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
550 if (entry)
551 {
552 entry->cause = cause;
553 }
554 this->mutex->unlock(this->mutex);
555 return TRUE;
556 }
557
558 METHOD(listener_t, ike_updown, bool,
559 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, bool up)
560 {
561 if (!up)
562 {
563 enumerator_t *enumerator;
564 child_sa_t *child_sa;
565
566 /* update usage for all children just before sending stop */
567 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
568 while (enumerator->enumerate(enumerator, &child_sa))
569 {
570 update_usage(this, ike_sa, child_sa);
571 }
572 enumerator->destroy(enumerator);
573
574 send_stop(this, ike_sa);
575 }
576 return TRUE;
577 }
578
579 METHOD(listener_t, message_hook, bool,
580 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
581 message_t *message, bool incoming, bool plain)
582 {
583 /* start accounting here, virtual IP now is set */
584 if (plain && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED &&
585 !incoming && !message->get_request(message))
586 {
587 if (ike_sa->get_version(ike_sa) == IKEV1 &&
588 message->get_exchange_type(message) == TRANSACTION)
589 {
590 send_start(this, ike_sa);
591 }
592 if (ike_sa->get_version(ike_sa) == IKEV2 &&
593 message->get_exchange_type(message) == IKE_AUTH)
594 {
595 send_start(this, ike_sa);
596 }
597 }
598 return TRUE;
599 }
600
601 METHOD(listener_t, ike_rekey, bool,
602 private_eap_radius_accounting_t *this, ike_sa_t *old, ike_sa_t *new)
603 {
604 entry_t *entry;
605
606 this->mutex->lock(this->mutex);
607 entry = this->sessions->remove(this->sessions, old->get_id(old));
608 if (entry)
609 {
610 /* update IKE_SA identifier */
611 entry->id->destroy(entry->id);
612 entry->id = new->get_id(new);
613 entry->id = entry->id->clone(entry->id);
614 /* fire new interim update job, old gets invalid */
615 schedule_interim(this, entry);
616
617 entry = this->sessions->put(this->sessions, entry->id, entry);
618 if (entry)
619 {
620 destroy_entry(entry);
621 }
622 }
623 this->mutex->unlock(this->mutex);
624
625 return TRUE;
626 }
627
628 METHOD(listener_t, child_rekey, bool,
629 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
630 child_sa_t *old, child_sa_t *new)
631 {
632 update_usage(this, ike_sa, old);
633
634 return TRUE;
635 }
636
637 METHOD(listener_t, child_updown, bool,
638 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
639 child_sa_t *child_sa, bool up)
640 {
641 if (!up && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED)
642 {
643 update_usage(this, ike_sa, child_sa);
644 }
645 return TRUE;
646 }
647
648 METHOD(eap_radius_accounting_t, destroy, void,
649 private_eap_radius_accounting_t *this)
650 {
651 singleton = NULL;
652 this->mutex->destroy(this->mutex);
653 this->sessions->destroy(this->sessions);
654 free(this);
655 }
656
657 /**
658 * See header
659 */
660 eap_radius_accounting_t *eap_radius_accounting_create()
661 {
662 private_eap_radius_accounting_t *this;
663
664 INIT(this,
665 .public = {
666 .listener = {
667 .alert = _alert,
668 .ike_updown = _ike_updown,
669 .ike_rekey = _ike_rekey,
670 .message = _message_hook,
671 .child_updown = _child_updown,
672 .child_rekey = _child_rekey,
673 },
674 .destroy = _destroy,
675 },
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),
681 );
682
683 singleton = this;
684 return &this->public;
685 }
686
687 /**
688 * See header
689 */
690 void eap_radius_accounting_start_interim(ike_sa_t *ike_sa, u_int32_t interval)
691 {
692 if (singleton)
693 {
694 entry_t *entry;
695
696 DBG1(DBG_CFG, "scheduling RADIUS Interim-Updates every %us", interval);
697 singleton->mutex->lock(singleton->mutex);
698 entry = get_or_create_entry(singleton, ike_sa);
699 entry->interim.interval = interval;
700 singleton->mutex->unlock(singleton->mutex);
701 }
702 }