Skip to content
Snippets Groups Projects
steer.c 12.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Copyright (C) 2025 IOPSYS Software Solutions AB. All rights reserved.
     */
    
    
    #include "steer.h"
    
    #include <libubox/list.h>
    
    #include <map_module.h>
    
    #include <stdbool.h>
    #include <stdio.h>
    #include <string.h>
    #include <time.h>
    
    
    #include "utils/debug.h"
    
    #include "cntlr.h"
    
    #include "acs.h"
    
    #include "cntlr_cmdu.h"
    #include "cntlr_ubus.h"
    
    #include "config.h"
    #include "sta.h"
    
    #include "steer_module.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),
    
    	    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])s->macaddr,
    
    					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;
    
    	struct wifi_steer_history *a;
    
    	trace("%s:--->\n", __func__);
    
    		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;
    
    
    	//FIXME: mode mappings
    
    
    	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);
    
    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,
    
    					 STEER_RESULT_FAIL_TIMEOUT);
    
    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",
    
    			  __func__, MAC2STR(s->macaddr));
    
    	cntlr_dbg(LOG_STEER,
    		  "%s: Try to steer " MACFMT " from " MACFMT " to " MACFMT "\n",
    
    		  __func__, MAC2STR(s->macaddr), MAC2STR(s->bssid),
    
    
    	switch (mode) {
    	case STEER_MODE_ASSOC_CTL:
    		ret = cntlr_send_client_assoc_ctrl_request(c,
    
    							   s->agent_almacaddr,
    
    							   ASSOC_CTRL_TIMED_BLOCK,
    							   10, /* validity period */
    
    							   s->macaddr,
    
    			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,
    
    						      s->agent_almacaddr,
    
    						      1, (uint8_t (*)[6])s->macaddr,
    
    						      1, (uint8_t (*)[6])target_bssid,
    
    			cntlr_dbg(LOG_STEER, "%s: Failed to send cmdu for steering sta!\n", __func__);
    
    		/* 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
    
    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 */
    
    
    			/* 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(s->macaddr),
    
    					   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,
    
    
    				return;
    			}
    
    			break;
    		case STEER_VERDICT_NOK:
    			if (c->cfg.steer.plugin_policy == STEER_PLUGIN_POLICY_AND)
    				return;
    
    			break;
    		default:
    			break;
    		}
    	}
    }