From 9c034423514f58121bfbf9ea020168bb00fafd19 Mon Sep 17 00:00:00 2001
From: Jakob Olsson <jakob.olsson@iopsys.eu>
Date: Thu, 26 Nov 2020 14:48:11 +0100
Subject: [PATCH] map-agent: implement backhaul STA steering req/resp (Section
 12)

---
 src/core/agent.c               | 158 +++++++++++++++++++++++++++++++--
 src/core/agent.h               |   8 +-
 src/core/agent_map.c           |  64 ++++++++++++-
 src/core/agent_map.h           |   2 +
 src/core/agent_tlv_generator.c |   2 +
 src/core/config.c              |  55 +++++++++++-
 src/core/config.h              |  11 +++
 7 files changed, 285 insertions(+), 15 deletions(-)

diff --git a/src/core/agent.c b/src/core/agent.c
index c3cae9a4d..92e731bdd 100644
--- a/src/core/agent.c
+++ b/src/core/agent.c
@@ -36,6 +36,9 @@
 
 #include <wsc.h>
 
+#include <uci.h>
+
+
 #include "utils/utils.h"
 #include "map_module.h"
 #include "utils.h"
@@ -54,6 +57,17 @@
 static struct agent *this_agent;
 static int signal_pending;
 
+static void bsta_steer_cb(struct uloop_timeout *t)
+{
+	struct netif_bk *bk = container_of(t, struct netif_bk, connect_timer);
+	uint8_t *bssid = NULL;
+
+	if (memcmp(bk->bsta_steer.prev_bssid, "\x00\x00\x00\x00\x00\x00", 6))
+		bssid = bk->bsta_steer.prev_bssid;
+
+	wifi_set_iface_bssid(bk->name, bssid);
+}
+
 static void agent_terminate(void)
 {
 	dbg("%s: called.\n", __func__);
@@ -219,6 +233,32 @@ static struct sta *find_sta_by_mac(struct agent *a, const unsigned char *mac)
 	return NULL;
 }
 
+/* find bkhaul by hwaddr */
+struct netif_bk *find_bkhaul_by_ifname(struct agent *a, char *ifname)
+{
+	struct netif_bk *p;
+
+	list_for_each_entry(p, &a->bklist, list) {
+		if (!strncmp(p->name, ifname, sizeof(p->name)))
+			return p;
+	}
+
+	return NULL;
+}
+
+/* find bkhaul by hwaddr */
+struct netif_bk *find_bkhaul_by_bssid(struct agent *a, uint8_t *bssid)
+{
+	struct netif_bk *p;
+
+	list_for_each_entry(p, &a->bklist, list) {
+		if (!memcmp(p->bssid, bssid, 6))
+			return p;
+	}
+
+	return NULL;
+}
+
 typedef void (*destructor)(void *);
 
 #define delete_expired_entries(vif, type, h, l, ts_member, tmo, func, _nr) \
@@ -1639,6 +1679,88 @@ static void wifi_radio_event_handler(void *c, struct blob_attr *msg)
 	dbg("%s: Unhandled!!! TODO\n", __func__);
 }
 
+static void wifi_bsta_event_handler(void *c, struct blob_attr *msg)
+{
+	char ifname[16] = {0}, event[16] = {0}, bssid_str[18] = {0};
+	struct agent *a = (struct agent *) c;
+	struct blob_attr *tb[3];
+	static const struct blobmsg_policy ev_attr[3] = {
+		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
+		[1] = { .name = "event", .type = BLOBMSG_TYPE_STRING },
+		[2] = { .name = "data", .type = BLOBMSG_TYPE_TABLE },
+	};
+	struct netif_bk *bk;
+	struct tlv_backhaul_steer_resp *p;
+	uint8_t bssid[6] = {0};
+	struct cmdu_cstruct *cmdu;
+
+	blobmsg_parse(ev_attr, 3, tb, blob_data(msg), blob_len(msg));
+
+	if (!tb[0] || !tb[1] || !tb[2])
+		return;
+
+	strncpy(ifname,	blobmsg_data(tb[0]), sizeof(ifname));
+	strncpy(event, blobmsg_data(tb[1]), sizeof(event));
+
+	if (tb[2]) {
+		struct blob_attr *data[1];
+		static const struct blobmsg_policy data_attr[1] = {
+			[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
+		};
+
+		blobmsg_parse(data_attr, 1, data, blobmsg_data(tb[2]),
+				blobmsg_data_len(tb[2]));
+
+		strncpy(bssid_str, blobmsg_data(data[0]), sizeof(bssid_str));
+	}
+
+	bk = find_bkhaul_by_ifname(a, ifname);
+	if (!bk)
+		return;
+
+	cmdu = bk->bsta_steer.cmdu;
+	if (!cmdu)
+		goto fail_cmdu;
+
+	p = (struct tlv_backhaul_steer_resp *) extract_tlv_by_type(cmdu,
+			MAP_TLV_BACKHAUL_STEERING_RESPONSE);
+	if (!p)
+		goto fail_cmdu;
+
+	hwaddr_aton(bssid_str, bssid);
+
+	if (memcmp(bssid, p->bssid, 6)) {
+		struct tlv_error_code *p1;
+		uint8_t **tlvs;
+
+		p->res_code = 0x01;
+		p1 = (struct tlv_error_code *) calloc(1,
+				sizeof(struct tlv_error_code));
+		if (!p1)
+			goto fail_cmdu;
+
+		p1->tlv_type = MAP_TLV_ERROR_CODE;
+		p1->reason_code = 0x05;
+
+		cmdu->num_tlvs++;
+
+		tlvs = (uint8_t **)realloc(cmdu->tlvs,
+				cmdu->num_tlvs * sizeof(uint8_t *));
+		if (!tlvs) {
+			free(p1);
+			goto fail_cmdu;
+		}
+		cmdu->tlvs = tlvs;
+		cmdu->tlvs[cmdu->num_tlvs - 1] = (uint8_t *) p1;
+	}
+
+	uloop_timeout_cancel(&bk->connect_timer);
+	agent_send_cmdu(a, cmdu);
+fail_cmdu:
+	map_free_cmdu(cmdu);
+	bk->bsta_steer.cmdu = NULL;
+}
+
 static void wifi_wps_creds_event_handler(void *c, struct blob_attr *msg)
 {
 	char encryption[32] = {0}, ifname[16] = {0}, ssid[33] = {0},
@@ -1681,6 +1803,7 @@ static void ubus_wifi_event_handler(struct ubus_context *ctx,
 		{ "wifi.radio", wifi_radio_event_handler },
 		{ "wifi.iface", wifi_iface_event_handler },
 		{ "wps_credentials", wifi_wps_creds_event_handler },
+		{ "wifi.bsta", wifi_bsta_event_handler }
 	};
 
 	str = blobmsg_format_json(msg, true);
@@ -2468,6 +2591,7 @@ static struct netif_bk *netif_alloc_bk(const char *ifname)
 		return NULL;
 
 	snprintf(n->name, 15, "%s", ifname);
+	n->connect_timer.cb = bsta_steer_cb;
 
 	return n;
 }
@@ -2596,22 +2720,42 @@ static void parse_dot11ac(struct netif_fh *fh, struct blob_attr *arg)
 	fh->tx_spatial_streams = 0x08;
 }
 
+static void parse_bk(struct ubus_request *req, int type,
+		struct blob_attr *msg)
+{
+	struct netif_bk *bk = (struct netif_bk *)req->priv;
+	char bssid[18] = { 0 };
+	struct blob_attr *tb[1];
+	static const struct blobmsg_policy ap_attr[1] = {
+		[0] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING }
+	};
+
+	blobmsg_parse(ap_attr, 1, tb, blob_data(msg), blob_len(msg));
+
+	if (!tb[0])
+		return;
+
+	strncpy(bssid, blobmsg_data(tb[0]), 17);
+	hwaddr_aton(bssid, bk->bssid);
+}
+
 static void parse_ap(struct ubus_request *req, int type,
 		struct blob_attr *msg)
 {
 	struct wifi_radio_element *radio;
 	struct netif_fh *fh = (struct netif_fh *)req->priv;
 	char bssid[18] = { 0 }, ifname[16] = { 0 };
-	struct blob_attr *tb[3];
-	static const struct blobmsg_policy ap_attr[3] = {
+	struct blob_attr *tb[4];
+	static const struct blobmsg_policy ap_attr[4] = {
 		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
 		[1] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING },
 		[2] = { .name = "capabilities", .type = BLOBMSG_TYPE_TABLE },
+		[3] = { .name = "channel", .type = BLOBMSG_TYPE_INT8 },
 	};
 
-	blobmsg_parse(ap_attr, 3, tb, blob_data(msg), blob_len(msg));
+	blobmsg_parse(ap_attr, 4, tb, blob_data(msg), blob_len(msg));
 
-	if (!tb[0] || !tb[1])
+	if (!tb[0] || !tb[1] || !tb[2])
 		return;
 
 	strncpy(ifname, blobmsg_data(tb[0]), 15);
@@ -2619,6 +2763,8 @@ static void parse_ap(struct ubus_request *req, int type,
 	strncpy(bssid, blobmsg_data(tb[1]), 17);
 	hwaddr_aton(bssid, fh->bssid);
 
+	fh->channel = blobmsg_get_u8(tb[2]);
+
 	if (tb[2]) {
 		struct blob_attr *data[2];
 		static const struct blobmsg_policy cap_attr[2] = {
@@ -2643,7 +2789,6 @@ static void parse_ap(struct ubus_request *req, int type,
 		return;
 
 	memcpy(radio->macaddr, fh->bssid, 6);
-
 }
 
 /* Initialize netif_fh/bk structs from ubus wifi objects */
@@ -2718,7 +2863,6 @@ static int agent_init_interfaces(struct agent *a)
 	}
 
 	list_for_each_entry(b, &cfg->bklist, list) {
-
 		wifi_object_t r_wobj = WIFI_OBJECT_INVALID;
 		wifi_object_t wobj = WIFI_OBJECT_INVALID;
 		const char *r_fmt = "wifi.radio.%s";
@@ -2755,6 +2899,8 @@ static int agent_init_interfaces(struct agent *a)
 			bn->cfg = b;
 			bn->agent = a;
 			list_add(&bn->list, &a->bklist);
+
+			ubus_call_object(a, wobj, "status", parse_bk, bn);
 		}
 	}
 
diff --git a/src/core/agent.h b/src/core/agent.h
index 013c6d4db..3f03cf13b 100644
--- a/src/core/agent.h
+++ b/src/core/agent.h
@@ -199,6 +199,12 @@ struct netif_bk {
 	wifi_object_t wifi;
 	wifi_object_t radio;
 	struct agent *agent;
+	struct {
+		uint8_t new_bssid[6];
+		uint8_t prev_bssid[6];
+		struct cmdu_cstruct *cmdu;
+	} bsta_steer;
+	struct uloop_timeout connect_timer;
 };
 
 struct agent_msg {
@@ -392,6 +398,7 @@ struct agent {
 	struct list_head pluginlist;
 };
 
+struct netif_bk *find_bkhaul_by_bssid(struct agent *a, uint8_t *bssid);
 
 extern int start_agent(void);
 extern void stop_agent(struct agent *a);
@@ -400,7 +407,6 @@ extern int wifiagent_steer_sta(struct ubus_context *ctx, char *ifname,
 		unsigned char *sta, int bsscnt, unsigned char *bsss,
 		int optime);
 
-
 extern int wifiagent_assoc_control_sta(char *fh_ifname, unsigned char *sta,
 		int enable, int tmo);
 
diff --git a/src/core/agent_map.c b/src/core/agent_map.c
index be223e572..9c798e0f0 100644
--- a/src/core/agent_map.c
+++ b/src/core/agent_map.c
@@ -22,6 +22,7 @@
 #endif
 
 #define TMP_WIFI_IFACE_MAX_NUM 16
+#define BSTA_STEER_TIMEOUT (7 * 1000)
 
 #ifndef _GNU_SOURCE
 #define _GNU_SOURCE
@@ -34,7 +35,6 @@
 #include <libubox/utils.h>
 #include <libubus.h>
 
-
 #include <uci.h>
 
 #include "map_module.h"
@@ -1138,10 +1138,65 @@ int handle_hld_message(void *agent, struct cmdu_cstruct *cmdu)
 	return 0;
 }
 
-int handle_backhaul_sta_steer_request(void *agent, struct cmdu_cstruct *cmdu)
+int handle_backhaul_sta_steer_request(void *agent,
+		struct cmdu_cstruct *rec_cmdu)
 {
 	trace("%s: --->\n", __func__);
+	struct agent *a = (struct agent *) agent;
+	struct tlv_backhaul_steer_resp *p;
+	struct tlv_backhaul_steer_req *req;
+	struct cmdu_cstruct *cmdu;
+	struct netif_bk *bk;
+	int ret;
+
+	req = (struct tlv_backhaul_steer_req *) extract_tlv_by_type(rec_cmdu,
+			MAP_TLV_BACKHAUL_STEERING_REQUEST);
+	if (!req)
+		return -1;
+
+	bk = find_bkhaul_by_bssid(a, req->addr);
+	if (!bk)
+		return -1;
+
+	cmdu = (struct cmdu_cstruct *)calloc(1,
+			sizeof(struct cmdu_cstruct));
+	if (!cmdu) {
+		fprintf(stderr, "Out of memory!\n");
+		return -1;
+	}
+
+	cmdu->message_type = CMDU_BACKHAUL_STEER_RESPONSE;
+	memcpy(cmdu->origin, rec_cmdu->origin, 6);
+	cmdu->message_id = rec_cmdu->message_id;
+	strncpy(cmdu->intf_name, rec_cmdu->intf_name, sizeof(cmdu->intf_name));
+
+	p = calloc(1, sizeof(struct tlv_backhaul_steer_resp));
+	if (!p)
+		goto out_cmdu;
+
+	p->tlv_type = MAP_TLV_BACKHAUL_STEERING_RESPONSE;
+	memcpy(p->bssid, req->bssid, 6);
+	memcpy(p->addr, req->addr, 6);
+
+	cmdu->num_tlvs++;
+	cmdu->tlvs = (uint8_t **)calloc(cmdu->num_tlvs, sizeof(uint8_t *));
+	if (!cmdu->tlvs)
+		goto out_p;
+
+	cmdu->tlvs[0] = (uint8_t *) p;
+
+	if (bk->bsta_steer.cmdu)
+		map_free_cmdu(bk->bsta_steer.cmdu);
+	bk->bsta_steer.cmdu = cmdu;
+
+	wifi_set_iface_bssid(bk->name, req->bssid);
+	uloop_timeout_set(&bk->connect_timer, BSTA_STEER_TIMEOUT);
 	return 0;
+out_p:
+	free(p);
+out_cmdu:
+	free(cmdu);
+	return -1;
 }
 
 int handle_channel_scan_request(void *agent, struct cmdu_cstruct *cmdu)
@@ -1413,8 +1468,9 @@ int agent_send_cmdu(struct agent *a, struct cmdu_cstruct *cmdu)
 	hwaddr_ntoa(cmdu->origin, dst_addr);
 	blobmsg_add_string(&b, "dst_macaddr", dst_addr);
 
-	trace("|%s:%d|cmdu:%s|dst:%s|\n", __func__, __LINE__,
-			map_stringify_cmdu_type(cmdu->message_type), dst_addr);
+	trace("|%s:%d|cmdu:%s|egress:%s|dst:%s|\n", __func__, __LINE__,
+			map_stringify_cmdu_type(cmdu->message_type),
+			cmdu->intf_name, dst_addr);
 	if (cmdu->num_tlvs > 0) {
 		for (i = 0; i < cmdu->num_tlvs; i++) {
 			len = 0;
diff --git a/src/core/agent_map.h b/src/core/agent_map.h
index a2f9b30d3..c9159b0eb 100644
--- a/src/core/agent_map.h
+++ b/src/core/agent_map.h
@@ -3,6 +3,8 @@
 #define AGENT_MAP_H
 
 
+uint8_t *extract_tlv_by_type(struct cmdu_cstruct *cmdu, uint8_t tlv_type);
+
 extern bool is_cmdu_for_us(struct agent *a, uint16_t type);
 
 extern int agent_handle_map_cmd(struct agent *a, char *data, int len);
diff --git a/src/core/agent_tlv_generator.c b/src/core/agent_tlv_generator.c
index dfa28546b..f85a1bca3 100644
--- a/src/core/agent_tlv_generator.c
+++ b/src/core/agent_tlv_generator.c
@@ -30,6 +30,8 @@
 #include <map1905/maputils.h>
 #include <wsc.h>
 
+#include <uci.h>
+
 #include "map_module.h"
 #include "utils.h"
 #include "debug.h"
diff --git a/src/core/config.c b/src/core/config.c
index a9c229fac..5661d95c0 100644
--- a/src/core/config.c
+++ b/src/core/config.c
@@ -53,7 +53,7 @@
 
 int verbose;
 
-static int set_value(struct uci_context *ctx, struct uci_package *pkg,
+int set_value(struct uci_context *ctx, struct uci_package *pkg,
 		struct uci_section *section, const char *key,
 		const char *value, enum uci_option_type type)
 {
@@ -73,10 +73,9 @@ static int set_value(struct uci_context *ctx, struct uci_package *pkg,
 	return -1;
 }
 
-static struct uci_section *config_get_iface_section(struct uci_context *ctx,
+struct uci_section *config_get_iface_section(struct uci_context *ctx,
 		struct uci_package *pkg, const char *type, const char *ifname)
 {
-
 	struct uci_element *e;
 	struct uci_section *section;
 
@@ -96,6 +95,54 @@ static struct uci_section *config_get_iface_section(struct uci_context *ctx,
 	return NULL;
 }
 
+struct uci_package *uci_load_pkg(struct uci_context **ctx, const char *config)
+{
+	struct uci_package *pkg;
+
+	if (!*ctx) {
+		*ctx = uci_alloc_context();
+		if (!*ctx)
+			return NULL;
+	}
+	if (uci_load(*ctx, config, &pkg) != UCI_OK) {
+		free(*ctx);
+		return NULL;
+	}
+
+	return pkg;
+}
+
+/* TODO: can it be generalized? */
+int wifi_set_iface_bssid(const char *ifname, uint8_t *bssid)
+{
+	struct uci_context *ctx = NULL;
+	struct uci_package *pkg;
+	struct uci_section *section;
+	char bssid_str[18] = {0};
+	int ret = -1;
+
+	pkg = uci_load_pkg(&ctx, "wireless");
+	if (!pkg)
+		return ret;
+
+	section = config_get_iface_section(ctx, pkg, "wifi-iface", ifname);
+	if (!section)
+		goto out_pkg;
+
+	if (bssid && !hwaddr_ntoa(bssid, bssid_str))
+		goto out_pkg;
+
+	ret = set_value(ctx, pkg, section, "bssid", bssid_str, UCI_TYPE_STRING);
+
+	uci_commit(ctx, &pkg, false);
+
+	uci_reload_services();
+out_pkg:
+	uci_unload(ctx, pkg);
+	uci_free_context(ctx);
+	return ret;
+}
+
 int config_del_iface(const char *config, const char *type, const char *ifname)
 {
 	struct uci_context *ctx;
@@ -492,7 +539,7 @@ static int ubus_call(const char *object, const char *method,
 	return UBUS_STATUS_OK;
 }
 
-static bool uci_reload_services(void)
+bool uci_reload_services(void)
 {
 	struct blob_buf bb;
 
diff --git a/src/core/config.h b/src/core/config.h
index 566d2b99a..f676ac23a 100644
--- a/src/core/config.h
+++ b/src/core/config.h
@@ -133,6 +133,17 @@ enum m2_process_status {
 	M2_PROCESS_TEARDOWN
 };
 
+/* TODO: move to a uci_utils.c */
+int set_value(struct uci_context *ctx, struct uci_package *pkg,
+		struct uci_section *section, const char *key,
+		const char *value, enum uci_option_type type);
+struct uci_section *config_get_iface_section(struct uci_context *ctx,
+		struct uci_package *pkg, const char *type, const char *ifname);
+bool uci_reload_services(void);
+struct uci_package *uci_load_pkg(struct uci_context **ctx, const char *config);
+int wifi_set_iface_bssid(const char *ifname, uint8_t *bssid);
+
+/* END TODO */
 
 int agent_config_init(struct agent_config *cfg);
 int agent_config_reload(struct agent_config *cfg);
-- 
GitLab