Support multiple virtual IPs on peer_cfg and ike_sa classes
[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 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 }
182
183 /**
184 * Send an accounting start message
185 */
186 static void send_start(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
187 {
188 radius_message_t *message;
189 entry_t *entry;
190 u_int32_t id, value;
191
192 id = ike_sa->get_unique_id(ike_sa);
193 INIT(entry,
194 .created = time_monotonic(NULL),
195 );
196 snprintf(entry->sid, sizeof(entry->sid), "%u-%u", this->prefix, id);
197
198 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
199 value = htonl(ACCT_STATUS_START);
200 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
201 message->add(message, RAT_ACCT_SESSION_ID,
202 chunk_create(entry->sid, strlen(entry->sid)));
203 add_ike_sa_parameters(message, ike_sa);
204 if (send_message(this, message))
205 {
206 this->mutex->lock(this->mutex);
207 entry = this->sessions->put(this->sessions, (void*)(uintptr_t)id, entry);
208 this->mutex->unlock(this->mutex);
209 free(entry);
210 }
211 message->destroy(message);
212 }
213
214 /**
215 * Send an account stop message
216 */
217 static void send_stop(private_eap_radius_accounting_t *this, ike_sa_t *ike_sa)
218 {
219 radius_message_t *message;
220 entry_t *entry;
221 u_int32_t id, value;
222
223 id = ike_sa->get_unique_id(ike_sa);
224 this->mutex->lock(this->mutex);
225 entry = this->sessions->remove(this->sessions, (void*)(uintptr_t)id);
226 this->mutex->unlock(this->mutex);
227 if (entry)
228 {
229 message = radius_message_create(RMC_ACCOUNTING_REQUEST);
230 value = htonl(ACCT_STATUS_STOP);
231 message->add(message, RAT_ACCT_STATUS_TYPE, chunk_from_thing(value));
232 message->add(message, RAT_ACCT_SESSION_ID,
233 chunk_create(entry->sid, strlen(entry->sid)));
234 add_ike_sa_parameters(message, ike_sa);
235 value = htonl(entry->sent);
236 message->add(message, RAT_ACCT_OUTPUT_OCTETS, chunk_from_thing(value));
237 value = htonl(entry->sent >> 32);
238 if (value)
239 {
240 message->add(message, RAT_ACCT_OUTPUT_GIGAWORDS,
241 chunk_from_thing(value));
242 }
243 value = htonl(entry->received);
244 message->add(message, RAT_ACCT_INPUT_OCTETS, chunk_from_thing(value));
245 value = htonl(entry->received >> 32);
246 if (value)
247 {
248 message->add(message, RAT_ACCT_INPUT_GIGAWORDS,
249 chunk_from_thing(value));
250 }
251 value = htonl(time_monotonic(NULL) - entry->created);
252 message->add(message, RAT_ACCT_SESSION_TIME, chunk_from_thing(value));
253
254 send_message(this, message);
255 message->destroy(message);
256 free(entry);
257 }
258 }
259
260 METHOD(listener_t, ike_updown, bool,
261 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa, bool up)
262 {
263 if (!up)
264 {
265 enumerator_t *enumerator;
266 child_sa_t *child_sa;
267
268 /* update usage for all children just before sending stop */
269 enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
270 while (enumerator->enumerate(enumerator, &child_sa))
271 {
272 update_usage(this, ike_sa, child_sa);
273 }
274 enumerator->destroy(enumerator);
275
276 send_stop(this, ike_sa);
277 }
278 return TRUE;
279 }
280
281 METHOD(listener_t, message_hook, bool,
282 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
283 message_t *message, bool incoming, bool plain)
284 {
285 /* start accounting here, virtual IP now is set */
286 if (plain && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED &&
287 message->get_exchange_type(message) == IKE_AUTH &&
288 !incoming && !message->get_request(message))
289 {
290 send_start(this, ike_sa);
291 }
292 return TRUE;
293 }
294
295 METHOD(listener_t, child_rekey, bool,
296 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
297 child_sa_t *old, child_sa_t *new)
298 {
299 update_usage(this, ike_sa, old);
300
301 return TRUE;
302 }
303
304 METHOD(listener_t, child_updown, bool,
305 private_eap_radius_accounting_t *this, ike_sa_t *ike_sa,
306 child_sa_t *child_sa, bool up)
307 {
308 if (!up && ike_sa->get_state(ike_sa) == IKE_ESTABLISHED)
309 {
310 update_usage(this, ike_sa, child_sa);
311 }
312 return TRUE;
313 }
314
315 METHOD(eap_radius_accounting_t, destroy, void,
316 private_eap_radius_accounting_t *this)
317 {
318 this->mutex->destroy(this->mutex);
319 this->sessions->destroy(this->sessions);
320 free(this);
321 }
322
323 /**
324 * See header
325 */
326 eap_radius_accounting_t *eap_radius_accounting_create()
327 {
328 private_eap_radius_accounting_t *this;
329
330 INIT(this,
331 .public = {
332 .listener = {
333 .ike_updown = _ike_updown,
334 .message = _message_hook,
335 .child_updown = _child_updown,
336 .child_rekey = _child_rekey,
337 },
338 .destroy = _destroy,
339 },
340 /* use system time as Session ID prefix */
341 .prefix = (u_int32_t)time(NULL),
342 .sessions = hashtable_create((hashtable_hash_t)hash,
343 (hashtable_equals_t)equals, 32),
344 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
345 );
346
347 return &this->public;
348 }