eap-radius: Add an option to exclude ports from Called/Calling-Station-Id
[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 * Format string we use for Called/Calling-Station-Id for a host
57 */
58 char *station_id_fmt;
59 };
60
61 /**
62 * Singleton instance of accounting
63 */
64 static private_eap_radius_accounting_t *singleton = NULL;
65
66 /**
67 * Acct-Terminate-Cause
68 */
69 typedef enum {
70 ACCT_CAUSE_USER_REQUEST = 1,
71 ACCT_CAUSE_LOST_CARRIER = 2,
72 ACCT_CAUSE_LOST_SERVICE = 3,
73 ACCT_CAUSE_IDLE_TIMEOUT = 4,
74 ACCT_CAUSE_SESSION_TIMEOUT = 5,
75 ACCT_CAUSE_ADMIN_RESET = 6,
76 ACCT_CAUSE_ADMIN_REBOOT = 7,
77 ACCT_CAUSE_PORT_ERROR = 8,
78 ACCT_CAUSE_NAS_ERROR = 9,
79 ACCT_CAUSE_NAS_REQUEST = 10,
80 ACCT_CAUSE_NAS_REBOOT = 11,
81 ACCT_CAUSE_PORT_UNNEEDED = 12,
82 ACCT_CAUSE_PORT_PREEMPTED = 13,
83 ACCT_CAUSE_PORT_SUSPENDED = 14,
84 ACCT_CAUSE_SERVICE_UNAVAILABLE = 15,
85 ACCT_CAUSE_CALLBACK = 16,
86 ACCT_CAUSE_USER_ERROR = 17,
87 ACCT_CAUSE_HOST_REQUEST = 18,
88 } radius_acct_terminate_cause_t;
89
90 /**
91 * Hashtable entry with usage stats
92 */
93 typedef struct {
94 /** IKE_SA identifier this entry is stored under */
95 ike_sa_id_t *id;
96 /** RADIUS accounting session ID */
97 char sid[16];
98 /** number of sent/received octets/packets */
99 struct {
100 u_int64_t sent;
101 u_int64_t received;
102 } bytes, packets;
103 /** session creation time */
104 time_t created;
105 /** terminate cause */
106 radius_acct_terminate_cause_t cause;
107 /* interim interval and timestamp of last update */
108 struct {
109 u_int32_t interval;
110 time_t last;
111 } interim;
112 /** did we send Accounting-Start */
113 bool start_sent;
114 } entry_t;
115
116 /**
117 * Destroy an entry_t
118 */
119 static void destroy_entry(entry_t *this)
120 {
121 this->id->destroy(this->id);
122 free(this);
123 }
124
125 /**
126 * Accounting message status types
127 */
128 typedef enum {
129 ACCT_STATUS_START = 1,
130 ACCT_STATUS_STOP = 2,
131 ACCT_STATUS_INTERIM_UPDATE = 3,
132 ACCT_STATUS_ACCOUNTING_ON = 7,
133 ACCT_STATUS_ACCOUNTING_OFF = 8,
134 } radius_acct_status_t;
135
136 /**
137 * Hashtable hash function
138 */
139 static u_int hash(ike_sa_id_t *key)
140 {
141 return key->get_responder_spi(key);
142 }
143
144 /**
145 * Hashtable equals function
146 */
147 static bool equals(ike_sa_id_t *a, ike_sa_id_t *b)
148 {
149 return a->equals(a, b);
150 }
151
152 /**
153 * Update usage counter when a CHILD_SA rekeys/goes down
154 */
155 static void update_usage(private_eap_radius_accounting_t *this,
156 ike_sa_t *ike_sa, child_sa_t *child_sa)
157 {
158 u_int64_t bytes_in, bytes_out, packets_in, packets_out;
159 entry_t *entry;
160
161 child_sa->get_usestats(child_sa, FALSE, NULL, &bytes_out, &packets_out);
162 child_sa->get_usestats(child_sa, TRUE, NULL, &bytes_in, &packets_in);
163
164 this->mutex->lock(this->mutex);
165 entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
166 if (entry)
167 {
168 entry->bytes.sent += bytes_out;
169 entry->bytes.received += bytes_in;
170 entry->packets.sent += packets_out;
171 entry->packets.received += packets_in;
172 }
173 this->mutex->unlock(this->mutex);
174 }
175
176 /**
177 * Send a RADIUS message, wait for response
178 */
179 static bool send_message(private_eap_radius_accounting_t *this,
180 radius_message_t *request)
181 {
182 radius_message_t *response;
183 radius_client_t *client;
184 bool ack = FALSE;
185
186 client = eap_radius_create_client();
187 if (client)
188 {
189 response = client->request(client, request);
190 if (response)
191 {
192 ack = response->get_code(response) == RMC_ACCOUNTING_RESPONSE;
193 response->destroy(response);
194 }
195 client->destroy(client);
196 }
197 return ack;
198 }
199
200 /**
201 * Add common IKE_SA parameters to RADIUS account message
202 */
203 static void add_ike_sa_parameters(private_eap_radius_accounting_t *this,
204 radius_message_t *message, ike_sa_t *ike_sa)
205 {
206 enumerator_t *enumerator;
207 host_t *vip, *host;
208 char buf[64];
209 chunk_t data;
210 u_int32_t value;
211
212 /* virtual NAS-Port-Type */
213 value = htonl(5);
214 message->add(message, RAT_NAS_PORT_TYPE, chunk_from_thing(value));
215 /* framed ServiceType */
216 value = htonl(2);
217 message->add(message, RAT_SERVICE_TYPE, chunk_from_thing(value));
218
219 value = htonl(ike_sa->get_unique_id(ike_sa));
220 message->add(message, RAT_NAS_PORT, chunk_from_thing(value));
221 message->add(message, RAT_NAS_PORT_ID,
222 chunk_from_str(ike_sa->get_name(ike_sa)));
223
224 host = ike_sa->get_my_host(ike_sa);
225 data = host->get_address(host);
226 switch (host->get_family(host))
227 {
228 case AF_INET:
229 message->add(message, RAT_NAS_IP_ADDRESS, data);
230 break;
231 case AF_INET6:
232 message->add(message, RAT_NAS_IPV6_ADDRESS, data);
233 default:
234 break;
235 }
236 snprintf(buf, sizeof(buf), this->station_id_fmt, host);
237 message->add(message, RAT_CALLED_STATION_ID, chunk_from_str(buf));
238 host = ike_sa->get_other_host(ike_sa);
239 snprintf(buf, sizeof(buf), this->station_id_fmt, host);
240 message->add(message, RAT_CALLING_STATION_ID, chunk_from_str(buf));
241
242 snprintf(buf, sizeof(buf), "%Y", ike_sa->get_other_eap_id(ike_sa));
243 message->add(message, RAT_USER_NAME, chunk_from_str(buf));
244
245 enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, FALSE);
246 while (enumerator->enumerate(enumerator, &vip))
247 {
248 switch (vip->get_family(vip))
249 {
250 case AF_INET:
251 message->add(message, RAT_FRAMED_IP_ADDRESS,
252 vip->get_address(vip));
253 break;
254 case AF_INET6:
255 /* we currently assign /128 prefixes, only (reserved, length) */
256 data = chunk_from_chars(0, 128);
257 data = chunk_cata("cc", data, vip->get_address(vip));
258 message->add(message, RAT_FRAMED_IPV6_PREFIX, data);
259 break;
260 default:
261 break;
262 }
263 }
264 enumerator->destroy(enumerator);
265 }
266
267 /**
268 * Get an existing or create a new entry from the locked session table
269 */
270 static entry_t* get_or_create_entry(private_eap_radius_accounting_t *this,
271 ike_sa_t *ike_sa)
272 {
273 ike_sa_id_t *id;
274 entry_t *entry;
275 time_t now;
276
277 entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
278 if (!entry)
279 {
280 now = time_monotonic(NULL);
281 id = ike_sa->get_id(ike_sa);
282
283 INIT(entry,
284 .id = id->clone(id),
285 .created = now,
286 .interim = {
287 .last = now,
288 },
289 /* default terminate cause, if none other catched */
290 .cause = ACCT_CAUSE_USER_REQUEST,
291 );
292 snprintf(entry->sid, sizeof(entry->sid), "%u-%u",
293 this->prefix, ike_sa->get_unique_id(ike_sa));
294 this->sessions->put(this->sessions, entry->id, entry);
295 }
296 return entry;
297 }
298
299 /* forward declaration */
300 static void schedule_interim(private_eap_radius_accounting_t *this,
301 entry_t *entry);
302
303 /**
304 * Data passed to send_interim() using callback job
305 */
306 typedef struct {
307 /** reference to radius accounting */
308 private_eap_radius_accounting_t *this;
309 /** IKE_SA identifier to send interim update to */
310 ike_sa_id_t *id;
311 } interim_data_t;
312
313 /**
314 * Clean up interim data
315 */
316 void destroy_interim_data(interim_data_t *this)
317 {
318 this->id->destroy(this->id);
319 free(this);
320 }
321
322 /**
323 * Send an interim update for entry of given IKE_SA identifier
324 */
325 static job_requeue_t send_interim(interim_data_t *data)
326 {
327 private_eap_radius_accounting_t *this = data->this;
328 u_int64_t bytes_in = 0, bytes_out = 0, packets_in = 0, packets_out = 0;
329 u_int64_t bytes, packets;
330 radius_message_t *message = NULL;
331 enumerator_t *enumerator;
332 child_sa_t *child_sa;
333 ike_sa_t *ike_sa;
334 entry_t *entry;
335 u_int32_t value;
336
337 ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, data->id);
338 if (!ike_sa)
339 {
340 return JOB_REQUEUE_NONE;
341 }
342 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
343 while (enumerator->enumerate(enumerator, &child_sa))
344 {
345 child_sa->get_usestats(child_sa, FALSE, NULL, &bytes, &packets);
346 bytes_out += bytes;
347 packets_out += packets;
348 child_sa->get_usestats(child_sa, TRUE, NULL, &bytes, &packets);
349 bytes_in += bytes;
350 packets_in += packets;
351 }
352 enumerator->destroy(enumerator);
353 charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
354
355 /* avoid any races by returning IKE_SA before acquiring lock */
356
357 this->mutex->lock(this->mutex);
358 entry = this->sessions->get(this->sessions, data->id);
359 if (entry)
360 {
361 entry->interim.last = time_monotonic(NULL);
362
363 bytes_in += entry->bytes.received;
364 bytes_out += entry->bytes.sent;
365 packets_in += entry->packets.received;
366 packets_out += entry->packets.sent;
367
368 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
369 value = htonl(ACCT_STATUS_INTERIM_UPDATE);
370 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
371 message->add(message, RAT_ACCT_SESSION_ID,
372 chunk_create(entry->sid, strlen(entry->sid)));
373 add_ike_sa_parameters(this, message, ike_sa);
374
375 value = htonl(bytes_out);
376 message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
377 value = htonl(bytes_out >> 32);
378 if (value)
379 {
380 message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
381 chunk_from_thing(value));
382 }
383 value = htonl(packets_out);
384 message->add(message, RAT_ACCT_OUTPUT_PACKETS, chunk_from_thing(value));
385
386 value = htonl(bytes_in);
387 message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
388 value = htonl(bytes_in >> 32);
389 if (value)
390 {
391 message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
392 chunk_from_thing(value));
393 }
394 value = htonl(packets_in);
395 message->add(message, RAT_ACCT_INPUT_PACKETS, chunk_from_thing(value));
396
397 value = htonl(entry->interim.last - entry->created);
398 message->add(message, RAT_ACCT_SESSION_TIME, chunk_from_thing(value));
399
400 schedule_interim(this, entry);
401 }
402 this->mutex->unlock(this->mutex);
403
404 if (message)
405 {
406 if (!send_message(this, message))
407 {
408 eap_radius_handle_timeout(data->id);
409 }
410 message->destroy(message);
411 }
412 return JOB_REQUEUE_NONE;
413 }
414
415 /**
416 * Schedule interim update for given entry
417 */
418 static void schedule_interim(private_eap_radius_accounting_t *this,
419 entry_t *entry)
420 {
421 if (entry->interim.interval)
422 {
423 interim_data_t *data;
424 timeval_t tv = {
425 .tv_sec = entry->interim.last + entry->interim.interval,
426 };
427
428 INIT(data,
429 .this = this,
430 .id = entry->id->clone(entry->id),
431 );
432 lib->scheduler->schedule_job_tv(lib->scheduler,
433 (job_t*)callback_job_create_with_prio(
434 (callback_job_cb_t)send_interim,
435 data, (void*)destroy_interim_data,
436 (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL), tv);
437 }
438 }
439
440 /**
441 * Send an accounting start message
442 */
443 static void send_start(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
444 {
445 radius_message_t *message;
446 entry_t *entry;
447 u_int32_t value;
448
449 this->mutex->lock(this->mutex);
450
451 entry = get_or_create_entry(this, ike_sa);
452 entry->start_sent = TRUE;
453
454 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
455 value = htonl(ACCT_STATUS_START);
456 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
457 message->add(message, RAT_ACCT_SESSION_ID,
458 chunk_create(entry->sid, strlen(entry->sid)));
459
460 schedule_interim(this, entry);
461 this->mutex->unlock(this->mutex);
462
463 add_ike_sa_parameters(this, message, ike_sa);
464 if (!send_message(this, message))
465 {
466 eap_radius_handle_timeout(ike_sa->get_id(ike_sa));
467 }
468 message->destroy(message);
469 }
470
471 /**
472 * Send an account stop message
473 */
474 static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
475 {
476 radius_message_t *message;
477 entry_t *entry;
478 u_int32_t value;
479
480 this->mutex->lock(this->mutex);
481 entry = this->sessions->remove(this->sessions, ike_sa->get_id(ike_sa));
482 this->mutex->unlock(this->mutex);
483 if (entry)
484 {
485 if (!entry->start_sent)
486 { /* we tried to authenticate this peer, but never sent a start */
487 destroy_entry(entry);
488 return;
489 }
490 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
491 value = htonl(ACCT_STATUS_STOP);
492 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
493 message->add(message, RAT_ACCT_SESSION_ID,
494 chunk_create(entry->sid, strlen(entry->sid)));
495 add_ike_sa_parameters(this, message, ike_sa);
496
497 value = htonl(entry->bytes.sent);
498 message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
499 value = htonl(entry->bytes.sent >> 32);
500 if (value)
501 {
502 message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
503 chunk_from_thing(value));
504 }
505 value = htonl(entry->packets.sent);
506 message->add(message, RAT_ACCT_OUTPUT_PACKETS, chunk_from_thing(value));
507
508 value = htonl(entry->bytes.received);
509 message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
510 value = htonl(entry->bytes.received >> 32);
511 if (value)
512 {
513 message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
514 chunk_from_thing(value));
515 }
516 value = htonl(entry->packets.received);
517 message->add(message, RAT_ACCT_INPUT_PACKETS, chunk_from_thing(value));
518
519 value = htonl(time_monotonic(NULL) - entry->created);
520 message->add(message, RAT_ACCT_SESSION_TIME, chunk_from_thing(value));
521
522
523 value = htonl(entry->cause);
524 message->add(message, RAT_ACCT_TERMINATE_CAUSE, chunk_from_thing(value));
525
526 if (!send_message(this, message))
527 {
528 eap_radius_handle_timeout(NULL);
529 }
530 message->destroy(message);
531 destroy_entry(entry);
532 }
533 }
534
535 METHOD(listener_t, alert, bool,
536 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, alert_t alert,
537 va_list args)
538 {
539 radius_acct_terminate_cause_t cause;
540 entry_t *entry;
541
542 switch (alert)
543 {
544 case ALERT_IKE_SA_EXPIRED:
545 cause = ACCT_CAUSE_SESSION_TIMEOUT;
546 break;
547 case ALERT_RETRANSMIT_SEND_TIMEOUT:
548 cause = ACCT_CAUSE_LOST_SERVICE;
549 break;
550 default:
551 return TRUE;
552 }
553 this->mutex->lock(this->mutex);
554 entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
555 if (entry)
556 {
557 entry->cause = cause;
558 }
559 this->mutex->unlock(this->mutex);
560 return TRUE;
561 }
562
563 METHOD(listener_t, ike_updown, bool,
564 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, bool up)
565 {
566 if (!up)
567 {
568 enumerator_t *enumerator;
569 child_sa_t *child_sa;
570
571 /* update usage for all children just before sending stop */
572 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
573 while (enumerator->enumerate(enumerator, &child_sa))
574 {
575 update_usage(this, ike_sa, child_sa);
576 }
577 enumerator->destroy(enumerator);
578
579 send_stop(this, ike_sa);
580 }
581 return TRUE;
582 }
583
584 METHOD(listener_t, message_hook, bool,
585 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
586 message_t *message, bool incoming, bool plain)
587 {
588 /* start accounting here, virtual IP now is set */
589 if (plain && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED &&
590 !incoming && !message->get_request(message))
591 {
592 if (ike_sa->get_version(ike_sa) == IKEV1 &&
593 message->get_exchange_type(message) == TRANSACTION)
594 {
595 send_start(this, ike_sa);
596 }
597 if (ike_sa->get_version(ike_sa) == IKEV2 &&
598 message->get_exchange_type(message) == IKE_AUTH)
599 {
600 send_start(this, ike_sa);
601 }
602 }
603 return TRUE;
604 }
605
606 METHOD(listener_t, ike_rekey, bool,
607 private_eap_radius_accounting_t *this, ike_sa_t *old, ike_sa_t *new)
608 {
609 entry_t *entry;
610
611 this->mutex->lock(this->mutex);
612 entry = this->sessions->remove(this->sessions, old->get_id(old));
613 if (entry)
614 {
615 /* update IKE_SA identifier */
616 entry->id->destroy(entry->id);
617 entry->id = new->get_id(new);
618 entry->id = entry->id->clone(entry->id);
619 /* fire new interim update job, old gets invalid */
620 schedule_interim(this, entry);
621
622 entry = this->sessions->put(this->sessions, entry->id, entry);
623 if (entry)
624 {
625 destroy_entry(entry);
626 }
627 }
628 this->mutex->unlock(this->mutex);
629
630 return TRUE;
631 }
632
633 METHOD(listener_t, child_rekey, bool,
634 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
635 child_sa_t *old, child_sa_t *new)
636 {
637 update_usage(this, ike_sa, old);
638
639 return TRUE;
640 }
641
642 METHOD(listener_t, child_updown, bool,
643 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
644 child_sa_t *child_sa, bool up)
645 {
646 if (!up && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED)
647 {
648 update_usage(this, ike_sa, child_sa);
649 }
650 return TRUE;
651 }
652
653 METHOD(eap_radius_accounting_t, destroy, void,
654 private_eap_radius_accounting_t *this)
655 {
656 charon->bus->remove_listener(charon->bus, &this->public.listener);
657 singleton = NULL;
658 this->mutex->destroy(this->mutex);
659 this->sessions->destroy(this->sessions);
660 free(this);
661 }
662
663 /**
664 * See header
665 */
666 eap_radius_accounting_t *eap_radius_accounting_create()
667 {
668 private_eap_radius_accounting_t *this;
669
670 INIT(this,
671 .public = {
672 .listener = {
673 .alert = _alert,
674 .ike_updown = _ike_updown,
675 .ike_rekey = _ike_rekey,
676 .message = _message_hook,
677 .child_updown = _child_updown,
678 .child_rekey = _child_rekey,
679 },
680 .destroy = _destroy,
681 },
682 /* use system time as Session ID prefix */
683 .prefix = (u_int32_t)time(NULL),
684 .sessions = hashtable_create((hashtable_hash_t)hash,
685 (hashtable_equals_t)equals, 32),
686 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
687 );
688 if (lib->settings->get_bool(lib->settings,
689 "%s.plugins.eap-radius.station_id_with_port", TRUE, charon->name))
690 {
691 this->station_id_fmt = "%#H";
692 }
693 else
694 {
695 this->station_id_fmt = "%H";
696 }
697 if (lib->settings->get_bool(lib->settings,
698 "%s.plugins.eap-radius.accounting", FALSE, charon->name))
699 {
700 singleton = this;
701 charon->bus->add_listener(charon->bus, &this->public.listener);
702 }
703 return &this->public;
704 }
705
706 /**
707 * See header
708 */
709 void eap_radius_accounting_start_interim(ike_sa_t *ike_sa, u_int32_t interval)
710 {
711 if (singleton)
712 {
713 entry_t *entry;
714
715 DBG1(DBG_CFG, "scheduling RADIUS Interim-Updates every %us", interval);
716 singleton->mutex->lock(singleton->mutex);
717 entry = get_or_create_entry(singleton, ike_sa);
718 entry->interim.interval = interval;
719 singleton->mutex->unlock(singleton->mutex);
720 }
721 }