#include "usteer.h"
#include "node.h"
+#include "event.h"
static bool
-below_assoc_threshold(struct sta_info *si_cur, struct sta_info *si_new)
+below_assoc_threshold(struct usteer_node *node_cur, struct usteer_node *node_new)
{
- int n_assoc_cur = si_cur->node->n_assoc;
- int n_assoc_new = si_new->node->n_assoc;
- bool ref_5g = si_cur->node->freq > 4000;
- bool node_5g = si_new->node->freq > 4000;
+ int n_assoc_cur = node_cur->n_assoc;
+ int n_assoc_new = node_new->n_assoc;
+ bool ref_5g = node_cur->freq > 4000;
+ bool node_5g = node_new->freq > 4000;
+
+ if (!config.load_balancing_threshold)
+ return false;
if (ref_5g && !node_5g)
n_assoc_new += config.band_steering_threshold;
n_assoc_new += config.load_balancing_threshold;
- if (n_assoc_new > n_assoc_cur) {
- MSG_T_STA("band_steering_threshold,load_balancing_threshold",
- si_cur->sta->addr, "exeeded (bs=%u, lb=%u)\n",
- config.band_steering_threshold,
- config.load_balancing_threshold);
- }
return n_assoc_new <= n_assoc_cur;
}
static bool
-better_signal_strength(struct sta_info *si_cur, struct sta_info *si_new)
+better_signal_strength(int signal_cur, int signal_new)
{
- const bool is_better = si_new->signal - si_cur->signal
+ const bool is_better = signal_new - signal_cur
> (int) config.signal_diff_threshold;
if (!config.signal_diff_threshold)
return false;
- if (is_better) {
- MSG_T_STA("signal_diff_threshold", si_cur->sta->addr,
- "exceeded (config=%i) (real=%i)\n",
- config.signal_diff_threshold,
- si_new->signal - si_cur->signal);
- }
return is_better;
}
static bool
-below_load_threshold(struct sta_info *si)
+below_load_threshold(struct usteer_node *node)
{
- return si->node->n_assoc >= config.load_kick_min_clients &&
- si->node->load > config.load_kick_threshold;
+ return node->n_assoc >= config.load_kick_min_clients &&
+ node->load > config.load_kick_threshold;
}
static bool
-has_better_load(struct sta_info *si_cur, struct sta_info *si_new)
+has_better_load(struct usteer_node *node_cur, struct usteer_node *node_new)
{
- return !below_load_threshold(si_cur) && below_load_threshold(si_new);
+ return !below_load_threshold(node_cur) && below_load_threshold(node_new);
}
-static bool
-below_max_assoc(struct sta_info *si)
+bool
+usteer_policy_node_below_max_assoc(struct usteer_node *node)
{
- struct usteer_node *node = si->node;
-
return !node->max_assoc || node->n_assoc < node->max_assoc;
}
static bool
-is_better_candidate(struct sta_info *si_cur, struct sta_info *si_new)
+over_min_signal(struct usteer_node *node, int signal)
{
- if (!below_max_assoc(si_new))
+ if (config.min_snr && signal < usteer_snr_to_signal(node, config.min_snr))
return false;
- return below_assoc_threshold(si_cur, si_new) ||
- better_signal_strength(si_cur, si_new) ||
- has_better_load(si_cur, si_new);
+ if (config.roam_trigger_snr && signal < usteer_snr_to_signal(node, config.roam_trigger_snr))
+ return false;
+
+ return true;
+}
+
+static uint32_t
+is_better_candidate(struct sta_info *si_cur, struct sta_info *si_new)
+{
+ struct usteer_node *current_node = si_cur->node;
+ struct usteer_node *new_node = si_new->node;
+ int current_signal = si_cur->signal;
+ int new_signal = si_new->signal;
+ uint32_t reasons = 0;
+
+ if (!usteer_policy_node_below_max_assoc(new_node))
+ return 0;
+
+ if (!over_min_signal(new_node, new_signal))
+ return 0;
+
+ if (below_assoc_threshold(current_node, new_node) &&
+ !below_assoc_threshold(new_node, current_node))
+ reasons |= (1 << UEV_SELECT_REASON_NUM_ASSOC);
+
+ if (better_signal_strength(current_signal, new_signal))
+ reasons |= (1 << UEV_SELECT_REASON_SIGNAL);
+
+ if (has_better_load(current_node, new_node) &&
+ !has_better_load(current_node, new_node))
+ reasons |= (1 << UEV_SELECT_REASON_LOAD);
+
+ return reasons;
}
static struct sta_info *
-find_better_candidate(struct sta_info *si_ref)
+find_better_candidate(struct sta_info *si_ref, struct uevent *ev, uint32_t required_criteria, uint64_t max_age)
{
- struct sta_info *si;
+ struct sta_info *si, *candidate = NULL;
struct sta *sta = si_ref->sta;
+ uint32_t reasons;
list_for_each_entry(si, &sta->nodes, list) {
if (si == si_ref)
continue;
- if (current_time - si->seen > config.seen_policy_timeout) {
- MSG_T_STA("seen_policy_timeout", si->sta->addr,
- "timeout exceeded (%u)\n", config.seen_policy_timeout);
+ if (current_time - si->seen > config.seen_policy_timeout)
continue;
- }
if (strcmp(si->node->ssid, si_ref->node->ssid) != 0)
continue;
- if (is_better_candidate(si_ref, si) &&
- !is_better_candidate(si, si_ref))
- return si;
+ if (max_age && max_age < current_time - si->seen)
+ continue;
+
+ reasons = is_better_candidate(si_ref, si);
+ if (!reasons)
+ continue;
+
+ if (!(reasons & required_criteria))
+ continue;
+
+ if (ev) {
+ ev->si_other = si;
+ ev->select_reasons = reasons;
+ }
+
+ if (!candidate || si->signal > candidate->signal)
+ candidate = si;
}
- return NULL;
+
+ return candidate;
}
-static int
-snr_to_signal(struct usteer_node *node, int snr)
+int
+usteer_snr_to_signal(struct usteer_node *node, int snr)
{
int noise = -95;
bool
usteer_check_request(struct sta_info *si, enum usteer_event_type type)
{
- struct sta_info *si_new;
+ struct uevent ev = {
+ .si_cur = si,
+ };
int min_signal;
+ bool ret = true;
if (type == EVENT_TYPE_AUTH)
- return true;
-
- if (type == EVENT_TYPE_ASSOC && !config.assoc_steering)
- return true;
+ goto out;
- if (si->stats[type].blocked_cur >= config.max_retry_band) {
- MSG_T_STA("max_retry_band", si->sta->addr,
- "max retry (%u) exceeded\n", config.max_retry_band);
- return true;
+ if (type == EVENT_TYPE_ASSOC) {
+ /* Check if assoc request has lower signal than min_signal.
+ * If this is the case, block assoc even when assoc steering is enabled.
+ *
+ * Otherwise, the client potentially ends up in a assoc - kick loop.
+ */
+ if (config.min_snr && si->signal < usteer_snr_to_signal(si->node, config.min_snr)) {
+ ev.reason = UEV_REASON_LOW_SIGNAL;
+ ev.threshold.cur = si->signal;
+ ev.threshold.ref = usteer_snr_to_signal(si->node, config.min_snr);
+ ret = false;
+ goto out;
+ } else if (!config.assoc_steering) {
+ goto out;
+ }
}
- min_signal = snr_to_signal(si->node, config.min_connect_snr);
+ min_signal = usteer_snr_to_signal(si->node, config.min_connect_snr);
if (si->signal < min_signal) {
- if (type != EVENT_TYPE_PROBE || config.debug_level >= MSG_DEBUG)
- MSG(VERBOSE, "Ignoring %s request from "MAC_ADDR_FMT" due to low signal (%d < %d)\n",
- event_types[type], MAC_ADDR_DATA(si->sta->addr),
- si->signal, min_signal);
- MSG_T_STA("min_connect_snr", si->sta->addr,
- "snr to low (config=%i) (real=%i)\n",
- min_signal, si->signal);
- return false;
+ ev.reason = UEV_REASON_LOW_SIGNAL;
+ ev.threshold.cur = si->signal;
+ ev.threshold.ref = min_signal;
+ ret = false;
+ goto out;
}
if (current_time - si->created < config.initial_connect_delay) {
- if (type != EVENT_TYPE_PROBE || config.debug_level >= MSG_DEBUG)
- MSG(VERBOSE, "Ignoring %s request from "MAC_ADDR_FMT" during initial connect delay\n",
- event_types[type], MAC_ADDR_DATA(si->sta->addr));
- MSG_T_STA("initial_connect_delay", si->sta->addr,
- "is below delay (%u)\n", config.initial_connect_delay);
- return false;
+ ev.reason = UEV_REASON_CONNECT_DELAY;
+ ev.threshold.cur = current_time - si->created;
+ ev.threshold.ref = config.initial_connect_delay;
+ ret = false;
+ goto out;
}
- si_new = find_better_candidate(si);
- if (!si_new)
- return true;
+ if (!find_better_candidate(si, &ev, UEV_SELECT_REASON_ALL, 0))
+ goto out;
- if (type != EVENT_TYPE_PROBE || config.debug_level >= MSG_DEBUG)
- MSG(VERBOSE, "Ignoring %s request from "MAC_ADDR_FMT", "
- "node (local/remote): %s/%s, "
- "signal=%d/%d, n_assoc=%d/%d\n", event_types[type],
- MAC_ADDR_DATA(si->sta->addr),
- usteer_node_name(si->node), usteer_node_name(si_new->node),
- si->signal, si_new->signal,
- si->node->n_assoc, si_new->node->n_assoc);
+ ev.reason = UEV_REASON_BETTER_CANDIDATE;
+ ev.node_cur = si->node;
+ ret = false;
- return false;
+out:
+ switch (type) {
+ case EVENT_TYPE_PROBE:
+ ev.type = UEV_PROBE_REQ_ACCEPT;
+ break;
+ case EVENT_TYPE_ASSOC:
+ ev.type = UEV_ASSOC_REQ_ACCEPT;
+ break;
+ case EVENT_TYPE_AUTH:
+ ev.type = UEV_AUTH_REQ_ACCEPT;
+ break;
+ default:
+ break;
+ }
+
+ if (!ret)
+ ev.type++;
+
+ if (!ret && si->stats[type].blocked_cur >= config.max_retry_band) {
+ ev.reason = UEV_REASON_RETRY_EXCEEDED;
+ ev.threshold.cur = si->stats[type].blocked_cur;
+ ev.threshold.ref = config.max_retry_band;
+ }
+ usteer_event(&ev);
+
+ return ret;
}
static bool
}
static void
-usteer_roam_set_state(struct sta_info *si, enum roam_trigger_state state)
+usteer_roam_set_state(struct sta_info *si, enum roam_trigger_state state,
+ struct uevent *ev)
{
- static const char * const state_names[] = {
-#define _S(n) [ROAM_TRIGGER_##n] = #n,
- __roam_trigger_states
-#undef _S
- };
-
si->roam_event = current_time;
if (si->roam_state == state) {
}
si->roam_state = state;
+ usteer_event(ev);
+}
+
+static void
+usteer_roam_sm_start_scan(struct sta_info *si, struct uevent *ev)
+{
+ /* Start scanning in case we are not timeout-constrained or timeout has expired */
+ if (!config.roam_scan_timeout ||
+ current_time > si->roam_scan_timeout_start + config.roam_scan_timeout) {
+ usteer_roam_set_state(si, ROAM_TRIGGER_SCAN, ev);
+ return;
+ }
+
+ /* We are currently in scan timeout / cooldown.
+ * Check if we are in ROAM_TRIGGER_IDLE state. Enter this state if not.
+ */
+ if (si->roam_state == ROAM_TRIGGER_IDLE)
+ return;
- MSG(VERBOSE, "Roam trigger SM for client "MAC_ADDR_FMT": state=%s, tries=%d, signal=%d\n",
- MAC_ADDR_DATA(si->sta->addr), state_names[state], si->roam_tries, si->signal);
+ /* Enter idle state */
+ usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, ev);
}
-static bool
-usteer_roam_trigger_sm(struct sta_info *si)
+static struct sta_info *
+usteer_roam_sm_found_better_node(struct sta_info *si, struct uevent *ev, enum roam_trigger_state next_state)
{
- struct sta_info *si_new;
- int min_signal;
+ uint64_t max_age = 2 * config.roam_scan_interval;
+ struct sta_info *candidate;
- min_signal = snr_to_signal(si->node, config.roam_trigger_snr);
+ if (max_age > current_time - si->roam_scan_start)
+ max_age = current_time - si->roam_scan_start;
+
+ candidate = find_better_candidate(si, ev, (1 << UEV_SELECT_REASON_SIGNAL), max_age);
+ if (candidate)
+ usteer_roam_set_state(si, next_state, ev);
+
+ return candidate;
+}
+
+static bool
+usteer_roam_trigger_sm(struct usteer_local_node *ln, struct sta_info *si)
+{
+ struct sta_info *candidate;
+ struct uevent ev = {
+ .si_cur = si,
+ };
switch (si->roam_state) {
case ROAM_TRIGGER_SCAN:
- if (current_time - si->roam_event < config.roam_scan_interval)
+ if (!si->roam_tries) {
+ si->roam_scan_start = current_time;
+ }
+
+ /* Check if we've found a better node regardless of the scan-interval */
+ if (usteer_roam_sm_found_better_node(si, &ev, ROAM_TRIGGER_SCAN_DONE))
break;
- if (find_better_candidate(si) ||
- si->roam_scan_done > si->roam_event) {
- usteer_roam_set_state(si, ROAM_TRIGGER_SCAN_DONE);
+ /* Only scan every scan-interval */
+ if (current_time - si->roam_event < config.roam_scan_interval)
break;
- }
- if (config.roam_scan_tries &&
- si->roam_tries >= config.roam_scan_tries) {
- usteer_roam_set_state(si, ROAM_TRIGGER_WAIT_KICK);
+ /* Check if no node was found within roam_scan_tries tries */
+ if (config.roam_scan_tries && si->roam_tries >= config.roam_scan_tries) {
+ if (!config.roam_scan_timeout) {
+ /* Prepare to kick client */
+ usteer_roam_set_state(si, ROAM_TRIGGER_SCAN_DONE, &ev);
+ } else {
+ /* Kick in scan timeout */
+ si->roam_scan_timeout_start = current_time;
+ usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev);
+ }
break;
}
+ /* Send beacon-request to client */
usteer_ubus_trigger_client_scan(si);
- usteer_roam_set_state(si, ROAM_TRIGGER_SCAN);
+ usteer_roam_sm_start_scan(si, &ev);
break;
case ROAM_TRIGGER_IDLE:
- if (find_better_candidate(si)) {
- usteer_roam_set_state(si, ROAM_TRIGGER_SCAN_DONE);
- break;
- }
-
- usteer_roam_set_state(si, ROAM_TRIGGER_SCAN);
+ usteer_roam_sm_start_scan(si, &ev);
break;
case ROAM_TRIGGER_SCAN_DONE:
- /* Check for stale scan results, kick back to SCAN state if necessary */
- if (current_time - si->roam_scan_done > 2 * config.roam_scan_interval) {
- usteer_roam_set_state(si, ROAM_TRIGGER_SCAN);
+ candidate = usteer_roam_sm_found_better_node(si, &ev, ROAM_TRIGGER_SCAN_DONE);
+ /* Kick back in case no better node is found */
+ if (!candidate) {
+ usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev);
break;
}
- si_new = find_better_candidate(si);
- if (!si_new)
- break;
-
- usteer_roam_set_state(si, ROAM_TRIGGER_WAIT_KICK);
- break;
-
- case ROAM_TRIGGER_WAIT_KICK:
- if (si->signal > min_signal)
- break;
-
- usteer_roam_set_state(si, ROAM_TRIGGER_NOTIFY_KICK);
- usteer_ubus_notify_client_disassoc(si);
+ usteer_ubus_bss_transition_request(si, 1, false, false, 100, candidate->node);
+ si->kick_time = current_time + config.roam_kick_delay;
+ usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev);
break;
- case ROAM_TRIGGER_NOTIFY_KICK:
- if (current_time - si->roam_event < config.roam_kick_delay * 100)
- break;
-
- usteer_roam_set_state(si, ROAM_TRIGGER_KICK);
- break;
- case ROAM_TRIGGER_KICK:
- usteer_ubus_kick_client(si);
- usteer_roam_set_state(si, ROAM_TRIGGER_IDLE);
- return true;
}
return false;
}
static void
-usteer_local_node_roam_check(struct usteer_local_node *ln)
+usteer_local_node_roam_check(struct usteer_local_node *ln, struct uevent *ev)
{
struct sta_info *si;
int min_signal;
return;
usteer_update_time();
- min_signal = snr_to_signal(&ln->node, min_signal);
+ min_signal = usteer_snr_to_signal(&ln->node, min_signal);
list_for_each_entry(si, &ln->node.sta_info, node_list) {
- if (!si->connected || si->signal >= min_signal ||
+ if (si->connected != STA_CONNECTED || si->signal >= min_signal ||
+ si->kick_time ||
+ (si->bss_transition_response.status_code && current_time - si->bss_transition_response.timestamp < config.steer_reject_timeout) ||
current_time - si->roam_kick < config.roam_trigger_interval) {
- usteer_roam_set_state(si, ROAM_TRIGGER_IDLE);
+ usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, ev);
continue;
}
* If the state machine kicked a client, other clients should wait
* until the next turn
*/
- if (usteer_roam_trigger_sm(si))
+ if (usteer_roam_trigger_sm(ln, si))
return;
}
}
static void
usteer_local_node_snr_kick(struct usteer_local_node *ln)
{
+ unsigned int min_count = DIV_ROUND_UP(config.min_snr_kick_delay, config.local_sta_update);
+ struct uevent ev = {
+ .node_local = &ln->node,
+ };
struct sta_info *si;
int min_signal;
if (!config.min_snr)
return;
- min_signal = snr_to_signal(&ln->node, config.min_snr);
+ min_signal = usteer_snr_to_signal(&ln->node, config.min_snr);
+ ev.threshold.ref = min_signal;
list_for_each_entry(si, &ln->node.sta_info, node_list) {
- if (!si->connected)
+ if (si->connected != STA_CONNECTED)
+ continue;
+
+ if (si->signal >= min_signal) {
+ si->below_min_snr = 0;
continue;
+ } else {
+ si->below_min_snr++;
+ }
- if (si->signal >= min_signal)
+ if (si->below_min_snr <= min_count)
continue;
si->kick_count++;
- MSG(VERBOSE, "Kicking client "MAC_ADDR_FMT" due to low SNR, signal=%d\n",
- MAC_ADDR_DATA(si->sta->addr), si->signal);
+ ev.type = UEV_SIGNAL_KICK;
+ ev.threshold.cur = si->signal;
+ ev.count = si->kick_count;
+ usteer_event(&ev);
usteer_ubus_kick_client(si);
return;
}
}
-void
-usteer_local_node_kick(struct usteer_local_node *ln)
+static void
+usteer_local_node_load_kick(struct usteer_local_node *ln)
{
struct usteer_node *node = &ln->node;
struct sta_info *kick1 = NULL, *kick2 = NULL;
struct sta_info *candidate = NULL;
struct sta_info *si;
-
- usteer_local_node_roam_check(ln);
- usteer_local_node_snr_kick(ln);
+ struct uevent ev = {
+ .node_local = &ln->node,
+ };
+ unsigned int min_count = DIV_ROUND_UP(config.load_kick_delay, config.local_sta_update);
if (!config.load_kick_enabled || !config.load_kick_threshold ||
!config.load_kick_delay)
return;
if (node->load < config.load_kick_threshold) {
- MSG_T("load_kick_threshold",
- "is below load for this node (config=%i) (real=%i)\n",
- config.load_kick_threshold, node->load);
+ if (!ln->load_thr_count)
+ return;
+
ln->load_thr_count = 0;
- return;
+ ev.type = UEV_LOAD_KICK_RESET;
+ ev.threshold.cur = node->load;
+ ev.threshold.ref = config.load_kick_threshold;
+ goto out;
}
- if (++ln->load_thr_count <=
- DIV_ROUND_UP(config.load_kick_delay, config.local_sta_update)) {
- MSG_T("load_kick_delay", "delay kicking (config=%i)\n",
- config.load_kick_delay);
- return;
- }
+ if (++ln->load_thr_count <= min_count) {
+ if (ln->load_thr_count > 1)
+ return;
- MSG(VERBOSE, "AP load threshold exceeded on %s (%d), try to kick a client\n",
- usteer_node_name(node), node->load);
+ ev.type = UEV_LOAD_KICK_TRIGGER;
+ ev.threshold.cur = node->load;
+ ev.threshold.ref = config.load_kick_threshold;
+ goto out;
+ }
ln->load_thr_count = 0;
if (node->n_assoc < config.load_kick_min_clients) {
- MSG_T("load_kick_min_clients",
- "min limit reached, stop kicking clients on this node "
- "(n_assoc=%i) (config=%i)\n",
- node->n_assoc, config.load_kick_min_clients);
- return;
+ ev.type = UEV_LOAD_KICK_MIN_CLIENTS;
+ ev.threshold.cur = node->n_assoc;
+ ev.threshold.ref = config.load_kick_min_clients;
+ goto out;
}
list_for_each_entry(si, &ln->node.sta_info, node_list) {
struct sta_info *tmp;
- if (!si->connected)
+ if (si->connected != STA_CONNECTED)
continue;
if (is_more_kickable(kick1, si))
kick1 = si;
- tmp = find_better_candidate(si);
+ tmp = find_better_candidate(si, NULL, (1 << UEV_SELECT_REASON_LOAD), 0);
if (!tmp)
continue;
}
}
- if (!kick1)
- return;
+ if (!kick1) {
+ ev.type = UEV_LOAD_KICK_NO_CLIENT;
+ goto out;
+ }
if (kick2)
kick1 = kick2;
- MSG(VERBOSE, "Kicking client "MAC_ADDR_FMT", signal=%d, better_candidate=%s\n",
- MAC_ADDR_DATA(kick1->sta->addr), kick1->signal,
- candidate ? usteer_node_name(candidate->node) : "(none)");
-
kick1->kick_count++;
+
+ ev.type = UEV_LOAD_KICK_CLIENT;
+ ev.si_cur = kick1;
+ ev.si_other = candidate;
+ ev.count = kick1->kick_count;
+
usteer_ubus_kick_client(kick1);
+
+out:
+ usteer_event(&ev);
+}
+
+static void
+usteer_local_node_perform_kick(struct usteer_local_node *ln)
+{
+ struct sta_info *si;
+
+ list_for_each_entry(si, &ln->node.sta_info, node_list) {
+ if (!si->kick_time || si->kick_time > current_time)
+ continue;
+
+ usteer_ubus_kick_client(si);
+ }
+}
+
+void
+usteer_local_node_kick(struct usteer_local_node *ln)
+{
+ struct uevent ev = {
+ .node_local = &ln->node,
+ };
+
+ usteer_local_node_perform_kick(ln);
+
+ usteer_local_node_snr_kick(ln);
+ usteer_local_node_load_kick(ln);
+ usteer_local_node_roam_check(ln, &ev);
}