[PATCH 2/2] hostapd, AP: log RADIUS accounting data

Dávid Benko davidbenko at davidbenko.dev
Sun Feb 23 14:42:39 PST 2025


Adds option to log all received RADIUS accounting information. This is
a follow-up patch for a new `acct_req_cb` in RADIUS server implementation
(f8de6f103768a2a00765a0ad2294c70e7f503a69).

Proposed callback logs all accounting status codes. Invalid requests are
discarded as of RFC 2866. Logged data include:
- NAS identification (NAS-Identifier, NAS-IP-Address or NAS-IPv6-Address)
- session ID (Acct-Session-Id)
- username
- device identification (Calling-Station-Id)
- session time
- input/output packet and byte counters (including gigawords as of RFC 2869)

This may be a base for possible extensions of RADIUS accounting in hostapd.
However, since there are far more robust alternatives (namely FreeRADIUS) and
hostapd is primarily used for restricted and/or simple deployments, I don't
consider them necessary. Other use cases can be covered by a custom
reimplementation of binary and a different `acct_req_cb` callback.

Signed-off-by: Dávid Benko <davidbenko at davidbenko.dev>
---
 hostapd/config_file.c |   2 +
 hostapd/hostapd.conf  |   3 +
 src/ap/ap_config.h    |   1 +
 src/ap/authsrv.c      | 126 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 132 insertions(+)

diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index a3cc57ac6..c52adb8f2 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -3149,6 +3149,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		bss->radius_server_auth_port = atoi(pos);
 	} else if (os_strcmp(buf, "radius_server_acct_port") == 0) {
 		bss->radius_server_acct_port = atoi(pos);
+	} else if (os_strcmp(buf, "radius_server_acct_log") == 0) {
+		bss->radius_server_acct_log = atoi(pos);
 	} else if (os_strcmp(buf, "radius_server_ipv6") == 0) {
 		bss->radius_server_ipv6 = atoi(pos);
 #endif /* RADIUS_SERVER */
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 2cd11f419..97a4dc38d 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -1798,6 +1798,9 @@ own_ip_addr=127.0.0.1
 # accounting while still enabling RADIUS authentication.
 #radius_server_acct_port=1813
 
+# Log received RADIUS accounting data
+#radius_server_acct_log=1
+
 # Use IPv6 with RADIUS server (IPv4 will also be supported using IPv6 API)
 #radius_server_ipv6=1
 
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 4a760eede..2b6cf4acf 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -467,6 +467,7 @@ struct hostapd_bss_config {
 	char *radius_server_clients;
 	int radius_server_auth_port;
 	int radius_server_acct_port;
+	int radius_server_acct_log;
 	int radius_server_ipv6;
 
 	int use_pae_group_addr; /* Whether to send EAPOL frames to PAE group
diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c
index 27c9f3f58..c5aba29f3 100644
--- a/src/ap/authsrv.c
+++ b/src/ap/authsrv.c
@@ -6,6 +6,8 @@
  * See README for more details.
  */
 
+#include <inttypes.h>
+
 #include "utils/includes.h"
 
 #include "utils/common.h"
@@ -101,6 +103,127 @@ out:
 }
 
 
+/**
+ * hostapd_radius_log_acct_req - Callback for logging received RADIUS
+ * accounting requests
+ * @ctx: Context (struct hostapd_data)
+ * @msg: Received RADIUS accounting request
+ * @status_type: Status type from the message (parsed Acct-Status-Type
+ * attribute)
+ * Returns: 0 on success, -1 on failure
+ */
+static int hostapd_radius_log_acct_req(void *ctx, struct radius_msg *msg,
+				       u32 status_type)
+{
+	/* Parse NAS identification (required by RFC 2866, section 4.1) */
+	char nas_id[RADIUS_MAX_ATTR_LEN + 1] = "";
+	if (radius_msg_get_attr(msg, RADIUS_ATTR_NAS_IDENTIFIER, (u8 *) nas_id,
+				sizeof(nas_id) - 1) == 0 &&
+	    radius_msg_get_attr(msg, RADIUS_ATTR_NAS_IP_ADDRESS, (u8 *) nas_id,
+				sizeof(nas_id) - 1) == 0 &&
+	    radius_msg_get_attr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS,
+				(u8 *) nas_id, sizeof(nas_id) - 1) == 0) {
+		wpa_printf(MSG_DEBUG,
+			   "RADIUS ACCT: request doesn't identify NAS");
+		return -1;
+	}
+
+	/* Process Accounting-On and Accounting-Off messages separately */
+	if (status_type == RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_ON ||
+	    status_type == RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_OFF) {
+		wpa_printf(MSG_INFO, "RADIUS ACCT: NAS='%s' status='%s'",
+			   nas_id,
+			   status_type == RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_ON
+				   ? "Accounting-On"
+				   : "Accounting-Off");
+		return 0;
+	}
+
+	/* Parse session ID (required by RFC 2866, section 5.5) */
+	char session_id[RADIUS_MAX_ATTR_LEN + 1] = "";
+	if (radius_msg_get_attr(msg, RADIUS_ATTR_ACCT_SESSION_ID,
+				(u8 *) session_id,
+				sizeof(session_id) - 1) == 0) {
+		wpa_printf(MSG_DEBUG,
+			   "RADIUS ACCT: request doesn't include session ID");
+		return -1;
+	}
+
+	/* Parse user name */
+	char username[RADIUS_MAX_ATTR_LEN + 1] = "";
+	radius_msg_get_attr(msg, RADIUS_ATTR_USER_NAME, (u8 *) username,
+			    sizeof(username) - 1);
+
+	/* Parse device identifier */
+	char calling_station_id[3 * ETH_ALEN] = "";
+	radius_msg_get_attr(msg, RADIUS_ATTR_CALLING_STATION_ID,
+			    (u8 *) calling_station_id,
+			    sizeof(calling_station_id) - 1);
+
+	u32 session_time = 0, terminate_cause = 0,
+	    bytes_in = 0, bytes_out = 0,
+	    packets_in = 0, packets_out = 0,
+	    gigawords_in = 0, gigawords_out = 0;
+	u64 total_bytes_in = 0, total_bytes_out = 0;
+
+	switch (status_type)
+	{
+	case RADIUS_ACCT_STATUS_TYPE_START:
+		wpa_printf(MSG_INFO, "RADIUS ACCT: NAS='%s' session='%s' "
+			   "status='Accounting-Start' station='%s' "
+			   "username='%s'", nas_id, session_id,
+			   calling_station_id, username);
+		break;
+	case RADIUS_ACCT_STATUS_TYPE_STOP:
+	case RADIUS_ACCT_STATUS_TYPE_INTERIM_UPDATE:
+		/* Parse counters */
+		radius_msg_get_attr_int32(msg, RADIUS_ATTR_ACCT_SESSION_TIME,
+					  &session_time);
+		radius_msg_get_attr_int32(msg,
+					  RADIUS_ATTR_ACCT_TERMINATE_CAUSE,
+					  &terminate_cause);
+		radius_msg_get_attr_int32(msg, RADIUS_ATTR_ACCT_INPUT_OCTETS,
+					  &bytes_in);
+		radius_msg_get_attr_int32(msg, RADIUS_ATTR_ACCT_OUTPUT_OCTETS,
+					  &bytes_out);
+		radius_msg_get_attr_int32(msg, RADIUS_ATTR_ACCT_INPUT_PACKETS,
+					  &packets_in);
+		radius_msg_get_attr_int32(msg, RADIUS_ATTR_ACCT_OUTPUT_PACKETS,
+					  &packets_out);
+		radius_msg_get_attr_int32(msg,
+					  RADIUS_ATTR_ACCT_INPUT_GIGAWORDS,
+					  &gigawords_in);
+		radius_msg_get_attr_int32(msg,
+					  RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS,
+					  &gigawords_out);
+
+		/* RFC 2869, section 5.1 and 5.2 */
+		total_bytes_in = ((u64) gigawords_in << 32) + bytes_in;
+		total_bytes_out = ((u64) gigawords_out << 32) + bytes_out;
+
+		wpa_printf(MSG_INFO, "RADIUS ACCT: NAS='%s' session='%s' "
+			   "status='%s' station='%s' username='%s' "
+			   "session_time=%" PRIu32 " term_cause=%" PRIu32 " "
+			   "pck_in=%" PRIu32 " pck_out=%" PRIu32 " "
+			   "bytes_in=%" PRIu64 " bytes_out=%" PRIu64,
+			   nas_id, session_id,
+			   status_type == RADIUS_ACCT_STATUS_TYPE_STOP
+				   ? "Accounting-Stop"
+				   : "Accounting-Interim-Update",
+			   calling_station_id, username, session_time,
+			   terminate_cause, packets_in, packets_out,
+			   total_bytes_in, total_bytes_out);
+		break;
+	default:
+		wpa_printf(MSG_DEBUG, "RADIUS ACCT: unknown request status "
+			   "type %" PRIu32, status_type);
+		return -1;
+	}
+
+	return 0;
+}
+
+
 static int hostapd_setup_radius_srv(struct hostapd_data *hapd)
 {
 	struct radius_server_conf srv;
@@ -128,6 +251,9 @@ static int hostapd_setup_radius_srv(struct hostapd_data *hapd)
 	srv.conf_ctx = hapd;
 	srv.ipv6 = conf->radius_server_ipv6;
 	srv.get_eap_user = hostapd_radius_get_eap_user;
+	srv.acct_req_cb = conf->radius_server_acct_log
+		? hostapd_radius_log_acct_req
+		: NULL;
 	srv.eap_req_id_text = conf->eap_req_id_text;
 	srv.eap_req_id_text_len = conf->eap_req_id_text_len;
 	srv.sqlite_file = conf->eap_user_sqlite;
-- 
2.25.1




More information about the Hostap mailing list