133965Sjdp/* 2218822Sdim * hostapd / RADIUS Accounting 3130561Sobrien * Copyright (c) 2002-2009, 2012, Jouni Malinen <j@w1.fi> 477298Sobrien * 533965Sjdp * This software may be distributed under the terms of the BSD license. 633965Sjdp * See README for more details. 733965Sjdp */ 833965Sjdp 933965Sjdp#include "utils/includes.h" 1033965Sjdp 1133965Sjdp#include "utils/common.h" 1233965Sjdp#include "utils/eloop.h" 1333965Sjdp#include "drivers/driver.h" 1433965Sjdp#include "radius/radius.h" 1533965Sjdp#include "radius/radius_client.h" 1633965Sjdp#include "hostapd.h" 1733965Sjdp#include "ieee802_1x.h" 1833965Sjdp#include "ap_config.h" 1933965Sjdp#include "sta_info.h" 20218822Sdim#include "ap_drv_ops.h" 2133965Sjdp#include "accounting.h" 2277298Sobrien 2333965Sjdp 24104834Sobrien/* Default interval in seconds for polling TX/RX octets from the driver if 2533965Sjdp * STA is not using interim accounting. This detects wrap arounds for 2633965Sjdp * input/output octets and updates Acct-{Input,Output}-Gigawords. */ 27104834Sobrien#define ACCT_DEFAULT_UPDATE_INTERVAL 300 2833965Sjdp 2933965Sjdpstatic void accounting_sta_interim(struct hostapd_data *hapd, 3033965Sjdp struct sta_info *sta); 3133965Sjdp 32218822Sdim 3333965Sjdpstatic struct radius_msg * accounting_msg(struct hostapd_data *hapd, 3433965Sjdp struct sta_info *sta, 3533965Sjdp int status_type) 3633965Sjdp{ 3733965Sjdp struct radius_msg *msg; 3833965Sjdp char buf[128]; 39218822Sdim u8 *val; 40130561Sobrien size_t len; 41130561Sobrien int i; 42130561Sobrien struct wpabuf *b; 4333965Sjdp 4433965Sjdp msg = radius_msg_new(RADIUS_CODE_ACCOUNTING_REQUEST, 4533965Sjdp radius_client_get_id(hapd->radius)); 4633965Sjdp if (msg == NULL) { 4733965Sjdp printf("Could not create net RADIUS packet\n"); 4833965Sjdp return NULL; 4933965Sjdp } 5033965Sjdp 5133965Sjdp if (sta) { 5277298Sobrien radius_msg_make_authenticator(msg, (u8 *) sta, sizeof(*sta)); 5333965Sjdp 5433965Sjdp os_snprintf(buf, sizeof(buf), "%08X-%08X", 55218822Sdim sta->acct_session_id_hi, sta->acct_session_id_lo); 56218822Sdim if (!radius_msg_add_attr(msg, RADIUS_ATTR_ACCT_SESSION_ID, 5733965Sjdp (u8 *) buf, os_strlen(buf))) { 5833965Sjdp printf("Could not add Acct-Session-Id\n"); 5933965Sjdp goto fail; 6033965Sjdp } 6133965Sjdp } else { 6233965Sjdp radius_msg_make_authenticator(msg, (u8 *) hapd, sizeof(*hapd)); 63130561Sobrien } 64130561Sobrien 65130561Sobrien if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_STATUS_TYPE, 66218822Sdim status_type)) { 67218822Sdim printf("Could not add Acct-Status-Type\n"); 6833965Sjdp goto fail; 6933965Sjdp } 7033965Sjdp 7133965Sjdp if (!hostapd_config_get_radius_attr(hapd->conf->radius_acct_req_attr, 72130561Sobrien RADIUS_ATTR_ACCT_AUTHENTIC) && 7333965Sjdp !radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_AUTHENTIC, 7489857Sobrien hapd->conf->ieee802_1x ? 7589857Sobrien RADIUS_ACCT_AUTHENTIC_RADIUS : 7689857Sobrien RADIUS_ACCT_AUTHENTIC_LOCAL)) { 7789857Sobrien printf("Could not add Acct-Authentic\n"); 78218822Sdim goto fail; 7989857Sobrien } 8089857Sobrien 81218822Sdim if (sta) { 82218822Sdim /* Use 802.1X identity if available */ 8389857Sobrien val = ieee802_1x_get_identity(sta->eapol_sm, &len); 8489857Sobrien 8589857Sobrien /* Use RADIUS ACL identity if 802.1X provides no identity */ 8689857Sobrien if (!val && sta->identity) { 8789857Sobrien val = (u8 *) sta->identity; 8889857Sobrien len = os_strlen(sta->identity); 8989857Sobrien } 9033965Sjdp 91218822Sdim /* Use STA MAC if neither 802.1X nor RADIUS ACL provided 9260484Sobrien * identity */ 9333965Sjdp if (!val) { 9433965Sjdp os_snprintf(buf, sizeof(buf), RADIUS_ADDR_FORMAT, 9533965Sjdp MAC2STR(sta->addr)); 9633965Sjdp val = (u8 *) buf; 9733965Sjdp len = os_strlen(buf); 9833965Sjdp } 99130561Sobrien 10033965Sjdp if (!radius_msg_add_attr(msg, RADIUS_ATTR_USER_NAME, val, 10133965Sjdp len)) { 102130561Sobrien printf("Could not add User-Name\n"); 10333965Sjdp goto fail; 10433965Sjdp } 10533965Sjdp } 10633965Sjdp 107130561Sobrien if (add_common_radius_attr(hapd, hapd->conf->radius_acct_req_attr, sta, 108130561Sobrien msg) < 0) 109130561Sobrien goto fail; 11033965Sjdp 11133965Sjdp if (sta) { 11233965Sjdp for (i = 0; ; i++) { 11333965Sjdp val = ieee802_1x_get_radius_class(sta->eapol_sm, &len, 11433965Sjdp i); 11533965Sjdp if (val == NULL) 11633965Sjdp break; 11733965Sjdp 11833965Sjdp if (!radius_msg_add_attr(msg, RADIUS_ATTR_CLASS, 11933965Sjdp val, len)) { 12033965Sjdp printf("Could not add Class\n"); 12133965Sjdp goto fail; 122130561Sobrien } 12333965Sjdp } 12433965Sjdp 12533965Sjdp b = ieee802_1x_get_radius_cui(sta->eapol_sm); 12633965Sjdp if (b && 12733965Sjdp !radius_msg_add_attr(msg, 128130561Sobrien RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, 129130561Sobrien wpabuf_head(b), wpabuf_len(b))) { 13033965Sjdp wpa_printf(MSG_ERROR, "Could not add CUI"); 13133965Sjdp goto fail; 13260484Sobrien } 13333965Sjdp 13433965Sjdp if (!b && sta->radius_cui && 13533965Sjdp !radius_msg_add_attr(msg, 13633965Sjdp RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, 13733965Sjdp (u8 *) sta->radius_cui, 13833965Sjdp os_strlen(sta->radius_cui))) { 13933965Sjdp wpa_printf(MSG_ERROR, "Could not add CUI from ACL"); 14033965Sjdp goto fail; 14133965Sjdp } 14233965Sjdp } 14333965Sjdp 144218822Sdim return msg; 14560484Sobrien 14660484Sobrien fail: 14760484Sobrien radius_msg_free(msg); 14833965Sjdp return NULL; 14933965Sjdp} 15033965Sjdp 15133965Sjdp 152218822Sdimstatic int accounting_sta_update_stats(struct hostapd_data *hapd, 153218822Sdim struct sta_info *sta, 154218822Sdim struct hostap_sta_driver_data *data) 155218822Sdim{ 156218822Sdim if (hostapd_drv_read_sta_data(hapd, data, sta->addr)) 157218822Sdim return -1; 158218822Sdim 159218822Sdim if (sta->last_rx_bytes > data->rx_bytes) 160218822Sdim sta->acct_input_gigawords++; 161218822Sdim if (sta->last_tx_bytes > data->tx_bytes) 162218822Sdim sta->acct_output_gigawords++; 163218822Sdim sta->last_rx_bytes = data->rx_bytes; 164218822Sdim sta->last_tx_bytes = data->tx_bytes; 165218822Sdim 166218822Sdim hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS, 167218822Sdim HOSTAPD_LEVEL_DEBUG, "updated TX/RX stats: " 168218822Sdim "Acct-Input-Octets=%lu Acct-Input-Gigawords=%u " 169218822Sdim "Acct-Output-Octets=%lu Acct-Output-Gigawords=%u", 170218822Sdim sta->last_rx_bytes, sta->acct_input_gigawords, 171218822Sdim sta->last_tx_bytes, sta->acct_output_gigawords); 172218822Sdim 17333965Sjdp return 0; 17433965Sjdp} 17533965Sjdp 17633965Sjdp 177218822Sdimstatic void accounting_interim_update(void *eloop_ctx, void *timeout_ctx) 17833965Sjdp{ 17933965Sjdp struct hostapd_data *hapd = eloop_ctx; 18033965Sjdp struct sta_info *sta = timeout_ctx; 18133965Sjdp int interval; 18233965Sjdp 18333965Sjdp if (sta->acct_interim_interval) { 18433965Sjdp accounting_sta_interim(hapd, sta); 18533965Sjdp interval = sta->acct_interim_interval; 18633965Sjdp } else { 18733965Sjdp struct hostap_sta_driver_data data; 18833965Sjdp accounting_sta_update_stats(hapd, sta, &data); 18938889Sjdp interval = ACCT_DEFAULT_UPDATE_INTERVAL; 19033965Sjdp } 19133965Sjdp 19233965Sjdp eloop_register_timeout(interval, 0, accounting_interim_update, 19333965Sjdp hapd, sta); 19433965Sjdp} 19533965Sjdp 19638889Sjdp 19733965Sjdp/** 19833965Sjdp * accounting_sta_start - Start STA accounting 199130561Sobrien * @hapd: hostapd BSS data 200218822Sdim * @sta: The station 201218822Sdim */ 202218822Sdimvoid accounting_sta_start(struct hostapd_data *hapd, struct sta_info *sta) 203218822Sdim{ 20433965Sjdp struct radius_msg *msg; 20533965Sjdp struct os_time t; 20633965Sjdp int interval; 20733965Sjdp 20833965Sjdp if (sta->acct_session_started) 20933965Sjdp return; 21033965Sjdp 21133965Sjdp hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS, 21233965Sjdp HOSTAPD_LEVEL_INFO, 213218822Sdim "starting accounting session %08X-%08X", 214218822Sdim sta->acct_session_id_hi, sta->acct_session_id_lo); 215218822Sdim 216218822Sdim os_get_time(&t); 217218822Sdim sta->acct_session_start = t.sec; 218104834Sobrien sta->last_rx_bytes = sta->last_tx_bytes = 0; 219218822Sdim sta->acct_input_gigawords = sta->acct_output_gigawords = 0; 220218822Sdim hostapd_drv_sta_clear_stats(hapd, sta->addr); 221218822Sdim 222218822Sdim if (!hapd->conf->radius->acct_server) 223218822Sdim return; 224218822Sdim 225218822Sdim if (sta->acct_interim_interval) 226218822Sdim interval = sta->acct_interim_interval; 227218822Sdim else 22833965Sjdp interval = ACCT_DEFAULT_UPDATE_INTERVAL; 229218822Sdim eloop_register_timeout(interval, 0, accounting_interim_update, 230104834Sobrien hapd, sta); 231218822Sdim 232218822Sdim msg = accounting_msg(hapd, sta, RADIUS_ACCT_STATUS_TYPE_START); 233218822Sdim if (msg && 23433965Sjdp radius_client_send(hapd->radius, msg, RADIUS_ACCT, sta->addr) < 0) 235218822Sdim radius_msg_free(msg); 236218822Sdim 237218822Sdim sta->acct_session_started = 1; 23833965Sjdp} 239218822Sdim 240218822Sdim 241218822Sdimstatic void accounting_sta_report(struct hostapd_data *hapd, 242218822Sdim struct sta_info *sta, int stop) 24333965Sjdp{ 244218822Sdim struct radius_msg *msg; 245218822Sdim int cause = sta->acct_terminate_cause; 246218822Sdim struct hostap_sta_driver_data data; 247218822Sdim struct os_time now; 248218822Sdim u32 gigawords; 249218822Sdim 250218822Sdim if (!hapd->conf->radius->acct_server) 25133965Sjdp return; 25233965Sjdp 25333965Sjdp msg = accounting_msg(hapd, sta, 25433965Sjdp stop ? RADIUS_ACCT_STATUS_TYPE_STOP : 25533965Sjdp RADIUS_ACCT_STATUS_TYPE_INTERIM_UPDATE); 25633965Sjdp if (!msg) { 25733965Sjdp printf("Could not create RADIUS Accounting message\n"); 25833965Sjdp return; 25933965Sjdp } 26033965Sjdp 261218822Sdim os_get_time(&now); 26233965Sjdp if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_SESSION_TIME, 263218822Sdim now.sec - sta->acct_session_start)) { 264218822Sdim printf("Could not add Acct-Session-Time\n"); 265218822Sdim goto fail; 26633965Sjdp } 26733965Sjdp 268218822Sdim if (accounting_sta_update_stats(hapd, sta, &data) == 0) { 26933965Sjdp if (!radius_msg_add_attr_int32(msg, 27033965Sjdp RADIUS_ATTR_ACCT_INPUT_PACKETS, 271130561Sobrien data.rx_packets)) { 272218822Sdim printf("Could not add Acct-Input-Packets\n"); 273130561Sobrien goto fail; 274104834Sobrien } 27533965Sjdp if (!radius_msg_add_attr_int32(msg, 276104834Sobrien RADIUS_ATTR_ACCT_OUTPUT_PACKETS, 27733965Sjdp data.tx_packets)) { 27833965Sjdp printf("Could not add Acct-Output-Packets\n"); 279218822Sdim goto fail; 28033965Sjdp } 28133965Sjdp if (!radius_msg_add_attr_int32(msg, 28233965Sjdp RADIUS_ATTR_ACCT_INPUT_OCTETS, 28333965Sjdp data.rx_bytes)) { 28433965Sjdp printf("Could not add Acct-Input-Octets\n"); 28533965Sjdp goto fail; 28633965Sjdp } 28733965Sjdp gigawords = sta->acct_input_gigawords; 28833965Sjdp#if __WORDSIZE == 64 28933965Sjdp gigawords += data.rx_bytes >> 32; 29033965Sjdp#endif 29133965Sjdp if (gigawords && 292218822Sdim !radius_msg_add_attr_int32( 293218822Sdim msg, RADIUS_ATTR_ACCT_INPUT_GIGAWORDS, 294218822Sdim gigawords)) { 295218822Sdim printf("Could not add Acct-Input-Gigawords\n"); 296218822Sdim goto fail; 297218822Sdim } 298218822Sdim if (!radius_msg_add_attr_int32(msg, 299218822Sdim RADIUS_ATTR_ACCT_OUTPUT_OCTETS, 300218822Sdim data.tx_bytes)) { 30133965Sjdp printf("Could not add Acct-Output-Octets\n"); 30233965Sjdp goto fail; 303218822Sdim } 30433965Sjdp gigawords = sta->acct_output_gigawords; 30533965Sjdp#if __WORDSIZE == 64 30633965Sjdp gigawords += data.tx_bytes >> 32; 30733965Sjdp#endif 30833965Sjdp if (gigawords && 30933965Sjdp !radius_msg_add_attr_int32( 31033965Sjdp msg, RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS, 31133965Sjdp gigawords)) { 312218822Sdim printf("Could not add Acct-Output-Gigawords\n"); 313218822Sdim goto fail; 31433965Sjdp } 31533965Sjdp } 31633965Sjdp 317130561Sobrien if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_EVENT_TIMESTAMP, 31833965Sjdp now.sec)) { 319104834Sobrien printf("Could not add Event-Timestamp\n"); 320218822Sdim goto fail; 32133965Sjdp } 32233965Sjdp 32333965Sjdp if (eloop_terminated()) 32460484Sobrien cause = RADIUS_ACCT_TERMINATE_CAUSE_ADMIN_REBOOT; 32560484Sobrien 32660484Sobrien if (stop && cause && 32789857Sobrien !radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_TERMINATE_CAUSE, 32889857Sobrien cause)) { 32989857Sobrien printf("Could not add Acct-Terminate-Cause\n"); 33060484Sobrien goto fail; 33160484Sobrien } 33260484Sobrien 33333965Sjdp if (radius_client_send(hapd->radius, msg, 33433965Sjdp stop ? RADIUS_ACCT : RADIUS_ACCT_INTERIM, 33533965Sjdp sta->addr) < 0) 336218822Sdim goto fail; 337218822Sdim return; 33833965Sjdp 33933965Sjdp fail: 34033965Sjdp radius_msg_free(msg); 341104834Sobrien} 342218822Sdim 34333965Sjdp 344218822Sdim/** 34533965Sjdp * accounting_sta_interim - Send a interim STA accounting report 34633965Sjdp * @hapd: hostapd BSS data 34733965Sjdp * @sta: The station 34833965Sjdp */ 34933965Sjdpstatic void accounting_sta_interim(struct hostapd_data *hapd, 35089857Sobrien struct sta_info *sta) 35133965Sjdp{ 35233965Sjdp if (sta->acct_session_started) 35333965Sjdp accounting_sta_report(hapd, sta, 0); 35433965Sjdp} 355130561Sobrien 35677298Sobrien 35777298Sobrien/** 35877298Sobrien * accounting_sta_stop - Stop STA accounting 359104834Sobrien * @hapd: hostapd BSS data 36077298Sobrien * @sta: The station 361104834Sobrien */ 36277298Sobrienvoid accounting_sta_stop(struct hostapd_data *hapd, struct sta_info *sta) 36377298Sobrien{ 364104834Sobrien if (sta->acct_session_started) { 36577298Sobrien accounting_sta_report(hapd, sta, 1); 366104834Sobrien eloop_cancel_timeout(accounting_interim_update, hapd, sta); 36733965Sjdp hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS, 36833965Sjdp HOSTAPD_LEVEL_INFO, 369104834Sobrien "stopped accounting session %08X-%08X", 37033965Sjdp sta->acct_session_id_hi, 37133965Sjdp sta->acct_session_id_lo); 372130561Sobrien sta->acct_session_started = 0; 37333965Sjdp } 37433965Sjdp} 375130561Sobrien 37633965Sjdp 37789857Sobrienvoid accounting_sta_get_id(struct hostapd_data *hapd, 37833965Sjdp struct sta_info *sta) 37933965Sjdp{ 38033965Sjdp sta->acct_session_id_lo = hapd->acct_session_id_lo++; 38189857Sobrien if (hapd->acct_session_id_lo == 0) { 38233965Sjdp hapd->acct_session_id_hi++; 38333965Sjdp } 38433965Sjdp sta->acct_session_id_hi = hapd->acct_session_id_hi; 385218822Sdim} 386218822Sdim 387218822Sdim 388218822Sdim/** 389218822Sdim * accounting_receive - Process the RADIUS frames from Accounting Server 390218822Sdim * @msg: RADIUS response message 39133965Sjdp * @req: RADIUS request message 39233965Sjdp * @shared_secret: RADIUS shared secret 39333965Sjdp * @shared_secret_len: Length of shared_secret in octets 39433965Sjdp * @data: Context data (struct hostapd_data *) 39533965Sjdp * Returns: Processing status 39633965Sjdp */ 397104834Sobrienstatic RadiusRxResult 398104834Sobrienaccounting_receive(struct radius_msg *msg, struct radius_msg *req, 39933965Sjdp const u8 *shared_secret, size_t shared_secret_len, 40033965Sjdp void *data) 40133965Sjdp{ 40233965Sjdp if (radius_msg_get_hdr(msg)->code != RADIUS_CODE_ACCOUNTING_RESPONSE) { 403218822Sdim printf("Unknown RADIUS message code\n"); 40433965Sjdp return RADIUS_RX_UNKNOWN; 405 } 406 407 if (radius_msg_verify(msg, shared_secret, shared_secret_len, req, 0)) { 408 printf("Incoming RADIUS packet did not have correct " 409 "Authenticator - dropped\n"); 410 return RADIUS_RX_INVALID_AUTHENTICATOR; 411 } 412 413 return RADIUS_RX_PROCESSED; 414} 415 416 417static void accounting_report_state(struct hostapd_data *hapd, int on) 418{ 419 struct radius_msg *msg; 420 421 if (!hapd->conf->radius->acct_server || hapd->radius == NULL) 422 return; 423 424 /* Inform RADIUS server that accounting will start/stop so that the 425 * server can close old accounting sessions. */ 426 msg = accounting_msg(hapd, NULL, 427 on ? RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_ON : 428 RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_OFF); 429 if (!msg) 430 return; 431 432 if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_TERMINATE_CAUSE, 433 RADIUS_ACCT_TERMINATE_CAUSE_NAS_REBOOT)) 434 { 435 printf("Could not add Acct-Terminate-Cause\n"); 436 radius_msg_free(msg); 437 return; 438 } 439 440 if (radius_client_send(hapd->radius, msg, RADIUS_ACCT, NULL) < 0) 441 radius_msg_free(msg); 442} 443 444 445/** 446 * accounting_init: Initialize accounting 447 * @hapd: hostapd BSS data 448 * Returns: 0 on success, -1 on failure 449 */ 450int accounting_init(struct hostapd_data *hapd) 451{ 452 struct os_time now; 453 454 /* Acct-Session-Id should be unique over reboots. If reliable clock is 455 * not available, this could be replaced with reboot counter, etc. */ 456 os_get_time(&now); 457 hapd->acct_session_id_hi = now.sec; 458 459 if (radius_client_register(hapd->radius, RADIUS_ACCT, 460 accounting_receive, hapd)) 461 return -1; 462 463 accounting_report_state(hapd, 1); 464 465 return 0; 466} 467 468 469/** 470 * accounting_deinit: Deinitilize accounting 471 * @hapd: hostapd BSS data 472 */ 473void accounting_deinit(struct hostapd_data *hapd) 474{ 475 accounting_report_state(hapd, 0); 476} 477