[PATCH usteer v2] policy: add station policy exemption list
AndreaCovelli
andcov23 at gmail.com
Thu Sep 10 09:03:14 PDT 2026
Allow users to configure station MAC addresses that should be exempt
from steering policy decisions.
Infrastructure links such as WDS or Multi-AP backhaul stations can
be associated to the same ESS as normal clients. Steering or kicking
those stations can disrupt downstream AP connectivity, while splitting
the backhaul SSID is not always desirable.
Setting per-station aggressiveness to zero disables active roaming and
band steering, but does not exempt stations from request admission,
low-signal or load kicks, or link measurement requests.
Add a policy_exempt_sta_list config array and skip matching stations
in request steering, roam transitions, delayed kicks, SNR/load kicks,
link measurement requests and BSS transition query handling. Keep policy
checks at the ubus action layer as a final guard. Exempt stations remain
tracked and visible in ubus status output with a policy_exempt flag.
An empty list preserves the existing steering behavior.
Signed-off-by: AndreaCovelli <andcov23 at gmail.com>
---
Changes in v2:
- Refresh against 7532e1252fb937fe34b6da05e86fd34babd91ef5, preserving the
aggressiveness configuration and updated kick/BSS-transition APIs.
- Submit the policy exemption independently of the APK date-version fix.
- Explain the coverage gap relative to aggressiveness=0.
Validation on 2026-09-10:
- Built all CMake targets with the cached OpenWrt 25.12.5 SDK toolchain
and staged dependencies, mediatek/filogic, GCC 14.3.0, using the
project's -Wall -Werror -Wmissing-declarations flags.
- Patch parsing, application to the exact base and whitespace checks.
I tested the refreshed v2 successfully on Cudy WR3000P.
Previous submission:
https://patchwork.ozlabs.org/project/openwrt/patch/20260503231728.135249-2-andcov23@gmail.com/
local_node.c | 6 ++++
openwrt/usteer/files/etc/config/usteer | 4 +++
openwrt/usteer/files/etc/init.d/usteer | 1 +
policy.c | 19 +++++++++++++
sta.c | 45 ++++++++++++++++++++++++++++++
ubus.c | 50 ++++++++++++++++++++++++++++++----
usteer.h | 5 ++++
7 files changed, 124 insertions(+), 6 deletions(-)
diff --git a/local_node.c b/local_node.c
index d90e4a5b0357c97505230744575f4b2a4fd7c496..570868cddf0cb58e939467278bece683661cf99f 100644
--- a/local_node.c
+++ b/local_node.c
@@ -697,6 +697,9 @@ usteer_local_node_request_link_measurement(struct usteer_local_node *ln)
if (si->connected != STA_CONNECTED)
continue;
+ if (usteer_sta_is_policy_exempt(si->sta->addr))
+ continue;
+
usteer_ubus_trigger_link_measurement(si);
}
}
@@ -747,6 +750,9 @@ usteer_local_node_process_bss_tm_queries(struct uloop_timeout *timeout)
if (!sta)
continue;
+ if (usteer_sta_is_policy_exempt(sta->addr))
+ continue;
+
si = usteer_sta_info_get(sta, node, false);
if (!si)
continue;
diff --git a/openwrt/usteer/files/etc/config/usteer b/openwrt/usteer/files/etc/config/usteer
index dc06b7cb0b8d131dea7799eb9a712ae665c825a2..f8b752a2e661f5c5885963bd5a969853a813f65d 100644
--- a/openwrt/usteer/files/etc/config/usteer
+++ b/openwrt/usteer/files/etc/config/usteer
@@ -173,3 +173,7 @@ config usteer
# List of SSIDs to enable steering on
#list ssid_list ''
+
+ # List of station MAC addresses to exempt from steering policy decisions,
+ # including automated kicks
+ #list policy_exempt_sta_list ''
diff --git a/openwrt/usteer/files/etc/init.d/usteer b/openwrt/usteer/files/etc/init.d/usteer
index 5c99becc8a9fc895044cb2251b676abf76e27048..3e51a2b3a7e5e9ffaa0aeb07e1cdfae20d89d259 100755
--- a/openwrt/usteer/files/etc/init.d/usteer
+++ b/openwrt/usteer/files/etc/init.d/usteer
@@ -72,6 +72,7 @@ uci_usteer() {
uci_option_to_json_string "$cfg" node_up_script
uci_option_to_json_string_array "$cfg" ssid_list
uci_option_to_json_string_array "$cfg" aggressiveness_mac_list
+ uci_option_to_json_string_array "$cfg" policy_exempt_sta_list
uci_option_to_json_string_array "$cfg" event_log_types
for opt in \
diff --git a/policy.c b/policy.c
index 16c4363ec1fc90dae9f73911cb0e7f0b1bb99ecc..637e200c6c14ad5b3c74001b0e7000a5875560c6 100644
--- a/policy.c
+++ b/policy.c
@@ -176,6 +176,9 @@ usteer_check_request(struct sta_info *si, enum usteer_event_type type)
int min_signal;
bool ret = true;
+ if (usteer_sta_is_policy_exempt(si->sta->addr))
+ goto out;
+
if (type == EVENT_TYPE_PROBE && !config.probe_steering)
goto out;
@@ -410,6 +413,9 @@ usteer_roam_trigger_sm(struct usteer_local_node *ln, struct sta_info *si)
bool usteer_policy_can_perform_roam(struct sta_info *si)
{
+ if (usteer_sta_is_policy_exempt(si->sta->addr))
+ return false;
+
/* Only trigger for connected STAs */
if (si->connected != STA_CONNECTED)
return false;
@@ -506,6 +512,11 @@ usteer_local_node_snr_kick(struct usteer_local_node *ln)
ev.threshold.ref = min_signal;
list_for_each_entry(si, &ln->node.sta_info, node_list) {
+ if (usteer_sta_is_policy_exempt(si->sta->addr)) {
+ si->below_min_snr = 0;
+ continue;
+ }
+
if (si->connected != STA_CONNECTED)
continue;
@@ -577,6 +588,9 @@ usteer_local_node_load_kick(struct usteer_local_node *ln)
list_for_each_entry(si, &ln->node.sta_info, node_list) {
struct sta_info *tmp;
+ if (usteer_sta_is_policy_exempt(si->sta->addr))
+ continue;
+
if (si->connected != STA_CONNECTED)
continue;
@@ -623,6 +637,11 @@ usteer_local_node_perform_kick(struct usteer_local_node *ln)
if (!si->kick_time || si->kick_time > current_time)
continue;
+ if (usteer_sta_is_policy_exempt(si->sta->addr)) {
+ si->kick_time = 0;
+ continue;
+ }
+
usteer_ubus_kick_client(si, KICK_REASON_BSS_TRANSITION);
}
}
diff --git a/sta.c b/sta.c
index 059e987a03b9f3fbbe1d404534f4a8cc112c17de..227b2ef691cdd38b4533a80d6c9851aa58d44f3a 100644
--- a/sta.c
+++ b/sta.c
@@ -17,6 +17,13 @@
* Copyright (C) 2020 John Crispin <john at phrozen.org>
*/
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/ethernet.h>
+#ifdef linux
+#include <netinet/ether.h>
+#endif
+
#include "usteer.h"
static int
@@ -28,6 +35,44 @@ avl_macaddr_cmp(const void *k1, const void *k2, void *ptr)
AVL_TREE(stations, avl_macaddr_cmp, false, NULL);
static struct usteer_timeout_queue tq;
+void config_set_policy_exempt_sta_list(struct blob_attr *data)
+{
+ free(config.policy_exempt_sta_list);
+
+ if (data && blobmsg_len(data) &&
+ blobmsg_check_attr_list(data, BLOBMSG_TYPE_STRING))
+ config.policy_exempt_sta_list = blob_memdup(data);
+ else
+ config.policy_exempt_sta_list = NULL;
+}
+
+void config_get_policy_exempt_sta_list(struct blob_buf *buf)
+{
+ if (config.policy_exempt_sta_list)
+ blobmsg_add_blob(buf, config.policy_exempt_sta_list);
+}
+
+bool usteer_sta_is_policy_exempt(const uint8_t *addr)
+{
+ struct blob_attr *cur;
+ int rem;
+
+ if (!addr || !config.policy_exempt_sta_list)
+ return false;
+
+ blobmsg_for_each_attr(cur, config.policy_exempt_sta_list, rem) {
+ uint8_t *exempt = (uint8_t *) ether_aton(blobmsg_get_string(cur));
+
+ if (!exempt)
+ continue;
+
+ if (!memcmp(exempt, addr, 6))
+ return true;
+ }
+
+ return false;
+}
+
static void
usteer_sta_del(struct sta *sta)
{
diff --git a/ubus.c b/ubus.c
index dca6f9a659d08baeecb78ab04b951fdb5e82c5ae..4b11ca1044480191310333bf2fb9f9dc560cd610 100644
--- a/ubus.c
+++ b/ubus.c
@@ -39,6 +39,12 @@ blobmsg_open_table_mac(struct blob_buf *buf, uint8_t *addr)
return blobmsg_open_table(buf, str);
}
+static bool
+usteer_ubus_sta_policy_exempt(struct sta_info *si)
+{
+ return si && si->sta && usteer_sta_is_policy_exempt(si->sta->addr);
+}
+
static int
usteer_ubus_get_clients(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
@@ -55,6 +61,8 @@ usteer_ubus_get_clients(struct ubus_context *ctx, struct ubus_object *obj,
_cur_n = blobmsg_open_table(&b, usteer_node_name(si->node));
blobmsg_add_u8(&b, "connected", si->connected);
blobmsg_add_u32(&b, "signal", si->signal);
+ blobmsg_add_u8(&b, "policy_exempt",
+ usteer_sta_is_policy_exempt(sta->addr));
blobmsg_close_table(&b, _cur_n);
}
blobmsg_close_table(&b, _s);
@@ -111,6 +119,8 @@ usteer_ubus_get_client_info(struct ubus_context *ctx, struct ubus_object *obj,
_cur_n = blobmsg_open_table(&b, usteer_node_name(si->node));
blobmsg_add_u8(&b, "connected", si->connected);
blobmsg_add_u32(&b, "signal", si->signal);
+ blobmsg_add_u8(&b, "policy_exempt",
+ usteer_sta_is_policy_exempt(sta->addr));
_s = blobmsg_open_table(&b, "stats");
for (i = 0; i < __EVENT_TYPE_MAX; i++)
usteer_ubus_add_stats(&si->stats[EVENT_TYPE_PROBE], event_types[i]);
@@ -190,7 +200,8 @@ struct cfg_item {
_cfg(ARRAY_CB, interfaces), \
_cfg(STRING_CB, node_up_script), \
_cfg(ARRAY_CB, event_log_types), \
- _cfg(ARRAY_CB, ssid_list)
+ _cfg(ARRAY_CB, ssid_list), \
+ _cfg(ARRAY_CB, policy_exempt_sta_list)
enum cfg_items {
#define _cfg(_type, _name) CFG_##_name
@@ -418,6 +429,8 @@ usteer_ubus_get_connected_clients(struct ubus_context *ctx, struct ubus_object *
s = blobmsg_open_table_mac(&b, si->sta->addr);
blobmsg_add_u32(&b, "signal", si->signal);
+ blobmsg_add_u8(&b, "policy_exempt",
+ usteer_sta_is_policy_exempt(si->sta->addr));
blobmsg_add_u64(&b, "created", current_time - si->created);
blobmsg_add_u64(&b, "connected", current_time - si->connected_since);
@@ -678,7 +691,12 @@ int usteer_ubus_bss_transition_request(struct sta_info *si,
uint8_t validity_period,
struct usteer_node *target_node)
{
- struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node);
+ struct usteer_local_node *ln;
+
+ if (usteer_ubus_sta_policy_exempt(si))
+ return 0;
+
+ ln = container_of(si->node, struct usteer_local_node, node);
blob_buf_init(&b, 0);
blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr));
@@ -710,10 +728,15 @@ int usteer_ubus_band_steering_request(struct sta_info *si,
bool abridged,
uint8_t validity_period)
{
- struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node);
+ struct usteer_local_node *ln;
struct usteer_node *node;
void *c;
+ if (usteer_ubus_sta_policy_exempt(si))
+ return 0;
+
+ ln = container_of(si->node, struct usteer_local_node, node);
+
blob_buf_init(&b, 0);
blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr));
blobmsg_add_u32(&b, "dialog_token", dialog_token);
@@ -744,7 +767,12 @@ int usteer_ubus_band_steering_request(struct sta_info *si,
int usteer_ubus_trigger_link_measurement(struct sta_info *si)
{
- struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node);
+ struct usteer_local_node *ln;
+
+ if (usteer_ubus_sta_policy_exempt(si))
+ return 0;
+
+ ln = container_of(si->node, struct usteer_local_node, node);
if (!usteer_sta_supports_link_measurement(si))
return 0;
@@ -758,7 +786,12 @@ int usteer_ubus_trigger_link_measurement(struct sta_info *si)
int usteer_ubus_trigger_client_scan(struct sta_info *si)
{
- struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node);
+ struct usteer_local_node *ln;
+
+ if (usteer_ubus_sta_policy_exempt(si))
+ return 0;
+
+ ln = container_of(si->node, struct usteer_local_node, node);
if (!usteer_sta_supports_beacon_measurement_mode(si, BEACON_MEASUREMENT_ACTIVE)) {
MSG(DEBUG, "STA does not support beacon measurement sta=" MAC_ADDR_FMT "\n", MAC_ADDR_DATA(si->sta->addr));
@@ -779,7 +812,12 @@ int usteer_ubus_trigger_client_scan(struct sta_info *si)
void usteer_ubus_kick_client(struct sta_info *si, uint32_t kick_reason_code)
{
- struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node);
+ struct usteer_local_node *ln;
+
+ if (usteer_ubus_sta_policy_exempt(si))
+ return;
+
+ ln = container_of(si->node, struct usteer_local_node, node);
blob_buf_init(&b, 0);
blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr));
diff --git a/usteer.h b/usteer.h
index 407d02da347e82736447f8092c5dd4b31fa65385..084af557b31b58445c5cfb944e48f51e4aca1b76 100644
--- a/usteer.h
+++ b/usteer.h
@@ -219,6 +219,7 @@ struct usteer_config {
uint32_t event_log_mask;
struct blob_attr *ssid_list;
+ struct blob_attr *policy_exempt_sta_list;
};
struct usteer_bss_tm_query {
@@ -373,6 +374,7 @@ struct sta_info *usteer_sta_info_get(struct sta *sta, struct usteer_node *node,
bool usteer_sta_supports_beacon_measurement_mode(struct sta_info *si, enum usteer_beacon_measurement_mode mode);
bool usteer_sta_supports_link_measurement(struct sta_info *si);
+bool usteer_sta_is_policy_exempt(const uint8_t *addr);
void usteer_sta_disconnected(struct sta_info *si);
void usteer_sta_info_update_timeout(struct sta_info *si, int timeout);
@@ -403,6 +405,9 @@ void config_get_ssid_list(struct blob_buf *buf);
void config_set_aggressiveness_mac_list(struct blob_attr *data);
void config_get_aggressiveness_mac_list(struct blob_buf *buf);
+void config_set_policy_exempt_sta_list(struct blob_attr *data);
+void config_get_policy_exempt_sta_list(struct blob_buf *buf);
+
int usteer_interface_init(void);
void usteer_interface_add(const char *name);
void usteer_sta_node_cleanup(struct usteer_node *node);
base-commit: 7532e1252fb937fe34b6da05e86fd34babd91ef5
--
2.53.0
More information about the openwrt-devel
mailing list