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