]>
Commit | Line | Data |
---|---|---|
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 | |
32 | typedef struct private_eap_radius_accounting_t private_eap_radius_accounting_t; | |
33 | ||
34 | /** | |
35 | * Private data of an eap_radius_accounting_t object. | |
36 | */ | |
37 | struct 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 | */ | |
73 | static private_eap_radius_accounting_t *singleton = NULL; | |
74 | ||
552b8ad5 MW |
75 | /** |
76 | * Acct-Terminate-Cause | |
77 | */ | |
78 | typedef 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 | */ |
102 | typedef 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 | */ | |
112 | static 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 | */ | |
123 | static 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 | */ | |
134 | typedef 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 | */ | |
144 | static 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 | */ | |
158 | typedef 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 | */ | |
185 | static 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 | */ | |
196 | typedef 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 | 207 | static 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 | 215 | static 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 | */ | |
223 | static 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 | */ | |
232 | static 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 | */ | |
240 | static 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 | */ | |
259 | static 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 | */ | |
283 | static 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 | */ | |
323 | static 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 | */ | |
374 | static 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 |
398 | static 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 | */ | |
465 | static 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 */ | |
492 | static 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 | */ | |
498 | typedef 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 | */ | |
508 | void 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 | */ | |
517 | static 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 | */ | |
641 | static 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 | */ | |
666 | static 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 | */ | |
682 | static 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 | */ | |
729 | static 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 |
806 | METHOD(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 |
834 | METHOD(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 | ||
855 | METHOD(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 |
872 | METHOD(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 |
883 | METHOD(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 |
912 | METHOD(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 |
929 | METHOD(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 |
977 | METHOD(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 | ||
988 | METHOD(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 | */ | |
1001 | eap_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 | */ | |
1050 | void 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 | } |