Add an option to delete any established IKE_SA if RADIUS server is not responding
[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 client->destroy(client);
168 }
169 return ack;
170 }
171
172 /**
173 * Add common IKE_SA parameters to RADIUS account message
174 */
175 static void add_ike_sa_parameters(radius_message_t *message, ike_sa_t *ike_sa)
176 {
177 enumerator_t *enumerator;
178 host_t *vip, *host;
179 char buf[64];
180 chunk_t data;
181 u_int32_t value;
182
183 /* virtual NAS-Port-Type */
184 value = htonl(5);
185 message->add(message, RAT_NAS_PORT_TYPE, chunk_from_thing(value));
186 /* framed ServiceType */
187 value = htonl(2);
188 message->add(message, RAT_SERVICE_TYPE, chunk_from_thing(value));
189
190 value = htonl(ike_sa->get_unique_id(ike_sa));
191 message->add(message, RAT_NAS_PORT, chunk_from_thing(value));
192 message->add(message, RAT_NAS_PORT_ID,
193 chunk_from_str(ike_sa->get_name(ike_sa)));
194
195 host = ike_sa->get_my_host(ike_sa);
196 data = host->get_address(host);
197 switch (host->get_family(host))
198 {
199 case AF_INET:
200 message->add(message, RAT_NAS_IP_ADDRESS, data);
201 break;
202 case AF_INET6:
203 message->add(message, RAT_NAS_IPV6_ADDRESS, data);
204 default:
205 break;
206 }
207 snprintf(buf, sizeof(buf), "%#H", host);
208 message->add(message, RAT_CALLED_STATION_ID, chunk_from_str(buf));
209 host = ike_sa->get_other_host(ike_sa);
210 snprintf(buf, sizeof(buf), "%#H", host);
211 message->add(message, RAT_CALLING_STATION_ID, chunk_from_str(buf));
212
213 snprintf(buf, sizeof(buf), "%Y", ike_sa->get_other_eap_id(ike_sa));
214 message->add(message, RAT_USER_NAME, chunk_from_str(buf));
215
216 enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, FALSE);
217 while (enumerator->enumerate(enumerator, &vip))
218 {
219 switch (vip->get_family(vip))
220 {
221 case AF_INET:
222 message->add(message, RAT_FRAMED_IP_ADDRESS,
223 vip->get_address(vip));
224 break;
225 case AF_INET6:
226 /* we currently assign /128 prefixes, only (reserved, length) */
227 data = chunk_from_chars(0, 128);
228 data = chunk_cata("cc", data, vip->get_address(vip));
229 message->add(message, RAT_FRAMED_IPV6_PREFIX, data);
230 break;
231 default:
232 break;
233 }
234 }
235 enumerator->destroy(enumerator);
236 }
237
238 /**
239 * Send an accounting start message
240 */
241 static void send_start(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
242 {
243 radius_message_t *message;
244 entry_t *entry;
245 u_int32_t id, value;
246
247 id = ike_sa->get_unique_id(ike_sa);
248 INIT(entry,
249 .created = time_monotonic(NULL),
250 /* default terminate cause, if none other catched */
251 .cause = ACCT_CAUSE_USER_REQUEST,
252 );
253 snprintf(entry->sid, sizeof(entry->sid), "%u-%u", this->prefix, id);
254
255 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
256 value = htonl(ACCT_STATUS_START);
257 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
258 message->add(message, RAT_ACCT_SESSION_ID,
259 chunk_create(entry->sid, strlen(entry->sid)));
260 add_ike_sa_parameters(message, ike_sa);
261 if (send_message(this, message))
262 {
263 this->mutex->lock(this->mutex);
264 entry = this->sessions->put(this->sessions, (void*)(uintptr_t)id, entry);
265 this->mutex->unlock(this->mutex);
266 }
267 else
268 {
269 eap_radius_handle_timeout(ike_sa->get_id(ike_sa));
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 if (!send_message(this, message))
327 {
328 eap_radius_handle_timeout(NULL);
329 }
330 message->destroy(message);
331 free(entry);
332 }
333 }
334
335 METHOD(listener_t, alert, bool,
336 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, alert_t alert,
337 va_list args)
338 {
339 radius_acct_terminate_cause_t cause;
340 entry_t *entry;
341
342 switch (alert)
343 {
344 case ALERT_IKE_SA_EXPIRED:
345 cause = ACCT_CAUSE_SESSION_TIMEOUT;
346 break;
347 case ALERT_RETRANSMIT_SEND_TIMEOUT:
348 cause = ACCT_CAUSE_LOST_SERVICE;
349 break;
350 default:
351 return TRUE;
352 }
353 this->mutex->lock(this->mutex);
354 entry = this->sessions->get(this->sessions,
355 (void*)(uintptr_t)ike_sa->get_unique_id(ike_sa));
356 if (entry)
357 {
358 entry->cause = cause;
359 }
360 this->mutex->unlock(this->mutex);
361 return TRUE;
362 }
363
364 METHOD(listener_t, ike_updown, bool,
365 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, bool up)
366 {
367 if (!up)
368 {
369 enumerator_t *enumerator;
370 child_sa_t *child_sa;
371
372 /* update usage for all children just before sending stop */
373 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
374 while (enumerator->enumerate(enumerator, &child_sa))
375 {
376 update_usage(this, ike_sa, child_sa);
377 }
378 enumerator->destroy(enumerator);
379
380 send_stop(this, ike_sa);
381 }
382 return TRUE;
383 }
384
385 METHOD(listener_t, message_hook, bool,
386 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
387 message_t *message, bool incoming, bool plain)
388 {
389 /* start accounting here, virtual IP now is set */
390 if (plain && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED &&
391 !incoming && !message->get_request(message))
392 {
393 if (ike_sa->get_version(ike_sa) == IKEV1 &&
394 message->get_exchange_type(message) == TRANSACTION)
395 {
396 send_start(this, ike_sa);
397 }
398 if (ike_sa->get_version(ike_sa) == IKEV2 &&
399 message->get_exchange_type(message) == IKE_AUTH)
400 {
401 send_start(this, ike_sa);
402 }
403 }
404 return TRUE;
405 }
406
407 METHOD(listener_t, ike_rekey, bool,
408 private_eap_radius_accounting_t *this, ike_sa_t *old, ike_sa_t *new)
409 {
410 entry_t *entry;
411
412 this->mutex->lock(this->mutex);
413 entry = this->sessions->remove(this->sessions,
414 (void*)(uintptr_t)old->get_unique_id(old));
415 if (entry)
416 {
417 entry = this->sessions->put(this->sessions,
418 (void*)(uintptr_t)new->get_unique_id(new), entry);
419 if (entry)
420 {
421 free(entry);
422 }
423 }
424 this->mutex->unlock(this->mutex);
425
426 return TRUE;
427 }
428
429 METHOD(listener_t, child_rekey, bool,
430 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
431 child_sa_t *old, child_sa_t *new)
432 {
433 update_usage(this, ike_sa, old);
434
435 return TRUE;
436 }
437
438 METHOD(listener_t, child_updown, bool,
439 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
440 child_sa_t *child_sa, bool up)
441 {
442 if (!up && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED)
443 {
444 update_usage(this, ike_sa, child_sa);
445 }
446 return TRUE;
447 }
448
449 METHOD(eap_radius_accounting_t, destroy, void,
450 private_eap_radius_accounting_t *this)
451 {
452 this->mutex->destroy(this->mutex);
453 this->sessions->destroy(this->sessions);
454 free(this);
455 }
456
457 /**
458 * See header
459 */
460 eap_radius_accounting_t *eap_radius_accounting_create()
461 {
462 private_eap_radius_accounting_t *this;
463
464 INIT(this,
465 .public = {
466 .listener = {
467 .alert = _alert,
468 .ike_updown = _ike_updown,
469 .ike_rekey = _ike_rekey,
470 .message = _message_hook,
471 .child_updown = _child_updown,
472 .child_rekey = _child_rekey,
473 },
474 .destroy = _destroy,
475 },
476 /* use system time as Session ID prefix */
477 .prefix = (u_int32_t)time(NULL),
478 .sessions = hashtable_create((hashtable_hash_t)hash,
479 (hashtable_equals_t)equals, 32),
480 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
481 );
482
483 return &this->public;
484 }