2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
15 * Copyright (C) 2020 embedd.ch
16 * Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
17 * Copyright (C) 2020 John Crispin <john@phrozen.org>
25 below_assoc_threshold(struct usteer_node
*node_cur
, struct usteer_node
*node_new
)
27 int n_assoc_cur
= node_cur
->n_assoc
;
28 int n_assoc_new
= node_new
->n_assoc
;
29 bool ref_5g
= node_cur
->freq
> 4000;
30 bool node_5g
= node_new
->freq
> 4000;
32 if (!config
.load_balancing_threshold
)
35 if (ref_5g
&& !node_5g
)
36 n_assoc_new
+= config
.band_steering_threshold
;
37 else if (!ref_5g
&& node_5g
)
38 n_assoc_cur
+= config
.band_steering_threshold
;
40 n_assoc_new
+= config
.load_balancing_threshold
;
42 return n_assoc_new
<= n_assoc_cur
;
46 better_signal_strength(int signal_cur
, int signal_new
)
48 const bool is_better
= signal_new
- signal_cur
49 > (int) config
.signal_diff_threshold
;
51 if (!config
.signal_diff_threshold
)
58 below_load_threshold(struct usteer_node
*node
)
60 return node
->n_assoc
>= config
.load_kick_min_clients
&&
61 node
->load
> config
.load_kick_threshold
;
65 has_better_load(struct usteer_node
*node_cur
, struct usteer_node
*node_new
)
67 return !below_load_threshold(node_cur
) && below_load_threshold(node_new
);
71 usteer_policy_node_below_max_assoc(struct usteer_node
*node
)
73 return !node
->max_assoc
|| node
->n_assoc
< node
->max_assoc
;
77 over_min_signal(struct usteer_node
*node
, int signal
)
79 if (config
.min_snr
&& signal
< usteer_snr_to_signal(node
, config
.min_snr
))
82 if (config
.roam_trigger_snr
&& signal
< usteer_snr_to_signal(node
, config
.roam_trigger_snr
))
89 is_better_candidate(struct sta_info
*si_cur
, struct sta_info
*si_new
)
91 struct usteer_node
*current_node
= si_cur
->node
;
92 struct usteer_node
*new_node
= si_new
->node
;
93 int current_signal
= si_cur
->signal
;
94 int new_signal
= si_new
->signal
;
97 if (!usteer_policy_node_below_max_assoc(new_node
))
100 if (!over_min_signal(new_node
, new_signal
))
103 if (below_assoc_threshold(current_node
, new_node
) &&
104 !below_assoc_threshold(new_node
, current_node
))
105 reasons
|= (1 << UEV_SELECT_REASON_NUM_ASSOC
);
107 if (better_signal_strength(current_signal
, new_signal
))
108 reasons
|= (1 << UEV_SELECT_REASON_SIGNAL
);
110 if (has_better_load(current_node
, new_node
) &&
111 !has_better_load(current_node
, new_node
))
112 reasons
|= (1 << UEV_SELECT_REASON_LOAD
);
117 static struct sta_info
*
118 find_better_candidate(struct sta_info
*si_ref
, struct uevent
*ev
, uint32_t required_criteria
, uint64_t max_age
)
120 struct sta_info
*si
, *candidate
= NULL
;
121 struct sta
*sta
= si_ref
->sta
;
124 list_for_each_entry(si
, &sta
->nodes
, list
) {
128 if (current_time
- si
->seen
> config
.seen_policy_timeout
)
131 if (strcmp(si
->node
->ssid
, si_ref
->node
->ssid
) != 0)
134 if (max_age
&& max_age
< current_time
- si
->seen
)
137 reasons
= is_better_candidate(si_ref
, si
);
141 if (!(reasons
& required_criteria
))
146 ev
->select_reasons
= reasons
;
149 if (!candidate
|| si
->signal
> candidate
->signal
)
157 usteer_snr_to_signal(struct usteer_node
*node
, int snr
)
171 usteer_check_request(struct sta_info
*si
, enum usteer_event_type type
)
179 if (type
== EVENT_TYPE_PROBE
&& !config
.probe_steering
)
182 if (type
== EVENT_TYPE_AUTH
)
185 if (type
== EVENT_TYPE_ASSOC
) {
186 /* Check if assoc request has lower signal than min_signal.
187 * If this is the case, block assoc even when assoc steering is enabled.
189 * Otherwise, the client potentially ends up in a assoc - kick loop.
191 if (config
.min_snr
&& si
->signal
< usteer_snr_to_signal(si
->node
, config
.min_snr
)) {
192 ev
.reason
= UEV_REASON_LOW_SIGNAL
;
193 ev
.threshold
.cur
= si
->signal
;
194 ev
.threshold
.ref
= usteer_snr_to_signal(si
->node
, config
.min_snr
);
197 } else if (!config
.assoc_steering
) {
202 min_signal
= usteer_snr_to_signal(si
->node
, config
.min_connect_snr
);
203 if (si
->signal
< min_signal
) {
204 ev
.reason
= UEV_REASON_LOW_SIGNAL
;
205 ev
.threshold
.cur
= si
->signal
;
206 ev
.threshold
.ref
= min_signal
;
211 if (current_time
- si
->created
< config
.initial_connect_delay
) {
212 ev
.reason
= UEV_REASON_CONNECT_DELAY
;
213 ev
.threshold
.cur
= current_time
- si
->created
;
214 ev
.threshold
.ref
= config
.initial_connect_delay
;
219 if (!find_better_candidate(si
, &ev
, UEV_SELECT_REASON_ALL
, 0))
222 ev
.reason
= UEV_REASON_BETTER_CANDIDATE
;
223 ev
.node_cur
= si
->node
;
228 case EVENT_TYPE_PROBE
:
229 ev
.type
= ret
? UEV_PROBE_REQ_ACCEPT
: UEV_PROBE_REQ_DENY
;
231 case EVENT_TYPE_ASSOC
:
232 ev
.type
= ret
? UEV_ASSOC_REQ_ACCEPT
: UEV_ASSOC_REQ_DENY
;
234 case EVENT_TYPE_AUTH
:
235 ev
.type
= ret
? UEV_AUTH_REQ_ACCEPT
: UEV_AUTH_REQ_DENY
;
241 if (!ret
&& si
->stats
[type
].blocked_cur
>= config
.max_retry_band
) {
242 ev
.reason
= UEV_REASON_RETRY_EXCEEDED
;
243 ev
.threshold
.cur
= si
->stats
[type
].blocked_cur
;
244 ev
.threshold
.ref
= config
.max_retry_band
;
252 is_more_kickable(struct sta_info
*si_cur
, struct sta_info
*si_new
)
257 if (si_new
->kick_count
> si_cur
->kick_count
)
260 return si_cur
->signal
> si_new
->signal
;
264 usteer_roam_set_state(struct sta_info
*si
, enum roam_trigger_state state
,
267 /* NOP in case we remain idle */
268 if (si
->roam_state
== state
&& si
->roam_state
== ROAM_TRIGGER_IDLE
) {
273 si
->roam_event
= current_time
;
275 if (si
->roam_state
== state
) {
281 si
->roam_state
= state
;
286 usteer_roam_sm_start_scan(struct sta_info
*si
, struct uevent
*ev
)
288 /* Start scanning in case we are not timeout-constrained or timeout has expired */
289 if (!config
.roam_scan_timeout
||
290 current_time
> si
->roam_scan_timeout_start
+ config
.roam_scan_timeout
) {
291 usteer_roam_set_state(si
, ROAM_TRIGGER_SCAN
, ev
);
295 /* We are currently in scan timeout / cooldown.
296 * Check if we are in ROAM_TRIGGER_IDLE state. Enter this state if not.
298 if (si
->roam_state
== ROAM_TRIGGER_IDLE
)
301 /* Enter idle state */
302 usteer_roam_set_state(si
, ROAM_TRIGGER_IDLE
, ev
);
305 static struct sta_info
*
306 usteer_roam_sm_found_better_node(struct sta_info
*si
, struct uevent
*ev
, enum roam_trigger_state next_state
)
308 uint64_t max_age
= 2 * config
.roam_scan_interval
;
309 struct sta_info
*candidate
;
311 if (max_age
> current_time
- si
->roam_scan_start
)
312 max_age
= current_time
- si
->roam_scan_start
;
314 candidate
= find_better_candidate(si
, ev
, (1 << UEV_SELECT_REASON_SIGNAL
), max_age
);
316 usteer_roam_set_state(si
, next_state
, ev
);
322 usteer_roam_trigger_sm(struct usteer_local_node
*ln
, struct sta_info
*si
)
324 struct sta_info
*candidate
;
329 switch (si
->roam_state
) {
330 case ROAM_TRIGGER_SCAN
:
331 if (!si
->roam_tries
) {
332 si
->roam_scan_start
= current_time
;
335 /* Check if we've found a better node regardless of the scan-interval */
336 if (usteer_roam_sm_found_better_node(si
, &ev
, ROAM_TRIGGER_SCAN_DONE
))
339 /* Only scan every scan-interval */
340 if (current_time
- si
->roam_event
< config
.roam_scan_interval
)
343 /* Check if no node was found within roam_scan_tries tries */
344 if (config
.roam_scan_tries
&& si
->roam_tries
>= config
.roam_scan_tries
) {
345 if (!config
.roam_scan_timeout
) {
346 /* Prepare to kick client */
347 usteer_roam_set_state(si
, ROAM_TRIGGER_SCAN_DONE
, &ev
);
349 /* Kick in scan timeout */
350 si
->roam_scan_timeout_start
= current_time
;
351 usteer_roam_set_state(si
, ROAM_TRIGGER_IDLE
, &ev
);
356 /* Send beacon-request to client */
357 usteer_ubus_trigger_client_scan(si
);
358 usteer_roam_sm_start_scan(si
, &ev
);
361 case ROAM_TRIGGER_IDLE
:
362 usteer_roam_sm_start_scan(si
, &ev
);
365 case ROAM_TRIGGER_SCAN_DONE
:
366 candidate
= usteer_roam_sm_found_better_node(si
, &ev
, ROAM_TRIGGER_SCAN_DONE
);
367 /* Kick back in case no better node is found */
369 usteer_roam_set_state(si
, ROAM_TRIGGER_IDLE
, &ev
);
373 usteer_ubus_bss_transition_request(si
, 1, false, false, 100, candidate
->node
);
374 si
->kick_time
= current_time
+ config
.roam_kick_delay
;
375 usteer_roam_set_state(si
, ROAM_TRIGGER_IDLE
, &ev
);
382 bool usteer_policy_can_perform_roam(struct sta_info
*si
)
384 /* Only trigger for connected STAs */
385 if (si
->connected
!= STA_CONNECTED
)
388 /* Skip on pending kick */
392 /* Skip on rejected transition */
393 if (si
->bss_transition_response
.status_code
&& current_time
- si
->bss_transition_response
.timestamp
< config
.steer_reject_timeout
)
396 /* Skip on previous kick attempt */
397 if (current_time
- si
->roam_kick
< config
.roam_trigger_interval
)
400 /* Skip if connection is established shorter than the trigger-interval */
401 if (current_time
- si
->connected_since
< config
.roam_trigger_interval
)
408 usteer_local_node_roam_sm_active(struct sta_info
*si
, int min_signal
)
410 if (!usteer_policy_can_perform_roam(si
))
413 /* Signal has to be below scan / roam threshold */
414 if (si
->signal
>= min_signal
)
421 usteer_local_node_roam_check(struct usteer_local_node
*ln
, struct uevent
*ev
)
426 if (config
.roam_scan_snr
)
427 min_signal
= config
.roam_scan_snr
;
428 else if (config
.roam_trigger_snr
)
429 min_signal
= config
.roam_trigger_snr
;
433 usteer_update_time();
434 min_signal
= usteer_snr_to_signal(&ln
->node
, min_signal
);
436 list_for_each_entry(si
, &ln
->node
.sta_info
, node_list
) {
437 if (!usteer_local_node_roam_sm_active(si
, min_signal
)) {
438 usteer_roam_set_state(si
, ROAM_TRIGGER_IDLE
, ev
);
443 * If the state machine kicked a client, other clients should wait
444 * until the next turn
446 if (usteer_roam_trigger_sm(ln
, si
))
452 usteer_local_node_snr_kick(struct usteer_local_node
*ln
)
454 unsigned int min_count
= DIV_ROUND_UP(config
.min_snr_kick_delay
, config
.local_sta_update
);
456 .node_local
= &ln
->node
,
464 min_signal
= usteer_snr_to_signal(&ln
->node
, config
.min_snr
);
465 ev
.threshold
.ref
= min_signal
;
467 list_for_each_entry(si
, &ln
->node
.sta_info
, node_list
) {
468 if (si
->connected
!= STA_CONNECTED
)
471 if (si
->signal
>= min_signal
) {
472 si
->below_min_snr
= 0;
478 if (si
->below_min_snr
<= min_count
)
481 ev
.type
= UEV_SIGNAL_KICK
;
482 ev
.threshold
.cur
= si
->signal
;
483 ev
.count
= si
->kick_count
;
486 usteer_ubus_kick_client(si
);
492 usteer_local_node_load_kick(struct usteer_local_node
*ln
)
494 struct usteer_node
*node
= &ln
->node
;
495 struct sta_info
*kick1
= NULL
, *kick2
= NULL
;
496 struct sta_info
*candidate
= NULL
;
499 .node_local
= &ln
->node
,
501 unsigned int min_count
= DIV_ROUND_UP(config
.load_kick_delay
, config
.local_sta_update
);
503 if (!config
.load_kick_enabled
|| !config
.load_kick_threshold
||
504 !config
.load_kick_delay
)
507 if (node
->load
< config
.load_kick_threshold
) {
508 if (!ln
->load_thr_count
)
511 ln
->load_thr_count
= 0;
512 ev
.type
= UEV_LOAD_KICK_RESET
;
513 ev
.threshold
.cur
= node
->load
;
514 ev
.threshold
.ref
= config
.load_kick_threshold
;
518 if (++ln
->load_thr_count
<= min_count
) {
519 if (ln
->load_thr_count
> 1)
522 ev
.type
= UEV_LOAD_KICK_TRIGGER
;
523 ev
.threshold
.cur
= node
->load
;
524 ev
.threshold
.ref
= config
.load_kick_threshold
;
528 ln
->load_thr_count
= 0;
529 if (node
->n_assoc
< config
.load_kick_min_clients
) {
530 ev
.type
= UEV_LOAD_KICK_MIN_CLIENTS
;
531 ev
.threshold
.cur
= node
->n_assoc
;
532 ev
.threshold
.ref
= config
.load_kick_min_clients
;
536 list_for_each_entry(si
, &ln
->node
.sta_info
, node_list
) {
537 struct sta_info
*tmp
;
539 if (si
->connected
!= STA_CONNECTED
)
542 if (is_more_kickable(kick1
, si
))
545 tmp
= find_better_candidate(si
, NULL
, (1 << UEV_SELECT_REASON_LOAD
), 0);
549 if (is_more_kickable(kick2
, si
)) {
556 ev
.type
= UEV_LOAD_KICK_NO_CLIENT
;
565 ev
.type
= UEV_LOAD_KICK_CLIENT
;
567 ev
.si_other
= candidate
;
568 ev
.count
= kick1
->kick_count
;
570 usteer_ubus_kick_client(kick1
);
577 usteer_local_node_perform_kick(struct usteer_local_node
*ln
)
581 list_for_each_entry(si
, &ln
->node
.sta_info
, node_list
) {
582 if (!si
->kick_time
|| si
->kick_time
> current_time
)
585 usteer_ubus_kick_client(si
);
590 usteer_local_node_kick(struct usteer_local_node
*ln
)
593 .node_local
= &ln
->node
,
596 usteer_local_node_perform_kick(ln
);
598 usteer_local_node_snr_kick(ln
);
599 usteer_local_node_load_kick(ln
);
600 usteer_local_node_roam_check(ln
, &ev
);