Skip to content
Snippets Groups Projects
cntlr.c 45.5 KiB
Newer Older
Anjan Chanda's avatar
Anjan Chanda committed
/*
 * cntlr.c - Multi-AP controller
Anjan Chanda's avatar
Anjan Chanda committed
 *
 * Copyright (C) 2020-2022 IOPSYS Software Solutions AB. All rights reserved.
Anjan Chanda's avatar
Anjan Chanda committed
 *
 * See LICENSE file for source code license information.
Anjan Chanda's avatar
Anjan Chanda committed
 *
 */
#include "cntlr.h"
Anjan Chanda's avatar
Anjan Chanda committed


#include <1905_tlvs.h>
#include <cmdu.h>
#include <errno.h>
#include <easy/easy.h>
#include <inttypes.h>
#include <libubox/blob.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubus.h>
#include <cmdu_ackq.h>
#include <map_module.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wifidefs.h>
#include "timer.h"
#if (EASYMESH_VERSION >= 3)
#ifdef USE_LIBDPP
#include <dpp_api.h>
#endif
#endif
#include "acs.h"
#include "cntlr_cmdu.h"
#include "cntlr_map.h"
#include "cntlr_ubus.h"
#include "config.h"
#if (EASYMESH_VERSION >= 3)
#include "dpp.h"
#endif
#include "libubox/blobmsg.h"
#include "libubox/list.h"
#include "sta.h"
#include "stdbool.h"
#include "timer_impl.h"
#include "topology.h"
#include "utils/debug.h"
#include "utils/utils.h"
#include "wifi_dataelements.h"
#include "wifi_opclass.h"
Anjan Chanda's avatar
Anjan Chanda committed

#define map_plugin	"ieee1905.map"
Anjan Chanda's avatar
Anjan Chanda committed

struct netif_iface *cntlr_find_iface(struct controller *c, uint8_t *macaddr)
	struct macaddr_entry *entry = NULL;
	entry = mactable_lookup(c->mac_table, macaddr, MAC_ENTRY_FBSS);
	return (struct netif_iface *)entry->data;
struct netif_iface *cntlr_find_iface_type(struct controller *c, uint8_t *macaddr,
{
	struct macaddr_entry *entry = NULL;

	entry = mactable_lookup(c->mac_table, macaddr, type);
	if (WARN_ON(!entry))
		return NULL;

	return (struct netif_iface *)entry->data;
}

struct netif_iface *cntlr_find_fbss(struct controller *c, uint8_t *macaddr)
{
	return cntlr_find_iface_type(c, macaddr, MAC_ENTRY_FBSS);
}

struct netif_iface *cntlr_find_bbss(struct controller *c, uint8_t *macaddr)
{
	return cntlr_find_iface_type(c, macaddr, MAC_ENTRY_BBSS);
}

struct netif_iface *cntlr_find_bss(struct controller *c, uint8_t *macaddr)
{
	return cntlr_find_iface_type(c, macaddr, MAC_ENTRY_FBSS | MAC_ENTRY_BBSS);
}

struct netif_radio *cntlr_find_radio(struct controller *c, uint8_t *macaddr)
	struct macaddr_entry *entry = NULL;
	entry = mactable_lookup(c->mac_table, macaddr, MAC_ENTRY_RADIO);
	if (WARN_ON(!entry))
		return NULL;
	return (struct netif_radio *)entry->data;
struct node *cntlr_find_node(struct controller *c, uint8_t *macaddr)
	struct macaddr_entry *entry = NULL;
	entry = mactable_lookup(c->mac_table, macaddr, MAC_ENTRY_ALID);
	return (struct node *)entry->data;
/* finds radio struct from interface macaddr */
struct netif_radio *cntlr_find_radio_with_bssid(struct controller *c, uint8_t *bssid)
Saurabh Verma's avatar
Saurabh Verma committed
	struct node *n = NULL;
	struct netif_radio *r = NULL;
	struct netif_iface *p = NULL;

	list_for_each_entry(n, &c->nodelist, list) {
		list_for_each_entry(r, &n->radiolist, list) {
			list_for_each_entry(p, &r->iflist, list) {
				if (!memcmp(p->bss->bssid, bssid, 6))
struct netif_radio *cntlr_find_radio_in_node_by_band(struct controller *c, struct node *n,
				       enum wifi_band band)
{
	struct netif_radio *r = NULL;

	list_for_each_entry(r, &n->radiolist, list) {
		if (r->radio_el->band == band)
			return r;
	}

	return NULL;
}

/* find link by macaddress */
struct netif_link *cntlr_find_link(struct controller *c, uint8_t *ul_macaddr, uint8_t *dl_macaddr)
Saurabh Verma's avatar
Saurabh Verma committed
	struct netif_link *l = NULL;

	list_for_each_entry(l, &c->linklist, list) {
		if (!memcmp(l->upstream->bss->bssid, ul_macaddr, 6)
				&& !memcmp(l->downstream->bss->bssid, dl_macaddr, 6))
Filip Matusiak's avatar
Filip Matusiak committed
/* TODO: untested */
int cntlr_get_node_assoclist(struct controller *c, uint8_t *node_alid,
			     int *num_sta, uint8_t *sta_macaddrs)
{
	struct sta *s = NULL;
Filip Matusiak's avatar
Filip Matusiak committed
	struct node *n;
	int limit = *num_sta;
	int ret = 0;
	int i = 0;

	*num_sta = 0;
Filip Matusiak's avatar
Filip Matusiak committed

	n = cntlr_find_node(c, node_alid);
	if (!n)
		return -1;

	list_for_each_entry(s, &n->stalist, list) {
		if (i < limit)
			memcpy(&sta_macaddrs[i*6], s->macaddr, 6);
Filip Matusiak's avatar
Filip Matusiak committed
		else
			ret = -E2BIG;

		i++;
struct bcnreq *cntlr_find_bcnreq(struct controller *c, uint8_t *sta, uint8_t *almacaddr)
Saurabh Verma's avatar
Saurabh Verma committed
	struct bcnreq *br = NULL;
	trace("%s: --->\n", __func__);

	list_for_each_entry(br, &c->bcnreqlist, list) {
		if (!memcmp(br->sta_mac, sta, 6) && !memcmp(br->agent_mac, almacaddr, 6))
/* find node based on bssid */
struct node *cntlr_find_node_with_bssid(struct controller *c, uint8_t *bssid)
Saurabh Verma's avatar
Saurabh Verma committed
	struct node *n = NULL;

	list_for_each_entry(n, &c->nodelist, list) {
		struct netif_radio *r = NULL;

		list_for_each_entry(r, &n->radiolist, list) {
			struct netif_iface *p = NULL;

			list_for_each_entry(p, &r->iflist, list) {
				if (!memcmp(p->bss->bssid, bssid, 6))
/* returns first found node with given STA MAC on its stalist */
struct node *cntlr_find_node_with_sta(struct controller *c, uint8_t *stamacaddr)
{
	struct node *n = NULL;

	list_for_each_entry(n, &c->nodelist, list) {
		struct sta *e = NULL;

		list_for_each_entry(e, &n->stalist, list) {
			if (!memcmp(e->macaddr, stamacaddr, 6))
				return n;
		}
	}

	return NULL;
}

bool cntlr_radio_support_ap_wifi7(struct wifi7_radio_capabilities *wifi7_caps)
{
	if (wifi7_caps->ap.str_support || wifi7_caps->ap.nstr_support ||
	    wifi7_caps->ap.emlsr_support || wifi7_caps->ap.emlmr_support)
		return true;

	return false;
}

bool cntlr_radio_support_bsta_wifi7(struct wifi7_radio_capabilities *wifi7_caps)
{
	if (wifi7_caps->bsta.str_support || wifi7_caps->bsta.nstr_support ||
	    wifi7_caps->bsta.emlsr_support || wifi7_caps->bsta.emlmr_support)
		return true;

	return false;
}

bool cntlr_node_support_ap_wifi7(struct node *n)
{
	struct netif_radio *r = NULL;

	list_for_each_entry(r, &n->radiolist, list) {
		if (cntlr_radio_support_ap_wifi7(&r->wifi7_caps))
			return true;
	}
	return false;
}


int cntlr_update_txlink_metric_data(struct controller *c, struct tlv_tx_linkmetric *txl, int len)
{
	int i = 0;
	int size = 0;
	struct tx_link_info *txlinfo;
	struct netif_link *txlink;
	struct link_metrics *metrics;

	/* For each tx link in the mesg */
	size = len - (sizeof(struct tlv_tx_linkmetric));
	size = size / (sizeof(struct tx_link_info));
	for (i = 0; i < size; i++) {
		txlinfo = (struct tx_link_info *)&txl->link[i];
		/* Find or alloc the backhaul link to the controller */
		txlink = cntlr_alloc_link(c, txlinfo->local_macaddr, txlinfo->neighbor_macaddr);
		if (!txlink) {
			trace("No link!\n");
			return -1;
		}
		metrics = txlink->metrics;
		metrics->l = txlink;
		memcpy(metrics->l->upstream->bss->bssid, txlinfo->local_macaddr, 6);
		memcpy(metrics->l->downstream->bss->bssid, txlinfo->neighbor_macaddr, 6);
		metrics->type = BUF_GET_BE16(txlinfo->mediatype);
		metrics->bridge = txlinfo->has_bridge;
		metrics->packet_tx_error = BUF_GET_BE32(txlinfo->errors);
		metrics->packet_trans = BUF_GET_BE32(txlinfo->packets);
		metrics->thp = BUF_GET_BE16(txlinfo->max_throughput);
		metrics->link_av = BUF_GET_BE16(txlinfo->availability);
		metrics->phy_rate = BUF_GET_BE16(txlinfo->phyrate);
	}
	c->num_tx_links += size;
	return 0;
}

int cntlr_update_rxlink_metric_data(struct controller *c, struct tlv_rx_linkmetric *rxl, int len)
{
	int i = 0;
	int size = 0;
	struct rx_link_info *rxlinfo;
	struct netif_link *rxlink;
	struct link_metrics *metrics;

	/* For each rx link in the mesg */
	size = len - (sizeof(struct tlv_rx_linkmetric));
	size = size / (sizeof(struct rx_link_info));
	for (i = 0; i < size; i++) {
		rxlinfo = (struct rx_link_info *)&rxl->link[i];
		/* Find or alloc the backhaul link to the controller */
		rxlink = cntlr_alloc_link(c, rxlinfo->local_macaddr, rxlinfo->neighbor_macaddr);
		if (!rxlink) {
			trace("No link!\n");
			return 0;
		}
		metrics = rxlink->metrics;
		metrics->l = rxlink;
		memcpy(metrics->l->upstream->bss->bssid, rxlinfo->local_macaddr, 6);
		memcpy(metrics->l->downstream->bss->bssid, rxlinfo->neighbor_macaddr, 6);
		metrics->type = BUF_GET_BE16(rxlinfo->mediatype);
		metrics->packet_rec = BUF_GET_BE32(rxlinfo->packets);
		metrics->packet_rx_error = BUF_GET_BE32(rxlinfo->errors);
		metrics->rssi = rxlinfo->rssi;
	}
	c->num_rx_links += size;
	return 0;
}


struct node *cntlr_add_node(struct controller *c, uint8_t *almacaddr)
	struct cmdu_buff *cmdu;
Jakob Olsson's avatar
Jakob Olsson committed
	char mac_str[18] = {0};
Jakob Olsson's avatar
Jakob Olsson committed
	int ret;

	if (!hwaddr_ntoa(almacaddr, mac_str))
Jakob Olsson's avatar
Jakob Olsson committed
		return NULL;

	n = cntlr_find_node(c, almacaddr);
Jakob Olsson's avatar
Jakob Olsson committed
	if (!n) {
		n = cntlr_alloc_node(c, almacaddr);
Jakob Olsson's avatar
Jakob Olsson committed
		if (!n) {
			err("|%s:%d| failed to allocate node "MACFMT"\n",
			    __func__, __LINE__, MAC2STR(almacaddr));
Jakob Olsson's avatar
Jakob Olsson committed
			return NULL;
		}
	} else {
		return n;
	}

	ret = cntlr_config_add_node(&c->cfg, mac_str);
	if (!ret) {
		dbg("|%s:%d| resync config\n", __func__, __LINE__);
		cntlr_resync_config(c, true);
	}

	/* update the timestamp of the node */
	if (n)
		timestamp_update(&n->last_tsp_seen);

Jakob Olsson's avatar
Jakob Olsson committed
#ifdef CONTROLLER_SYNC_DYNAMIC_CNTLR_CONFIG
	if (!hwaddr_equal(c->almacaddr, almacaddr))
		cntlr_sync_dyn_controller_config(c, almacaddr);
Jakob Olsson's avatar
Jakob Olsson committed
#endif

#if (EASYMESH_VERSION > 2)
Anjan Chanda's avatar
Anjan Chanda committed
	if (c->cfg.qos.enabled)
		cntlr_qos_sync_node(c, n->almacaddr);
	cmdu = cntlr_gen_bk_caps_query(c, n->almacaddr);
	if (!cmdu)
		return n;

	send_cmdu(c, cmdu);
	cmdu_free(cmdu);

	cmdu = cntlr_gen_ap_capability_query(c, n->almacaddr);
	if (!cmdu)
		return n;

	send_cmdu(c, cmdu);
	cmdu_free(cmdu);
Jakob Olsson's avatar
Jakob Olsson committed
	return n;
}

Anjan Chanda's avatar
Anjan Chanda committed
static void cntlr_log_nodes(struct controller *c)
{
	struct node *n = NULL;
Anjan Chanda's avatar
Anjan Chanda committed
	int i = 0;

	list_for_each_entry(n, &c->nodelist, list) {
		cntlr_dbg(LOG_MISC,
			  "  %d | agent = %p,  hwaddr = '"MACFMT"')\n",
			  i++, n, MAC2STR(n->almacaddr));
void cntlr_send_max_wifi_bh_hops_policy(struct controller *c)
	struct node *node = NULL;

	if (c->cfg.max_node_bh_hops == 0)
		return;


	trace("%s: max_node_bh_hops %d\n",
		  __func__, c->cfg.max_node_bh_hops);

	list_for_each_entry(node, &c->nodelist, list) {
		enum bh_control {
			BLOCK = 0x00,
			UNBLOCK = 0x01
		} bh_control = UNBLOCK;
		struct bh_topology_dev *topo_dev = topology_find_device(&c->topology, node->almacaddr);
		struct netif_radio *radio = NULL;
			err("%s: device not found in bh topo model, logical error.\n", __func__);
			continue;
		}

		if (topo_dev->bh.depth == UNKNOWN_TREE_LEVEL) {
			warn("%s: Level in BH treee unknown for: " MACFMT "\n", __func__, MAC2STR(node->almacaddr));
		if (c->cfg.max_node_bh_hops <= topo_dev->bh.wifi_hops_from_root)
			bh_control = BLOCK;

		list_for_each_entry(radio, &node->radiolist, list) {
			struct netif_iface *iface = NULL;

			list_for_each_entry(iface, &radio->iflist, list) {
				if (iface->bss->is_bbss) {
					const uint8_t NO_VALIDITY_PERIOD = 0;
					uint8_t ALL_BSTAS[6] = {
						0xff, 0xff, 0xff, 0xff, 0xff, 0xff
					};
					uint16_t mid;

					trace("%s: sending BH assoc_mode %s, node al_mac: " MACFMT
					      ", wifi_hops_from_root: %d, bssid: " MACFMT"\n",
					      __func__,
					      bh_control ? "UNBLOCK" : "BLOCK",
					      MAC2STR(node->almacaddr),
					      topo_dev->bh.wifi_hops_from_root,
					      MAC2STR(iface->bss->bssid));

					cntlr_send_client_assoc_ctrl_request(
						c, node->almacaddr, iface->bss->bssid,
						bh_control, NO_VALIDITY_PERIOD, 1,
						ALL_BSTAS, &mid);
				}
			}
		}
	}
}

void cntlr_bcn_metrics_timer_cb(atimer_t *t)
{
	struct sta *s = container_of(t, struct sta, bcn_metrics_timer);
	struct controller *c = s->cntlr;

	cntlr_dbg(LOG_STA, "%s: No Beacon Report from STA " MACFMT" yet.\n",
		  __func__, MAC2STR(s->macaddr));
	if (!s->bcn_response_tmo) {
		s->bcn_response_tmo = true;
		timer_set(&s->bcn_metrics_timer, 30 * 1000);
		cntlr_dbg(LOG_STA, "%s: Wait for 30s more.\n", __func__);
	} else {
		cntlr_del_bcnreq(c, s->macaddr, s->agent_almacaddr);
static void cntlr_freeze_sta(struct controller *c, struct sta *s)
{
	timer_del(&s->bcn_metrics_timer);
	timer_del(&s->btm_req_timer);
	timer_del(&s->ageout_timer);

	sta_free_assoc_frame(s);
	sta_free_bcn_metrics(s);
	cntlr_clean_bcnreqlist_sta(c, s);
}

static void cntlr_remove_sta(struct controller *c, struct sta *s)
{
	struct node *n = NULL;

	do {
		n = cntlr_find_node_with_sta(c, s->macaddr);
		if (n)
			node_del_sta(n, s);
	} while (n);

	cntlr_freeze_sta(c, s);
	cntlr_del_sta(c->sta_table, s);
}

void cntlr_sta_ageout_timer_cb(atimer_t *t)
{
	trace("%s: --->\n", __func__);

	struct sta *s = container_of(t, struct sta, ageout_timer);
	struct controller *c = s->cntlr;

	if (!c)
		return;

	cntlr_dbg(LOG_STA, "%s: Delete STA " MACFMT " after %ds of disassociation\n",
		  __func__, MAC2STR(s->macaddr), c->cfg.stale_sta_timeout);
	cntlr_remove_sta(c, s);
}

int node_add_sta(struct node *n, struct sta *s)
	if (WARN_ON(node_find_sta(n, s->macaddr))) {
		cntlr_dbg(LOG_STA,
			  "%s: Warn! STA " MACFMT " already in node " MACFMT"\n",
			  __func__, MAC2STR(s->macaddr), MAC2STR(n->almacaddr));
		return -1;
	memcpy(s->agent_almacaddr, n->almacaddr, 6);
	list_add(&s->list, &n->stalist);
	n->sta_count++;
	sta_inc_ref(s);

	return 0;
int node_del_sta(struct node *n, struct sta *s)
	if (WARN_ON(!node_find_sta(n, s->macaddr))) {
		cntlr_dbg(LOG_STA,
			  "%s: Warn! STA " MACFMT " not in node " MACFMT"\n",
			  __func__, MAC2STR(s->macaddr), MAC2STR(n->almacaddr));
		return -1;
	list_del(&s->list);
	n->sta_count--;
	sta_dec_ref(s);

	return 0;
struct sta *node_find_sta(struct node *n, uint8_t *macaddr)
{
	struct sta *s = NULL;

	list_for_each_entry(s, &n->stalist, list) {
		if (!memcmp(s->macaddr, macaddr, 6))
			return s;
	}

	return NULL;
}

void node_update_stalist(struct node *n, uint8_t *stalist, int num)
{
	struct sta *s = NULL, *tmp;

	list_for_each_entry_safe(s, tmp, &n->stalist, list) {
		bool found = false;

		for (int i = 0; i < num; i++) {
			if (!memcmp(s->macaddr, &stalist[i*6], 6)) {
				found = true;
				break;
			}
		}

		if (!found) {
			list_del(&s->list);
			n->sta_count--;
			sta_dec_ref(s);
struct unassoc_sta_metrics *cntlr_find_usta_metric(struct controller *c,
                                                   struct sta *s,
                                                   uint8_t *agent_macaddr)
	struct unassoc_sta_metrics *u = NULL;
	list_for_each_entry(u, &s->de_sta->umetriclist, list) {
		if (!memcmp(u->agent_macaddr, agent_macaddr, 6))
			return u;
	}
struct cmdu_buff *cntlr_query_sta_metric(struct controller *c, struct sta *s)
{
	if (!c || !s)
		return NULL;

	if (hwaddr_is_zero(s->agent_almacaddr)) {
		cntlr_warn(LOG_STA, "%s: Agent macaddr is zero\n", __func__);
	return cntlr_gen_sta_metric_query(c, s->agent_almacaddr, s->macaddr);
static void cntlr_get_all_sta_metrics(struct controller *c)
Filip Matusiak's avatar
Filip Matusiak committed
	struct node *n = NULL;
Filip Matusiak's avatar
Filip Matusiak committed
	list_for_each_entry(n, &c->nodelist, list) {
		struct sta *s = NULL;
Filip Matusiak's avatar
Filip Matusiak committed
		list_for_each_entry(s, &n->stalist, list) {
			struct cmdu_buff *cmdu;
			cmdu = cntlr_query_sta_metric(c, s);
Filip Matusiak's avatar
Filip Matusiak committed
			if (!cmdu)
				continue;

			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		}
struct wifi_bss_element *cntlr_alloc_wifi_bss(struct controller *c,
					      uint8_t *macaddr)
{
	struct wifi_bss_element *bss = NULL;

	bss = calloc(1, sizeof(struct wifi_bss_element));
	if (!bss)
		return NULL;

	memcpy(bss->bssid, macaddr, 6);
	bss->sta_assoc_allowed = true;
struct netif_iface *cntlr_radio_add_iface(struct controller *c,
					      struct netif_radio *r,
Anjan Chanda's avatar
Anjan Chanda committed
{
	struct netif_iface *n;
Anjan Chanda's avatar
Anjan Chanda committed

		n->bss->enabled = true;
Anjan Chanda's avatar
Anjan Chanda committed
	}

	n = calloc(1, sizeof(*n));
	if (!n)
		return NULL;
Anjan Chanda's avatar
Anjan Chanda committed

	n->bss = cntlr_alloc_wifi_bss(c, macaddr);
	n->band = wifi_opclass_get_band(r->radio_el->cur_opclass.opclass[0].id);
	if (n->band == BAND_UNKNOWN) {
		int ret;

		n->band = r->radio_el->band;
		ret = cntlr_config_add_node_radio(&c->cfg, r->agent->almacaddr, r->radio_el->macaddr, n->band);
		if (!ret)
			cntlr_resync_config(c, true);
	}
	n->bss->is_fbss = true;
	n->bss->is_bbss = false;
	n->bss->enabled = true;
	list_add(&n->list, &r->iflist);
	n->agent = r->agent;
#if (EASYMESH_VERSION >= 6)
	/* send channel selection request to propagate puncture bitmap */
	cntlr_send_channel_selection(c, r->agent->almacaddr, r->radio_el->macaddr, NULL);
	mactable_add_entry(c->mac_table, macaddr, MAC_ENTRY_FBSS, (void *)n);
Anjan Chanda's avatar
Anjan Chanda committed
}

static struct wifi_radio_element *cntlr_alloc_wifi_radio(struct controller *c, struct node *n,
							 struct netif_radio *r)
	struct wifi_radio_element *radio_el = NULL;
	radio_el = calloc(1, sizeof(struct wifi_radio_element));
	if (!radio_el)
	INIT_LIST_HEAD(&radio_el->scanlist);
	radio_el->acs = cntlr_radio_acs_alloc(c, n, r);
	return radio_el;
struct netif_radio *cntlr_node_add_radio(struct controller *c, struct node *n,
		uint8_t *macaddr)
Anjan Chanda's avatar
Anjan Chanda committed
{
	struct netif_radio *r;
	struct radio_policy *p;
Anjan Chanda's avatar
Anjan Chanda committed

Anjan Chanda's avatar
Anjan Chanda committed

	r = calloc(1, sizeof(*r));
	if (!r)
		return NULL;
Anjan Chanda's avatar
Anjan Chanda committed

	r->radio_el = cntlr_alloc_wifi_radio(c, n, r);
	if (!r->radio_el) {
		free(r);
		return NULL;
	}

	INIT_LIST_HEAD(&r->iflist);
	list_add(&r->list, &n->radiolist);
	r->agent = n;

	memcpy(r->radio_el->macaddr, macaddr, 6);
Anjan Chanda's avatar
Anjan Chanda committed

	mactable_add_entry(c->mac_table, macaddr, MAC_ENTRY_RADIO, (void *)r);
	cntlr_send_channel_selection(c, n->almacaddr, macaddr, NULL);
	p = cntlr_get_radio_policy(&c->cfg, macaddr);
	if (p)
		r->radio_el->band = p->band;
Anjan Chanda's avatar
Anjan Chanda committed
}

int cntlr_radio_get_beacon_channel(struct wifi_radio_element *radio,
				   uint8_t *opclass, uint8_t *channel)
{
	struct wifi_radio_opclass *curr = &radio->cur_opclass;

	*channel = 0;
	*opclass = 0;

	for (int i = 0; i < curr->num_opclass; i++) {
		struct wifi_radio_opclass_entry *e = &curr->opclass[i];

		if (e->bandwidth == 20) {
			*opclass = e->id;
			*channel = e->channel[0].channel;
			return 0;
		}
	}

	return -1;
}

struct node *cntlr_alloc_node(struct controller *c, uint8_t *almacaddr)
Anjan Chanda's avatar
Anjan Chanda committed
{
	struct node *n;

	n = calloc(1, sizeof(struct node));
	if (!n) {
		warn("OOM: node malloc failed!\n");
		return NULL;
Anjan Chanda's avatar
Anjan Chanda committed
	}

	n->cntlr = c;
	n->depth = -1;
	n->scan_supported = true;
	n->np = NULL; //c->cfg.apolicy;
	memcpy(n->almacaddr, almacaddr, 6);
	n->map_profile = MULTIAP_PROFILE_1;
Anjan Chanda's avatar
Anjan Chanda committed

Filip Matusiak's avatar
Filip Matusiak committed
	INIT_LIST_HEAD(&n->stalist);
	INIT_LIST_HEAD(&n->radiolist);
Filip Matusiak's avatar
Filip Matusiak committed
	n->sta_count = 0;
	list_add(&n->list, &c->nodelist);
Anjan Chanda's avatar
Anjan Chanda committed

	mactable_add_entry(c->mac_table, almacaddr, MAC_ENTRY_ALID, (void *)n);
	dbg("%s %d --------- " MACFMT "\n", __func__, __LINE__, MAC2STR(almacaddr));
Anjan Chanda's avatar
Anjan Chanda committed
}

uint32_t cntlr_estimate_max_throughput_for_node(struct controller *c, uint8_t *node_almacaddr)
{
	const struct backhaul_info *b = NULL;
	const struct bh_topology_dev *dev;
	uint32_t est_thput = 0xffffffff;

	dev = topology_find_device(&c->topology, node_almacaddr);
	if (!dev)
		return est_thput;

	while (dev->bh.parent) {
		b = &dev->bh;
		if (b->own_iface) {
			bool is_wifi = !!(b->own_iface->media_type & 0x0100);
			struct sta *s = NULL;

			if (!is_wifi) {
				dev = b->parent;
				continue;
			}

			s = cntlr_find_sta(c->sta_table, (uint8_t *)b->own_iface->macaddr);
			if (s && s->is_bsta && s->de_sta) {
				if (s->de_sta->dl_est_thput < est_thput)
					est_thput = s->de_sta->dl_est_thput;
			}
		}

		dev = b->parent;
	}

	if (est_thput != 0xffffffff)
		est_thput /= 2;		/* TODO: MLO link */

	return est_thput;
}

void cntlr_clean_bcnreqlist(struct controller *c)
{
	dbg("%s: --->\n", __func__);
	struct bcnreq *b = NULL, *tmp;

	list_for_each_entry_safe(b, tmp, &c->bcnreqlist, list) {
		list_del(&b->list);
		free(b);
	}
}

void cntlr_clean_bcnreqlist_sta(struct controller *c, struct sta *s)
{
	dbg("%s: --->\n", __func__);
	struct bcnreq *b = NULL, *tmp;

	list_for_each_entry_safe(b, tmp, &c->bcnreqlist, list) {
		if (!memcmp(b->sta_mac, s->macaddr, 6)) {
			dbg("%s Remove Beacon Request for STA " MACFMT "\n",
			    __func__, MAC2STR(b->sta_mac));
			list_del(&b->list);
			free(b);
		}
	}
}

void cntlr_clean_linklist(struct controller *c)
{
        struct netif_link *l = NULL, *tmp;

        list_for_each_entry_safe(l, tmp, &c->linklist, list) {
		free(l->metrics);
                list_del(&l->list);
                free(l);
        }
static void radio_clean_iflist(struct controller *c, struct netif_radio *r)
Stanislaw Gruszka's avatar
Stanislaw Gruszka committed
{
	struct netif_iface *ni = NULL, *tmp;
	struct netif_link *l = NULL, *temp;
Stanislaw Gruszka's avatar
Stanislaw Gruszka committed

	list_for_each_entry_safe(ni, tmp, &r->iflist, list) {

		/* Here we need to clean the linklist */
		list_for_each_entry_safe(l, temp, &c->linklist, list) {
			if (l->upstream == ni || l->downstream == ni) {
				free(l->metrics);
				list_del(&l->list);
				free(l);
			}
		}
		mactable_del_entry(c->mac_table, ni->bss->bssid, MAC_ENTRY_FBSS);
Stanislaw Gruszka's avatar
Stanislaw Gruszka committed
		list_del(&ni->list);
		free(ni);
	}
}
static void radio_clean_radio_el(struct netif_radio *r)
{
	struct wifi_scanres_element *b = NULL, *tmp;

	list_for_each_entry_safe(b, tmp, &r->radio_el->scanlist, list) {
		cntlr_radio_clean_scanlist_el(b);
	}

	cntlr_radio_acs_free(r->radio_el->acs);
	r->radio_el->acs = NULL;
static void cntlr_clean_radiolist(struct controller *c, struct node *n)
Stanislaw Gruszka's avatar
Stanislaw Gruszka committed
{
	struct netif_radio *r = NULL, *tmp;

	list_for_each_entry_safe(r, tmp, &n->radiolist, list) {
		radio_clean_iflist(c, r);
		mactable_del_entry(c->mac_table, r->radio_el->macaddr, MAC_ENTRY_RADIO);
Stanislaw Gruszka's avatar
Stanislaw Gruszka committed
		list_del(&r->list);
		free(r);
	}
}

Filip Matusiak's avatar
Filip Matusiak committed
static void node_clean_stalist(struct controller *c, struct node *n)
{
	struct sta *s = NULL, *tmp;

	if (!c || !n)
		return;

	list_for_each_entry_safe(s, tmp, &n->stalist, list) {
		node_del_sta(n, s);
	if (WARN_ON(n->sta_count != 0)) {
		err("%s: Misalligned station counter (cnt=%d)!\n",
		    __func__, n->sta_count);
Filip Matusiak's avatar
Filip Matusiak committed
		n->sta_count = 0;
	}
}

static void cntlr_clean_mac_hashtable(struct controller *c)
{
	mactable_flush(c->mac_table);
static void cntlr_clean_all_sta(struct controller *c)
{
	struct hlist_node *tmp = NULL;
	int i;

	for (i = 0; i < MAC_HASHTABLE_SIZE; i++) {
		struct sta *s = NULL;

		if (hlist_empty(&c->sta_table[i]))
			continue;

		hlist_for_each_entry_safe(s, tmp, &c->sta_table[i], hlist) {
			hlist_del(&s->hlist, &c->sta_table[i]);
			cntlr_freeze_sta(c, s);
			cntlr_free_sta(s);
		}
	}

	c->num_sta = 0;
}

Stanislaw Gruszka's avatar
Stanislaw Gruszka committed
static void cntlr_clean_nodelist(struct controller *c)
{
	struct node *n = NULL, *tmp;

	list_for_each_entry_safe(n, tmp, &c->nodelist, list) {
Filip Matusiak's avatar
Filip Matusiak committed
		node_clean_stalist(c, n);
		cntlr_clean_radiolist(c, n);
		list_del(&n->list);
struct netif_link *cntlr_alloc_link(struct controller *c,
		uint8_t *upstream, uint8_t *downstream)
{
	struct netif_link *l;

	l = cntlr_find_link(c, upstream, downstream);
	if (l)
		return l;

	l = calloc(1, sizeof(struct netif_link));
	if (!l)
		return NULL;

	l->metrics = calloc(1, sizeof(struct link_metrics));
	if (!l->metrics)
	l->upstream = cntlr_find_iface(c, upstream);
	if (!l->upstream)
		goto out_metrics;
	l->downstream = cntlr_find_iface(c, downstream);
	if (!l->downstream)
		goto out_metrics;
	trace("Adding link | " MACFMT " <---> " MACFMT " |\n",
		  MAC2STR(l->upstream->bss->bssid),
		  MAC2STR(l->downstream->bss->bssid));
	list_add(&l->list, &c->linklist);

	return l;

out_metrics:
	free(l->metrics);
out:
	free(l);
static void cntlr_radar_exit(atimer_t *t)
Anjan Chanda's avatar
Anjan Chanda committed
{
	/*TODO: before change channel due to radar, save old chandef.
	 * Restore that chandef upon exit from radar nop.
	 */
}

Jakob Olsson's avatar
Jakob Olsson committed
static void cntlr_ieee1905_cmdu_event_handler(void *cntlr,
		struct blob_attr *msg)
	static const struct blobmsg_policy cmdu_attrs[6] = {
		[0] = { .name = "type", .type = BLOBMSG_TYPE_INT16 },
		[1] = { .name = "mid", .type = BLOBMSG_TYPE_INT16 },
		[2] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[3] = { .name = "source", .type = BLOBMSG_TYPE_STRING },
		[4] = { .name = "origin", .type = BLOBMSG_TYPE_STRING },
		[5] = { .name = "cmdu", .type = BLOBMSG_TYPE_STRING },
	struct controller *c = (struct controller *)cntlr;
	char in_ifname[16] = {0};
	struct blob_attr *tb[6];
	char src[18] = { 0 }, src_origin[18] = { 0 };
	uint8_t *tlv = NULL;
	char *tlvstr = NULL;
	uint16_t type;
	uint8_t srcmac[6], origin[6];
	sigset_t waiting_mask;

	sigpending(&waiting_mask);
	if (sigismember(&waiting_mask, SIGINT) ||
			sigismember(&waiting_mask, SIGTERM))
		return;
	blobmsg_parse(cmdu_attrs, 6, tb, blob_data(msg), blob_len(msg));

	if (!tb[0] || !tb[1])
		return;

	if (tb[0]) {
		int t;

		t = blobmsg_get_u16(tb[0]);
		if (t < 0)
			return;

		type = (uint16_t)t;
		if (!is_cmdu_for_us(c, type))
		mid = (uint16_t)blobmsg_get_u16(tb[1]);
Jakob Olsson's avatar
Jakob Olsson committed
	if (tb[2])
		strncpy(in_ifname, blobmsg_data(tb[2]), 15);

	if (tb[3]) {
		strncpy(src, blobmsg_data(tb[3]), 17);
		hwaddr_aton(src, srcmac);
	}

	if (tb[4]) {
		strncpy(src_origin, blobmsg_data(tb[4]), 17);
		hwaddr_aton(src_origin, origin);
	}


	if (tb[5]) {
		len = blobmsg_data_len(tb[5]) - 16;
		strncpy(tlvstr, (blobmsg_data(tb[5]) + 16), len);
		tlv = calloc(1, len);

		strtob(tlvstr, len, tlv);
		free(tlvstr);
	cntlr_handle_map_event(c, type, mid, in_ifname, srcmac, origin, tlv, len);
static void cntlr_query_nodes(atimer_t *t)
{
	struct controller *c = container_of(t, struct controller, query_nodes);
	struct node *n = NULL;

	list_for_each_entry(n, &c->nodelist, list) {
		struct cmdu_buff *cmdu;

		cmdu = cntlr_gen_bk_caps_query(c, n->almacaddr);
		if (cmdu) {
			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		}

		cmdu = cntlr_gen_ap_capability_query(c, n->almacaddr);
		if (cmdu) {
			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		}

		cmdu = cntlr_gen_topology_query(c, n->almacaddr);
		if (cmdu) {
			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		}
	}

	timer_set(&c->query_nodes, 60 * 1000);
Anjan Chanda's avatar
Anjan Chanda committed

bool cntlr_check_config_diff(struct controller *c, uint32_t diff)
{
	bool reloaded = false;

	if (diff & CONFIG_DIFF_CREDENTIALS || diff & CONFIG_DIFF_VLAN) {
		struct cmdu_buff *cmdu;
		uint8_t origin[6] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x13};

		trace("Config changed, triggering renew!\n");
		cmdu = cntlr_gen_ap_autoconfig_renew(c, origin);
		if (cmdu) {
			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
			reloaded = true;
		}
	} else if (diff & (CONFIG_DIFF_AGENT_POLICY | CONFIG_DIFF_AGENT_POLICY_CNT)) {
		struct node_policy *p = NULL;
Saurabh Verma's avatar
Saurabh Verma committed
		struct node *n = NULL;

		trace("agent policy config changed\n");

		/* send the policy config cmdu to the marked agent */
		list_for_each_entry(n, &c->nodelist, list) {
			struct cmdu_buff *cmdu;
			int num_bk = 0;
			uint8_t bk_id[16 * 6] = {0};
			uint8_t radio_id[MAX_NUM_RADIO * 6] = {0};
			struct netif_radio *r = NULL;
			int num_radio = 0;

			if ((diff & CONFIG_DIFF_AGENT_POLICY) && !n->np->is_policy_diff)
				continue;

			list_for_each_entry(r, &n->radiolist, list) {
				struct netif_iface *iface = NULL;

				       r->radio_el->macaddr, 6);

				list_for_each_entry(iface, &r->iflist, list) {
					if (!iface->bss->is_bbss)
						continue;

					memcpy(&bk_id[num_bk * 6],
					       iface->bss->bssid, 6);
					num_bk++;
				}

			cmdu = cntlr_gen_policy_config_req(c, n->almacaddr,
					n->np, num_radio, radio_id, num_bk,
					bk_id);
			if (cmdu) {
				send_cmdu(c, cmdu);
				cmdu_free(cmdu);
				reloaded = true;
			}
		}

		/* reset is_policy_diff to false; */
Saurabh Verma's avatar
Saurabh Verma committed
		list_for_each_entry(p, &c->cfg.nodelist, list) {
Saurabh Verma's avatar
Saurabh Verma committed
		}
#if (EASYMESH_VERSION > 2)
	if ((diff & CONFIG_DIFF_QOS) && c->cfg.qos.enabled) {
		struct node *n = NULL;

Anjan Chanda's avatar
Anjan Chanda committed
		cntlr_dbg(LOG_QOS, "qos config changed\n");

		/* send the policy config cmdu to the marked agent */
		list_for_each_entry(n, &c->nodelist, list) {
			cntlr_qos_sync_node(c, n->almacaddr);
#if (EASYMESH_VERSION >= 6)
	if ((diff & CONFIG_DIFF_AP_MLD) || (diff & CONFIG_DIFF_PUNCT_BITMAP)) {
		trace("ap mld config changed\n");

		/* send the ap mld config cmdu to the agent */
		list_for_each_entry(n, &c->nodelist, list)
			cntlr_send_ap_mld_configuration_request(c, n);
	}
	if (diff & CONFIG_DIFF_BSTA_MLD) {
		struct node *n = NULL;

		trace("bsta mld config changed\n");

		/* send the ap mld config cmdu to the agent */
		list_for_each_entry(n, &c->nodelist, list)
			cntlr_send_bsta_mld_configuration_request(c, n);
	}
#ifdef CONTROLLER_SYNC_DYNAMIC_CNTLR_CONFIG
int cntlr_sync_dyn_controller_config(struct controller *c, uint8_t *agent)
{
	struct node *n = NULL;
	uint8_t proto = 0xab;
	struct cmdu_buff *cmdu;

	if (agent && !hwaddr_is_zero(agent)) {
		cmdu = cntlr_gen_higher_layer_data(c, agent, proto, NULL, 0);
		if (!cmdu)
			return -1;

		send_cmdu(c, cmdu);
		cmdu_free(cmdu);
	} else {
		list_for_each_entry(n, &c->nodelist, list) {
			if (hwaddr_equal(c->almacaddr, n->almacaddr))
				continue; /* skip locally running agent */

			cmdu = cntlr_gen_higher_layer_data(c, n->almacaddr, proto, NULL, 0);
			if (!cmdu)
				return -1;

			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		}
	}

	return 0;
}
bool cntlr_resync_config(struct controller *c, bool reload)
Saurabh Verma's avatar
Saurabh Verma committed
	struct node_policy *np = NULL;

	diff = cntlr_config_reload(&c->cfg);

	list_for_each_entry(np, &c->cfg.nodelist, list) {
		struct node *n;
Jakob Olsson's avatar
Jakob Olsson committed
		n = cntlr_find_node(c, np->agent_id);
	if (reload)
		cntlr_check_config_diff(c, diff);

#ifdef CONTROLLER_SYNC_DYNAMIC_CNTLR_CONFIG
	/* in dyn-controller mode, sync controller's config in network */
	if (diff)
		cntlr_sync_dyn_controller_config(c, NULL);
static void cntlr_signal_periodic_run(atimer_t *t)
{
	struct controller *c = container_of(t, struct controller, signal_handler);
	sigset_t waiting_mask;

	sigpending(&waiting_mask);

	if (sigismember(&waiting_mask, SIGHUP)) {
		cntlr_dbg(LOG_TIMER, "|%s:%d| Received SIGHUP, reload config\n", __func__, __LINE__);
	cntlr_dbg(LOG_TIMER, "%s --->\n", __func__);
	timer_set(&c->signal_handler, 1 * 1000);
static void cntlr_ageout_nodes(struct controller *c)
{
	struct node *n = NULL, *tmp;
	struct netif_radio *r = NULL, *temp;

	trace("%s: --->\n", __func__);

	/* Here we need to see that all nodes in nodelist */
	/* and check there timestamp */
	list_for_each_entry_safe(n, tmp, &c->nodelist, list) {
		if (timestamp_expired(&n->last_tsp_seen, NODE_EXPIRE_TIME * 1000)) {

			list_for_each_entry_safe(r, temp, &n->radiolist, list) {
				cntlr_config_del_radio(n->almacaddr);
			cntlr_config_del_node(n->almacaddr);
			topology_remove_device(&c->topology, n->almacaddr);
Filip Matusiak's avatar
Filip Matusiak committed
			node_clean_stalist(c, n);
			cntlr_clean_radiolist(c, n);
			mactable_del_entry(c->mac_table, n->almacaddr, MAC_ENTRY_ALID);
			list_del(&n->list);
			free(n);
			if (c->num_nodes > 0)
				c->num_nodes--;
		}
	}
}

static void cntlr_renew_nodes_configuration(struct controller *c)
{
	trace("%s: --->\n", __func__);
	struct node *node = NULL;

	/* When at least one running node was configured with older config, */
	/* send autoconfig renew */
	list_for_each_entry(node, &c->nodelist, list) {
		if (!timestamp_invalid(&node->last_config) &&
		    timestamp_less_than(&node->last_config, &c->cfg.last_change) &&
		    timestamp_greater_than(&node->last_cmdu, &c->cfg.last_change)) {
			uint8_t multicast_addr[6] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x13};
			struct cmdu_buff *cmdu =
				cntlr_gen_ap_autoconfig_renew(c, multicast_addr);

			dbg("Found possibly unconfigured node: "MACFMT"\n", MAC2STR(node->almacaddr));

			if (cmdu) {
				dbg("Sending AP_AUTOCONFIGURATION_RENEW\n");
				send_cmdu(c, cmdu);
				cmdu_free(cmdu);
			}

			break;
		}
#if (EASYMESH_VERSION >= 6)
		else if (timestamp_less_than(&node->last_apmld_ack, &c->cfg.last_apmld_change) &&
			 timestamp_greater_than(&node->last_cmdu, &c->cfg.last_apmld_change)) {

			dbg("Found possibly unconfigured AP MLD node: "MACFMT"\n", MAC2STR(node->almacaddr));

			cntlr_send_ap_mld_configuration_request(c, node);
		} else if (timestamp_less_than(&node->last_bstamld_ack, &c->cfg.last_bstamld_change) &&
			   timestamp_greater_than(&node->last_cmdu, &c->cfg.last_bstamld_change)) {

			dbg("Found possibly unconfigured BSTA MLD node: "MACFMT"\n", MAC2STR(node->almacaddr));

			cntlr_send_bsta_mld_configuration_request(c, node);
		}
#endif

static void combined_link_metric_periodic_collection(struct controller *c)
{
	trace("%s: --->\n", __func__);
	uint8_t *radiolist = NULL, *bsslist = NULL;
	struct cmdu_buff *cmdu;
	struct node *n = NULL;

	/* AP metrics query for each agent */
	/* For each agent */
	list_for_each_entry(n, &c->nodelist, list) {
		struct netif_radio *r = NULL;
		int num_bss = 0, num_radio = 0;
		uint8_t hwaddr[6];

		num_radio = 0;
		num_bss = 0;
		memcpy(hwaddr, n->almacaddr, 6);
		/* For each radio */
		list_for_each_entry(r, &n->radiolist, list) {
			struct netif_iface *bss = NULL;
			/* Building a radiolist of all radios */
			new_radiolist = (uint8_t *)realloc(radiolist,
							6 * (num_radio + 1) * sizeof(uint8_t));

			if (!new_radiolist) {
				trace("realloc of radiolist failed\n");
				goto error;

			radiolist = new_radiolist;
			num_radio++;
			radio_index = (num_radio - 1) * 6;
			memcpy(radiolist + radio_index, r->radio_el->macaddr, 6);
			/* For each bss in radio */
			list_for_each_entry(bss, &r->iflist, list) {
				if (!bss->bss->is_fbss && !bss->bss->is_bbss)
					/* if bss is a bsta */
				/* Building a bsslist of all BSS */
				new_bsslist = (uint8_t *)realloc(bsslist,
							6 * (num_bss + 1) * sizeof(uint8_t));

				if (!new_bsslist) {
					trace("realloc of bsslist failed\n");
					goto error;

				bsslist = new_bsslist;
				num_bss++;
				bss_index = (num_bss - 1) * 6;
				memcpy(bsslist + bss_index, bss->bss->bssid, 6);
		if (num_bss > 0) {
			cmdu = cntlr_gen_ap_metrics_query(c, hwaddr, num_bss, bsslist, num_radio, radiolist);
			if (!cmdu) {
				trace("cmdu cntlr_gen_ap_metrics_query failed!\n");
				goto error;
			}
			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		} else {
			dbg("Skip sending AP metrics query, no BSS to query\n");
		}
		cmdu = cntlr_gen_1905_link_metric_query(c, n->almacaddr);
		if (!cmdu) {
			trace("cmdu cntlr_gen_1905_link_metric_query failed!\n");
		send_cmdu(c, cmdu);
		cmdu_free(cmdu);
	}

	/* query i1905d base CMDU */
	cmdu = ieee1905_ubus_buildcmdu(c->ubus_ctx, CMDU_TYPE_LINK_METRIC_RESPONSE);
	if (!cmdu)
		dbg("No response from stack when generating 0x%04x\n",
				CMDU_TYPE_LINK_METRIC_RESPONSE);

	n = cntlr_find_node(c, c->almacaddr);
		cmdu_free(cmdu);
		goto error;

	handle_link_metrics_response(c, cmdu, n);
	cmdu_free(cmdu);
error:
	if (radiolist)
		free(radiolist);
	if (bsslist)
		free(bsslist);
static void cntlr_metric_collection(struct controller *c)
Anjan Chanda's avatar
Anjan Chanda committed
{

	cntlr_log_nodes(c);

	//forall_node_get_fhinfo(c);   /* replaced from per-node refresh bss */
Anjan Chanda's avatar
Anjan Chanda committed

	cntlr_get_all_sta_metrics(c);
	/* TODO: */
	//forall_node_get_usta_metrics(c);

Anjan Chanda's avatar
Anjan Chanda committed
	/* TODO: update only when a node is added or removed */
	//forall_node_update_neighbors(c);
Anjan Chanda's avatar
Anjan Chanda committed

	/* Call AP metrics query and 1905 link metrics query for data collection */
	combined_link_metric_periodic_collection(c);

	cntlr_renew_nodes_configuration(c);
Anjan Chanda's avatar
Anjan Chanda committed
}

static void cntlr_acs_run(atimer_t *t)
{
	struct controller *c = container_of(t, struct controller, acs);

	/* Run ACS recalc here */
	dbg("acs timeout - run recalc\n");
	cntlr_acs_recalc(c);
	if (c->cfg.acs && c->cfg.acs_timeout)
		timer_set(&c->acs, c->cfg.acs_timeout * 1000);
static void cntlr_steer_sched_run(atimer_t *t)
{
	struct controller *c = container_of(t, struct controller, steer_sched_timer);
	int i;

	cntlr_dbg(LOG_STEER, "%s: --->\n", __func__);

	for (i = 0; i < c->inform_sta_num; i++) {
		struct sta *s = cntlr_find_sta(c->sta_table, &c->inform_stalist[i * 6]);

		if (!s)
			continue;

		if (c->cfg.steer.enable_sta_steer && !s->is_bsta)
			cntlr_inform_steer_modules(c, s, c->inform_cmdu_type);
		else if (c->cfg.steer.enable_bsta_steer && s->is_bsta)
			cntlr_inform_bsteer_modules(c, s, c->inform_cmdu_type);
	}
}

static void cntlr_start(atimer_t *t)
{
	struct controller *c = container_of(t, struct controller, start_timer);

	if (c->state == CNTLR_INIT) {
		c->state = CNTLR_START;
		cntlr_publish_object(c, "map.controller");
		//cntlr_publish_dbg_object(c, "map.controller.dbg");	//TODO: open
static void cntlr_discovery(atimer_t *t)
{
	struct controller *c = container_of(t, struct controller, discovery_timer);
	struct cmdu_buff *cmdu;
	cmdu = cntlr_gen_ap_autoconfig_search(c, 0x02, 0x00);
	if (!cmdu)
		return;

	c->mid_2g = send_cmdu(c, cmdu);
	cmdu = cntlr_gen_ap_autoconfig_search(c, 0x02, 0x01);
	if (!cmdu)
		return;

	c->mid_5g = send_cmdu(c, cmdu);
	timer_set(t, 180 * 1000);
int cntlr_map_sub_cb(void *bus, void *priv, void *data)
	struct blob_attr *msg = (struct blob_attr *)data;
	str = blobmsg_format_json(msg, true);
	trace("Received notification '%s'\n", str);
	cntlr_ieee1905_cmdu_event_handler(priv, msg);
int cntlr_map_del_cb(void *bus, void *priv, void *data)
	struct controller *c = (struct controller *)priv;
	uint32_t *obj = (uint32_t *)data;
	c->subscribed = false;
	fprintf(stdout, "Object 0x%x no longer present\n", *obj);
static int cntlr_subscribe_for_cmdus(struct controller *c)
	mapmodule_cmdu_mask_t cmdu_mask = {0};

	map_prepare_cmdu_mask(cmdu_mask,
			CMDU_TYPE_TOPOLOGY_DISCOVERY,
			CMDU_TYPE_TOPOLOGY_NOTIFICATION,
			CMDU_TYPE_TOPOLOGY_QUERY,
			CMDU_TYPE_TOPOLOGY_RESPONSE,
			CMDU_TYPE_VENDOR_SPECIFIC,
			CMDU_TYPE_AP_AUTOCONFIGURATION_SEARCH,
			CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE,
			CMDU_TYPE_AP_AUTOCONFIGURATION_WSC,
			CMDU_1905_ACK,
			CMDU_BEACON_METRICS_RESPONSE,
			CMDU_AP_METRICS_RESPONSE,
			CMDU_ASSOC_STA_LINK_METRICS_RESPONSE,
			CMDU_UNASSOC_STA_LINK_METRIC_RESPONSE,
			CMDU_CHANNEL_SCAN_REQUEST,
			CMDU_CHANNEL_SCAN_REPORT,
			CMDU_CLIENT_DISASSOCIATION_STATS,
			CMDU_ASSOCIATION_STATUS_NOTIFICATION,
			CMDU_BACKHAUL_STA_CAPABILITY_QUERY,
			CMDU_BACKHAUL_STA_CAPABILITY_REPORT,
			CMDU_CHANNEL_PREFERENCE_REPORT,
			CMDU_CLIENT_STEERING_BTM_REPORT,
			CMDU_STEERING_COMPLETED,
			CMDU_CHANNEL_SELECTION_RESPONSE,
			CMDU_OPERATING_CHANNEL_REPORT,
Lejla Murselovic's avatar
Lejla Murselovic committed
			CMDU_AP_CAPABILITY_QUERY,
			CMDU_AP_CAPABILITY_REPORT,
			CMDU_CLIENT_CAPABILITY_REPORT,
			CMDU_HIGHER_LAYER_DATA,
			CMDU_BACKHAUL_STEER_RESPONSE,
Filip Matusiak's avatar
Filip Matusiak committed
			CMDU_PROXIED_ENCAP_DPP,
Filip Matusiak's avatar
Filip Matusiak committed
			CMDU_DIRECT_ENCAP_DPP,
			CMDU_BSS_CONFIG_REQUEST,
			CMDU_BSS_CONFIG_RESULT,
			CMDU_CHIRP_NOTIFICATION,
			CMDU_DPP_BOOTSTRAPING_URI,
#endif
#if (EASYMESH_VERSION > 5)
			CMDU_EARLY_AP_CAPABILITY_REPORT,
	memcpy(c->cmdu_mask, cmdu_mask, sizeof(c->cmdu_mask));
	trace("%s: wait for map-plugin\n", __func__);
	cntlr_wait_for_object_timeout(c, map_plugin, -1, &map_id);
	c->map_oid = map_id;

	/* register as client to the map module */
	ret = map_subscribe(c->ubus_ctx,
			    &c->map_oid,
			    "mapcontroller", &cmdu_mask, c,
			    cntlr_map_sub_cb,
			    cntlr_map_del_cb,
			    &c->subscriber);
	if (!ret) {
		c->subscribed = true;
	} else {
		trace("Failed to 'register' with %s (err = %s)\n",
		      map_plugin, ubus_strerror(ret));
static int cntlr_ackq_timeout_cb(struct cmdu_ackq *q, struct cmdu_ackq_entry *e)
{
	struct controller *a = container_of(q, struct controller, cmdu_ack_q);
	struct cmdu_buff *cmdu = (struct cmdu_buff *) e->cookie;
	int ret;

	trace("%s: ---> cmdu = %04x to "MACFMT" \n", __func__,
		cmdu_get_type(cmdu), MAC2STR(cmdu->origin));

	if (e->resend_cnt-- > 0) {
		ret = send_cmdu_ubus(a, cmdu);
		if (ret < 0)
			err("%s fail to send cmdu\n", __func__);

		return CMDU_ACKQ_TMO_REARM;
	}

	return CMDU_ACKQ_TMO_DELETE;
}

static void cntlr_ackq_delete_cb(struct cmdu_ackq *q, struct cmdu_ackq_entry *e)
{
	struct cmdu_buff *cmdu = (struct cmdu_buff *) e->cookie;

	trace("%s: ---> cmdu = %04x to "MACFMT" \n", __func__,
		cmdu_get_type(cmdu), MAC2STR(cmdu->origin));

	cmdu_free(cmdu);
}

static void uobj_add_event_handler(void *cntlr, struct blob_attr *msg)
{
	char path[32] = {0};
	uint32_t id = 0;
	struct controller *c = (struct controller *) cntlr;
	struct blob_attr *tb[2];
	static const struct blobmsg_policy ev_attr[2] = {
		[0] = { .name = "id", .type = BLOBMSG_TYPE_INT32 },
		[1] = { .name = "path", .type = BLOBMSG_TYPE_STRING }
	};

	blobmsg_parse(ev_attr, 2, tb, blob_data(msg), blob_len(msg));

	if (!tb[0] || !tb[1])
		return;

	strncpy(path, blobmsg_data(tb[1]), sizeof(path) - 1);
	id = (uint32_t) blobmsg_get_u32(tb[0]);
	dbg("|%s:%d| path = [%s] id = [%d] [%u]\n", __func__, __LINE__, path,
			id, id);
	if (!strncmp(path, map_plugin, strlen(map_plugin))) {
		/* TODO: how to handle failure? */
		cntlr_subscribe_for_cmdus(c);
	}
}


static void cntlr_event_handler(struct ubus_context *ctx,
		struct ubus_event_handler *ev,
		const char *type, struct blob_attr *msg)
{
	int i;
	char *str;
	struct controller *c = container_of(ev, struct controller, evh);
	struct wifi_ev_handler {
		const char *ev_type;
		void (*handler)(void *ctx, struct blob_attr *ev_data);
	} evs[] = {
		{ "ubus.object.add", uobj_add_event_handler }
	};

	str = blobmsg_format_json(msg, true);
	if (!str)
		return;

	info("[ &controller = %p ] Received [event = %s]  [val = %s]\n",
			c, type, str);

	for (i = 0; i < ARRAY_SIZE(evs); i++) {
		if (!strcmp(type, evs[i].ev_type))
			evs[i].handler(c, msg);
	}

	free(str);
}

static void cntlr_periodic_run(atimer_t *t)
{
	struct controller *c = container_of(t, struct controller, heartbeat);

	c->uptime += 1;

	cntlr_dbg(LOG_TIMER, "|%s:%d| periodic time (elapsed:%"PRIu64")\n",
		  __func__, __LINE__, c->uptime);

	if ((c->uptime % METRIC_REP_INTERVAL) == 0)
		cntlr_metric_collection(c);

#if 1	//TODO: move from here
	if (c->uptime % 7 == 0) {
		struct node *n = NULL;

		list_for_each_entry(n, &c->nodelist, list) {
			uint32_t est_thput = cntlr_estimate_max_throughput_for_node(c, n->almacaddr);

			cntlr_info(LOG_DEFAULT, "** Node = " MACFMT ": est-throughput = %d\n",
				   MAC2STR(n->almacaddr), est_thput);
			n->est_thput_dl = est_thput;
		}
	}
#endif
	cntlr_ageout_nodes(c);
	timer_set(&c->heartbeat, 1 * 1000);
}
void run_controller(void *opts)
Anjan Chanda's avatar
Anjan Chanda committed
{
	struct log_options *lopts = (struct log_options *)opts;
Anjan Chanda's avatar
Anjan Chanda committed
	struct controller *c;
	struct ubus_context *ctx;
Anjan Chanda's avatar
Anjan Chanda committed

	sigemptyset(&base_mask);
	sigaddset(&base_mask, SIGHUP);

	sigprocmask(SIG_SETMASK, &base_mask, NULL);
Anjan Chanda's avatar
Anjan Chanda committed
	set_sighandler(SIGPIPE, SIG_IGN);

	c = calloc(1, sizeof(struct controller));
	if (!c)
Stanislaw Gruszka's avatar
Stanislaw Gruszka committed
		return;
Anjan Chanda's avatar
Anjan Chanda committed

	memcpy(&c->log, lopts, sizeof(*lopts));
	restart_logging(&c->log);
	cntlr_info(LOG_MISC,
		   "Starting Controller build %s %s, EasyMesh ver%d.\n",
		   __DATE__, __TIME__, EASYMESH_VERSION);

	cntlr_dbg(LOG_MISC, "%s: cntlr = %p\n", __func__, c);
Anjan Chanda's avatar
Anjan Chanda committed

	uloop_init();
	ctx = ubus_connect(ubus_socket);
	if (!ctx) {
		err("Failed to connect to ubus\n");
		free(c);
		stop_logging();
Stanislaw Gruszka's avatar
Stanislaw Gruszka committed
		return;
Anjan Chanda's avatar
Anjan Chanda committed
	}
	c->ubus_ctx = ctx;
	INIT_LIST_HEAD(&c->nodelist);
	INIT_LIST_HEAD(&c->bcnreqlist);
	c->num_nodes = 0;
	INIT_LIST_HEAD(&c->linklist);
	INIT_LIST_HEAD(&c->sclist);
	init_topology(&c->topology);
	cmdu_ackq_init(&c->cmdu_ack_q);
	c->cmdu_ack_q.timeout_cb = cntlr_ackq_timeout_cb;
	c->cmdu_ack_q.delete_cb = cntlr_ackq_delete_cb;

#if (EASYMESH_VERSION > 2)
	{
#ifdef USE_LIBDPP
		char *argv[] = {"-I", "-C", "-V", "2"};
		int argc = 4;
		int ret;

		ret = dpp_init(&c->dpp, argc, argv);
		if (ret) {
			cntlr_dbg(LOG_DPP, "Failed to init dpp context\n");
			goto out_exit;
		}

		dpp_register_cb(c->dpp, dpp_frame_handler);
		dpp_set_ctx_private_data(c->dpp, c);

#endif
#if 0
		dpp_cntlr_read_uris(c);
#endif
	cntlr_config_defaults(c, &c->cfg);

	ret = cntlr_get_ieee1905_almac(c, c->almacaddr);
	memcpy(c->cfg.id, c->almacaddr, 6);
	cntlr_resync_config(c, false);

	if (!c->cfg.enabled)
		goto out_exit;

		/* diff always 1 after first round, will cause failures on
		 * first reload if not un-set
		 */
		struct node_policy *np = NULL;
		list_for_each_entry(np, &c->cfg.nodelist, list) {
			np->is_policy_diff = false;
			n = cntlr_add_node(c, np->agent_id);
	timer_init(&c->discovery_timer, cntlr_discovery);
	timer_init(&c->start_timer, cntlr_start);
	timer_init(&c->heartbeat, cntlr_periodic_run);
	timer_init(&c->radar_timer, cntlr_radar_exit);
	timer_init(&c->signal_handler, cntlr_signal_periodic_run);
	timer_init(&c->query_nodes, cntlr_query_nodes);
	timer_init(&c->acs, cntlr_acs_run);
	timer_init(&c->steer_sched_timer, cntlr_steer_sched_run);
	timer_set(&c->heartbeat, 1 * 1000);
	timer_set(&c->start_timer, waitext ? 5 * 1000 : 0);
	timer_set(&c->discovery_timer, 0);
	timer_set(&c->signal_handler, 5 * 1000);
	timer_set(&c->query_nodes, 60 * 1000);
	if (c->cfg.acs && c->cfg.acs_timeout)
		timer_set(&c->acs, c->cfg.acs_timeout * 1000);
	c->evh.cb = cntlr_event_handler;
	ubus_register_event_handler(ctx, &c->evh, "ubus.object.*");

	cntlr_subscribe_for_cmdus(c);
	/* The counters in MultiAPSteeringSummaryStats are all reset on reboot. */
	memset(&c->dlem.network.steer_summary, 0, sizeof(struct wifi_steer_summary));
	cntlr_dbg(LOG_MISC, "current wifi_cntlr profile %d\n", c->cfg.map_profile);
Anjan Chanda's avatar
Anjan Chanda committed
	uloop_run();
#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
	err("free dpp!!\n");
	dpp_free(c->dpp);
#endif
#endif
	cntlr_unload_steer_modules(c);
Stanislaw Gruszka's avatar
Stanislaw Gruszka committed
	map_unsubscribe(ctx, c->subscriber);
	cntlr_clean_mac_hashtable(c);
	cntlr_clean_bcnreqlist(c);
	cntlr_clean_linklist(c);
	cntlr_clean_nodelist(c);
	cntlr_clean_all_sta(c);
	free_topology(&c->topology);
	ubus_unregister_event_handler(ctx, &c->evh);
	cntlr_remove_object(c);
	//cntlr_remove_dbg_object(c);	//TODO
	cmdu_ackq_free(&c->cmdu_ack_q);
	cntlr_config_clean(&c->cfg);
Anjan Chanda's avatar
Anjan Chanda committed
	uloop_done();
	stop_logging();
Anjan Chanda's avatar
Anjan Chanda committed
}