xref: /freebsd/contrib/wpa/src/ap/accounting.c (revision 1c05a6ea6b849ff95e539c31adea887c644a6a01)
1 /*
2  * hostapd / RADIUS Accounting
3  * Copyright (c) 2002-2009, 2012, Jouni Malinen <j@w1.fi>
4  *
5  * This software may be distributed under the terms of the BSD license.
6  * See README for more details.
7  */
8 
9 #include "utils/includes.h"
10 
11 #include "utils/common.h"
12 #include "utils/eloop.h"
13 #include "eapol_auth/eapol_auth_sm.h"
14 #include "eapol_auth/eapol_auth_sm_i.h"
15 #include "radius/radius.h"
16 #include "radius/radius_client.h"
17 #include "hostapd.h"
18 #include "ieee802_1x.h"
19 #include "ap_config.h"
20 #include "sta_info.h"
21 #include "ap_drv_ops.h"
22 #include "accounting.h"
23 
24 
25 /* Default interval in seconds for polling TX/RX octets from the driver if
26  * STA is not using interim accounting. This detects wrap arounds for
27  * input/output octets and updates Acct-{Input,Output}-Gigawords. */
28 #define ACCT_DEFAULT_UPDATE_INTERVAL 300
29 
30 static void accounting_sta_interim(struct hostapd_data *hapd,
31 				   struct sta_info *sta);
32 
33 
34 static struct radius_msg * accounting_msg(struct hostapd_data *hapd,
35 					  struct sta_info *sta,
36 					  int status_type)
37 {
38 	struct radius_msg *msg;
39 	char buf[128];
40 	u8 *val;
41 	size_t len;
42 	int i;
43 	struct wpabuf *b;
44 
45 	msg = radius_msg_new(RADIUS_CODE_ACCOUNTING_REQUEST,
46 			     radius_client_get_id(hapd->radius));
47 	if (msg == NULL) {
48 		wpa_printf(MSG_INFO, "Could not create new RADIUS packet");
49 		return NULL;
50 	}
51 
52 	if (sta) {
53 		radius_msg_make_authenticator(msg, (u8 *) sta, sizeof(*sta));
54 
55 		if ((hapd->conf->wpa & 2) &&
56 		    !hapd->conf->disable_pmksa_caching &&
57 		    sta->eapol_sm && sta->eapol_sm->acct_multi_session_id_hi) {
58 			os_snprintf(buf, sizeof(buf), "%08X+%08X",
59 				    sta->eapol_sm->acct_multi_session_id_hi,
60 				    sta->eapol_sm->acct_multi_session_id_lo);
61 			if (!radius_msg_add_attr(
62 				    msg, RADIUS_ATTR_ACCT_MULTI_SESSION_ID,
63 				    (u8 *) buf, os_strlen(buf))) {
64 				wpa_printf(MSG_INFO,
65 					   "Could not add Acct-Multi-Session-Id");
66 				goto fail;
67 			}
68 		}
69 	} else {
70 		radius_msg_make_authenticator(msg, (u8 *) hapd, sizeof(*hapd));
71 	}
72 
73 	if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_STATUS_TYPE,
74 				       status_type)) {
75 		wpa_printf(MSG_INFO, "Could not add Acct-Status-Type");
76 		goto fail;
77 	}
78 
79 	if (!hostapd_config_get_radius_attr(hapd->conf->radius_acct_req_attr,
80 					    RADIUS_ATTR_ACCT_AUTHENTIC) &&
81 	    !radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_AUTHENTIC,
82 				       hapd->conf->ieee802_1x ?
83 				       RADIUS_ACCT_AUTHENTIC_RADIUS :
84 				       RADIUS_ACCT_AUTHENTIC_LOCAL)) {
85 		wpa_printf(MSG_INFO, "Could not add Acct-Authentic");
86 		goto fail;
87 	}
88 
89 	if (sta) {
90 		/* Use 802.1X identity if available */
91 		val = ieee802_1x_get_identity(sta->eapol_sm, &len);
92 
93 		/* Use RADIUS ACL identity if 802.1X provides no identity */
94 		if (!val && sta->identity) {
95 			val = (u8 *) sta->identity;
96 			len = os_strlen(sta->identity);
97 		}
98 
99 		/* Use STA MAC if neither 802.1X nor RADIUS ACL provided
100 		 * identity */
101 		if (!val) {
102 			os_snprintf(buf, sizeof(buf), RADIUS_ADDR_FORMAT,
103 				    MAC2STR(sta->addr));
104 			val = (u8 *) buf;
105 			len = os_strlen(buf);
106 		}
107 
108 		if (!radius_msg_add_attr(msg, RADIUS_ATTR_USER_NAME, val,
109 					 len)) {
110 			wpa_printf(MSG_INFO, "Could not add User-Name");
111 			goto fail;
112 		}
113 	}
114 
115 	if (add_common_radius_attr(hapd, hapd->conf->radius_acct_req_attr, sta,
116 				   msg) < 0)
117 		goto fail;
118 
119 	if (sta) {
120 		for (i = 0; ; i++) {
121 			val = ieee802_1x_get_radius_class(sta->eapol_sm, &len,
122 							  i);
123 			if (val == NULL)
124 				break;
125 
126 			if (!radius_msg_add_attr(msg, RADIUS_ATTR_CLASS,
127 						 val, len)) {
128 				wpa_printf(MSG_INFO, "Could not add Class");
129 				goto fail;
130 			}
131 		}
132 
133 		b = ieee802_1x_get_radius_cui(sta->eapol_sm);
134 		if (b &&
135 		    !radius_msg_add_attr(msg,
136 					 RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
137 					 wpabuf_head(b), wpabuf_len(b))) {
138 			wpa_printf(MSG_ERROR, "Could not add CUI");
139 			goto fail;
140 		}
141 
142 		if (!b && sta->radius_cui &&
143 		    !radius_msg_add_attr(msg,
144 					 RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
145 					 (u8 *) sta->radius_cui,
146 					 os_strlen(sta->radius_cui))) {
147 			wpa_printf(MSG_ERROR, "Could not add CUI from ACL");
148 			goto fail;
149 		}
150 	}
151 
152 	return msg;
153 
154  fail:
155 	radius_msg_free(msg);
156 	return NULL;
157 }
158 
159 
160 static int accounting_sta_update_stats(struct hostapd_data *hapd,
161 				       struct sta_info *sta,
162 				       struct hostap_sta_driver_data *data)
163 {
164 	if (hostapd_drv_read_sta_data(hapd, data, sta->addr))
165 		return -1;
166 
167 	if (sta->last_rx_bytes > data->rx_bytes)
168 		sta->acct_input_gigawords++;
169 	if (sta->last_tx_bytes > data->tx_bytes)
170 		sta->acct_output_gigawords++;
171 	sta->last_rx_bytes = data->rx_bytes;
172 	sta->last_tx_bytes = data->tx_bytes;
173 
174 	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
175 		       HOSTAPD_LEVEL_DEBUG, "updated TX/RX stats: "
176 		       "Acct-Input-Octets=%lu Acct-Input-Gigawords=%u "
177 		       "Acct-Output-Octets=%lu Acct-Output-Gigawords=%u",
178 		       sta->last_rx_bytes, sta->acct_input_gigawords,
179 		       sta->last_tx_bytes, sta->acct_output_gigawords);
180 
181 	return 0;
182 }
183 
184 
185 static void accounting_interim_update(void *eloop_ctx, void *timeout_ctx)
186 {
187 	struct hostapd_data *hapd = eloop_ctx;
188 	struct sta_info *sta = timeout_ctx;
189 	int interval;
190 
191 	if (sta->acct_interim_interval) {
192 		accounting_sta_interim(hapd, sta);
193 		interval = sta->acct_interim_interval;
194 	} else {
195 		struct hostap_sta_driver_data data;
196 		accounting_sta_update_stats(hapd, sta, &data);
197 		interval = ACCT_DEFAULT_UPDATE_INTERVAL;
198 	}
199 
200 	eloop_register_timeout(interval, 0, accounting_interim_update,
201 			       hapd, sta);
202 }
203 
204 
205 /**
206  * accounting_sta_start - Start STA accounting
207  * @hapd: hostapd BSS data
208  * @sta: The station
209  */
210 void accounting_sta_start(struct hostapd_data *hapd, struct sta_info *sta)
211 {
212 	struct radius_msg *msg;
213 	int interval;
214 
215 	if (sta->acct_session_started)
216 		return;
217 
218 	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
219 		       HOSTAPD_LEVEL_INFO,
220 		       "starting accounting session %08X-%08X",
221 		       sta->acct_session_id_hi, sta->acct_session_id_lo);
222 
223 	os_get_reltime(&sta->acct_session_start);
224 	sta->last_rx_bytes = sta->last_tx_bytes = 0;
225 	sta->acct_input_gigawords = sta->acct_output_gigawords = 0;
226 	hostapd_drv_sta_clear_stats(hapd, sta->addr);
227 
228 	if (!hapd->conf->radius->acct_server)
229 		return;
230 
231 	if (sta->acct_interim_interval)
232 		interval = sta->acct_interim_interval;
233 	else
234 		interval = ACCT_DEFAULT_UPDATE_INTERVAL;
235 	eloop_register_timeout(interval, 0, accounting_interim_update,
236 			       hapd, sta);
237 
238 	msg = accounting_msg(hapd, sta, RADIUS_ACCT_STATUS_TYPE_START);
239 	if (msg &&
240 	    radius_client_send(hapd->radius, msg, RADIUS_ACCT, sta->addr) < 0)
241 		radius_msg_free(msg);
242 
243 	sta->acct_session_started = 1;
244 }
245 
246 
247 static void accounting_sta_report(struct hostapd_data *hapd,
248 				  struct sta_info *sta, int stop)
249 {
250 	struct radius_msg *msg;
251 	int cause = sta->acct_terminate_cause;
252 	struct hostap_sta_driver_data data;
253 	struct os_reltime now_r, diff;
254 	struct os_time now;
255 	u32 gigawords;
256 
257 	if (!hapd->conf->radius->acct_server)
258 		return;
259 
260 	msg = accounting_msg(hapd, sta,
261 			     stop ? RADIUS_ACCT_STATUS_TYPE_STOP :
262 			     RADIUS_ACCT_STATUS_TYPE_INTERIM_UPDATE);
263 	if (!msg) {
264 		wpa_printf(MSG_INFO, "Could not create RADIUS Accounting message");
265 		return;
266 	}
267 
268 	os_get_reltime(&now_r);
269 	os_get_time(&now);
270 	os_reltime_sub(&now_r, &sta->acct_session_start, &diff);
271 	if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_SESSION_TIME,
272 				       diff.sec)) {
273 		wpa_printf(MSG_INFO, "Could not add Acct-Session-Time");
274 		goto fail;
275 	}
276 
277 	if (accounting_sta_update_stats(hapd, sta, &data) == 0) {
278 		if (!radius_msg_add_attr_int32(msg,
279 					       RADIUS_ATTR_ACCT_INPUT_PACKETS,
280 					       data.rx_packets)) {
281 			wpa_printf(MSG_INFO, "Could not add Acct-Input-Packets");
282 			goto fail;
283 		}
284 		if (!radius_msg_add_attr_int32(msg,
285 					       RADIUS_ATTR_ACCT_OUTPUT_PACKETS,
286 					       data.tx_packets)) {
287 			wpa_printf(MSG_INFO, "Could not add Acct-Output-Packets");
288 			goto fail;
289 		}
290 		if (!radius_msg_add_attr_int32(msg,
291 					       RADIUS_ATTR_ACCT_INPUT_OCTETS,
292 					       data.rx_bytes)) {
293 			wpa_printf(MSG_INFO, "Could not add Acct-Input-Octets");
294 			goto fail;
295 		}
296 		gigawords = sta->acct_input_gigawords;
297 #if __WORDSIZE == 64
298 		gigawords += data.rx_bytes >> 32;
299 #endif
300 		if (gigawords &&
301 		    !radius_msg_add_attr_int32(
302 			    msg, RADIUS_ATTR_ACCT_INPUT_GIGAWORDS,
303 			    gigawords)) {
304 			wpa_printf(MSG_INFO, "Could not add Acct-Input-Gigawords");
305 			goto fail;
306 		}
307 		if (!radius_msg_add_attr_int32(msg,
308 					       RADIUS_ATTR_ACCT_OUTPUT_OCTETS,
309 					       data.tx_bytes)) {
310 			wpa_printf(MSG_INFO, "Could not add Acct-Output-Octets");
311 			goto fail;
312 		}
313 		gigawords = sta->acct_output_gigawords;
314 #if __WORDSIZE == 64
315 		gigawords += data.tx_bytes >> 32;
316 #endif
317 		if (gigawords &&
318 		    !radius_msg_add_attr_int32(
319 			    msg, RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS,
320 			    gigawords)) {
321 			wpa_printf(MSG_INFO, "Could not add Acct-Output-Gigawords");
322 			goto fail;
323 		}
324 	}
325 
326 	if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_EVENT_TIMESTAMP,
327 				       now.sec)) {
328 		wpa_printf(MSG_INFO, "Could not add Event-Timestamp");
329 		goto fail;
330 	}
331 
332 	if (eloop_terminated())
333 		cause = RADIUS_ACCT_TERMINATE_CAUSE_ADMIN_REBOOT;
334 
335 	if (stop && cause &&
336 	    !radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_TERMINATE_CAUSE,
337 				       cause)) {
338 		wpa_printf(MSG_INFO, "Could not add Acct-Terminate-Cause");
339 		goto fail;
340 	}
341 
342 	if (radius_client_send(hapd->radius, msg,
343 			       stop ? RADIUS_ACCT : RADIUS_ACCT_INTERIM,
344 			       sta->addr) < 0)
345 		goto fail;
346 	return;
347 
348  fail:
349 	radius_msg_free(msg);
350 }
351 
352 
353 /**
354  * accounting_sta_interim - Send a interim STA accounting report
355  * @hapd: hostapd BSS data
356  * @sta: The station
357  */
358 static void accounting_sta_interim(struct hostapd_data *hapd,
359 				   struct sta_info *sta)
360 {
361 	if (sta->acct_session_started)
362 		accounting_sta_report(hapd, sta, 0);
363 }
364 
365 
366 /**
367  * accounting_sta_stop - Stop STA accounting
368  * @hapd: hostapd BSS data
369  * @sta: The station
370  */
371 void accounting_sta_stop(struct hostapd_data *hapd, struct sta_info *sta)
372 {
373 	if (sta->acct_session_started) {
374 		accounting_sta_report(hapd, sta, 1);
375 		eloop_cancel_timeout(accounting_interim_update, hapd, sta);
376 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
377 			       HOSTAPD_LEVEL_INFO,
378 			       "stopped accounting session %08X-%08X",
379 			       sta->acct_session_id_hi,
380 			       sta->acct_session_id_lo);
381 		sta->acct_session_started = 0;
382 	}
383 }
384 
385 
386 void accounting_sta_get_id(struct hostapd_data *hapd,
387 				  struct sta_info *sta)
388 {
389 	sta->acct_session_id_lo = hapd->acct_session_id_lo++;
390 	if (hapd->acct_session_id_lo == 0) {
391 		hapd->acct_session_id_hi++;
392 	}
393 	sta->acct_session_id_hi = hapd->acct_session_id_hi;
394 }
395 
396 
397 /**
398  * accounting_receive - Process the RADIUS frames from Accounting Server
399  * @msg: RADIUS response message
400  * @req: RADIUS request message
401  * @shared_secret: RADIUS shared secret
402  * @shared_secret_len: Length of shared_secret in octets
403  * @data: Context data (struct hostapd_data *)
404  * Returns: Processing status
405  */
406 static RadiusRxResult
407 accounting_receive(struct radius_msg *msg, struct radius_msg *req,
408 		   const u8 *shared_secret, size_t shared_secret_len,
409 		   void *data)
410 {
411 	if (radius_msg_get_hdr(msg)->code != RADIUS_CODE_ACCOUNTING_RESPONSE) {
412 		wpa_printf(MSG_INFO, "Unknown RADIUS message code");
413 		return RADIUS_RX_UNKNOWN;
414 	}
415 
416 	if (radius_msg_verify(msg, shared_secret, shared_secret_len, req, 0)) {
417 		wpa_printf(MSG_INFO, "Incoming RADIUS packet did not have correct Authenticator - dropped");
418 		return RADIUS_RX_INVALID_AUTHENTICATOR;
419 	}
420 
421 	return RADIUS_RX_PROCESSED;
422 }
423 
424 
425 static void accounting_report_state(struct hostapd_data *hapd, int on)
426 {
427 	struct radius_msg *msg;
428 
429 	if (!hapd->conf->radius->acct_server || hapd->radius == NULL)
430 		return;
431 
432 	/* Inform RADIUS server that accounting will start/stop so that the
433 	 * server can close old accounting sessions. */
434 	msg = accounting_msg(hapd, NULL,
435 			     on ? RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_ON :
436 			     RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_OFF);
437 	if (!msg)
438 		return;
439 
440 	if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_TERMINATE_CAUSE,
441 				       RADIUS_ACCT_TERMINATE_CAUSE_NAS_REBOOT))
442 	{
443 		wpa_printf(MSG_INFO, "Could not add Acct-Terminate-Cause");
444 		radius_msg_free(msg);
445 		return;
446 	}
447 
448 	if (radius_client_send(hapd->radius, msg, RADIUS_ACCT, NULL) < 0)
449 		radius_msg_free(msg);
450 }
451 
452 
453 /**
454  * accounting_init: Initialize accounting
455  * @hapd: hostapd BSS data
456  * Returns: 0 on success, -1 on failure
457  */
458 int accounting_init(struct hostapd_data *hapd)
459 {
460 	struct os_time now;
461 
462 	/* Acct-Session-Id should be unique over reboots. Using a random number
463 	 * is preferred. If that is not available, take the current time. Mix
464 	 * in microseconds to make this more likely to be unique. */
465 	os_get_time(&now);
466 	if (os_get_random((u8 *) &hapd->acct_session_id_hi,
467 			  sizeof(hapd->acct_session_id_hi)) < 0)
468 		hapd->acct_session_id_hi = now.sec;
469 	hapd->acct_session_id_hi ^= now.usec;
470 
471 	if (radius_client_register(hapd->radius, RADIUS_ACCT,
472 				   accounting_receive, hapd))
473 		return -1;
474 
475 	accounting_report_state(hapd, 1);
476 
477 	return 0;
478 }
479 
480 
481 /**
482  * accounting_deinit: Deinitialize accounting
483  * @hapd: hostapd BSS data
484  */
485 void accounting_deinit(struct hostapd_data *hapd)
486 {
487 	accounting_report_state(hapd, 0);
488 }
489