243c76304e2e62745bfa6fcae75abfbab47740f3
[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 <utils/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 host_t *vip;
153 char buf[64];
154 chunk_t data;
155
156 snprintf(buf, sizeof(buf), "%Y", ike_sa->get_other_eap_id(ike_sa));
157 message->add(message, RAT_USER_NAME, chunk_create(buf, strlen(buf)));
158 snprintf(buf, sizeof(buf), "%#H", ike_sa->get_other_host(ike_sa));
159 message->add(message, RAT_CALLING_STATION_ID, chunk_create(buf, strlen(buf)));
160 vip = ike_sa->get_virtual_ip(ike_sa, FALSE);
161 if (vip && vip->get_family(vip) == AF_INET)
162 {
163 message->add(message, RAT_FRAMED_IP_ADDRESS, vip->get_address(vip));
164 }
165 if (vip && vip->get_family(vip) == AF_INET6)
166 {
167 /* we currently assign /128 prefixes, only (reserved, length) */
168 data = chunk_from_chars(0, 128);
169 data = chunk_cata("cc", data, vip->get_address(vip));
170 message->add(message, RAT_FRAMED_IPV6_PREFIX, data);
171 }
172 }
173
174 /**
175 * Send an accounting start message
176 */
177 static void send_start(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
178 {
179 radius_message_t *message;
180 entry_t *entry;
181 u_int32_t id, value;
182
183 id = ike_sa->get_unique_id(ike_sa);
184 INIT(entry,
185 .created = time_monotonic(NULL),
186 );
187 snprintf(entry->sid, sizeof(entry->sid), "%u-%u", this->prefix, id);
188
189 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
190 value = htonl(ACCT_STATUS_START);
191 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
192 message->add(message, RAT_ACCT_SESSION_ID,
193 chunk_create(entry->sid, strlen(entry->sid)));
194 add_ike_sa_parameters(message, ike_sa);
195 if (send_message(this, message))
196 {
197 this->mutex->lock(this->mutex);
198 entry = this->sessions->put(this->sessions, (void*)(uintptr_t)id, entry);
199 this->mutex->unlock(this->mutex);
200 free(entry);
201 }
202 message->destroy(message);
203 }
204
205 /**
206 * Send an account stop message
207 */
208 static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
209 {
210 radius_message_t *message;
211 entry_t *entry;
212 u_int32_t id, value;
213
214 id = ike_sa->get_unique_id(ike_sa);
215 this->mutex->lock(this->mutex);
216 entry = this->sessions->remove(this->sessions, (void*)(uintptr_t)id);
217 this->mutex->unlock(this->mutex);
218 if (entry)
219 {
220 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
221 value = htonl(ACCT_STATUS_STOP);
222 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
223 message->add(message, RAT_ACCT_SESSION_ID,
224 chunk_create(entry->sid, strlen(entry->sid)));
225 add_ike_sa_parameters(message, ike_sa);
226 value = htonl(entry->sent);
227 message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
228 value = htonl(entry->sent >> 32);
229 if (value)
230 {
231 message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
232 chunk_from_thing(value));
233 }
234 value = htonl(entry->received);
235 message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
236 value = htonl(entry->received >> 32);
237 if (value)
238 {
239 message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
240 chunk_from_thing(value));
241 }
242 value = htonl(time_monotonic(NULL) - entry->created);
243 message->add(message, RAT_ACCT_SESSION_TIME, chunk_from_thing(value));
244
245 send_message(this, message);
246 message->destroy(message);
247 free(entry);
248 }
249 }
250
251 METHOD(listener_t, ike_updown, bool,
252 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, bool up)
253 {
254 if (!up)
255 {
256 enumerator_t *enumerator;
257 child_sa_t *child_sa;
258
259 /* update usage for all children just before sending stop */
260 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
261 while (enumerator->enumerate(enumerator, &child_sa))
262 {
263 update_usage(this, ike_sa, child_sa);
264 }
265 enumerator->destroy(enumerator);
266
267 send_stop(this, ike_sa);
268 }
269 return TRUE;
270 }
271
272 METHOD(listener_t, message_hook, bool,
273 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
274 message_t *message, bool incoming, bool plain)
275 {
276 /* start accounting here, virtual IP now is set */
277 if (plain && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED &&
278 message->get_exchange_type(message) == IKE_AUTH &&
279 !incoming && !message->get_request(message))
280 {
281 send_start(this, ike_sa);
282 }
283 return TRUE;
284 }
285
286 METHOD(listener_t, child_rekey, bool,
287 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
288 child_sa_t *old, child_sa_t *new)
289 {
290 update_usage(this, ike_sa, old);
291
292 return TRUE;
293 }
294
295 METHOD(listener_t, child_updown, bool,
296 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
297 child_sa_t *child_sa, bool up)
298 {
299 if (!up && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED)
300 {
301 update_usage(this, ike_sa, child_sa);
302 }
303 return TRUE;
304 }
305
306 METHOD(eap_radius_accounting_t, destroy, void,
307 private_eap_radius_accounting_t *this)
308 {
309 this->mutex->destroy(this->mutex);
310 this->sessions->destroy(this->sessions);
311 free(this);
312 }
313
314 /**
315 * See header
316 */
317 eap_radius_accounting_t *eap_radius_accounting_create()
318 {
319 private_eap_radius_accounting_t *this;
320
321 INIT(this,
322 .public = {
323 .listener = {
324 .ike_updown = _ike_updown,
325 .message = _message_hook,
326 .child_updown = _child_updown,
327 .child_rekey = _child_rekey,
328 },
329 .destroy = _destroy,
330 },
331 /* use system time as Session ID prefix */
332 .prefix = (u_int32_t)time(NULL),
333 .sessions = hashtable_create((hashtable_hash_t)hash,
334 (hashtable_equals_t)equals, 32),
335 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
336 );
337
338 return &this->public;
339 }