]> git.ipfire.org Git - thirdparty/strongswan.git/blame - src/libcharon/plugins/eap_radius/eap_radius_accounting.c
eap-radius: Change trigger for Accounting Start messages for IKEv1
[thirdparty/strongswan.git] / src / libcharon / plugins / eap_radius / eap_radius_accounting.c
CommitLineData
0399edef 1/*
7fbe79bc
TB
2 * Copyright (C) 2015 Tobias Brunner
3 * Hochschule fuer Technik Rapperswil
4 *
0399edef
MW
5 * Copyright (C) 2012 Martin Willi
6 * Copyright (C) 2012 revosec AG
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 * for more details.
17 */
18
19#include "eap_radius_accounting.h"
f0f94e2c 20#include "eap_radius_plugin.h"
0399edef
MW
21
22#include <time.h>
23
f0f94e2c
MW
24#include <radius_message.h>
25#include <radius_client.h>
0399edef 26#include <daemon.h>
7fbe79bc 27#include <collections/array.h>
12642a68 28#include <collections/hashtable.h>
0399edef 29#include <threading/mutex.h>
d019764a 30#include <processing/jobs/callback_job.h>
0399edef
MW
31
32typedef struct private_eap_radius_accounting_t private_eap_radius_accounting_t;
33
34/**
35 * Private data of an eap_radius_accounting_t object.
36 */
37struct private_eap_radius_accounting_t {
38
39 /**
40 * Public eap_radius_accounting_t interface.
41 */
42 eap_radius_accounting_t public;
43
44 /**
d019764a 45 * Hashtable with sessions, ike_sa_id_t => entry_t
0399edef
MW
46 */
47 hashtable_t *sessions;
48
49 /**
50 * Mutex to lock sessions
51 */
52 mutex_t *mutex;
53
54 /**
55 * Session ID prefix
56 */
57 u_int32_t prefix;
b2b99e61
MW
58
59 /**
60 * Format string we use for Called/Calling-Station-Id for a host
61 */
62 char *station_id_fmt;
aea7ce3c
MW
63
64 /**
65 * Disable accounting unless IKE_SA has at least one virtual IP
66 */
67 bool acct_req_vip;
0399edef
MW
68};
69
d019764a
MW
70/**
71 * Singleton instance of accounting
72 */
73static private_eap_radius_accounting_t *singleton = NULL;
74
552b8ad5
MW
75/**
76 * Acct-Terminate-Cause
77 */
78typedef enum {
79 ACCT_CAUSE_USER_REQUEST = 1,
80 ACCT_CAUSE_LOST_CARRIER = 2,
81 ACCT_CAUSE_LOST_SERVICE = 3,
82 ACCT_CAUSE_IDLE_TIMEOUT = 4,
83 ACCT_CAUSE_SESSION_TIMEOUT = 5,
84 ACCT_CAUSE_ADMIN_RESET = 6,
85 ACCT_CAUSE_ADMIN_REBOOT = 7,
86 ACCT_CAUSE_PORT_ERROR = 8,
87 ACCT_CAUSE_NAS_ERROR = 9,
88 ACCT_CAUSE_NAS_REQUEST = 10,
89 ACCT_CAUSE_NAS_REBOOT = 11,
90 ACCT_CAUSE_PORT_UNNEEDED = 12,
91 ACCT_CAUSE_PORT_PREEMPTED = 13,
92 ACCT_CAUSE_PORT_SUSPENDED = 14,
93 ACCT_CAUSE_SERVICE_UNAVAILABLE = 15,
94 ACCT_CAUSE_CALLBACK = 16,
95 ACCT_CAUSE_USER_ERROR = 17,
96 ACCT_CAUSE_HOST_REQUEST = 18,
97} radius_acct_terminate_cause_t;
98
7fbe79bc 99/**
2b511240 100 * Usage stats for bytes and packets
7fbe79bc
TB
101 */
102typedef struct {
7fbe79bc
TB
103 struct {
104 u_int64_t sent;
105 u_int64_t received;
106 } bytes, packets;
2b511240
TB
107} usage_t;
108
109/**
110 * Add usage stats (modifies a)
111 */
112static inline void add_usage(usage_t *a, usage_t b)
113{
114 a->bytes.sent += b.bytes.sent;
115 a->bytes.received += b.bytes.received;
116 a->packets.sent += b.packets.sent;
117 a->packets.received += b.packets.received;
118}
119
120/**
121 * Subtract usage stats (modifies a)
122 */
123static inline void sub_usage(usage_t *a, usage_t b)
124{
125 a->bytes.sent -= b.bytes.sent;
126 a->bytes.received -= b.bytes.received;
127 a->packets.sent -= b.packets.sent;
128 a->packets.received -= b.packets.received;
129}
130
131/**
132 * Usage stats for a cached/migrated SAs
133 */
134typedef struct {
135 /** unique CHILD_SA identifier */
136 u_int32_t id;
137 /** usage stats for this SA */
138 usage_t usage;
7fbe79bc
TB
139} sa_entry_t;
140
2b511240
TB
141/**
142 * Clone an sa_entry_t
143 */
144static sa_entry_t *clone_sa(sa_entry_t *sa)
145{
146 sa_entry_t *this;
147
148 INIT(this,
149 .id = sa->id,
150 .usage = sa->usage,
151 );
152 return this;
153}
154
0399edef
MW
155/**
156 * Hashtable entry with usage stats
157 */
158typedef struct {
d019764a
MW
159 /** IKE_SA identifier this entry is stored under */
160 ike_sa_id_t *id;
0399edef 161 /** RADIUS accounting session ID */
c4b63322 162 char sid[24];
7fbe79bc 163 /** number of sent/received octets/packets for expired SAs */
2b511240 164 usage_t usage;
7fbe79bc
TB
165 /** list of cached SAs, sa_entry_t (sorted by their unique ID) */
166 array_t *cached;
2b511240
TB
167 /** list of migrated SAs, sa_entry_t (sorted by their unique ID) */
168 array_t *migrated;
0399edef
MW
169 /** session creation time */
170 time_t created;
552b8ad5
MW
171 /** terminate cause */
172 radius_acct_terminate_cause_t cause;
d019764a
MW
173 /* interim interval and timestamp of last update */
174 struct {
175 u_int32_t interval;
176 time_t last;
177 } interim;
178 /** did we send Accounting-Start */
179 bool start_sent;
0399edef
MW
180} entry_t;
181
d019764a
MW
182/**
183 * Destroy an entry_t
184 */
185static void destroy_entry(entry_t *this)
186{
7fbe79bc 187 array_destroy_function(this->cached, (void*)free, NULL);
2b511240 188 array_destroy_function(this->migrated, (void*)free, NULL);
d019764a
MW
189 this->id->destroy(this->id);
190 free(this);
191}
192
0399edef
MW
193/**
194 * Accounting message status types
195 */
196typedef enum {
197 ACCT_STATUS_START = 1,
198 ACCT_STATUS_STOP = 2,
199 ACCT_STATUS_INTERIM_UPDATE = 3,
200 ACCT_STATUS_ACCOUNTING_ON = 7,
201 ACCT_STATUS_ACCOUNTING_OFF = 8,
202} radius_acct_status_t;
203
204/**
205 * Hashtable hash function
206 */
d019764a 207static u_int hash(ike_sa_id_t *key)
0399edef 208{
d019764a 209 return key->get_responder_spi(key);
0399edef
MW
210}
211
212/**
213 * Hashtable equals function
214 */
d019764a 215static bool equals(ike_sa_id_t *a, ike_sa_id_t *b)
0399edef 216{
d019764a 217 return a->equals(a, b);
0399edef
MW
218}
219
7fbe79bc
TB
220/**
221 * Sort cached SAs
222 */
223static int sa_sort(const void *a, const void *b, void *user)
224{
225 const sa_entry_t *ra = a, *rb = b;
226 return ra->id - rb->id;
227}
228
229/**
230 * Find a cached SA
231 */
232static int sa_find(const void *a, const void *b)
233{
234 return sa_sort(a, b, NULL);
235}
236
2b511240
TB
237/**
238 * Update or create usage counters of a cached SA
239 */
240static void update_sa(entry_t *entry, u_int32_t id, usage_t usage)
241{
242 sa_entry_t *sa, lookup;
243
244 lookup.id = id;
245 if (array_bsearch(entry->cached, &lookup, sa_find, &sa) == -1)
246 {
247 INIT(sa,
248 .id = id,
249 );
250 array_insert_create(&entry->cached, ARRAY_TAIL, sa);
251 array_sort(entry->cached, sa_sort, NULL);
252 }
253 sa->usage = usage;
254}
255
0399edef
MW
256/**
257 * Update usage counter when a CHILD_SA rekeys/goes down
258 */
259static void update_usage(private_eap_radius_accounting_t *this,
260 ike_sa_t *ike_sa, child_sa_t *child_sa)
261{
2b511240 262 usage_t usage;
0399edef
MW
263 entry_t *entry;
264
2b511240
TB
265 child_sa->get_usestats(child_sa, TRUE, NULL, &usage.bytes.received,
266 &usage.packets.received);
267 child_sa->get_usestats(child_sa, FALSE, NULL, &usage.bytes.sent,
268 &usage.packets.sent);
0399edef
MW
269
270 this->mutex->lock(this->mutex);
d019764a 271 entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
0399edef
MW
272 if (entry)
273 {
2b511240
TB
274 update_sa(entry, child_sa->get_unique_id(child_sa), usage);
275 }
276 this->mutex->unlock(this->mutex);
277}
278
279/**
280 * Collect usage stats for all CHILD_SAs of the given IKE_SA, optionally returns
281 * the total number of bytes and packets
282 */
283static array_t *collect_stats(ike_sa_t *ike_sa, usage_t *total)
284{
285 enumerator_t *enumerator;
286 child_sa_t *child_sa;
287 array_t *stats;
288 sa_entry_t *sa;
289 usage_t usage;
290
291 if (total)
292 {
293 *total = (usage_t){};
294 }
295
296 stats = array_create(0, 0);
297 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
298 while (enumerator->enumerate(enumerator, &child_sa))
299 {
300 INIT(sa,
301 .id = child_sa->get_unique_id(child_sa),
302 );
303 array_insert(stats, ARRAY_TAIL, sa);
304 array_sort(stats, sa_sort, NULL);
305
306 child_sa->get_usestats(child_sa, TRUE, NULL, &usage.bytes.received,
307 &usage.packets.received);
308 child_sa->get_usestats(child_sa, FALSE, NULL, &usage.bytes.sent,
309 &usage.packets.sent);
310 sa->usage = usage;
311 if (total)
7fbe79bc 312 {
2b511240 313 add_usage(total, usage);
7fbe79bc 314 }
0399edef 315 }
2b511240
TB
316 enumerator->destroy(enumerator);
317 return stats;
0399edef
MW
318}
319
8dbef6da
TB
320/**
321 * Cleanup cached SAs
322 */
323static void cleanup_sas(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
324 entry_t *entry)
325{
326 enumerator_t *enumerator;
327 child_sa_t *child_sa;
328 sa_entry_t *sa, *found;
329 array_t *sas;
330
331 sas = array_create(0, 0);
332 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
333 while (enumerator->enumerate(enumerator, &child_sa))
334 {
335 INIT(sa,
336 .id = child_sa->get_unique_id(child_sa),
337 );
338 array_insert(sas, ARRAY_TAIL, sa);
339 array_sort(sas, sa_sort, NULL);
340 }
341 enumerator->destroy(enumerator);
342
343 enumerator = array_create_enumerator(entry->cached);
344 while (enumerator->enumerate(enumerator, &sa))
345 {
346 if (array_bsearch(sas, sa, sa_find, &found) == -1)
347 {
348 /* SA is gone, add its latest stats to the total for this IKE_SA
349 * and remove the cache entry */
2b511240 350 add_usage(&entry->usage, sa->usage);
8dbef6da
TB
351 array_remove_at(entry->cached, enumerator);
352 free(sa);
353 }
354 }
355 enumerator->destroy(enumerator);
2b511240
TB
356 enumerator = array_create_enumerator(entry->migrated);
357 while (enumerator->enumerate(enumerator, &sa))
358 {
359 if (array_bsearch(sas, sa, sa_find, &found) == -1)
360 {
361 /* SA is gone, subtract stats from the total for this IKE_SA */
362 sub_usage(&entry->usage, sa->usage);
363 array_remove_at(entry->migrated, enumerator);
364 free(sa);
365 }
366 }
367 enumerator->destroy(enumerator);
8dbef6da
TB
368 array_destroy_function(sas, (void*)free, NULL);
369}
370
0399edef
MW
371/**
372 * Send a RADIUS message, wait for response
373 */
374static bool send_message(private_eap_radius_accounting_t *this,
375 radius_message_t *request)
376{
377 radius_message_t *response;
378 radius_client_t *client;
379 bool ack = FALSE;
380
f0f94e2c 381 client = eap_radius_create_client();
0399edef
MW
382 if (client)
383 {
384 response = client->request(client, request);
385 if (response)
386 {
387 ack = response->get_code(response) == RMC_ACCOUNTING_RESPONSE;
388 response->destroy(response);
389 }
390 client->destroy(client);
391 }
392 return ack;
393}
394
d15ae70c
MW
395/**
396 * Add common IKE_SA parameters to RADIUS account message
397 */
b2b99e61
MW
398static void add_ike_sa_parameters(private_eap_radius_accounting_t *this,
399 radius_message_t *message, ike_sa_t *ike_sa)
d15ae70c 400{
101d26ba 401 enumerator_t *enumerator;
68c12fd9 402 host_t *vip, *host;
fc8ca5f2 403 char buf[MAX_RADIUS_ATTRIBUTE_SIZE + 1];
3a2660f1 404 chunk_t data;
68c12fd9
MW
405 u_int32_t value;
406
407 /* virtual NAS-Port-Type */
408 value = htonl(5);
409 message->add(message, RAT_NAS_PORT_TYPE, chunk_from_thing(value));
410 /* framed ServiceType */
411 value = htonl(2);
412 message->add(message, RAT_SERVICE_TYPE, chunk_from_thing(value));
413
414 value = htonl(ike_sa->get_unique_id(ike_sa));
415 message->add(message, RAT_NAS_PORT, chunk_from_thing(value));
416 message->add(message, RAT_NAS_PORT_ID,
417 chunk_from_str(ike_sa->get_name(ike_sa)));
418
419 host = ike_sa->get_my_host(ike_sa);
420 data = host->get_address(host);
421 switch (host->get_family(host))
422 {
423 case AF_INET:
424 message->add(message, RAT_NAS_IP_ADDRESS, data);
425 break;
426 case AF_INET6:
427 message->add(message, RAT_NAS_IPV6_ADDRESS, data);
428 default:
429 break;
430 }
b2b99e61 431 snprintf(buf, sizeof(buf), this->station_id_fmt, host);
68c12fd9
MW
432 message->add(message, RAT_CALLED_STATION_ID, chunk_from_str(buf));
433 host = ike_sa->get_other_host(ike_sa);
b2b99e61 434 snprintf(buf, sizeof(buf), this->station_id_fmt, host);
68c12fd9 435 message->add(message, RAT_CALLING_STATION_ID, chunk_from_str(buf));
d15ae70c
MW
436
437 snprintf(buf, sizeof(buf), "%Y", ike_sa->get_other_eap_id(ike_sa));
68c12fd9 438 message->add(message, RAT_USER_NAME, chunk_from_str(buf));
101d26ba
MW
439
440 enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, FALSE);
441 while (enumerator->enumerate(enumerator, &vip))
3a2660f1 442 {
101d26ba
MW
443 switch (vip->get_family(vip))
444 {
445 case AF_INET:
446 message->add(message, RAT_FRAMED_IP_ADDRESS,
447 vip->get_address(vip));
448 break;
449 case AF_INET6:
450 /* we currently assign /128 prefixes, only (reserved, length) */
451 data = chunk_from_chars(0, 128);
452 data = chunk_cata("cc", data, vip->get_address(vip));
453 message->add(message, RAT_FRAMED_IPV6_PREFIX, data);
454 break;
455 default:
456 break;
457 }
3a2660f1 458 }
37095ce1 459 enumerator->destroy(enumerator);
d15ae70c
MW
460}
461
d019764a
MW
462/**
463 * Get an existing or create a new entry from the locked session table
464 */
465static entry_t* get_or_create_entry(private_eap_radius_accounting_t *this,
2b511240 466 ike_sa_id_t *id, u_int32_t unique)
d019764a 467{
d019764a
MW
468 entry_t *entry;
469 time_t now;
470
2b511240 471 entry = this->sessions->get(this->sessions, id);
d019764a
MW
472 if (!entry)
473 {
474 now = time_monotonic(NULL);
d019764a
MW
475
476 INIT(entry,
477 .id = id->clone(id),
478 .created = now,
479 .interim = {
480 .last = now,
481 },
482 /* default terminate cause, if none other catched */
483 .cause = ACCT_CAUSE_USER_REQUEST,
484 );
2b511240 485 snprintf(entry->sid, sizeof(entry->sid), "%u-%u", this->prefix, unique);
d019764a
MW
486 this->sessions->put(this->sessions, entry->id, entry);
487 }
488 return entry;
489}
490
491/* forward declaration */
492static void schedule_interim(private_eap_radius_accounting_t *this,
493 entry_t *entry);
494
495/**
496 * Data passed to send_interim() using callback job
497 */
498typedef struct {
499 /** reference to radius accounting */
500 private_eap_radius_accounting_t *this;
501 /** IKE_SA identifier to send interim update to */
502 ike_sa_id_t *id;
503} interim_data_t;
504
505/**
506 * Clean up interim data
507 */
508void destroy_interim_data(interim_data_t *this)
509{
510 this->id->destroy(this->id);
511 free(this);
512}
513
514/**
515 * Send an interim update for entry of given IKE_SA identifier
516 */
517static job_requeue_t send_interim(interim_data_t *data)
518{
519 private_eap_radius_accounting_t *this = data->this;
2b511240 520 usage_t usage;
d019764a
MW
521 radius_message_t *message = NULL;
522 enumerator_t *enumerator;
d019764a
MW
523 ike_sa_t *ike_sa;
524 entry_t *entry;
525 u_int32_t value;
7fbe79bc
TB
526 array_t *stats;
527 sa_entry_t *sa, *found;
d019764a
MW
528
529 ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, data->id);
530 if (!ike_sa)
531 {
532 return JOB_REQUEUE_NONE;
533 }
2b511240 534 stats = collect_stats(ike_sa, &usage);
d019764a
MW
535 charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
536
537 /* avoid any races by returning IKE_SA before acquiring lock */
538
539 this->mutex->lock(this->mutex);
540 entry = this->sessions->get(this->sessions, data->id);
541 if (entry)
542 {
543 entry->interim.last = time_monotonic(NULL);
544
7fbe79bc
TB
545 enumerator = array_create_enumerator(entry->cached);
546 while (enumerator->enumerate(enumerator, &sa))
547 {
548 if (array_bsearch(stats, sa, sa_find, &found) != -1)
549 {
550 /* SA is still around, update stats (e.g. for IKEv1 where
551 * SA might get used even after rekeying) */
2b511240 552 sa->usage = found->usage;
7fbe79bc
TB
553 }
554 else
555 {
2b511240 556 /* SA is gone, add its last stats to the total for this IKE_SA
7fbe79bc 557 * and remove the cache entry */
2b511240 558 add_usage(&entry->usage, sa->usage);
7fbe79bc
TB
559 array_remove_at(entry->cached, enumerator);
560 free(sa);
561 }
562 }
563 enumerator->destroy(enumerator);
564
2b511240
TB
565 enumerator = array_create_enumerator(entry->migrated);
566 while (enumerator->enumerate(enumerator, &sa))
567 {
568 if (array_bsearch(stats, sa, sa_find, &found) != -1)
569 {
570 /* SA is still around, but we have to compensate */
571 sub_usage(&usage, sa->usage);
572 }
573 else
574 {
575 /* SA is gone, subtract stats from the total for this IKE_SA */
576 sub_usage(&entry->usage, sa->usage);
577 array_remove_at(entry->migrated, enumerator);
578 free(sa);
579 }
580 }
581 enumerator->destroy(enumerator);
582
583 add_usage(&usage, entry->usage);
d019764a
MW
584
585 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
586 value = htonl(ACCT_STATUS_INTERIM_UPDATE);
587 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
588 message->add(message, RAT_ACCT_SESSION_ID,
589 chunk_create(entry->sid, strlen(entry->sid)));
b2b99e61 590 add_ike_sa_parameters(this, message, ike_sa);
d019764a 591
2b511240 592 value = htonl(usage.bytes.sent);
d019764a 593 message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
2b511240 594 value = htonl(usage.bytes.sent >> 32);
d019764a
MW
595 if (value)
596 {
597 message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
598 chunk_from_thing(value));
599 }
2b511240 600 value = htonl(usage.packets.sent);
d019764a
MW
601 message->add(message, RAT_ACCT_OUTPUT_PACKETS, chunk_from_thing(value));
602
2b511240 603 value = htonl(usage.bytes.received);
d019764a 604 message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
2b511240 605 value = htonl(usage.bytes.received >> 32);
d019764a
MW
606 if (value)
607 {
608 message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
609 chunk_from_thing(value));
610 }
2b511240 611 value = htonl(usage.packets.received);
d019764a
MW
612 message->add(message, RAT_ACCT_INPUT_PACKETS, chunk_from_thing(value));
613
614 value = htonl(entry->interim.last - entry->created);
615 message->add(message, RAT_ACCT_SESSION_TIME, chunk_from_thing(value));
616
617 schedule_interim(this, entry);
618 }
619 this->mutex->unlock(this->mutex);
7fbe79bc 620 array_destroy_function(stats, (void*)free, NULL);
d019764a
MW
621
622 if (message)
623 {
624 if (!send_message(this, message))
625 {
00b91c43
TB
626 if (lib->settings->get_bool(lib->settings,
627 "%s.plugins.eap-radius.accounting_close_on_timeout",
628 TRUE, lib->ns))
629 {
630 eap_radius_handle_timeout(data->id);
631 }
d019764a
MW
632 }
633 message->destroy(message);
634 }
635 return JOB_REQUEUE_NONE;
636}
637
638/**
639 * Schedule interim update for given entry
640 */
641static void schedule_interim(private_eap_radius_accounting_t *this,
642 entry_t *entry)
643{
644 if (entry->interim.interval)
645 {
646 interim_data_t *data;
647 timeval_t tv = {
648 .tv_sec = entry->interim.last + entry->interim.interval,
649 };
650
651 INIT(data,
652 .this = this,
653 .id = entry->id->clone(entry->id),
654 );
655 lib->scheduler->schedule_job_tv(lib->scheduler,
656 (job_t*)callback_job_create_with_prio(
657 (callback_job_cb_t)send_interim,
658 data, (void*)destroy_interim_data,
659 (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL), tv);
660 }
661}
662
aea7ce3c
MW
663/**
664 * Check if an IKE_SA has assigned a virtual IP (to peer)
665 */
666static bool has_vip(ike_sa_t *ike_sa)
667{
668 enumerator_t *enumerator;
669 host_t *host;
670 bool found;
671
672 enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, FALSE);
673 found = enumerator->enumerate(enumerator, &host);
674 enumerator->destroy(enumerator);
675
676 return found;
677}
678
0399edef
MW
679/**
680 * Send an accounting start message
681 */
682static void send_start(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
683{
0399edef 684 radius_message_t *message;
0399edef 685 entry_t *entry;
d019764a 686 u_int32_t value;
0399edef 687
aea7ce3c
MW
688 if (this->acct_req_vip && !has_vip(ike_sa))
689 {
690 return;
691 }
692
d019764a
MW
693 this->mutex->lock(this->mutex);
694
2b511240
TB
695 entry = get_or_create_entry(this, ike_sa->get_id(ike_sa),
696 ike_sa->get_unique_id(ike_sa));
d019764a 697 entry->start_sent = TRUE;
0399edef 698
3bc18292 699 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
0399edef
MW
700 value = htonl(ACCT_STATUS_START);
701 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
702 message->add(message, RAT_ACCT_SESSION_ID,
703 chunk_create(entry->sid, strlen(entry->sid)));
d019764a 704
3633b801
TB
705 if (!entry->interim.interval)
706 {
707 entry->interim.interval = lib->settings->get_time(lib->settings,
708 "%s.plugins.eap-radius.accounting_interval", 0, lib->ns);
709 if (entry->interim.interval)
710 {
711 DBG1(DBG_CFG, "scheduling RADIUS Interim-Updates every %us",
712 entry->interim.interval);
713 }
714 }
d019764a
MW
715 schedule_interim(this, entry);
716 this->mutex->unlock(this->mutex);
717
b2b99e61 718 add_ike_sa_parameters(this, message, ike_sa);
d019764a 719 if (!send_message(this, message))
1ba1cd0c
MW
720 {
721 eap_radius_handle_timeout(ike_sa->get_id(ike_sa));
722 }
0399edef
MW
723 message->destroy(message);
724}
725
726/**
727 * Send an account stop message
728 */
729static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
730{
731 radius_message_t *message;
7fbe79bc 732 enumerator_t *enumerator;
0399edef 733 entry_t *entry;
7fbe79bc 734 sa_entry_t *sa;
e8526ae9 735 u_int32_t value;
0399edef 736
0399edef 737 this->mutex->lock(this->mutex);
d019764a 738 entry = this->sessions->remove(this->sessions, ike_sa->get_id(ike_sa));
0399edef
MW
739 this->mutex->unlock(this->mutex);
740 if (entry)
741 {
d019764a
MW
742 if (!entry->start_sent)
743 { /* we tried to authenticate this peer, but never sent a start */
744 destroy_entry(entry);
745 return;
746 }
7fbe79bc
TB
747 enumerator = array_create_enumerator(entry->cached);
748 while (enumerator->enumerate(enumerator, &sa))
749 {
2b511240
TB
750 add_usage(&entry->usage, sa->usage);
751 }
752 enumerator->destroy(enumerator);
753
754 enumerator = array_create_enumerator(entry->migrated);
755 while (enumerator->enumerate(enumerator, &sa))
756 {
757 sub_usage(&entry->usage, sa->usage);
7fbe79bc
TB
758 }
759 enumerator->destroy(enumerator);
760
3bc18292 761 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
0399edef
MW
762 value = htonl(ACCT_STATUS_STOP);
763 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
764 message->add(message, RAT_ACCT_SESSION_ID,
765 chunk_create(entry->sid, strlen(entry->sid)));
b2b99e61 766 add_ike_sa_parameters(this, message, ike_sa);
b4568ca2 767
2b511240 768 value = htonl(entry->usage.bytes.sent);
0399edef 769 message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
2b511240 770 value = htonl(entry->usage.bytes.sent >> 32);
0399edef
MW
771 if (value)
772 {
773 message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
774 chunk_from_thing(value));
775 }
2b511240 776 value = htonl(entry->usage.packets.sent);
b4568ca2
MW
777 message->add(message, RAT_ACCT_OUTPUT_PACKETS, chunk_from_thing(value));
778
2b511240 779 value = htonl(entry->usage.bytes.received);
0399edef 780 message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
2b511240 781 value = htonl(entry->usage.bytes.received >> 32);
0399edef
MW
782 if (value)
783 {
784 message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
785 chunk_from_thing(value));
786 }
2b511240 787 value = htonl(entry->usage.packets.received);
b4568ca2
MW
788 message->add(message, RAT_ACCT_INPUT_PACKETS, chunk_from_thing(value));
789
0399edef
MW
790 value = htonl(time_monotonic(NULL) - entry->created);
791 message->add(message, RAT_ACCT_SESSION_TIME, chunk_from_thing(value));
792
552b8ad5
MW
793
794 value = htonl(entry->cause);
795 message->add(message, RAT_ACCT_TERMINATE_CAUSE, chunk_from_thing(value));
796
1ba1cd0c
MW
797 if (!send_message(this, message))
798 {
799 eap_radius_handle_timeout(NULL);
800 }
0399edef 801 message->destroy(message);
d019764a 802 destroy_entry(entry);
0399edef
MW
803 }
804}
805
552b8ad5
MW
806METHOD(listener_t, alert, bool,
807 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, alert_t alert,
808 va_list args)
809{
810 radius_acct_terminate_cause_t cause;
811 entry_t *entry;
812
813 switch (alert)
814 {
815 case ALERT_IKE_SA_EXPIRED:
816 cause = ACCT_CAUSE_SESSION_TIMEOUT;
817 break;
818 case ALERT_RETRANSMIT_SEND_TIMEOUT:
819 cause = ACCT_CAUSE_LOST_SERVICE;
820 break;
821 default:
822 return TRUE;
823 }
824 this->mutex->lock(this->mutex);
d019764a 825 entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
552b8ad5
MW
826 if (entry)
827 {
828 entry->cause = cause;
829 }
830 this->mutex->unlock(this->mutex);
831 return TRUE;
832}
833
0399edef
MW
834METHOD(listener_t, ike_updown, bool,
835 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, bool up)
836{
837 if (!up)
838 {
32dc2b02
MW
839 enumerator_t *enumerator;
840 child_sa_t *child_sa;
841
842 /* update usage for all children just before sending stop */
843 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
844 while (enumerator->enumerate(enumerator, &child_sa))
845 {
846 update_usage(this, ike_sa, child_sa);
847 }
848 enumerator->destroy(enumerator);
849
0399edef
MW
850 send_stop(this, ike_sa);
851 }
852 return TRUE;
853}
854
855METHOD(listener_t, message_hook, bool,
856 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
b1f2f05c 857 message_t *message, bool incoming, bool plain)
0399edef
MW
858{
859 /* start accounting here, virtual IP now is set */
b1f2f05c 860 if (plain && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED &&
0399edef
MW
861 !incoming && !message->get_request(message))
862 {
cf85ebbf
MW
863 if (ike_sa->get_version(ike_sa) == IKEV2 &&
864 message->get_exchange_type(message) == IKE_AUTH)
865 {
866 send_start(this, ike_sa);
867 }
0399edef
MW
868 }
869 return TRUE;
870}
871
186d25cb
TB
872METHOD(listener_t, assign_vips, bool,
873 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, bool assign)
874{
875 /* start accounting as soon as the virtual IP is set */
876 if (assign && ike_sa->get_version(ike_sa) == IKEV1)
877 {
878 send_start(this, ike_sa);
879 }
880 return TRUE;
881}
882
df75cc5c
MW
883METHOD(listener_t, ike_rekey, bool,
884 private_eap_radius_accounting_t *this, ike_sa_t *old, ike_sa_t *new)
885{
886 entry_t *entry;
887
888 this->mutex->lock(this->mutex);
d019764a 889 entry = this->sessions->remove(this->sessions, old->get_id(old));
df75cc5c
MW
890 if (entry)
891 {
d019764a
MW
892 /* update IKE_SA identifier */
893 entry->id->destroy(entry->id);
894 entry->id = new->get_id(new);
895 entry->id = entry->id->clone(entry->id);
896 /* fire new interim update job, old gets invalid */
897 schedule_interim(this, entry);
898
8dbef6da
TB
899 cleanup_sas(this, new, entry);
900
d019764a 901 entry = this->sessions->put(this->sessions, entry->id, entry);
df75cc5c
MW
902 if (entry)
903 {
d019764a 904 destroy_entry(entry);
df75cc5c
MW
905 }
906 }
907 this->mutex->unlock(this->mutex);
908
909 return TRUE;
910}
911
0399edef
MW
912METHOD(listener_t, child_rekey, bool,
913 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
914 child_sa_t *old, child_sa_t *new)
915{
8dbef6da
TB
916 entry_t *entry;
917
0399edef 918 update_usage(this, ike_sa, old);
8dbef6da
TB
919 this->mutex->lock(this->mutex);
920 entry = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
921 if (entry)
922 {
923 cleanup_sas(this, ike_sa, entry);
924 }
925 this->mutex->unlock(this->mutex);
0399edef
MW
926 return TRUE;
927}
928
2b511240
TB
929METHOD(listener_t, children_migrate, bool,
930 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, ike_sa_id_t *new,
931 u_int32_t unique)
932{
933 enumerator_t *enumerator;
934 sa_entry_t *sa, *sa_new, *cached;
935 entry_t *entry_old, *entry_new;
936 array_t *stats;
937
938 if (!new)
939 {
940 return TRUE;
941 }
942 stats = collect_stats(ike_sa, NULL);
943 this->mutex->lock(this->mutex);
944 entry_old = this->sessions->get(this->sessions, ike_sa->get_id(ike_sa));
945 if (entry_old)
946 {
947 entry_new = get_or_create_entry(this, new, unique);
948 enumerator = array_create_enumerator(stats);
949 while (enumerator->enumerate(enumerator, &sa))
950 {
951 /* if the SA was already rekeyed/cached we cache it too on the new
952 * SA to track it properly until it's finally gone */
953 if (array_bsearch(entry_old->cached, sa, sa_find, &cached) != -1)
954 {
955 sa_new = clone_sa(sa);
956 array_insert_create(&entry_new->cached, ARRAY_TAIL, sa_new);
957 array_sort(entry_new->cached, sa_sort, NULL);
958 }
959 /* if the SA was used, we store it to compensate on the new SA */
960 if (sa->usage.bytes.sent || sa->usage.bytes.received ||
961 sa->usage.packets.sent || sa->usage.packets.received)
962 {
963 sa_new = clone_sa(sa);
964 array_insert_create(&entry_new->migrated, ARRAY_TAIL, sa_new);
965 array_sort(entry_new->migrated, sa_sort, NULL);
966 /* store/update latest stats on old SA to report in Stop */
967 update_sa(entry_old, sa->id, sa->usage);
968 }
969 }
970 enumerator->destroy(enumerator);
971 }
972 this->mutex->unlock(this->mutex);
973 array_destroy_function(stats, (void*)free, NULL);
974 return TRUE;
975}
976
0399edef
MW
977METHOD(listener_t, child_updown, bool,
978 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
979 child_sa_t *child_sa, bool up)
980{
32dc2b02 981 if (!up && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED)
0399edef
MW
982 {
983 update_usage(this, ike_sa, child_sa);
984 }
985 return TRUE;
986}
987
988METHOD(eap_radius_accounting_t, destroy, void,
989 private_eap_radius_accounting_t *this)
990{
e813d218 991 charon->bus->remove_listener(charon->bus, &this->public.listener);
d019764a 992 singleton = NULL;
0399edef
MW
993 this->mutex->destroy(this->mutex);
994 this->sessions->destroy(this->sessions);
995 free(this);
996}
997
998/**
999 * See header
1000 */
1001eap_radius_accounting_t *eap_radius_accounting_create()
1002{
1003 private_eap_radius_accounting_t *this;
1004
1005 INIT(this,
1006 .public = {
1007 .listener = {
552b8ad5 1008 .alert = _alert,
0399edef 1009 .ike_updown = _ike_updown,
df75cc5c 1010 .ike_rekey = _ike_rekey,
0399edef 1011 .message = _message_hook,
186d25cb 1012 .assign_vips = _assign_vips,
0399edef
MW
1013 .child_updown = _child_updown,
1014 .child_rekey = _child_rekey,
2b511240 1015 .children_migrate = _children_migrate,
0399edef
MW
1016 },
1017 .destroy = _destroy,
1018 },
1019 /* use system time as Session ID prefix */
1020 .prefix = (u_int32_t)time(NULL),
1021 .sessions = hashtable_create((hashtable_hash_t)hash,
1022 (hashtable_equals_t)equals, 32),
1023 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
1024 );
b2b99e61 1025 if (lib->settings->get_bool(lib->settings,
d223fe80 1026 "%s.plugins.eap-radius.station_id_with_port", TRUE, lib->ns))
b2b99e61
MW
1027 {
1028 this->station_id_fmt = "%#H";
1029 }
1030 else
1031 {
1032 this->station_id_fmt = "%H";
1033 }
e813d218 1034 if (lib->settings->get_bool(lib->settings,
d223fe80 1035 "%s.plugins.eap-radius.accounting", FALSE, lib->ns))
e813d218
MW
1036 {
1037 singleton = this;
1038 charon->bus->add_listener(charon->bus, &this->public.listener);
1039 }
aea7ce3c
MW
1040 this->acct_req_vip = lib->settings->get_bool(lib->settings,
1041 "%s.plugins.eap-radius.accounting_requires_vip",
d223fe80 1042 FALSE, lib->ns);
aea7ce3c 1043
0399edef
MW
1044 return &this->public;
1045}
d019764a
MW
1046
1047/**
1048 * See header
1049 */
1050void eap_radius_accounting_start_interim(ike_sa_t *ike_sa, u_int32_t interval)
1051{
1052 if (singleton)
1053 {
1054 entry_t *entry;
1055
1056 DBG1(DBG_CFG, "scheduling RADIUS Interim-Updates every %us", interval);
1057 singleton->mutex->lock(singleton->mutex);
2b511240
TB
1058 entry = get_or_create_entry(singleton, ike_sa->get_id(ike_sa),
1059 ike_sa->get_unique_id(ike_sa));
d019764a
MW
1060 entry->interim.interval = interval;
1061 singleton->mutex->unlock(singleton->mutex);
1062 }
1063}