Newer
Older
/*
* Copyright (C) 2025 IOPSYS Software Solutions AB. All rights reserved.
*/
#include <easymesh.h>
#include <map_module.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "utils/debug.h"
#include "cntlr_cmdu.h"
#include "cntlr_ubus.h"
#include "config.h"
#include "sta.h"
#include "timer.h"
#include "wifi_dataelements.h"
#if 0 // unused
/* use unassociated STA measurements to steer */
void cntlr_check_usta_steer(struct controller *c, struct sta *s)
{
struct unassoc_sta_metrics *best = NULL, *u = NULL;
struct node *n = s->fh->agent;
struct netif_iface *best_fh = s->fh;
struct steer_control_config *scc;
scc = get_steer_control_config(c);
if (!scc)
return;
if (!scc->use_usta_metrics) {
dbg("%s %d Will not use unassociated STA metrics \
data to steer\n", __func__, __LINE__);
return;
}
dbg("%s %d for "MACFMT" attached to bssid " MACFMT " node = " \
MACFMT "\n", __func__, __LINE__, MAC2STR(s->macaddr),
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
MAC2STR(s->bssid), MAC2STR(n->almacaddr));
list_for_each_entry(u, &s->umetriclist, list) {
dbg("%s %d check usta node "MACFMT"\n",
__func__, __LINE__, MAC2STR(u->agent->almacaddr));
if (!best) {
best = u;
continue;
}
dbg("%s %d best ul_rcpi %u this ul_rcpi %u\n", __func__, __LINE__,
best->ul_rcpi, u->ul_rcpi);
if ((best->ul_rcpi - u->ul_rcpi) > USTA_STEER_UL_RCPI_DELTA) {
dbg("%s %d new best usta node "MACFMT" with ul_rcpi %d\n",
__func__, __LINE__,
MAC2STR(u->agent->almacaddr),
u->ul_rcpi);
best = u;
}
}
if (!best)
return;
if (best_fh && !hwaddr_is_zero(best_fh->bss->bssid)
&& memcmp(best_fh->bss->bssid, s->bssid, 6)) {
struct cmdu_buff *cmdu;
int ret = 0;
if ((s->type == IEEE1905 && !scc->enable_bsta_steer) ||
(s->type == NON_IEEE1905 && !scc->enable_sta_steer)) {
trace("|%s:%d| better bssid found, but will not steer "MACFMT",\
because the 'enable_(b)sta_steer' is not set!\n",
__func__, __LINE__, MAC2STR(s->macaddr));
return;
}
dbg("%s %d better bssid found! try to steer " MACFMT " \
from " MACFMT " to " MACFMT "\n",
__func__, __LINE__,
MAC2STR(s->macaddr), MAC2STR(s->bssid),
MAC2STR(best_fh->bss->bssid));
if (s->type == IEEE1905) {
cmdu = cntlr_gen_backhaul_steer_request(c, s->agent_almacaddr,
s->macaddr,
best_fh->bss->bssid,
0, 0);
if (cmdu) {
send_cmdu(c, cmdu);
cmdu_free(cmdu);
}
} else {
ret = cntlr_send_client_steer_request(c, s->fh->agent->almacaddr,
s->bssid, 0,
1, (uint8_t (*)[6])best_fh->bss->bssid,
STEER_MODE_BTM_REQ); /* mandate */
if (ret)
warn("%s: Failed to send cmdu for steering sta!\n", __func__);
}
}
}
#endif
struct wifi_steer_history *sta_lookup_steer_attempt(struct sta *s,
uint8_t *src_bssid,
uint8_t *dst_bssid)
struct wifi_multiap_sta *mapsta = &s->de_sta->mapsta;
cntlr_dbg(LOG_STEER, "%s: sta = " MACFMT", src-ap = "MACFMT"\n", __func__,
MAC2STR(s->macaddr), MAC2STR(src_bssid));
/* find imcomplete steering attempt */
for (i = 0; i < mapsta->num_steer_hist; i++) {
int idx = (mapsta->first + i) % MAX_STEER_HISTORY;
if (!memcmp(mapsta->steer_history[idx].src_bssid, src_bssid, 6) &&
!mapsta->steer_history[idx].complete) {
if (!dst_bssid ||
!memcmp(mapsta->steer_history[idx].dst_bssid, dst_bssid, 6)) {
return &mapsta->steer_history[idx];
cntlr_dbg(LOG_STEER, "%s: Steer attempt for sta = " MACFMT" not found\n",
__func__, MAC2STR(s->macaddr));
}
void cntlr_update_sta_steer_counters(struct controller *c,
uint8_t *sta_mac,
uint8_t *src_bssid,
uint8_t *dst_bssid,
uint32_t mode,
enum steer_trigger trigger,
uint8_t dst_rcpi)
{
struct sta *s = cntlr_find_sta(c->sta_table, sta_mac);
struct wifi_multiap_sta *mapsta;
cntlr_dbg(LOG_STA, "%s: Unknown STA "MACFMT"\n", __func__,
MAC2STR(sta_mac));
return;
}
mapsta = &s->de_sta->mapsta;
/* update history entry */
a = &mapsta->steer_history[mapsta->next];
memset(a, 0, sizeof(struct wifi_steer_history));
timestamp_update(&a->time);
time(&a->steer_time);
if (src_bssid)
memcpy(a->src_bssid, src_bssid, 6);
if (dst_bssid)
memcpy(a->dst_bssid, dst_bssid, 6);
a->trigger = trigger;
if (trigger == STEER_TRIGGER_LINK_QUALITY) {
a->src_rcpi = s->de_sta->rcpi;
a->dst_rcpi = dst_rcpi;
}
mapsta->next = (mapsta->next + 1) % MAX_STEER_HISTORY;
if (mapsta->num_steer_hist < MAX_STEER_HISTORY) {
mapsta->num_steer_hist++;
} else {
mapsta->first = (mapsta->next + 1) % MAX_STEER_HISTORY;
switch (mode) {
case STEER_MODE_ASSOC_CTL:
a->method = STEER_METHOD_ASSOC_CTL;
break;
case STEER_MODE_BTM_REQ:
a->method = STEER_METHOD_BTM_REQ;
s->de_sta->mapsta.steer_summary.btm_attempt_cnt++;
c->dlem.network.steer_summary.btm_attempt_cnt++;
break;
case STEER_MODE_OPPORTUNITY:
a->method = STEER_METHOD_BTM_REQ;
/*TODO: add counter for opportunity (incl blacklis count) */
break;
default:
a->method = STEER_METHOD_BTM_REQ; /* default method */
break;
}
/* Record tsp for most recent steer attempt */
timestamp_update(&s->de_sta->mapsta.steer_summary.last_attempt_tsp);
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
void cntlr_notify_client_steer_req_evt(struct controller *c,
uint8_t *bssid, uint32_t sta_nr, uint8_t stas[][6],
uint32_t bssid_nr, uint8_t target_bssid[][6])
{
char ev_data[1024] = {0};
snprintf(ev_data, sizeof(ev_data),
"{\"bssid\":\""MACFMT"\"",
MAC2STR(bssid));
if (sta_nr) {
char mac[64] = {0};
strncat(ev_data, ",\"sta_mac\":", sizeof(ev_data) - strlen(ev_data));
snprintf(mac, sizeof(mac), "\""MACFMT"\"", MAC2STR(stas[0]));
strncat(ev_data, mac, sizeof(ev_data) - strlen(ev_data));
// TODO: use blob_buf directly to provide further STA MACs
}
if (bssid_nr) {
char mac[64] = {0};
strncat(ev_data, ",\"target_bssid\":", sizeof(ev_data) - strlen(ev_data));
snprintf(mac, sizeof(mac), "\""MACFMT"\"", MAC2STR(target_bssid[0]));
strncat(ev_data, mac, sizeof(ev_data) - strlen(ev_data));
// TODO: use blob_buf directly to provide further target MACs
}
strncat(ev_data, "}", sizeof(ev_data) - strlen(ev_data));
cntlr_notify_event(c, "client_steer_request", ev_data);
}
void cntlr_notify_client_steer_result(struct controller *c,
uint8_t *sta_mac, int result)
{
char ev_data[1024] = {0};
snprintf(ev_data, sizeof(ev_data),
"{\"sta_mac\":\""MACFMT"\""
",\"status\":%d}",
MAC2STR(sta_mac), result);
cntlr_notify_event(c, "client_steer_result", ev_data);
}
void cntlr_btm_req_timer_cb(atimer_t *t)
{
trace("%s:--->\n", __func__);
struct sta *s = container_of(t, struct sta, btm_req_timer);
struct controller *c = s->cntlr;
s->de_sta->mapsta.steer_summary.btm_failure_cnt++;
c->dlem.network.steer_summary.btm_failure_cnt++;
cntlr_notify_client_steer_result(c, s->macaddr,
int cntlr_steer_sta(struct controller *c, struct sta *s, uint8_t *target_bssid,
uint32_t mode, uint32_t reason)
{
int ret = 0;
uint16_t mid;
trace("%s:--->\n", __func__);
if (!target_bssid || hwaddr_is_zero(target_bssid)) {
cntlr_dbg(LOG_STEER, "%s: steer verdict = OK, but target AP = NULL\n", __func__);
if (!memcmp(target_bssid, s->bssid, 6)) {
s->de_sta->mapsta.steer_summary.no_candidate_cnt++;
c->dlem.network.steer_summary.no_candidate_cnt++;
cntlr_dbg(LOG_STEER,
"%s: " MACFMT " connected to best AP! No steer needed.\n",
cntlr_dbg(LOG_STEER,
"%s: Try to steer " MACFMT " from " MACFMT " to " MACFMT "\n",
__func__, MAC2STR(s->macaddr), MAC2STR(s->bssid),
MAC2STR(target_bssid));
switch (mode) {
case STEER_MODE_ASSOC_CTL:
ret = cntlr_send_client_assoc_ctrl_request(c,
ASSOC_CTRL_TIMED_BLOCK,
10, /* validity period */
&mid);
if (ret) {
cntlr_dbg(LOG_STEER, "%s: Failed to send cmdu for assoc control!\n", __func__);
//s->de_sta->mapsta.failed_steer_attempts++;
return ret;
}
/* Keep mid & check assoc control succesful in ACK msg */
s->latest_assoc_cntrl_mid = mid;
cntlr_dbg(LOG_STEER, "%s: STA assoc control mid = %u\n",
__func__, mid);
break;
case STEER_MODE_BTM_REQ:
case STEER_MODE_OPPORTUNITY:
ret = cntlr_send_client_steer_request(c,
1, (uint8_t (*)[6])target_bssid,
mode,
reason);
cntlr_dbg(LOG_STEER, "%s: Failed to send cmdu for steering sta!\n", __func__);
return ret;
}
/* Expect Client Steering BTM Report message and
* Tunneled BTM-Response message for the STA.
*/
timer_set(&s->btm_req_timer, BTM_RESP_EXP_TIMEOUT * 1000);
break;
case STEER_MODE_UNDEFINED:
default:
dbg("%s: steer mode is undefined\n", __func__);
return 0;
}
cntlr_update_sta_steer_counters(c, s->macaddr, s->bssid,
target_bssid, mode,
STEER_TRIGGER_LINK_QUALITY,
/* to->rcpi */ 255); //FIXME
return 0;
}
void cntlr_update_sta_steer_data(struct controller *c, struct sta *s)
struct steer_sta *ss = s->steer_data;
struct node *n = NULL;
struct netif_radio *r;
trace("%s:--->\n", __func__);
if (s->state == STA_DISCONNECTED) {
ss->bss.connected = 0;
return;
}
ss->bss.connected = 1;
memcpy(ss->bss.bssid, s->bssid, 6);
memcpy(ss->bss.agent, s->agent_almacaddr, 6);
memset(ss->bss.ssid, 0, sizeof(ss->bss.ssid));
memcpy(ss->bss.ssid, s->ssid, s->ssidlen);
r = cntlr_find_radio_with_bssid(c, s->bssid);
if (r) {
struct radio_policy *rp;
rp = cntlr_get_radio_policy(&c->cfg, r->radio_el->macaddr);
if (rp) {
ss->bss.rcpi_threshold = rp->rcpi_threshold;
ss->bss.report_rcpi_threshold = rp->report_rcpi_threshold;
ss->bss.report_rcpi_hysteresis_margin = rp->report_rcpi_hysteresis_margin;
ss->bss.util_threshold = rp->util_threshold;
ss->bss.report_util_threshold = rp->report_util_threshold;
ss->bss.opclass = ctrl_radio_cur_opclass_id(r->radio_el);
ss->bss.channel = ctrl_radio_cur_opclass_ctrl_chan(r->radio_el);
memset(&ss->target, 0, sizeof(struct steer_sta_target_bss));
/* update nbrlist */
memset(ss->nbrlist, 0, ss->num_nbr * sizeof(struct steer_sta_target_bss));
ss->num_nbr = 0;
list_for_each_entry(n, &c->nodelist, list) {
int ret;
list_for_each_entry(r, &n->radiolist, list) {
struct netif_iface *p = NULL;
uint8_t ctrl_channel = 0; /* 20MHz */
uint8_t opclass = 0;
/* if (s->fh->band != r->radio_el->band) {
continue;
}
*/
ret = cntlr_radio_get_beacon_channel(r->radio_el, &opclass, &ctrl_channel);
if (ret)
continue;
list_for_each_entry(p, &r->iflist, list) {
struct steer_sta_target_bss *t = &ss->nbrlist[ss->num_nbr];
if (memcmp(p->bss->ssid, s->ssid, s->ssidlen))
//TODO: appropriate check when 'is_bsta = true'
if (!p->bss->is_fbss /* || !p->bss->sta_assoc_allowed */)
continue;
memcpy(t->bssid, p->bss->bssid, 6);
memcpy(t->agent, n->almacaddr, 6);
memcpy(t->ruid, r->radio_el->macaddr, 6);
t->opclass = opclass;
t->channel = ctrl_channel;
ss->num_nbr++;
if (ss->num_nbr >= MAX_NUM_NBRS)
return;
}
void cntlr_inform_bsteer_modules(struct controller *c, struct sta *s, uint16_t rxcmdu_type)
{
//TODO: bSTA steer plugins
}
void cntlr_inform_steer_modules(struct controller *c, struct sta *s, uint16_t rxcmdu_type)
{
struct steer_control *sc = NULL;
cntlr_trace(LOG_STEER, "%s: for " MACFMT", and cmdu = %s\n", __func__,
MAC2STR(s->macaddr), map_cmdu_type2str(rxcmdu_type));
list_for_each_entry(sc, &c->sclist, list) {
struct steer_sta *ss = s->steer_data;
ret = cntlr_maybe_steer_sta(sc, ss, rxcmdu_type);
if (ret)
continue;
switch (ss->verdict) {
case STEER_VERDICT_OK:
if (c->cfg.steer.plugin_policy == STEER_PLUGIN_POLICY_OR) {
cntlr_warn(LOG_STEER,
"Steer STA: " MACFMT ", src-BSSID:" MACFMT
", target-BSSID:" MACFMT ", reason: %s\n",
MAC2STR(ss->target.bssid),
ss->reason == STEER_REASON_LOW_RCPI ? "link quality" :
ss->reason == STEER_REASON_LOW_THPUT ? "phyrate" :
cntlr_steer_sta(c, s, ss->target.bssid,