Moved data structures to new collections subfolder
[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 * Hashtable entry with usage stats
57 */
58 typedef struct {
59 /** RADIUS accounting session ID */
60 char sid[16];
61 /** number of octets sent */
62 u_int64_t sent;
63 /** number of octets received */
64 u_int64_t received;
65 /** session creation time */
66 time_t created;
67 } entry_t;
68
69 /**
70 * Accounting message status types
71 */
72 typedef enum {
73 ACCT_STATUS_START = 1,
74 ACCT_STATUS_STOP = 2,
75 ACCT_STATUS_INTERIM_UPDATE = 3,
76 ACCT_STATUS_ACCOUNTING_ON = 7,
77 ACCT_STATUS_ACCOUNTING_OFF = 8,
78 } radius_acct_status_t;
79
80 /**
81 * Hashtable hash function
82 */
83 static u_int hash(uintptr_t key)
84 {
85 return key;
86 }
87
88 /**
89 * Hashtable equals function
90 */
91 static bool equals(uintptr_t a, uintptr_t b)
92 {
93 return a == b;
94 }
95
96 /**
97 * Update usage counter when a CHILD_SA rekeys/goes down
98 */
99 static void update_usage(private_eap_radius_accounting_t *this,
100 ike_sa_t *ike_sa, child_sa_t *child_sa)
101 {
102 u_int64_t sent, received;
103 entry_t *entry;
104
105 child_sa->get_usestats(child_sa, FALSE, NULL, &sent);
106 child_sa->get_usestats(child_sa, TRUE, NULL, &received);
107
108 this->mutex->lock(this->mutex);
109 entry = this->sessions->get(this->sessions,
110 (void*)(uintptr_t)ike_sa->get_unique_id(ike_sa));
111 if (entry)
112 {
113 entry->sent += sent;
114 entry->received += received;
115 }
116 this->mutex->unlock(this->mutex);
117 }
118
119 /**
120 * Send a RADIUS message, wait for response
121 */
122 static bool send_message(private_eap_radius_accounting_t *this,
123 radius_message_t *request)
124 {
125 radius_message_t *response;
126 radius_client_t *client;
127 bool ack = FALSE;
128
129 client = eap_radius_create_client();
130 if (client)
131 {
132 response = client->request(client, request);
133 if (response)
134 {
135 ack = response->get_code(response) == RMC_ACCOUNTING_RESPONSE;
136 response->destroy(response);
137 }
138 else
139 {
140 charon->bus->alert(charon->bus, ALERT_RADIUS_NOT_RESPONDING);
141 }
142 client->destroy(client);
143 }
144 return ack;
145 }
146
147 /**
148 * Add common IKE_SA parameters to RADIUS account message
149 */
150 static void add_ike_sa_parameters(radius_message_t *message, ike_sa_t *ike_sa)
151 {
152 enumerator_t *enumerator;
153 host_t *vip;
154 char buf[64];
155 chunk_t data;
156
157 snprintf(buf, sizeof(buf), "%Y", ike_sa->get_other_eap_id(ike_sa));
158 message->add(message, RAT_USER_NAME, chunk_create(buf, strlen(buf)));
159 snprintf(buf, sizeof(buf), "%#H", ike_sa->get_other_host(ike_sa));
160 message->add(message, RAT_CALLING_STATION_ID, chunk_create(buf, strlen(buf)));
161
162 enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, FALSE);
163 while (enumerator->enumerate(enumerator, &vip))
164 {
165 switch (vip->get_family(vip))
166 {
167 case AF_INET:
168 message->add(message, RAT_FRAMED_IP_ADDRESS,
169 vip->get_address(vip));
170 break;
171 case AF_INET6:
172 /* we currently assign /128 prefixes, only (reserved, length) */
173 data = chunk_from_chars(0, 128);
174 data = chunk_cata("cc", data, vip->get_address(vip));
175 message->add(message, RAT_FRAMED_IPV6_PREFIX, data);
176 break;
177 default:
178 break;
179 }
180 }
181 enumerator->destroy(enumerator);
182 }
183
184 /**
185 * Send an accounting start message
186 */
187 static void send_start(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
188 {
189 radius_message_t *message;
190 entry_t *entry;
191 u_int32_t id, value;
192
193 id = ike_sa->get_unique_id(ike_sa);
194 INIT(entry,
195 .created = time_monotonic(NULL),
196 );
197 snprintf(entry->sid, sizeof(entry->sid), "%u-%u", this->prefix, id);
198
199 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
200 value = htonl(ACCT_STATUS_START);
201 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
202 message->add(message, RAT_ACCT_SESSION_ID,
203 chunk_create(entry->sid, strlen(entry->sid)));
204 add_ike_sa_parameters(message, ike_sa);
205 if (send_message(this, message))
206 {
207 this->mutex->lock(this->mutex);
208 entry = this->sessions->put(this->sessions, (void*)(uintptr_t)id, entry);
209 this->mutex->unlock(this->mutex);
210 }
211 message->destroy(message);
212 free(entry);
213 }
214
215 /**
216 * Send an account stop message
217 */
218 static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
219 {
220 radius_message_t *message;
221 entry_t *entry;
222 u_int32_t id, value;
223
224 id = ike_sa->get_unique_id(ike_sa);
225 this->mutex->lock(this->mutex);
226 entry = this->sessions->remove(this->sessions, (void*)(uintptr_t)id);
227 this->mutex->unlock(this->mutex);
228 if (entry)
229 {
230 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
231 value = htonl(ACCT_STATUS_STOP);
232 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
233 message->add(message, RAT_ACCT_SESSION_ID,
234 chunk_create(entry->sid, strlen(entry->sid)));
235 add_ike_sa_parameters(message, ike_sa);
236 value = htonl(entry->sent);
237 message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
238 value = htonl(entry->sent >> 32);
239 if (value)
240 {
241 message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
242 chunk_from_thing(value));
243 }
244 value = htonl(entry->received);
245 message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
246 value = htonl(entry->received >> 32);
247 if (value)
248 {
249 message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
250 chunk_from_thing(value));
251 }
252 value = htonl(time_monotonic(NULL) - entry->created);
253 message->add(message, RAT_ACCT_SESSION_TIME, chunk_from_thing(value));
254
255 send_message(this, message);
256 message->destroy(message);
257 free(entry);
258 }
259 }
260
261 METHOD(listener_t, ike_updown, bool,
262 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, bool up)
263 {
264 if (!up)
265 {
266 enumerator_t *enumerator;
267 child_sa_t *child_sa;
268
269 /* update usage for all children just before sending stop */
270 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
271 while (enumerator->enumerate(enumerator, &child_sa))
272 {
273 update_usage(this, ike_sa, child_sa);
274 }
275 enumerator->destroy(enumerator);
276
277 send_stop(this, ike_sa);
278 }
279 return TRUE;
280 }
281
282 METHOD(listener_t, message_hook, bool,
283 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
284 message_t *message, bool incoming, bool plain)
285 {
286 /* start accounting here, virtual IP now is set */
287 if (plain && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED &&
288 !incoming && !message->get_request(message))
289 {
290 if (ike_sa->get_version(ike_sa) == IKEV1 &&
291 message->get_exchange_type(message) == TRANSACTION)
292 {
293 send_start(this, ike_sa);
294 }
295 if (ike_sa->get_version(ike_sa) == IKEV2 &&
296 message->get_exchange_type(message) == IKE_AUTH)
297 {
298 send_start(this, ike_sa);
299 }
300 }
301 return TRUE;
302 }
303
304 METHOD(listener_t, child_rekey, bool,
305 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
306 child_sa_t *old, child_sa_t *new)
307 {
308 update_usage(this, ike_sa, old);
309
310 return TRUE;
311 }
312
313 METHOD(listener_t, child_updown, bool,
314 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
315 child_sa_t *child_sa, bool up)
316 {
317 if (!up && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED)
318 {
319 update_usage(this, ike_sa, child_sa);
320 }
321 return TRUE;
322 }
323
324 METHOD(eap_radius_accounting_t, destroy, void,
325 private_eap_radius_accounting_t *this)
326 {
327 this->mutex->destroy(this->mutex);
328 this->sessions->destroy(this->sessions);
329 free(this);
330 }
331
332 /**
333 * See header
334 */
335 eap_radius_accounting_t *eap_radius_accounting_create()
336 {
337 private_eap_radius_accounting_t *this;
338
339 INIT(this,
340 .public = {
341 .listener = {
342 .ike_updown = _ike_updown,
343 .message = _message_hook,
344 .child_updown = _child_updown,
345 .child_rekey = _child_rekey,
346 },
347 .destroy = _destroy,
348 },
349 /* use system time as Session ID prefix */
350 .prefix = (u_int32_t)time(NULL),
351 .sessions = hashtable_create((hashtable_hash_t)hash,
352 (hashtable_equals_t)equals, 32),
353 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
354 );
355
356 return &this->public;
357 }