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