[PATCH 6/8] wifi_stats: hostapd control interface and hostapd_cli commands

Iegor Sergieienkov iegor at nova-labs.com
Thu Aug 27 07:16:23 PDT 2026


Add control interface commands for inspecting and changing the wifi_stats
configuration at runtime:

 * WIFI_STATS_STATUS
 * WIFI_STATS_GET_INTERVAL, WIFI_STATS_SET_INTERVAL
 * WIFI_STATS_GET_WINDOW, WIFI_STATS_SET_WINDOW
 * WIFI_STATS_GET_SPAN, WIFI_STATS_SET_SPAN
 * WIFI_STATS_GET_EWMA_WEIGHT, WIFI_STATS_SET_EWMA_WEIGHT
 * WIFI_STATS_GET_METRIC, WIFI_STATS_SET_METRIC
 * WIFI_STATS_CONNECT_INFO <addr>

WIFI_STATS_CONNECT_INFO formats the Connect-Info string for a station
through the same function used for the RADIUS attribute, which makes the
formatting observable without an accounting server being configured.

Matching hostapd_cli commands are added for each of these.

Signed-off-by: Iegor Sergieienkov <iegor at nova-labs.com>
---
 hostapd/ctrl_iface.c  | 374 ++++++++++++++++++++++++++++++++++++++++++
 hostapd/hostapd_cli.c | 117 +++++++++++++
 2 files changed, 491 insertions(+)

diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c
index 380a7bc24..ef5964d8c 100644
--- a/hostapd/ctrl_iface.c
+++ b/hostapd/ctrl_iface.c
@@ -67,6 +67,9 @@
 #include "ap/dfs.h"
 #include "ap/nan_usd_ap.h"
 #include "ap/robust_av.h"
+#ifdef CONFIG_WIFI_STATS
+#include "wifi_stats/wifi_stats.h"
+#endif /* CONFIG_WIFI_STATS */
 #include "wps/wps_defs.h"
 #include "wps/wps.h"
 #include "fst/fst_ctrl_iface.h"
@@ -1490,6 +1493,342 @@ static int hostapd_ctrl_iface_set(struct hostapd_data *hapd, char *cmd)
 }
 
 
+#ifdef CONFIG_WIFI_STATS
+static int hostapd_ctrl_iface_wifi_stats_get_uint(unsigned int val, char *buf,
+						  size_t buflen)
+{
+	int ret;
+
+	ret = os_snprintf(buf, buflen, "%u\n", val);
+	if (os_snprintf_error(buflen, ret))
+		return -1;
+	return ret;
+}
+
+
+static int hostapd_ctrl_iface_wifi_stats_parse_uint(const char *cmd,
+						     unsigned int min,
+						     unsigned int max,
+						     const char *label,
+						     unsigned long *val)
+{
+	char *endptr;
+
+	*val = strtoul(cmd, &endptr, 10);
+	if (endptr == cmd || *endptr != '\0' || *val < min || *val > max) {
+		wpa_printf(MSG_DEBUG,
+			   "Invalid wifi_stats %s: %s (must be %u-%u)",
+			   label, cmd, min, max);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static int hostapd_ctrl_iface_wifi_stats_set_interval(struct hostapd_data *hapd,
+						      char *cmd)
+{
+	unsigned long val;
+
+	if (!hapd->iface->wifi_stats) {
+		wpa_printf(MSG_ERROR, "wifi_stats not initialized");
+		return -1;
+	}
+
+	if (hostapd_ctrl_iface_wifi_stats_parse_uint(
+		    cmd, WIFI_STATS_MIN_INTERVAL, WIFI_STATS_MAX_INTERVAL,
+		    "interval", &val) < 0)
+		return -1;
+
+	if (wifi_stats_set_interval(hapd->iface->wifi_stats,
+				    (unsigned int)val) < 0) {
+		wpa_printf(MSG_ERROR, "Failed to set wifi_stats interval");
+		return -1;
+	}
+
+	hapd->iconf->wifi_stats_interval = (unsigned int)val;
+
+	return 0;
+}
+
+
+static int hostapd_ctrl_iface_wifi_stats_get_interval(struct hostapd_data *hapd,
+						      char *buf, size_t buflen)
+{
+	if (!hapd->iface->wifi_stats)
+		return -1;
+
+	return hostapd_ctrl_iface_wifi_stats_get_uint(
+		wifi_stats_get_interval(hapd->iface->wifi_stats), buf, buflen);
+}
+
+
+static int hostapd_ctrl_iface_wifi_stats_set_window(struct hostapd_data *hapd,
+						    char *cmd)
+{
+	unsigned long val;
+
+	if (!hapd->iface->wifi_stats) {
+		wpa_printf(MSG_ERROR, "wifi_stats not initialized");
+		return -1;
+	}
+
+	if (hostapd_ctrl_iface_wifi_stats_parse_uint(
+		    cmd, WIFI_STATS_MIN_WINDOW, WIFI_STATS_MAX_WINDOW,
+		    "window", &val) < 0)
+		return -1;
+
+	if (wifi_stats_set_window(hapd->iface->wifi_stats,
+				  (unsigned int)val) < 0) {
+		wpa_printf(MSG_ERROR, "Failed to set wifi_stats window");
+		return -1;
+	}
+
+	hapd->iconf->wifi_stats_wba_window = (unsigned int)val;
+
+	return 0;
+}
+
+
+static int hostapd_ctrl_iface_wifi_stats_get_window(struct hostapd_data *hapd,
+						    char *buf, size_t buflen)
+{
+	if (!hapd->iface->wifi_stats)
+		return -1;
+
+	return hostapd_ctrl_iface_wifi_stats_get_uint(
+		wifi_stats_get_window(hapd->iface->wifi_stats), buf, buflen);
+}
+
+
+static int hostapd_ctrl_iface_wifi_stats_set_metric(struct hostapd_data *hapd,
+						    char *cmd)
+{
+	char *pos;
+	wifi_stats_metric_type_t metric;
+	wifi_stats_agg_type_t algorithm;
+
+	if (!hapd->iface->wifi_stats) {
+		wpa_printf(MSG_ERROR, "wifi_stats not initialized");
+		return -1;
+	}
+
+	pos = os_strchr(cmd, ' ');
+	if (!pos)
+		return -1;
+	*pos++ = '\0';
+
+	metric = wifi_stats_metric_type_from_str(cmd);
+	if (metric >= WIFI_STATS_METRIC_COUNT) {
+		wpa_printf(MSG_DEBUG, "Unknown metric: %s", cmd);
+		return -1;
+	}
+
+	algorithm = wifi_stats_agg_type_from_str(pos);
+	if (algorithm >= WIFI_STATS_AGG_COUNT) {
+		wpa_printf(MSG_DEBUG, "Unknown algorithm: %s", pos);
+		return -1;
+	}
+
+	if (wifi_stats_set_metric_config(hapd->iface->wifi_stats,
+					 metric, algorithm) < 0)
+		return -1;
+
+	hapd->iconf->wifi_stats_metrics[metric].algorithm = algorithm;
+	hapd->iconf->wifi_stats_metrics[metric].configured = 1;
+
+	return 0;
+}
+
+
+static int hostapd_ctrl_iface_wifi_stats_get_metric(struct hostapd_data *hapd,
+						    char *cmd, char *buf,
+						    size_t buflen)
+{
+	wifi_stats_metric_type_t metric;
+	int ret;
+
+	if (!hapd->iface->wifi_stats)
+		return -1;
+
+	metric = wifi_stats_metric_type_from_str(cmd);
+	if (metric >= WIFI_STATS_METRIC_COUNT)
+		return -1;
+
+	ret = os_snprintf(buf, buflen, "%s\n",
+			  wifi_stats_agg_type_to_str(
+				  wifi_stats_get_metric_config(
+					  hapd->iface->wifi_stats, metric)));
+	if (os_snprintf_error(buflen, ret))
+		return -1;
+	return ret;
+}
+
+
+static int hostapd_ctrl_iface_wifi_stats_set_span(struct hostapd_data *hapd,
+						  char *cmd)
+{
+	wifi_stats_span_mode_t mode;
+
+	if (!hapd->iface->wifi_stats) {
+		wpa_printf(MSG_ERROR, "wifi_stats not initialized");
+		return -1;
+	}
+
+	mode = wifi_stats_span_mode_from_str(cmd);
+	if (mode >= WIFI_STATS_SPAN_COUNT) {
+		wpa_printf(MSG_DEBUG,
+			   "Invalid wifi_stats span: %s (must be report or window)",
+			   cmd);
+		return -1;
+	}
+
+	if (wifi_stats_set_span_mode(hapd->iface->wifi_stats, mode) < 0) {
+		wpa_printf(MSG_ERROR, "Failed to set wifi_stats span");
+		return -1;
+	}
+
+	hapd->iconf->wifi_stats_span = mode;
+
+	return 0;
+}
+
+
+static int hostapd_ctrl_iface_wifi_stats_get_span(struct hostapd_data *hapd,
+						  char *buf, size_t buflen)
+{
+	int ret;
+
+	if (!hapd->iface->wifi_stats)
+		return -1;
+
+	ret = os_snprintf(buf, buflen, "%s\n",
+			  wifi_stats_span_mode_to_str(
+				  wifi_stats_get_span_mode(hapd->iface->wifi_stats)));
+	if (os_snprintf_error(buflen, ret))
+		return -1;
+	return ret;
+}
+
+
+static int hostapd_ctrl_iface_wifi_stats_set_ewma_weight(
+	struct hostapd_data *hapd, char *cmd)
+{
+	unsigned long val;
+
+	if (!hapd->iface->wifi_stats) {
+		wpa_printf(MSG_ERROR, "wifi_stats not initialized");
+		return -1;
+	}
+
+	if (hostapd_ctrl_iface_wifi_stats_parse_uint(
+		    cmd, WIFI_STATS_MIN_EWMA_WEIGHT, WIFI_STATS_MAX_EWMA_WEIGHT,
+		    "ewma weight", &val) < 0)
+		return -1;
+
+	if (wifi_stats_set_ewma_weight(hapd->iface->wifi_stats,
+				       (unsigned int)val) < 0) {
+		wpa_printf(MSG_ERROR, "Failed to set wifi_stats ewma weight");
+		return -1;
+	}
+
+	hapd->iconf->wifi_stats_ewma_weight = (unsigned int)val;
+
+	return 0;
+}
+
+
+static int hostapd_ctrl_iface_wifi_stats_get_ewma_weight(
+	struct hostapd_data *hapd, char *buf, size_t buflen)
+{
+	if (!hapd->iface->wifi_stats)
+		return -1;
+
+	return hostapd_ctrl_iface_wifi_stats_get_uint(
+		wifi_stats_get_ewma_weight(hapd->iface->wifi_stats), buf,
+		buflen);
+}
+
+
+static int hostapd_ctrl_iface_wifi_stats_status(struct hostapd_data *hapd,
+						char *buf, size_t buflen)
+{
+	char *pos, *end;
+	int written;
+	int i;
+
+	if (!hapd->iface->wifi_stats)
+		return -1;
+
+	pos = buf;
+	end = buf + buflen;
+
+	written = os_snprintf(pos, end - pos,
+			      "interval=%u\nwindow=%u\nwba_enabled=%d\nspan=%s\newma_weight=%u\n",
+			      wifi_stats_get_interval(hapd->iface->wifi_stats),
+			      wifi_stats_get_window(hapd->iface->wifi_stats),
+			      hapd->iconf->wifi_stats_wba_enabled,
+			      wifi_stats_span_mode_to_str(
+				      wifi_stats_get_span_mode(hapd->iface->wifi_stats)),
+			      wifi_stats_get_ewma_weight(hapd->iface->wifi_stats));
+	if (os_snprintf_error(end - pos, written))
+		return -1;
+	pos += written;
+
+	for (i = 0; i < WIFI_STATS_METRIC_COUNT; i++) {
+		written = os_snprintf(pos, end - pos, "%s=%s\n",
+				      wifi_stats_metric_type_to_str(i),
+				      wifi_stats_agg_type_to_str(
+					      wifi_stats_get_metric_config(
+						      hapd->iface->wifi_stats, i)));
+		if (os_snprintf_error(end - pos, written))
+			return -1;
+		pos += written;
+	}
+
+	return pos - buf;
+}
+
+
+static int hostapd_ctrl_iface_wifi_stats_connect_info(struct hostapd_data *hapd,
+						      char *cmd, char *buf,
+						      size_t buflen)
+{
+	u8 addr[ETH_ALEN];
+	char conn_info[WIFI_STATS_MAX_CONN_INFO_LEN + 1];
+	struct sta_info *sta;
+	int written;
+
+	if (!hapd->iface->wifi_stats)
+		return -1;
+
+	if (hwaddr_aton(cmd, addr)) {
+		wpa_printf(MSG_DEBUG, "wifi_stats: Invalid MAC address: %s",
+			   cmd);
+		return -1;
+	}
+
+	sta = ap_get_sta(hapd, addr);
+	if (!sta) {
+		wpa_printf(MSG_DEBUG,
+			   "wifi_stats: Station " MACSTR " not found for Connect-Info query",
+			   MAC2STR(addr));
+		return -1;
+	}
+
+	if (wifi_stats_format_connection_info(hapd->iface->wifi_stats, hapd,
+					      sta, addr, conn_info,
+					      sizeof(conn_info)) < 0)
+		return -1;
+
+	written = os_snprintf(buf, buflen, "%s\n", conn_info);
+	if (os_snprintf_error(buflen, written))
+		return -1;
+	return written;
+}
+#endif /* CONFIG_WIFI_STATS */
+
 static int hostapd_ctrl_iface_get(struct hostapd_data *hapd, char *cmd,
 				  char *buf, size_t buflen)
 {
@@ -4854,6 +5193,41 @@ static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd,
 		if (hostapd_ctrl_afc_send_request(hapd, buf + 16) < 0)
 			reply_len = -1;
 #endif /* CONFIG_AFC */
+#ifdef CONFIG_WIFI_STATS
+	} else if (os_strncmp(buf, "WIFI_STATS_SET_INTERVAL ", 24) == 0) {
+		if (hostapd_ctrl_iface_wifi_stats_set_interval(hapd, buf + 24) < 0)
+			reply_len = -1;
+	} else if (os_strcmp(buf, "WIFI_STATS_GET_INTERVAL") == 0) {
+		reply_len = hostapd_ctrl_iface_wifi_stats_get_interval(
+			hapd, reply, reply_size);
+	} else if (os_strncmp(buf, "WIFI_STATS_SET_WINDOW ", 22) == 0) {
+		if (hostapd_ctrl_iface_wifi_stats_set_window(hapd, buf + 22) < 0)
+			reply_len = -1;
+	} else if (os_strcmp(buf, "WIFI_STATS_GET_WINDOW") == 0) {
+		reply_len = hostapd_ctrl_iface_wifi_stats_get_window(hapd, reply, reply_size);
+	} else if (os_strncmp(buf, "WIFI_STATS_SET_METRIC ", 22) == 0) {
+		if (hostapd_ctrl_iface_wifi_stats_set_metric(hapd, buf + 22) < 0)
+			reply_len = -1;
+	} else if (os_strncmp(buf, "WIFI_STATS_GET_METRIC ", 22) == 0) {
+		reply_len = hostapd_ctrl_iface_wifi_stats_get_metric(
+			hapd, buf + 22, reply, reply_size);
+	} else if (os_strncmp(buf, "WIFI_STATS_SET_SPAN ", 20) == 0) {
+		if (hostapd_ctrl_iface_wifi_stats_set_span(hapd, buf + 20) < 0)
+			reply_len = -1;
+	} else if (os_strcmp(buf, "WIFI_STATS_GET_SPAN") == 0) {
+		reply_len = hostapd_ctrl_iface_wifi_stats_get_span(hapd, reply, reply_size);
+	} else if (os_strncmp(buf, "WIFI_STATS_SET_EWMA_WEIGHT ", 27) == 0) {
+		if (hostapd_ctrl_iface_wifi_stats_set_ewma_weight(hapd, buf + 27) < 0)
+			reply_len = -1;
+	} else if (os_strcmp(buf, "WIFI_STATS_GET_EWMA_WEIGHT") == 0) {
+		reply_len = hostapd_ctrl_iface_wifi_stats_get_ewma_weight(
+			hapd, reply, reply_size);
+	} else if (os_strcmp(buf, "WIFI_STATS_STATUS") == 0) {
+		reply_len = hostapd_ctrl_iface_wifi_stats_status(hapd, reply, reply_size);
+	} else if (os_strncmp(buf, "WIFI_STATS_CONNECT_INFO ", 24) == 0) {
+		reply_len = hostapd_ctrl_iface_wifi_stats_connect_info(
+			hapd, buf + 24, reply, reply_size);
+#endif /* CONFIG_WIFI_STATS */
 	} else {
 		os_memcpy(reply, "UNKNOWN COMMAND\n", 16);
 		reply_len = 16;
diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c
index cf35241fd..b3a9c9bfb 100644
--- a/hostapd/hostapd_cli.c
+++ b/hostapd/hostapd_cli.c
@@ -1764,6 +1764,92 @@ static int hostapd_cli_cmd_driver(struct wpa_ctrl *ctrl, int argc, char *argv[])
 }
 #endif /* ANDROID */
 
+#ifdef CONFIG_WIFI_STATS
+static int hostapd_cli_cmd_wifi_stats_status(struct wpa_ctrl *ctrl, int argc,
+					     char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "WIFI_STATS_STATUS");
+}
+
+
+static int hostapd_cli_cmd_wifi_stats_get_interval(struct wpa_ctrl *ctrl,
+						   int argc, char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "WIFI_STATS_GET_INTERVAL");
+}
+
+
+static int hostapd_cli_cmd_wifi_stats_set_interval(struct wpa_ctrl *ctrl,
+						   int argc, char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "WIFI_STATS_SET_INTERVAL", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_wifi_stats_get_window(struct wpa_ctrl *ctrl,
+						 int argc, char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "WIFI_STATS_GET_WINDOW");
+}
+
+
+static int hostapd_cli_cmd_wifi_stats_set_window(struct wpa_ctrl *ctrl,
+						 int argc, char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "WIFI_STATS_SET_WINDOW", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_wifi_stats_get_span(struct wpa_ctrl *ctrl,
+					       int argc, char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "WIFI_STATS_GET_SPAN");
+}
+
+
+static int hostapd_cli_cmd_wifi_stats_set_span(struct wpa_ctrl *ctrl,
+					       int argc, char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "WIFI_STATS_SET_SPAN", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_wifi_stats_get_ewma_weight(struct wpa_ctrl *ctrl,
+						      int argc, char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "WIFI_STATS_GET_EWMA_WEIGHT");
+}
+
+
+static int hostapd_cli_cmd_wifi_stats_set_ewma_weight(struct wpa_ctrl *ctrl,
+						      int argc, char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "WIFI_STATS_SET_EWMA_WEIGHT", 1, argc,
+			       argv);
+}
+
+
+static int hostapd_cli_cmd_wifi_stats_get_metric(struct wpa_ctrl *ctrl,
+						 int argc, char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "WIFI_STATS_GET_METRIC", 1, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_wifi_stats_set_metric(struct wpa_ctrl *ctrl,
+						 int argc, char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "WIFI_STATS_SET_METRIC", 2, argc, argv);
+}
+
+
+static int hostapd_cli_cmd_wifi_stats_connect_info(struct wpa_ctrl *ctrl,
+						   int argc, char *argv[])
+{
+	return hostapd_cli_cmd(ctrl, "WIFI_STATS_CONNECT_INFO", 1, argc, argv);
+}
+#endif /* CONFIG_WIFI_STATS */
+
 
 #ifdef CONFIG_AFC
 
@@ -2046,6 +2132,37 @@ static const struct hostapd_cli_cmd hostapd_cli_commands[] = {
 	{ "afc_send_request", hostapd_cli_cmd_afc_send_request, NULL,
 	  " = Send another AFC request and upadte the timeout"},
 #endif /* CONFIG_AFC */
+#ifdef CONFIG_WIFI_STATS
+	{ "wifi_stats_status", hostapd_cli_cmd_wifi_stats_status, NULL,
+	  "= show wifi_stats configuration" },
+	{ "wifi_stats_get_interval", hostapd_cli_cmd_wifi_stats_get_interval, NULL,
+	  "= get stats collection interval" },
+	{ "wifi_stats_set_interval", hostapd_cli_cmd_wifi_stats_set_interval, NULL,
+	  "<seconds> = set stats collection interval (1-3600)" },
+	{ "wifi_stats_get_window", hostapd_cli_cmd_wifi_stats_get_window, NULL,
+	  "= get aggregation window" },
+	{ "wifi_stats_set_window", hostapd_cli_cmd_wifi_stats_set_window, NULL,
+	  "<seconds> = set aggregation window (1-3600)" },
+	{ "wifi_stats_get_span", hostapd_cli_cmd_wifi_stats_get_span, NULL,
+	  "= get aggregation span mode" },
+	{ "wifi_stats_set_span", hostapd_cli_cmd_wifi_stats_set_span, NULL,
+	  "<report|window> = set aggregation span mode" },
+	{ "wifi_stats_get_ewma_weight",
+	  hostapd_cli_cmd_wifi_stats_get_ewma_weight, NULL,
+	  "= get AVG-EXP exponential weight" },
+	{ "wifi_stats_set_ewma_weight",
+	  hostapd_cli_cmd_wifi_stats_set_ewma_weight, NULL,
+	  "<weight> = set AVG-EXP exponential weight (1-9, alpha=1/2^weight)" },
+	{ "wifi_stats_get_metric",
+	  hostapd_cli_cmd_wifi_stats_get_metric, NULL,
+	  "<metric> = get algorithm for metric "
+	  "(RxBitRate|TxBitRate|RSSI|FrameLoss|FrameRetry)" },
+	{ "wifi_stats_set_metric", hostapd_cli_cmd_wifi_stats_set_metric, NULL,
+	  "<metric> <algorithm> = set algorithm (MIN|MAX|AVG-LIN|AVG-EXP|ACC|NONE)" },
+	{ "wifi_stats_connect_info", hostapd_cli_cmd_wifi_stats_connect_info,
+	  hostapd_complete_stations,
+	  "<addr> = show Connect-Info string for a station" },
+#endif /* CONFIG_WIFI_STATS */
 	{ NULL, NULL, NULL, NULL }
 };
 
-- 
2.43.0




More information about the Hostap mailing list