diff --git a/src/agent.c b/src/agent.c
index 89a0439a05b8d6d7b58ff7ecc80543c41314161e..d48fb6f7e39c3a0f3bbd18fcc5d9e75b721656b9 100644
--- a/src/agent.c
+++ b/src/agent.c
@@ -779,26 +779,10 @@ void agent_link_ap_to_cfg(struct agent *a)
 
 static void agent_config_load_post_action(struct agent *a)
 {
-	struct wifi_radio_element *re = NULL;
-
 	agent_link_ap_to_cfg(a);
 
 	if (a->cfg.pcfg)
 		a->pvid = a->cfg.pcfg->pvid;
-
-	/* Update restrict_stalist from assoc_ctrllist */
-	list_for_each_entry(re, &a->radiolist, list) {
-		struct netif_ap *p = NULL;
-
-		list_for_each_entry(p, &re->aplist, list) {
-
-			/* Copy restrictions from cfg to runtime */
-			assoc_ctrl_sync_from_config(a, p);
-
-			/* Block STAs in WiFi */
-			assoc_ctrl_apply_restriction(a, p);
-		}
-	}
 }
 
 int agent_config_reload(struct agent *a)
@@ -5196,6 +5180,7 @@ static void refresh_bssinfo(atimer_t *t)
 	struct netif_ap *ap = container_of(t, struct netif_ap, bss_timer);
 	struct agent *a = ap->agent;
 	struct wifi_ap_status ap_status = {};
+	bool bssid_unset = !!hwaddr_is_zero(ap->bssid);
 	int i;
 
 	trace("%s: ap = %s\n", __func__, ap->ifname);
@@ -5210,6 +5195,10 @@ static void refresh_bssinfo(atimer_t *t)
 #endif
 		ap->channel = ap_status.ap.bss.channel;
 		memcpy(ap->bssid, ap_status.ap.bss.bssid, 6);
+		if (bssid_unset && !hwaddr_is_zero(ap->bssid)) {
+			/* bssid got set, update STA restrictions */
+			assoc_ctrl_sync_from_file(a, ap);
+		}
 		memcpy(ap->ssid, ap_status.ap.bss.ssid, 32);
 		/* others.. */
 		if (ap_status.ap.bss.load.utilization != 0xff)
@@ -6720,10 +6709,12 @@ static int agent_ap_add_stations(struct agent *a, struct netif_ap *ap)
 void agent_init_interfaces_post_actions(struct agent *a)
 {
 	trace("%s: --->\n", __func__);
+
 	struct wifi_radio_element *re;
-	struct netif_ap *ap = NULL;
 
 	list_for_each_entry(re, &a->radiolist, list) {
+		struct netif_ap *ap = NULL;
+
 		list_for_each_entry(ap, &re->aplist, list) {
 			int num_sta = 0;
 
@@ -6777,6 +6768,8 @@ int agent_get_system_info(struct agent *a)
 
 int agent_init_interfaces(struct agent *a)
 {
+	trace("%s: --->\n", __func__);
+
 	struct agent_config *cfg = &a->cfg;
 	struct wifi_radio_element *re;
 	struct netif_apcfg *f;
@@ -6905,7 +6898,14 @@ int agent_init_interfaces(struct agent *a)
 		}
 
 		bss = &fn->bss;
-		memcpy(bss->bssid, fn->bssid, 6);
+		if (memcmp(bss->bssid, fn->bssid, 6)) {
+			memcpy(bss->bssid, fn->bssid, 6);
+			/* New AP or order has changed - read STA restrictions from file */
+			assoc_ctrl_sync_from_file(a, fn);
+		} else {
+			/* No change in AP list, only reapply restrictions in WiFi */
+			assoc_ctrl_apply_restriction(a, fn);
+		}
 		strncpy(bss->ssid, fn->ssid, sizeof(bss->ssid) - 1);
 		bss->enabled = fn->enabled;
 
@@ -7571,9 +7571,6 @@ static void netif_reset(struct netif_ap *ap)
 	/* clear nbrlist */
 	clear_nbrlist(ap);
 
-	/* clear restricted sta list */
-	clear_restrict_stalist(ap);
-
 	/* cancel timers */
 	timer_del(&ap->nbr_timer);
 	timer_del(&ap->bss_timer);
@@ -7588,6 +7585,8 @@ static void netif_reset(struct netif_ap *ap)
 static void netif_free(struct netif_ap *ap)
 {
 	netif_reset(ap);
+	/* clear restricted sta list */
+	clear_restrict_stalist(ap);
 	list_del(&ap->list);
 	free(ap);
 }
diff --git a/src/agent_tlv.c b/src/agent_tlv.c
index 3dda667d2f95233d7f4a8bb3bbc5f4559525635c..4a3208b7bd4be2cdd8d31aeaa0666fc1ce862522 100644
--- a/src/agent_tlv.c
+++ b/src/agent_tlv.c
@@ -3035,37 +3035,6 @@ char *get_timestamp_old(time_t *t, char **tbuf)
 	return *tbuf;
 }
 
-char *get_timestamp(time_t *t, char *tbuf)
-{
-	char tmpbuf[64] = {0};
-	struct tm res;
-	char sign;
-	long int toff, toff_hour, toff_min;
-	const time_t now = time(t);
-
-	if (!tbuf)
-			return NULL;
-
-	/* E.g. "2019-02-11T06:42:31.23039-08:00" */
-
-	localtime_r(&now, &res);
-	tzset();
-	toff = timezone;
-	sign = toff > 0 ? '-' : '+';
-	toff *= -1L;
-
-	toff_hour = toff / 3600;
-	toff_min = (toff % 3600) / 60;
-
-	snprintf(tmpbuf, 63, "%04d-%02d-%02dT%02d:%02d:%02d%c%02ld:%02ld",
-		 res.tm_year + 1900, res.tm_mon + 1, res.tm_mday,
-		 res.tm_hour, res.tm_min, res.tm_sec,
-		 sign, toff_hour, toff_min);
-
-	snprintf(tbuf, 64, "%s", tmpbuf);
-	return tbuf;
-}
-
 int agent_gen_timestamp_tlv(struct agent *agent, struct cmdu_buff *frm)
 {
 	int ret;
@@ -3080,7 +3049,7 @@ int agent_gen_timestamp_tlv(struct agent *agent, struct cmdu_buff *frm)
 	/* Define the TLV */
 	t->type = MAP_TLV_TIMESTAMP;
 	data = (struct tlv_timestamp *) t->data;
-	get_timestamp(NULL, tsp);
+	time_to_timestr(NULL, tsp);
 	data->len = strlen(tsp);
 	memcpy(data->timestamp, (uint8_t *)tsp, data->len);
 	t->len = sizeof(*data) + data->len;
@@ -3183,7 +3152,7 @@ int agent_gen_ch_scan_response_tlv(struct agent *a, struct cmdu_buff *cmdu,
 	trace("\t %s:INFO: radio " MACFMT ", opclass %d, channel %d\n", __func__,
 		  MAC2STR(radio_mac), opclass_id, ch->channel);
 
-	get_timestamp(NULL, tsp);
+	time_to_timestr(NULL, tsp);
 	memcpy(ch->tsp, tsp, strlen(tsp));
 	t = cmdu_reserve_scan_response_tlv(cmdu, reserve_len,
 				tsp, radio_mac, opclass_id, ch, status, &offset);
diff --git a/src/assoc_ctrl.c b/src/assoc_ctrl.c
index 29d0a26f93fa2bccb9f315432190ab9ed5d0a83d..47ca5c2dde6f856a122ef3ccf16747bbae95e368 100644
--- a/src/assoc_ctrl.c
+++ b/src/assoc_ctrl.c
@@ -4,6 +4,9 @@
  * Copyright (C) 2025 IOPSYS Software Solutions AB. All rights reserved.
  *
  */
+#include <json-c/json.h>
+#include <libubox/blobmsg_json.h>
+
 #include <easymesh.h>
 
 #include "agent.h"
@@ -17,18 +20,358 @@
 #include "utils/debug.h"
 #include "utils/utils.h"
 
-static void assoc_ctrl_update_config(struct agent *a, uint8_t *mac,
-		struct netif_apcfg *cfg, int add)
+static void assoc_ctrl_remove_client(struct json_object *arr, uint8_t *macaddr)
+{
+	int len, i;
+
+	len = json_object_array_length(arr);
+	for (i = 0; i < len; i++) {
+		struct json_object *t;
+		const char *p;
+		uint8_t mac[6];
+
+		t = json_object_array_get_idx(arr, i);
+		if (!t) {
+			warn("%s: couldn't get entry:%d from restriction array",
+			     __func__, i);
+			continue;
+		}
+
+		p = json_get_string(t, "macaddr");
+		if (!p) {
+			warn("%s: couldn't get macaddr from entry:%d",
+			     __func__, i);
+			continue;
+		}
+
+		if (!hwaddr_aton(p, mac)) {
+			warn("%s: couldn't convert macaddr from entry:%d",
+			     __func__, i);
+			continue;
+		}
+
+		if (!memcmp(mac, macaddr, 6)) {
+			if (json_object_array_del_idx(arr, i, 1) < 0) {
+				warn("%s: couldn't remove entry:%d from restriction array",
+					 __func__, i);
+				return;
+			}
+			break;
+		}
+	}
+}
+
+static void assoc_ctrl_write_client(struct json_object *arr, uint8_t *macaddr,
+			uint16_t validity, uint8_t mode)
+{
+	struct json_object *val, *new;
+	char macstr[18] = {0};
+	char time_str[64] = {0};
+
+	if (!arr)
+		return;
+
+	if (!json_object_is_type(arr, json_type_array)) {
+		warn("%s: clients is not an array\n", __func__);
+		return;
+	}
+
+	/* remove client if it already exists */
+	assoc_ctrl_remove_client(arr, macaddr);
+
+	if (mode == ASSOC_CTRL_UNBLOCK) {
+		/* client is unblocked, no need to add */
+		return;
+	}
+
+	new = json_object_new_object();
+	if (!new)
+		return;
+
+	/* macaddr */
+	if (!hwaddr_ntoa(macaddr, macstr)) {
+		warn("%s: couldn't convert macstr", __func__);
+		return;
+	}
+
+	snprintf(macstr, sizeof(macstr), MACFMT, MAC2STR(macaddr)); /* Flawfinder: ignore */
+	val = json_object_new_string(macstr);
+	if (!val)
+		return;
+	json_object_object_add(new, "macaddr", val);
+
+	/* start_time */
+	time_to_timestr(NULL, time_str);
+	val = json_object_new_string(time_str);
+	if (!val)
+		return;
+	json_object_object_add(new, "start_time", val);
+
+	/* duration */
+	val = json_object_new_int(validity);
+	if (!val)
+		return;
+	json_object_object_add(new, "duration", val);
+
+	/* mode */
+	val = json_object_new_int(mode);
+	if (!val)
+		return;
+	json_object_object_add(new, "mode", val);
+
+	json_object_array_add(arr, new);
+}
+
+/* generate a restricted client entry if it does not exist */
+static int assoc_ctrl_update_restrict_file(uint8_t *bssid, uint8_t *macaddr,
+			uint16_t validity, uint8_t mode)
 {
-	char stastr[18] = {0};
+	struct json_object *restr_json;
+	struct json_object *bssids, *bssid_obj = NULL;
+	struct json_object *clients;
+	json_bool ret;
+	int len, i;
+
+	dbg("%s: file:%s\n", __func__, ASSOC_CTRL_FILE);
+
+	/* Get the json object from the file */
+	restr_json = json_object_from_file(ASSOC_CTRL_FILE);
+	if (!restr_json) {
+		dbg("%s: failed to read json:%s, error:%s. "\
+		     "Try to generate new\n", __func__,
+		     ASSOC_CTRL_FILE, json_util_get_last_err());
+
+		restr_json = json_object_new_object();
+		if (!restr_json) {
+			warn("%s: failed to create json obj, error:%s. ",
+			     __func__, json_util_get_last_err());
+			goto out;
+		}
+	}
+
+	/* Get the bssids array */
+	ret = json_object_object_get_ex(restr_json, "bssids", &bssids);
+	if (!ret) {
+		/* Create bssids array if not found */
+		bssids = json_object_new_array();
+		if (!bssids) {
+			warn("%s: failed to add bssid array, error:%s. ",
+			     __func__, json_util_get_last_err());
+			goto out_bssid;
+		}
+		json_object_object_add(restr_json, "bssids", bssids);
+	} else if (!json_object_is_type(bssids, json_type_array)) {
+		warn("%s: file: %s has wrong format\n",
+		     __func__, ASSOC_CTRL_FILE);
+		goto out_bssid;
+	}
+
+	/* Check if bssid already exists in bssid array */
+	len = json_object_array_length(bssids);
+	for (i = 0; i < len; i++) {
+		struct json_object *t;
+		uint8_t t_bssid[6];
+		const char *p;
+
+		t = json_object_array_get_idx(bssids, i);
+		if (!t) {
+			warn("%s: couldn't get entry:%d from bssid array", __func__, i);
+			continue;
+		}
+
+		p = json_get_string(t, "bssid");
+		if (!p) {
+			warn("%s: couldn't get bssid from entry:%d", __func__, i);
+			continue;
+		}
 
-	hwaddr_ntoa(mac, stastr);
-	config_update2("mapagent", &a->cfg, "ap",
-		"ifname", cfg->name,
-		"assoc_ctrl", add, stastr, 18);
+		if (!hwaddr_aton(p, t_bssid)) {
+			warn("%s: couldn't convert bssid from entry:%d", __func__, i);
+			continue;
+		}
+
+		if (!memcmp(bssid, t_bssid, ETH_ALEN)) {
+			/* bssid already exists in file */
+			bssid_obj = t;
+			break;
+		}
+	}
+
+	/* Create new bssid object if not found */
+	if (!bssid_obj) {
+		char bssidstr[18] = {0};
+		struct json_object *val;
+
+		bssid_obj = json_object_new_object();
+		if (WARN_ON(!bssid_obj))
+			goto out_bssid;
+
+		/* bssid */
+		hwaddr_ntoa(bssid, bssidstr);
+		val = json_object_new_string(bssidstr);
+		if (!val) {
+			json_object_put(bssid_obj);
+			goto out_bssid;
+		}
+		json_object_object_add(bssid_obj, "bssid", val);
+
+		/* Add bssid object to bssids array */
+		json_object_array_add(bssids, bssid_obj);
+	}
 
-	add ? stax_add_entry(&cfg->assoc_ctrllist, mac)
-		: stax_del_entry(&cfg->assoc_ctrllist, mac);
+	/* Get the clients array */
+	ret = json_object_object_get_ex(bssid_obj, "clients", &clients);
+	if (!ret) {
+		/* Create client array if not found */
+		clients = json_object_new_array();
+		if (!clients) {
+			json_object_put(bssid_obj);
+			goto out_bssid;
+		}
+		json_object_object_add(bssid_obj, "clients", clients);
+	} else {
+		if (!json_object_is_type(clients, json_type_array)) {
+			warn("|%s:%d| file:%s is not expected format\n", __func__,
+			     __LINE__, ASSOC_CTRL_FILE);
+			json_object_put(bssid_obj);
+			goto out_bssid;
+		}
+	}
+
+	/* Add (or remove) the client entry */
+	assoc_ctrl_write_client(clients, macaddr, validity, mode);
+
+	/* Update restricted clients file */
+	json_object_to_file(ASSOC_CTRL_FILE, restr_json);
+
+out_bssid:
+	json_object_put(restr_json);
+out:
+	return 0;
+}
+
+struct rsta {
+	uint8_t macaddr[6];
+	uint8_t mode;
+	uint16_t validity;
+};
+
+static int assoc_ctrl_read_restrictions(struct agent *a, uint8_t *bssid,
+			struct rsta *fr, int *num_fr)
+{
+	struct blob_buf bssids = { 0 };
+	struct blob_attr *b;
+	static const struct blobmsg_policy attr[] = {
+		[0] = { .name = "bssids", .type = BLOBMSG_TYPE_ARRAY },
+	};
+	struct blob_attr *tb[ARRAY_SIZE(attr)];
+	int rem;
+	int ret = 0;
+
+	blob_buf_init(&bssids, 0);
+
+	if (!blobmsg_add_json_from_file(&bssids, ASSOC_CTRL_FILE)) {
+		warn("Failed to parse %s\n", ASSOC_CTRL_FILE);
+		ret = -1;
+		goto out;
+	}
+
+	blobmsg_parse(attr, ARRAY_SIZE(attr), tb, blob_data(bssids.head), blob_len(bssids.head));
+
+	if (!tb[0])
+		goto out;
+
+	blobmsg_for_each_attr(b, tb[0], rem) {
+		static const struct blobmsg_policy bssid_attr[2] = {
+			[0] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING },
+			[1] = { .name = "clients", .type = BLOBMSG_TYPE_ARRAY },
+		};
+		struct blob_attr *tb1[ARRAY_SIZE(bssid_attr)];
+		char bssid_str[18] = {0};
+		uint8_t bssid_mac[6] = {0};
+		struct blob_attr *client;
+		int rem1;
+
+		blobmsg_parse(bssid_attr, ARRAY_SIZE(bssid_attr), tb1, blobmsg_data(b), blob_len(b));
+		if (!tb1[0] || !tb1[1])
+			continue;
+
+		strncpy(bssid_str, blobmsg_data(tb1[0]), sizeof(bssid_str) - 1);
+		if (!hwaddr_aton(bssid_str, bssid_mac)) {
+			warn("Failed to convert macaddr %s\n", bssid_str);
+			continue;
+		}
+
+		if (memcmp(bssid, bssid_mac, ETH_ALEN))
+			continue;
+
+		blobmsg_for_each_attr(client, tb1[1], rem1) {
+			static const struct blobmsg_policy client_attr[4] = {
+				[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
+				[1] = { .name = "start_time", .type = BLOBMSG_TYPE_STRING },
+				[2] = { .name = "duration", .type = BLOBMSG_TYPE_INT32 },
+				[3] = { .name = "mode", .type = BLOBMSG_TYPE_INT32 },
+			};
+			struct blob_attr *tb2[ARRAY_SIZE(client_attr)];
+			char mac_str[18] = {0};
+			uint8_t macaddr[6] = {0};
+			int32_t duration, elapsed;
+			time_t start, now = time(NULL);
+
+			blobmsg_parse(client_attr, ARRAY_SIZE(client_attr), tb2, blobmsg_data(client), blob_len(client));
+			if (!tb2[0] || !tb2[1] || !tb2[2] || !tb2[3])
+				continue;
+
+			strncpy(mac_str, blobmsg_data(tb2[0]), sizeof(mac_str) - 1);
+			if (!hwaddr_aton(mac_str, macaddr)) {
+				warn("Failed to convert macaddr %s\n", mac_str);
+				continue;
+			}
+
+			memcpy(fr[*num_fr].macaddr, macaddr, ETH_ALEN);
+			fr[*num_fr].mode = blobmsg_get_u32(tb2[3]);
+			start = timestr_to_time(blobmsg_get_string(tb2[1]));
+			duration = blobmsg_get_u32(tb2[2]);
+			elapsed = (int32_t)difftime(now, start);
+			if (elapsed < 0 || elapsed > duration) {
+				fr[*num_fr].validity = 0;
+			} else {
+				fr[*num_fr].validity = duration - elapsed;
+			}
+
+			(*num_fr)++;
+		}
+	}
+
+out:
+	blob_buf_free(&bssids);
+
+	return ret;
+}
+
+static void assoc_ctrl_clean_expired(struct agent *a, uint8_t *bssid,
+			struct rsta *fr, int *num_fr)
+{
+	int i;
+
+	for (i = 0; i < *num_fr; i++) {
+		if ((fr[i].mode == ASSOC_CTRL_TIMED_BLOCK || fr[i].mode == ASSOC_CTRL_BLOCK)
+				&& fr[i].validity == 0) {
+			int j;
+
+			/* remove from file */
+			assoc_ctrl_update_restrict_file(bssid, fr[i].macaddr,
+					0, ASSOC_CTRL_UNBLOCK);
+			/* remove fr[i] from fr */
+			for (j = i; j < *num_fr - 1; j++)
+				memcpy(&fr[j], &fr[j + 1], sizeof(struct rsta));
+			memset(&fr[*num_fr - 1], 0, sizeof(struct rsta));
+			/* decrease num_fr */
+			(*num_fr)--;
+			/* decrease i */
+			i--;
+		}
+	}
 }
 
 static void agent_disconnect_stas(struct agent *a, int num_sta,
@@ -110,6 +453,7 @@ static void wifi_sta_restrict_timeout_cb(atimer_t *t)
 
 	/* Unblock STA */
 	wifi_restrict_sta(ap, s->macaddr, false);
+	assoc_ctrl_update_restrict_file(ap->bssid, s->macaddr, 0, ASSOC_CTRL_UNBLOCK);
 	list_del(&s->list);
 	free(s);
 }
@@ -126,47 +470,49 @@ static struct restrict_sta_entry *find_restricted_sta(struct netif_ap *ap, uint8
 }
 
 int assoc_ctrl_add_sta(struct agent *a, struct netif_ap *ap,
-		uint32_t num_sta, uint8_t sta_list[][6],
-		uint16_t validity_period, uint8_t mode,
+		uint32_t num_fr, struct rsta fr[],
 		bool update_config)
 {
 	trace("agent: %s: --->\n", __func__);
 
 	int i = 0;
 
-	if (!sta_list)
+	if (!fr)
 		return -1;
 
-	for (i = 0; i < num_sta; i++) {
+	for (i = 0; i < num_fr; i++) {
 		struct restrict_sta_entry *s;
 
-		s = find_restricted_sta(ap, sta_list[i]);
+		s = find_restricted_sta(ap, fr[i].macaddr);
 		if (!s) {
 			/* Add new entry to restricted list */
 			s = calloc(1, sizeof(struct restrict_sta_entry));
 			if (!s)
 				return -1;
 
-			memcpy(s->macaddr, sta_list[i], 6);
+			memcpy(s->macaddr, fr[i].macaddr, 6);
 			memcpy(s->bssid, ap->bssid, 6);
 			s->agent = a;
-			s->mode = mode;
+			s->mode = fr[i].mode;
 			list_add_tail(&s->list, &ap->restrict_stalist);
 
 			timer_init(&s->restrict_timer, wifi_sta_restrict_timeout_cb);
 		}
 
-		if (validity_period > 0)
-			/* If timeout is positive, create a timed sta assoc-control
-			 * entry but don't update config. At the timer expiry, remove
-			 * sta from assoc-control list.
-			 */
-			timer_set(&s->restrict_timer, validity_period * 1000);
-		else if (update_config)
-			/* If timeout is 0 or negative, permanently add sta to the
-			 * AP's assoc control list by creating a config entry.
-			 */
-			assoc_ctrl_update_config(a, sta_list[i], ap->cfg, 1);
+		/* Update validity timer */
+		if ((s->mode == ASSOC_CTRL_TIMED_BLOCK || s->mode == ASSOC_CTRL_BLOCK)) {
+			if (fr[i].validity > 0)
+				timer_set(&s->restrict_timer, fr[i].validity * 1000);
+			else {
+				/* error */
+				continue;
+			}
+		}
+
+		/* Update restriction file */
+		if (update_config)
+			assoc_ctrl_update_restrict_file(ap->bssid,
+					fr[i].macaddr, fr[i].validity, fr[i].mode);
 	}
 
 	return 0;
@@ -177,13 +523,27 @@ int assoc_ctrl_block_sta(struct agent *a, struct netif_ap *ap,
 		uint32_t num_sta, uint8_t sta_list[][6])
 {
 	trace("agent: %s: --->\n", __func__);
-	int ret = 0;
 
-	/* Add STA to restricted list */
-	ret = assoc_ctrl_add_sta(a, ap, num_sta, sta_list,
-			validity_period, mode, true);
+	struct rsta fr[ASSOC_CTRL_MAX_STA];
+	int num_fr = 0;
+	int i, ret = 0;
+
+	if (num_sta > ASSOC_CTRL_MAX_STA) {
+		err("[%s:%d] Invalid number of STAs\n", __func__, __LINE__);
+		return -1;
+	}
+
+	for (i = 0; i < num_sta; i++) {
+		memcpy(fr[num_fr].macaddr, sta_list[i], ETH_ALEN);
+		fr[num_fr].mode = mode;
+		fr[num_fr].validity = validity_period;
+		num_fr++;
+	}
+
+	/* Add STAs to runtime & file lists */
+	ret = assoc_ctrl_add_sta(a, ap, num_fr, fr, true);
 	if (!ret)
-		/* Block STA in WiFi */
+		/* Block STAs in WiFi */
 		ret = assoc_ctrl_restrict_sta(a, num_sta, sta_list, ap, mode);
 
 	return ret;
@@ -201,18 +561,21 @@ int assoc_ctrl_del_sta(struct agent *a, struct netif_ap *ap,
 		return -1;
 
 	for (i = 0; i < num_sta; i++) {
-		/* Stop validity timer and remove entry from restricted list */
+
+		/* Stop validity timer and remove entry from runtime list */
 		list_for_each_entry_safe(s, tmp, &ap->restrict_stalist, list) {
 			if (!memcmp(s->macaddr, sta_list[i], 6)) {
-				if (update_config)
-					/* Update config to remove the STA */
-					assoc_ctrl_update_config(a, sta_list[i], ap->cfg, 0);
-
 				timer_del(&s->restrict_timer);
 				list_del(&s->list);
 				free(s);
 			}
 		}
+
+		/* Remove STA from restriction file */
+		if (update_config) {
+			assoc_ctrl_update_restrict_file(ap->bssid,
+					sta_list[i], 0, ASSOC_CTRL_UNBLOCK);
+		}
 	}
 
 	return 0;
@@ -224,23 +587,23 @@ int assoc_ctrl_unblock_sta(struct agent *a, struct netif_ap *ap,
 	trace("agent: %s: --->\n", __func__);
 	int ret = 0;
 
-	/* Remove STA from restricted list */
+	/* Remove STAs from runtime and file lists */
 	ret = assoc_ctrl_del_sta(a, ap, num_sta, sta_list, true);
 	if (!ret)
-		/* Unblock STA in WiFi */
+		/* Unblock STAs in WiFi */
 		ret = assoc_ctrl_restrict_sta(a, num_sta, sta_list, ap, ASSOC_CTRL_UNBLOCK);
 
 	return ret;
 }
 
-int assoc_ctrl_sync_from_config(struct agent *a, struct netif_ap *ap)
+/* Sync runtime list from file list only - do not apply restriction */
+int assoc_ctrl_sync_from_file(struct agent *a, struct netif_ap *ap)
 {
 	trace("agent: %s: --->\n", __func__);
 
-	struct stax *x = NULL;
+	struct rsta fr[ASSOC_CTRL_MAX_STA];
+	int num_fr = 0;
 	struct restrict_sta_entry *s = NULL;
-	uint8_t stalist[MAX_STA][6] = {0};
-	int num_sta = 0;
 	int ret = 0;
 
 	if (!ap || !ap->cfg) {
@@ -248,33 +611,37 @@ int assoc_ctrl_sync_from_config(struct agent *a, struct netif_ap *ap)
 		return -EINVAL;
 	}
 
-	/* 1. Add STAs missing in runtime list */
-	list_for_each_entry(x, &ap->cfg->assoc_ctrllist, list) {
-		memcpy(stalist[num_sta++], x->macaddr, 6);
-	}
+	/* Read STA restrictions from file list */
+	ret = assoc_ctrl_read_restrictions(a, ap->bssid, fr, &num_fr);
+	if (ret)
+		return ret;
 
-	if (num_sta > 0)
-		/* Update the runtime list and restart timer */
-		ret |= assoc_ctrl_add_sta(a, ap, num_sta, stalist,
-				0 /* FIXME tmo from cfg */,
-				ASSOC_CTRL_INDEF_BLOCK /* FIXME mode from cfg */,
-				false);
+	/* Remove all outdated entries from file */
+	assoc_ctrl_clean_expired(a, ap->bssid, fr, &num_fr);
 
-	/* 2. Remove STAs that are not in config list */
+	/* 1. Add/Update STAs: file list -> runtime list */
+	if (num_fr > 0)
+		ret |= assoc_ctrl_add_sta(a, ap, num_fr, fr, false);
+
+	/* 2. Remove STAs from runtime if not present in file */
 	list_for_each_entry(s, &ap->restrict_stalist, list) {
 		bool found = false;
 		int i = 0;
 
-		for (i = 0; i < num_sta; i++) {
-			if (!memcmp(stalist[i], x->macaddr, 6)) {
+		for (i = 0; i < num_fr; i++) {
+			if (!memcmp(fr[i].macaddr, s->macaddr, 6)) {
 				found = true;
 				break;
 			}
 		}
 
 		if (!found) {
-			/* Remove STA from runtime list */
-			ret |= assoc_ctrl_del_sta(a, ap, 1, &x->macaddr, false);
+			uint8_t sta_list[1][6];
+
+			/* Remove STA from runtime list & unlock in WiFi */
+			memcpy(sta_list[0], s->macaddr, 6);
+			ret |= assoc_ctrl_unblock_sta(a, ap, 1, sta_list);
+
 		}
 	}
 
@@ -289,11 +656,12 @@ int assoc_ctrl_apply_restriction(struct agent *a, struct netif_ap *ap)
 	uint8_t wifi_res_stas[ASSOC_CTRL_MAX_STA * 6] = {0};
 	int num_wifi_res_stas = ASSOC_CTRL_MAX_STA;
 	struct restrict_sta_entry *s = NULL;
-	int i;
 	int ret = 0;
 
-	if (list_empty(&ap->restrict_stalist))
+	if (list_empty(&ap->restrict_stalist)) {
+		dbg("%s: restrict STA list empty for %s\n", __func__, ap->ifname);
 		return 0;
+	}
 
 	/* Get the list of STAs currently blocked in WiFi */
 	ret = wifi_get_restricted_stas(ap, wifi_res_stas, &num_wifi_res_stas);
@@ -302,24 +670,44 @@ int assoc_ctrl_apply_restriction(struct agent *a, struct netif_ap *ap)
 		return -1;
 	}
 
-	/* Invoke block_sta for STAs that are in runtime list but not in blocked_stas */
+	/* Invoke block_sta for STAs on runtime list not blocked in Wi-Fi */
 	list_for_each_entry(s, &ap->restrict_stalist, list) {
+		bool blocked = false;
+		int i;
 
 		for (i = 0; i < num_wifi_res_stas; i++) {
+			if (!memcmp(&wifi_res_stas[i * 6], s->macaddr, 6)) {
+				/* STA blocked in WiFi */
+				blocked = true;
+				break;
+			}
+		}
 
-			if (!memcmp(&wifi_res_stas[i * 6], s->macaddr, 6))
-				/* STA already blocked in WiFi */
-				continue;
+		if (blocked) {
+			/* STA already blocked in WiFi */
+			dbg("%s: STA already blocked in WiFi\n", __func__);
+			continue;
+		}
 
-			assoc_ctrl_restrict_sta(a, 1, &s->macaddr, ap, s->mode);
+		if ((s->mode == ASSOC_CTRL_TIMED_BLOCK || s->mode == ASSOC_CTRL_BLOCK)
+				&& !timer_pending(&s->restrict_timer)) {
+			/* Timer expired: do not block in WiFi */
+			dbg("%s: Timer expired for " MACFMT " - won't block in WiFi\n",
+			    __func__, MAC2STR(s->macaddr));
+			continue;
 		}
+
+		dbg("%s: Block STA: " MACFMT " in %s\n",
+		    __func__, MAC2STR(s->macaddr), ap->ifname);
+		assoc_ctrl_restrict_sta(a, 1, &s->macaddr, ap, s->mode);
 	}
 
-	/* Theoretically, it could be blocked for other reasons, i.e. network admin-issued block
-	 * manually or via webGUI. Uncomment this part if we want to unblock STAs anyway.
+	/* Theoretically, it could be blocked for other reasons,
+	 * i.e. network admin-issued block, manually or via webGUI.
+	 * Uncomment following code if we want to unblock STAs anyway.
 	 */
 #if 0
-	/* Invoke unblock_sta for STAs that are in blocked_stas but not in runtime list */
+	/* Unblock STAs in Wi-FI if not found on runtime list */
 	for (i = 0; i < num_wifi_res_stas; i++) {
 		bool found = false;
 
@@ -403,7 +791,7 @@ int assoc_ctrl_process_request(void *agent, uint8_t *p,
 	}
 
 	switch (data->control) {
-	case ASSOC_CTRL_BLOCK:
+	case ASSOC_CTRL_BLOCK: /* Client Blocking */
 		/* Block - check for an Error Scenario:
 		 * Send an error TLV in case STA is already associated
 		 * with the BSSID for which the 'Block' mode is being set.
@@ -430,8 +818,8 @@ int assoc_ctrl_process_request(void *agent, uint8_t *p,
 			goto send_ack;
 		}
 		/* no break - checkpatch ignore */
-	case ASSOC_CTRL_TIMED_BLOCK:
-		/* tmo needed for block and timed block modes */
+	case ASSOC_CTRL_TIMED_BLOCK: /* Timed Block */
+		/* Validity Period mandatory for Client Blocking and Timed Block */
 		validity_period = BUF_GET_BE16(data->validity_period);
 		if (!validity_period) {
 			warn("Validity period unset, while expected!\n");
@@ -439,12 +827,12 @@ int assoc_ctrl_process_request(void *agent, uint8_t *p,
 			goto send_ack;
 		}
 		/* no break - checkpatch ignore */
-	case ASSOC_CTRL_INDEF_BLOCK:
+	case ASSOC_CTRL_INDEF_BLOCK: /* Indefinite Block */
 		/* Add STA to blocking list for block/timed block/indef block */
 		WARN_ON(assoc_ctrl_block_sta(a, ap, validity_period, data->control,
 					data->num_sta, sta_list));
 		break;
-	case ASSOC_CTRL_UNBLOCK:
+	case ASSOC_CTRL_UNBLOCK: /* Client Unblocking */
 		/* Rem STA from blocking list and stop the restrict timer */
 		WARN_ON(assoc_ctrl_unblock_sta(a, ap, data->num_sta, sta_list));
 		break;
diff --git a/src/assoc_ctrl.h b/src/assoc_ctrl.h
index b16e0e68c11562f93aadd8f7d59c73bba1bfc723..575e7ff66ba732f546ae5df2a540f15e253db31a 100644
--- a/src/assoc_ctrl.h
+++ b/src/assoc_ctrl.h
@@ -7,16 +7,21 @@
 struct agent;
 struct netif_ap;
 
+#if defined MAX_STA && MAX_STA > 0 && MAX_STA <= 32
+#define ASSOC_CTRL_MAX_STA MAX_STA
+#else
 #define ASSOC_CTRL_MAX_STA 32
+#endif
+
+#define ASSOC_CTRL_FILE "/etc/multiap/assoc_ctrl.json"
 
 int assoc_ctrl_block_sta(struct agent *a, struct netif_ap *ap,
 		uint16_t validity_period, uint8_t mode,
 		uint32_t sta_count, uint8_t stalist[][6]);
 int assoc_ctrl_unblock_sta(struct agent *a, struct netif_ap *ap,
 		uint32_t sta_count, uint8_t stalist[][6]);
-int assoc_ctrl_sync_from_config(struct agent *a, struct netif_ap *ap);
+int assoc_ctrl_sync_from_file(struct agent *a, struct netif_ap *ap);
 int assoc_ctrl_apply_restriction(struct agent *a, struct netif_ap *ap);
 int assoc_ctrl_process_request(void *agent, uint8_t *p, struct cmdu_buff *cmdu);
 int assoc_ctrl_reapply(struct netif_ap *ap);
-
 #endif
diff --git a/src/config.c b/src/config.c
index d939bb35f380224fb94818acf9fb46631bafa2b1..77e8466a7c760bf98c6997c151431aa27d271a99 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1894,12 +1894,6 @@ void agent_config_dump(struct agent_config *cfg)
 			 *	pol->dump_config(pol, pol->policy);
 			 */
 		}
-
-		x = NULL;
-		dbg("  Assoc Ctrl Lists -------\n");
-		list_for_each_entry(x, &n->assoc_ctrllist, list) {
-			dbg("    mac: " MACFMT "\n", MAC2STR(x->macaddr));
-		}
 	}
 }
 
@@ -1940,8 +1934,6 @@ struct netif_apcfg *create_fronthaul_iface_config(struct agent_config *cfg,
 		list_add(&pol->list, &new->steer_policylist);
 	}
 
-	INIT_LIST_HEAD(&new->assoc_ctrllist);
-
 	/* f->cfg = new; */
 	dbg("%s: %s netif_ap->cfg = %p\n", __func__, new->name, new);
 
@@ -2879,7 +2871,6 @@ static int agent_config_get_ap(struct agent_config *a,
 		AP_BAND,
 		AP_STEER,
 		AP_DEVICE,
-		AP_ASSOC_CTRL,
 		AP_BTM_RETRY,
 		AP_BTM_RETRY_SECS,
 		AP_ASSOC_CTRL_SECS,
@@ -2901,7 +2892,6 @@ static int agent_config_get_ap(struct agent_config *a,
 		{ .name = "band", .type = UCI_TYPE_STRING },
 		{ .name = "steer", .type = UCI_TYPE_LIST },
 		{ .name = "device", .type = UCI_TYPE_STRING },
-		{ .name = "assoc_ctrl", .type = UCI_TYPE_LIST },
 		{ .name = "btm_retry", .type = UCI_TYPE_STRING },
 		{ .name = "btm_retry_secs", .type = UCI_TYPE_STRING },
 		{ .name = "assoc_ctrl_secs", .type = UCI_TYPE_STRING },
@@ -2995,16 +2985,6 @@ static int agent_config_get_ap(struct agent_config *a,
 		return -1;
 	}
 
-	if (tb[AP_ASSOC_CTRL]) {
-		struct uci_element *xi;
-		uint8_t macaddr[ETH_ALEN];
-
-		uci_foreach_element(&tb[AP_ASSOC_CTRL]->v.list, xi) {
-			hwaddr_aton(xi->name, macaddr);
-			stax_add_entry(&ap->assoc_ctrllist, macaddr);
-		}
-	}
-
 	if (tb[AP_BTM_RETRY])
 		ap->steer_btm_retry = atoi(tb[AP_BTM_RETRY]->v.string);
 
diff --git a/src/config.h b/src/config.h
index 80b4096a71803399cb25631180315e6f0d37b6dd..204898cc766108b892d9c1e74755e534029105df 100644
--- a/src/config.h
+++ b/src/config.h
@@ -68,6 +68,7 @@ struct steer_policy {
 
 struct stax {
 	uint8_t macaddr[6];
+
 	struct list_head list;
 };
 
@@ -133,9 +134,6 @@ struct netif_apcfg {
 	/** ordered list of policies effective on per-bss interface */
 	struct list_head steer_policylist;
 
-	/** STAs assoc controlled; list of stax structs */
-	struct list_head assoc_ctrllist;
-
 	enum agent_steer_policy policy;
 
 	struct list_head list;  /* link to next netif_config */
diff --git a/src/timer.c b/src/timer.c
index 9fd2846e4e3bb74810562cbec9198d0d6aff7417..e96d413bf430d1616c3fc8cb133746ea99b1f7e2 100644
--- a/src/timer.c
+++ b/src/timer.c
@@ -13,6 +13,8 @@
 
 #include <libubox/uloop.h>
 #include <stdint.h>
+#include <stdio.h>
+#include <string.h>
 #include <sys/time.h>
 #include <time.h>
 
@@ -96,3 +98,90 @@ bool timestamp_less_or_equal(const struct timespec *lhs, const struct timespec *
 {
 	return !timestamp_less_than(rhs, lhs);
 }
+
+char *time_to_timestr(const time_t *t, char *tsp)
+{
+	char tmpbuf[64] = {0};
+	struct tm res;
+	char sign;
+	long toff, toff_hour, toff_min;
+	time_t tt;
+
+	if (!tsp)
+		return NULL;
+
+	if (!t)
+		/* use now */
+		time(&tt);
+	else
+		tt = *t;
+
+	/* E.g. "2019-02-11T06:42:31.23039-08:00" */
+
+	localtime_r(&tt, &res);
+	tzset();
+	toff = timezone;
+	sign = toff > 0 ? '-' : '+';
+	toff *= -1L;
+
+	toff_hour = toff / 3600;
+	toff_min = (toff % 3600) / 60;
+
+	snprintf(tmpbuf, sizeof(tmpbuf), "%04d-%02d-%02dT%02d:%02d:%02d%c%02ld:%02ld",
+			 res.tm_year + 1900, res.tm_mon + 1, res.tm_mday,
+			 res.tm_hour, res.tm_min, res.tm_sec,
+			 sign, toff_hour, toff_min);
+
+	snprintf(tsp, 64, "%s", tmpbuf);
+
+	return tsp;
+}
+
+/* get time adjustment seconds (time(tzone) - time(UTC) secs) */
+static long timestamp_get_off_sec(const char *tsp)
+{
+	char *tzone;
+	int toff = 0, sign;
+	int toff_hour, toff_min;
+
+	/* Example timestamp: "2019-02-11T06:42:31-08:00" */
+
+	tzone = strchr(tsp, '+');
+	if (!tzone) {
+		tzone = strrchr(tsp, '-'); /* last occurence */
+		sign = -1L;
+	} else {
+		sign = 1L;
+	}
+
+	if (tzone) {
+		int ret = 0;
+
+		ret = sscanf(tzone+1, "%02d:%02d", &toff_hour, &toff_min);
+		if (ret == 2) {
+			toff = toff_hour * 3600 + toff_min * 60; // seconds
+			toff *= -sign;
+		}
+	}
+
+	return toff;
+}
+
+/* Returns time alligned to UTC+0 */
+time_t timestr_to_time(const char *tsp)
+{
+	struct tm tm_time;
+	time_t res;
+
+	/* Example timestamp: "2019-02-11T06:42:31-08:00" */
+	memset(&tm_time, 0, sizeof(tm_time));
+	strptime(tsp, "%Y-%m-%dT%H:%M:%S", &tm_time);
+
+	tzset();
+	res = mktime(&tm_time);
+
+	/* Allign by toff to get UTC+0 */
+	res += timestamp_get_off_sec(tsp);
+
+	return res;
+}
diff --git a/src/timer.h b/src/timer.h
index 9e7b3274f61bed9a7bcc88885796faed04500c8e..3324ad9ab681d4333893d9dbd4cd2487fed53158 100644
--- a/src/timer.h
+++ b/src/timer.h
@@ -34,5 +34,7 @@ bool timestamp_greater_than(const struct timespec *lhs, const struct timespec *r
 bool timestamp_greater_or_equal(const struct timespec *lhs, const struct timespec *rhs);
 bool timestamp_less_or_equal(const struct timespec *lhs, const struct timespec *rhs);
 
+char *time_to_timestr(const time_t *t, char *tsp);
+time_t timestr_to_time(const char *tsp);
 
 #endif /* ATIMER_H */