]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
authsrv: Log RADIUS accounting data
authorDávid Benko <davidbenko@davidbenko.dev>
Sun, 23 Feb 2025 22:42:39 +0000 (23:42 +0100)
committerJouni Malinen <j@w1.fi>
Sun, 2 Mar 2025 17:30:13 +0000 (19:30 +0200)
Add option to log all received RADIUS accounting information. This is
a follow-up patch for a new `acct_req_cb` in RADIUS server implementation.

The 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@davidbenko.dev>
hostapd/config_file.c
hostapd/hostapd.conf
src/ap/ap_config.h
src/ap/authsrv.c

index a3cc57ac63cccc0088bf49f58956c07f829f0eef..c52adb8f2c2af17771afeccc08e99280ea352440 100644 (file)
@@ -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 */
index 2cd11f419ff52b46686ca48fa9e78a7196abe2d0..97a4dc38da10d45dca14c5cb68aa533c55f3fb49 100644 (file)
@@ -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
 
index 4a760eedee020100570355cb22ca60d5ac88d4b8..2b6cf4acfe3537e1a44e672027d45d0c1c7f5869 100644 (file)
@@ -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
index 27c9f3f5882e4b52a65ea811a6ccf01fb415307f..3e8031810f4566599c898570df03a86e5631cbe3 100644 (file)
@@ -14,6 +14,7 @@
 #include "eap_server/eap.h"
 #include "eap_server/eap_sim_db.h"
 #include "eapol_auth/eapol_auth_sm.h"
+#include "radius/radius.h"
 #include "radius/radius_server.h"
 #include "hostapd.h"
 #include "ap_config.h"
@@ -101,6 +102,114 @@ 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)
+{
+       char nas_id[RADIUS_MAX_ATTR_LEN + 1] = "";
+       char session_id[RADIUS_MAX_ATTR_LEN + 1] = "";
+       char username[RADIUS_MAX_ATTR_LEN + 1] = "";
+       char calling_station_id[3 * ETH_ALEN] = "";
+       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;
+       unsigned long long total_bytes_in = 0, total_bytes_out = 0;
+
+       /* Parse NAS identification (required by RFC 2866, section 4.1) */
+       if (radius_msg_get_attr(msg, RADIUS_ATTR_NAS_IDENTIFIER, (u8 *) nas_id,
+                               sizeof(nas_id) - 1))
+               nas_id[0] = '\0';
+
+       /* 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) */
+       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 */
+       radius_msg_get_attr(msg, RADIUS_ATTR_USER_NAME, (u8 *) username,
+                           sizeof(username) - 1);
+
+       /* Parse device identifier */
+       radius_msg_get_attr(msg, RADIUS_ATTR_CALLING_STATION_ID,
+                           (u8 *) calling_station_id,
+                           sizeof(calling_station_id) - 1);
+
+       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=%u term_cause=%u pck_in=%u pck_out=%u bytes_in=%llu bytes_out=%llu",
+                          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 %u",
+                          status_type);
+               return -1;
+       }
+
+       return 0;
+}
+
+
 static int hostapd_setup_radius_srv(struct hostapd_data *hapd)
 {
        struct radius_server_conf srv;
@@ -128,6 +237,8 @@ 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;
+       if (conf->radius_server_acct_log)
+               srv.acct_req_cb = hostapd_radius_log_acct_req;
        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;