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