From bd49fe3fb7397d576fd43fffaca9f5fad64115e9 Mon Sep 17 00:00:00 2001 From: Filip Matusiak <filip.matusiak@iopsys.eu> Date: Tue, 25 Mar 2025 16:50:24 +0100 Subject: [PATCH 1/5] Add methods to store and recover timestamp string --- src/agent_tlv.c | 35 ++----------------- src/timer.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++ src/timer.h | 2 ++ 3 files changed, 93 insertions(+), 33 deletions(-) diff --git a/src/agent_tlv.c b/src/agent_tlv.c index 3dda667d2..4a3208b7b 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/timer.c b/src/timer.c index 9fd2846e4..e96d413bf 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 9e7b3274f..3324ad9ab 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 */ -- GitLab From 5989265dd1758a68fa68528d1d88c366152f7ce2 Mon Sep 17 00:00:00 2001 From: Filip Matusiak <filip.matusiak@iopsys.eu> Date: Fri, 28 Feb 2025 11:04:10 +0100 Subject: [PATCH 2/5] assoc ctrl: store restricted clients in file Additionally synchronize restrictions of runtime and file --- src/agent.c | 26 +-- src/assoc_ctrl.c | 508 ++++++++++++++++++++++++++++++++++++++++------- src/assoc_ctrl.h | 9 +- 3 files changed, 451 insertions(+), 92 deletions(-) diff --git a/src/agent.c b/src/agent.c index 89a0439a0..9f5291804 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) @@ -6720,10 +6704,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 +6763,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; @@ -6909,6 +6897,10 @@ int agent_init_interfaces(struct agent *a) strncpy(bss->ssid, fn->ssid, sizeof(bss->ssid) - 1); bss->enabled = fn->enabled; + /* FIXME: only read file once upon start (issues with bssid zero) */ + /* Update STA restrictions based on restriction file */ + assoc_ctrl_sync_from_file(a, fn); + for (k = 0; k < num_subfr; k++) wifi_subscribe_frame(f->name, subfr[k].type, subfr[k].stype); diff --git a/src/assoc_ctrl.c b/src/assoc_ctrl.c index 29d0a26f9..e7f92a243 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,334 @@ #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) +{ + 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; + } + + 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); + } + + /* 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; +}; + +int assoc_ctrl_read_restrictions(struct agent *a, uint8_t *bssid, + struct rsta *fr, int *num_fr) { - char stastr[18] = {0}; + 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; - hwaddr_ntoa(mac, stastr); - config_update2("mapagent", &a->cfg, "ap", - "ifname", cfg->name, - "assoc_ctrl", add, stastr, 18); + 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; - add ? stax_add_entry(&cfg->assoc_ctrllist, mac) - : stax_del_entry(&cfg->assoc_ctrllist, mac); + 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; + /* TODO: remove outdated entry from file */ + } + + (*num_fr)++; + } + } + +out: + blob_buf_free(&bssids); + + return ret; } static void agent_disconnect_stas(struct agent *a, int num_sta, @@ -110,6 +429,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 +446,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 +499,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 +537,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 +563,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 +587,35 @@ 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); - /* 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 +630,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 +644,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 +765,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 +792,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 +801,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 b16e0e68c..575e7ff66 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 -- GitLab From 4a46e3c6083c51fb95124acf7d6e617a2a0644e3 Mon Sep 17 00:00:00 2001 From: Filip Matusiak <filip.matusiak@iopsys.eu> Date: Fri, 28 Mar 2025 14:50:22 +0100 Subject: [PATCH 3/5] assoc ctrl: deprecate use of assoc_ctrllist in AP config --- src/config.c | 20 -------------------- src/config.h | 4 +--- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/src/config.c b/src/config.c index d939bb35f..77e8466a7 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 80b4096a7..204898cc7 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 */ -- GitLab From 5ac827300fdda51aeb14d776740bf06363eb6ce3 Mon Sep 17 00:00:00 2001 From: Filip Matusiak <filip.matusiak@iopsys.eu> Date: Wed, 23 Apr 2025 09:37:16 +0200 Subject: [PATCH 4/5] assoc ctrl: Remove outdated entries from file --- src/assoc_ctrl.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/assoc_ctrl.c b/src/assoc_ctrl.c index e7f92a243..47ca5c2dd 100644 --- a/src/assoc_ctrl.c +++ b/src/assoc_ctrl.c @@ -256,7 +256,7 @@ struct rsta { uint16_t validity; }; -int assoc_ctrl_read_restrictions(struct agent *a, uint8_t *bssid, +static int assoc_ctrl_read_restrictions(struct agent *a, uint8_t *bssid, struct rsta *fr, int *num_fr) { struct blob_buf bssids = { 0 }; @@ -337,7 +337,6 @@ int assoc_ctrl_read_restrictions(struct agent *a, uint8_t *bssid, fr[*num_fr].validity = 0; } else { fr[*num_fr].validity = duration - elapsed; - /* TODO: remove outdated entry from file */ } (*num_fr)++; @@ -350,6 +349,31 @@ out: 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, uint8_t sta_macs[][6], struct netif_ap *ap) { @@ -592,6 +616,8 @@ int assoc_ctrl_sync_from_file(struct agent *a, struct netif_ap *ap) if (ret) return ret; + /* Remove all outdated entries from file */ + assoc_ctrl_clean_expired(a, ap->bssid, fr, &num_fr); /* 1. Add/Update STAs: file list -> runtime list */ if (num_fr > 0) -- GitLab From ea8f8ff2dc10298095fe73a6f16b79a6eaa38647 Mon Sep 17 00:00:00 2001 From: Filip Matusiak <filip.matusiak@iopsys.eu> Date: Mon, 26 May 2025 11:30:49 +0200 Subject: [PATCH 5/5] assoc_ctrl: only sync from file once upon reload --- src/agent.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/agent.c b/src/agent.c index 9f5291804..d48fb6f7e 100644 --- a/src/agent.c +++ b/src/agent.c @@ -5180,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); @@ -5194,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) @@ -6893,14 +6898,17 @@ 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; - /* FIXME: only read file once upon start (issues with bssid zero) */ - /* Update STA restrictions based on restriction file */ - assoc_ctrl_sync_from_file(a, fn); - for (k = 0; k < num_subfr; k++) wifi_subscribe_frame(f->name, subfr[k].type, subfr[k].stype); @@ -7563,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); @@ -7580,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); } -- GitLab