[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