Send Acct-Terminate-Cause based on some alerts catched on the bus
[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
27 typedef struct private_eap_radius_accounting_t private_eap_radius_accounting_t;
28
29 /**
30 * Private data of an eap_radius_accounting_t object.
31 */
32 struct private_eap_radius_accounting_t {
33
34 /**
35 * Public eap_radius_accounting_t interface.
36 */
37 eap_radius_accounting_t public;
38
39 /**
40 * Hashtable with sessions, IKE_SA unique id => entry_t
41 */
42 hashtable_t *sessions;
43
44 /**
45 * Mutex to lock sessions
46 */
47 mutex_t *mutex;
48
49 /**
50 * Session ID prefix
51 */
52 u_int32_t prefix;
53 };
54
55 /**
56 * Acct-Terminate-Cause
57 */
58 typedef enum {
59 ACCT_CAUSE_USER_REQUEST = 1,
60 ACCT_CAUSE_LOST_CARRIER = 2,
61 ACCT_CAUSE_LOST_SERVICE = 3,
62 ACCT_CAUSE_IDLE_TIMEOUT = 4,
63 ACCT_CAUSE_SESSION_TIMEOUT = 5,
64 ACCT_CAUSE_ADMIN_RESET = 6,
65 ACCT_CAUSE_ADMIN_REBOOT = 7,
66 ACCT_CAUSE_PORT_ERROR = 8,
67 ACCT_CAUSE_NAS_ERROR = 9,
68 ACCT_CAUSE_NAS_REQUEST = 10,
69 ACCT_CAUSE_NAS_REBOOT = 11,
70 ACCT_CAUSE_PORT_UNNEEDED = 12,
71 ACCT_CAUSE_PORT_PREEMPTED = 13,
72 ACCT_CAUSE_PORT_SUSPENDED = 14,
73 ACCT_CAUSE_SERVICE_UNAVAILABLE = 15,
74 ACCT_CAUSE_CALLBACK = 16,
75 ACCT_CAUSE_USER_ERROR = 17,
76 ACCT_CAUSE_HOST_REQUEST = 18,
77 } radius_acct_terminate_cause_t;
78
79 /**
80 * Hashtable entry with usage stats
81 */
82 typedef struct {
83 /** RADIUS accounting session ID */
84 char sid[16];
85 /** number of sent/received octets/packets */
86 struct {
87 u_int64_t sent;
88 u_int64_t received;
89 } bytes, packets;
90 /** session creation time */
91 time_t created;
92 /** terminate cause */
93 radius_acct_terminate_cause_t cause;
94 } entry_t;
95
96 /**
97 * Accounting message status types
98 */
99 typedef enum {
100 ACCT_STATUS_START = 1,
101 ACCT_STATUS_STOP = 2,
102 ACCT_STATUS_INTERIM_UPDATE = 3,
103 ACCT_STATUS_ACCOUNTING_ON = 7,
104 ACCT_STATUS_ACCOUNTING_OFF = 8,
105 } radius_acct_status_t;
106
107 /**
108 * Hashtable hash function
109 */
110 static u_int hash(uintptr_t key)
111 {
112 return key;
113 }
114
115 /**
116 * Hashtable equals function
117 */
118 static bool equals(uintptr_t a, uintptr_t b)
119 {
120 return a == b;
121 }
122
123 /**
124 * Update usage counter when a CHILD_SA rekeys/goes down
125 */
126 static void update_usage(private_eap_radius_accounting_t *this,
127 ike_sa_t *ike_sa, child_sa_t *child_sa)
128 {
129 u_int64_t bytes_in, bytes_out, packets_in, packets_out;
130 entry_t *entry;
131
132 child_sa->get_usestats(child_sa, FALSE, NULL, &bytes_out, &packets_out);
133 child_sa->get_usestats(child_sa, TRUE, NULL, &bytes_in, &packets_in);
134
135 this->mutex->lock(this->mutex);
136 entry = this->sessions->get(this->sessions,
137 (void*)(uintptr_t)ike_sa->get_unique_id(ike_sa));
138 if (entry)
139 {
140 entry->bytes.sent += bytes_out;
141 entry->bytes.received += bytes_in;
142 entry->packets.sent += packets_out;
143 entry->packets.received += packets_in;
144 }
145 this->mutex->unlock(this->mutex);
146 }
147
148 /**
149 * Send a RADIUS message, wait for response
150 */
151 static bool send_message(private_eap_radius_accounting_t *this,
152 radius_message_t *request)
153 {
154 radius_message_t *response;
155 radius_client_t *client;
156 bool ack = FALSE;
157
158 client = eap_radius_create_client();
159 if (client)
160 {
161 response = client->request(client, request);
162 if (response)
163 {
164 ack = response->get_code(response) == RMC_ACCOUNTING_RESPONSE;
165 response->destroy(response);
166 }
167 else
168 {
169 charon->bus->alert(charon->bus, ALERT_RADIUS_NOT_RESPONDING);
170 }
171 client->destroy(client);
172 }
173 return ack;
174 }
175
176 /**
177 * Add common IKE_SA parameters to RADIUS account message
178 */
179 static void add_ike_sa_parameters(radius_message_t *message, ike_sa_t *ike_sa)
180 {
181 enumerator_t *enumerator;
182 host_t *vip, *host;
183 char buf[64];
184 chunk_t data;
185 u_int32_t value;
186
187 /* virtual NAS-Port-Type */
188 value = htonl(5);
189 message->add(message, RAT_NAS_PORT_TYPE, chunk_from_thing(value));
190 /* framed ServiceType */
191 value = htonl(2);
192 message->add(message, RAT_SERVICE_TYPE, chunk_from_thing(value));
193
194 value = htonl(ike_sa->get_unique_id(ike_sa));
195 message->add(message, RAT_NAS_PORT, chunk_from_thing(value));
196 message->add(message, RAT_NAS_PORT_ID,
197 chunk_from_str(ike_sa->get_name(ike_sa)));
198
199 host = ike_sa->get_my_host(ike_sa);
200 data = host->get_address(host);
201 switch (host->get_family(host))
202 {
203 case AF_INET:
204 message->add(message, RAT_NAS_IP_ADDRESS, data);
205 break;
206 case AF_INET6:
207 message->add(message, RAT_NAS_IPV6_ADDRESS, data);
208 default:
209 break;
210 }
211 snprintf(buf, sizeof(buf), "%#H", host);
212 message->add(message, RAT_CALLED_STATION_ID, chunk_from_str(buf));
213 host = ike_sa->get_other_host(ike_sa);
214 snprintf(buf, sizeof(buf), "%#H", host);
215 message->add(message, RAT_CALLING_STATION_ID, chunk_from_str(buf));
216
217 snprintf(buf, sizeof(buf), "%Y", ike_sa->get_other_eap_id(ike_sa));
218 message->add(message, RAT_USER_NAME, chunk_from_str(buf));
219
220 enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, FALSE);
221 while (enumerator->enumerate(enumerator, &vip))
222 {
223 switch (vip->get_family(vip))
224 {
225 case AF_INET:
226 message->add(message, RAT_FRAMED_IP_ADDRESS,
227 vip->get_address(vip));
228 break;
229 case AF_INET6:
230 /* we currently assign /128 prefixes, only (reserved, length) */
231 data = chunk_from_chars(0, 128);
232 data = chunk_cata("cc", data, vip->get_address(vip));
233 message->add(message, RAT_FRAMED_IPV6_PREFIX, data);
234 break;
235 default:
236 break;
237 }
238 }
239 enumerator->destroy(enumerator);
240 }
241
242 /**
243 * Send an accounting start message
244 */
245 static void send_start(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
246 {
247 radius_message_t *message;
248 entry_t *entry;
249 u_int32_t id, value;
250
251 id = ike_sa->get_unique_id(ike_sa);
252 INIT(entry,
253 .created = time_monotonic(NULL),
254 /* default terminate cause, if none other catched */
255 .cause = ACCT_CAUSE_USER_REQUEST,
256 );
257 snprintf(entry->sid, sizeof(entry->sid), "%u-%u", this->prefix, id);
258
259 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
260 value = htonl(ACCT_STATUS_START);
261 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
262 message->add(message, RAT_ACCT_SESSION_ID,
263 chunk_create(entry->sid, strlen(entry->sid)));
264 add_ike_sa_parameters(message, ike_sa);
265 if (send_message(this, message))
266 {
267 this->mutex->lock(this->mutex);
268 entry = this->sessions->put(this->sessions, (void*)(uintptr_t)id, entry);
269 this->mutex->unlock(this->mutex);
270 }
271 message->destroy(message);
272 free(entry);
273 }
274
275 /**
276 * Send an account stop message
277 */
278 static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
279 {
280 radius_message_t *message;
281 entry_t *entry;
282 u_int32_t id, value;
283
284 id = ike_sa->get_unique_id(ike_sa);
285 this->mutex->lock(this->mutex);
286 entry = this->sessions->remove(this->sessions, (void*)(uintptr_t)id);
287 this->mutex->unlock(this->mutex);
288 if (entry)
289 {
290 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
291 value = htonl(ACCT_STATUS_STOP);
292 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
293 message->add(message, RAT_ACCT_SESSION_ID,
294 chunk_create(entry->sid, strlen(entry->sid)));
295 add_ike_sa_parameters(message, ike_sa);
296
297 value = htonl(entry->bytes.sent);
298 message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
299 value = htonl(entry->bytes.sent >> 32);
300 if (value)
301 {
302 message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
303 chunk_from_thing(value));
304 }
305 value = htonl(entry->packets.sent);
306 message->add(message, RAT_ACCT_OUTPUT_PACKETS, chunk_from_thing(value));
307
308 value = htonl(entry->bytes.received);
309 message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
310 value = htonl(entry->bytes.received >> 32);
311 if (value)
312 {
313 message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
314 chunk_from_thing(value));
315 }
316 value = htonl(entry->packets.received);
317 message->add(message, RAT_ACCT_INPUT_PACKETS, chunk_from_thing(value));
318
319 value = htonl(time_monotonic(NULL) - entry->created);
320 message->add(message, RAT_ACCT_SESSION_TIME, chunk_from_thing(value));
321
322
323 value = htonl(entry->cause);
324 message->add(message, RAT_ACCT_TERMINATE_CAUSE, chunk_from_thing(value));
325
326 send_message(this, message);
327 message->destroy(message);
328 free(entry);
329 }
330 }
331
332 METHOD(listener_t, alert, bool,
333 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, alert_t alert,
334 va_list args)
335 {
336 radius_acct_terminate_cause_t cause;
337 entry_t *entry;
338
339 switch (alert)
340 {
341 case ALERT_IKE_SA_EXPIRED:
342 cause = ACCT_CAUSE_SESSION_TIMEOUT;
343 break;
344 case ALERT_RETRANSMIT_SEND_TIMEOUT:
345 cause = ACCT_CAUSE_LOST_SERVICE;
346 break;
347 default:
348 return TRUE;
349 }
350 this->mutex->lock(this->mutex);
351 entry = this->sessions->get(this->sessions,
352 (void*)(uintptr_t)ike_sa->get_unique_id(ike_sa));
353 if (entry)
354 {
355 entry->cause = cause;
356 }
357 this->mutex->unlock(this->mutex);
358 return TRUE;
359 }
360
361 METHOD(listener_t, ike_updown, bool,
362 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, bool up)
363 {
364 if (!up)
365 {
366 enumerator_t *enumerator;
367 child_sa_t *child_sa;
368
369 /* update usage for all children just before sending stop */
370 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
371 while (enumerator->enumerate(enumerator, &child_sa))
372 {
373 update_usage(this, ike_sa, child_sa);
374 }
375 enumerator->destroy(enumerator);
376
377 send_stop(this, ike_sa);
378 }
379 return TRUE;
380 }
381
382 METHOD(listener_t, message_hook, bool,
383 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
384 message_t *message, bool incoming, bool plain)
385 {
386 /* start accounting here, virtual IP now is set */
387 if (plain && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED &&
388 !incoming && !message->get_request(message))
389 {
390 if (ike_sa->get_version(ike_sa) == IKEV1 &&
391 message->get_exchange_type(message) == TRANSACTION)
392 {
393 send_start(this, ike_sa);
394 }
395 if (ike_sa->get_version(ike_sa) == IKEV2 &&
396 message->get_exchange_type(message) == IKE_AUTH)
397 {
398 send_start(this, ike_sa);
399 }
400 }
401 return TRUE;
402 }
403
404 METHOD(listener_t, ike_rekey, bool,
405 private_eap_radius_accounting_t *this, ike_sa_t *old, ike_sa_t *new)
406 {
407 entry_t *entry;
408
409 this->mutex->lock(this->mutex);
410 entry = this->sessions->remove(this->sessions,
411 (void*)(uintptr_t)old->get_unique_id(old));
412 if (entry)
413 {
414 entry = this->sessions->put(this->sessions,
415 (void*)(uintptr_t)new->get_unique_id(new), entry);
416 if (entry)
417 {
418 free(entry);
419 }
420 }
421 this->mutex->unlock(this->mutex);
422
423 return TRUE;
424 }
425
426 METHOD(listener_t, child_rekey, bool,
427 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
428 child_sa_t *old, child_sa_t *new)
429 {
430 update_usage(this, ike_sa, old);
431
432 return TRUE;
433 }
434
435 METHOD(listener_t, child_updown, bool,
436 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
437 child_sa_t *child_sa, bool up)
438 {
439 if (!up && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED)
440 {
441 update_usage(this, ike_sa, child_sa);
442 }
443 return TRUE;
444 }
445
446 METHOD(eap_radius_accounting_t, destroy, void,
447 private_eap_radius_accounting_t *this)
448 {
449 this->mutex->destroy(this->mutex);
450 this->sessions->destroy(this->sessions);
451 free(this);
452 }
453
454 /**
455 * See header
456 */
457 eap_radius_accounting_t *eap_radius_accounting_create()
458 {
459 private_eap_radius_accounting_t *this;
460
461 INIT(this,
462 .public = {
463 .listener = {
464 .alert = _alert,
465 .ike_updown = _ike_updown,
466 .ike_rekey = _ike_rekey,
467 .message = _message_hook,
468 .child_updown = _child_updown,
469 .child_rekey = _child_rekey,
470 },
471 .destroy = _destroy,
472 },
473 /* use system time as Session ID prefix */
474 .prefix = (u_int32_t)time(NULL),
475 .sessions = hashtable_create((hashtable_hash_t)hash,
476 (hashtable_equals_t)equals, 32),
477 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
478 );
479
480 return &this->public;
481 }