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