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 sta_info
*si_cur
, struct sta_info
*si_new
)
27 int n_assoc_cur
= si_cur
->node
->n_assoc
;
28 int n_assoc_new
= si_new
->node
->n_assoc
;
29 bool ref_5g
= si_cur
->node
->freq
> 4000;
30 bool node_5g
= si_new
->node
->freq
> 4000;
32 if (ref_5g
&& !node_5g
)
33 n_assoc_new
+= config
.band_steering_threshold
;
34 else if (!ref_5g
&& node_5g
)
35 n_assoc_cur
+= config
.band_steering_threshold
;
37 n_assoc_new
+= config
.load_balancing_threshold
;
39 return n_assoc_new
<= n_assoc_cur
;
43 better_signal_strength(struct sta_info
*si_cur
, struct sta_info
*si_new
)
45 const bool is_better
= si_new
->signal
- si_cur
->signal
46 > (int) config
.signal_diff_threshold
;
48 if (!config
.signal_diff_threshold
)
55 below_load_threshold(struct sta_info
*si
)
57 return si
->node
->n_assoc
>= config
.load_kick_min_clients
&&
58 si
->node
->load
> config
.load_kick_threshold
;
62 has_better_load(struct sta_info
*si_cur
, struct sta_info
*si_new
)
64 return !below_load_threshold(si_cur
) && below_load_threshold(si_new
);
68 below_max_assoc(struct sta_info
*si
)
70 struct usteer_node
*node
= si
->node
;
72 return !node
->max_assoc
|| node
->n_assoc
< node
->max_assoc
;
76 over_min_signal(struct sta_info
*si
)
78 if (config
.min_snr
&& si
->signal
< usteer_snr_to_signal(si
->node
, config
.min_snr
))
81 if (config
.roam_trigger_snr
&& si
->signal
< usteer_snr_to_signal(si
->node
, config
.roam_trigger_snr
))
88 is_better_candidate(struct sta_info
*si_cur
, struct sta_info
*si_new
)
92 if (!below_max_assoc(si_new
))
95 if (!over_min_signal(si_new
))
98 if (below_assoc_threshold(si_cur
, si_new
) &&
99 !below_assoc_threshold(si_new
, si_cur
))
100 reasons
|= (1 << UEV_SELECT_REASON_NUM_ASSOC
);
102 if (better_signal_strength(si_cur
, si_new
))
103 reasons
|= (1 << UEV_SELECT_REASON_SIGNAL
);
105 if (has_better_load(si_cur
, si_new
) &&
106 !has_better_load(si_cur
, si_new
))
107 reasons
|= (1 << UEV_SELECT_REASON_LOAD
);
112 static struct sta_info
*
113 find_better_candidate(struct sta_info
*si_ref
, struct uevent
*ev
, uint32_t required_criteria
)
116 struct sta
*sta
= si_ref
->sta
;
119 list_for_each_entry(si
, &sta
->nodes
, list
) {
123 if (current_time
- si
->seen
> config
.seen_policy_timeout
)
126 if (strcmp(si
->node
->ssid
, si_ref
->node
->ssid
) != 0)
129 reasons
= is_better_candidate(si_ref
, si
);
133 if (!(reasons
& required_criteria
))
138 ev
->select_reasons
= reasons
;
148 usteer_snr_to_signal(struct usteer_node
*node
, int snr
)
162 usteer_check_request(struct sta_info
*si
, enum usteer_event_type type
)
170 if (type
== EVENT_TYPE_AUTH
)
173 if (type
== EVENT_TYPE_ASSOC
) {
174 /* Check if assoc request has lower signal than min_signal.
175 * If this is the case, block assoc even when assoc steering is enabled.
177 * Otherwise, the client potentially ends up in a assoc - kick loop.
179 if (config
.min_snr
&& si
->signal
< usteer_snr_to_signal(si
->node
, config
.min_snr
)) {
180 ev
.reason
= UEV_REASON_LOW_SIGNAL
;
181 ev
.threshold
.cur
= si
->signal
;
182 ev
.threshold
.ref
= usteer_snr_to_signal(si
->node
, config
.min_snr
);
185 } else if (!config
.assoc_steering
) {
190 min_signal
= usteer_snr_to_signal(si
->node
, config
.min_connect_snr
);
191 if (si
->signal
< min_signal
) {
192 ev
.reason
= UEV_REASON_LOW_SIGNAL
;
193 ev
.threshold
.cur
= si
->signal
;
194 ev
.threshold
.ref
= min_signal
;
199 if (current_time
- si
->created
< config
.initial_connect_delay
) {
200 ev
.reason
= UEV_REASON_CONNECT_DELAY
;
201 ev
.threshold
.cur
= current_time
- si
->created
;
202 ev
.threshold
.ref
= config
.initial_connect_delay
;
207 if (!find_better_candidate(si
, &ev
, UEV_SELECT_REASON_ALL
))
210 ev
.reason
= UEV_REASON_BETTER_CANDIDATE
;
211 ev
.node_cur
= si
->node
;
216 case EVENT_TYPE_PROBE
:
217 ev
.type
= UEV_PROBE_REQ_ACCEPT
;
219 case EVENT_TYPE_ASSOC
:
220 ev
.type
= UEV_ASSOC_REQ_ACCEPT
;
222 case EVENT_TYPE_AUTH
:
223 ev
.type
= UEV_AUTH_REQ_ACCEPT
;
232 if (!ret
&& si
->stats
[type
].blocked_cur
>= config
.max_retry_band
) {
233 ev
.reason
= UEV_REASON_RETRY_EXCEEDED
;
234 ev
.threshold
.cur
= si
->stats
[type
].blocked_cur
;
235 ev
.threshold
.ref
= config
.max_retry_band
;
243 is_more_kickable(struct sta_info
*si_cur
, struct sta_info
*si_new
)
248 if (si_new
->kick_count
> si_cur
->kick_count
)
251 return si_cur
->signal
> si_new
->signal
;
255 usteer_roam_set_state(struct sta_info
*si
, enum roam_trigger_state state
,
258 si
->roam_event
= current_time
;
260 if (si
->roam_state
== state
) {
261 if (si
->roam_state
== ROAM_TRIGGER_IDLE
) {
271 si
->roam_state
= state
;
276 usteer_roam_trigger_sm(struct sta_info
*si
)
283 min_signal
= usteer_snr_to_signal(si
->node
, config
.roam_trigger_snr
);
285 switch (si
->roam_state
) {
286 case ROAM_TRIGGER_SCAN
:
287 if (current_time
- si
->roam_event
< config
.roam_scan_interval
)
290 if (find_better_candidate(si
, &ev
, (1 << UEV_SELECT_REASON_SIGNAL
)) ||
291 si
->roam_scan_done
> si
->roam_event
) {
292 usteer_roam_set_state(si
, ROAM_TRIGGER_SCAN_DONE
, &ev
);
296 if (config
.roam_scan_tries
&&
297 si
->roam_tries
>= config
.roam_scan_tries
) {
298 usteer_roam_set_state(si
, ROAM_TRIGGER_WAIT_KICK
, &ev
);
302 usteer_ubus_trigger_client_scan(si
);
303 usteer_roam_set_state(si
, ROAM_TRIGGER_SCAN
, &ev
);
306 case ROAM_TRIGGER_IDLE
:
307 if (find_better_candidate(si
, &ev
, (1 << UEV_SELECT_REASON_SIGNAL
))) {
308 usteer_roam_set_state(si
, ROAM_TRIGGER_SCAN_DONE
, &ev
);
312 usteer_roam_set_state(si
, ROAM_TRIGGER_SCAN
, &ev
);
315 case ROAM_TRIGGER_SCAN_DONE
:
316 /* Check for stale scan results, kick back to SCAN state if necessary */
317 if (current_time
- si
->roam_scan_done
> 2 * config
.roam_scan_interval
) {
318 usteer_roam_set_state(si
, ROAM_TRIGGER_SCAN
, &ev
);
322 if (find_better_candidate(si
, &ev
, (1 << UEV_SELECT_REASON_SIGNAL
)))
323 usteer_roam_set_state(si
, ROAM_TRIGGER_WAIT_KICK
, &ev
);
327 case ROAM_TRIGGER_WAIT_KICK
:
328 if (si
->signal
> min_signal
)
331 usteer_roam_set_state(si
, ROAM_TRIGGER_NOTIFY_KICK
, &ev
);
332 usteer_ubus_notify_client_disassoc(si
);
334 case ROAM_TRIGGER_NOTIFY_KICK
:
335 if (current_time
- si
->roam_event
< config
.roam_kick_delay
* 100)
338 usteer_roam_set_state(si
, ROAM_TRIGGER_KICK
, &ev
);
340 case ROAM_TRIGGER_KICK
:
341 usteer_ubus_kick_client(si
);
342 usteer_roam_set_state(si
, ROAM_TRIGGER_IDLE
, &ev
);
350 usteer_local_node_roam_check(struct usteer_local_node
*ln
, struct uevent
*ev
)
355 if (config
.roam_scan_snr
)
356 min_signal
= config
.roam_scan_snr
;
357 else if (config
.roam_trigger_snr
)
358 min_signal
= config
.roam_trigger_snr
;
362 usteer_update_time();
363 min_signal
= usteer_snr_to_signal(&ln
->node
, min_signal
);
365 list_for_each_entry(si
, &ln
->node
.sta_info
, node_list
) {
366 if (si
->connected
!= STA_CONNECTED
|| si
->signal
>= min_signal
||
367 current_time
- si
->roam_kick
< config
.roam_trigger_interval
) {
368 usteer_roam_set_state(si
, ROAM_TRIGGER_IDLE
, ev
);
373 * If the state machine kicked a client, other clients should wait
374 * until the next turn
376 if (usteer_roam_trigger_sm(si
))
382 usteer_local_node_snr_kick(struct usteer_local_node
*ln
)
385 .node_local
= &ln
->node
,
393 min_signal
= usteer_snr_to_signal(&ln
->node
, config
.min_snr
);
394 ev
.threshold
.ref
= min_signal
;
396 list_for_each_entry(si
, &ln
->node
.sta_info
, node_list
) {
397 if (si
->connected
!= STA_CONNECTED
)
400 if (si
->signal
>= min_signal
)
405 ev
.type
= UEV_SIGNAL_KICK
;
406 ev
.threshold
.cur
= si
->signal
;
407 ev
.count
= si
->kick_count
;
410 usteer_ubus_kick_client(si
);
416 usteer_local_node_kick(struct usteer_local_node
*ln
)
418 struct usteer_node
*node
= &ln
->node
;
419 struct sta_info
*kick1
= NULL
, *kick2
= NULL
;
420 struct sta_info
*candidate
= NULL
;
423 .node_local
= &ln
->node
,
425 unsigned int min_count
= DIV_ROUND_UP(config
.load_kick_delay
, config
.local_sta_update
);
427 usteer_local_node_roam_check(ln
, &ev
);
428 usteer_local_node_snr_kick(ln
);
430 if (!config
.load_kick_enabled
|| !config
.load_kick_threshold
||
431 !config
.load_kick_delay
)
434 if (node
->load
< config
.load_kick_threshold
) {
435 if (!ln
->load_thr_count
)
438 ln
->load_thr_count
= 0;
439 ev
.type
= UEV_LOAD_KICK_RESET
;
440 ev
.threshold
.cur
= node
->load
;
441 ev
.threshold
.ref
= config
.load_kick_threshold
;
445 if (++ln
->load_thr_count
<= min_count
) {
446 if (ln
->load_thr_count
> 1)
449 ev
.type
= UEV_LOAD_KICK_TRIGGER
;
450 ev
.threshold
.cur
= node
->load
;
451 ev
.threshold
.ref
= config
.load_kick_threshold
;
455 ln
->load_thr_count
= 0;
456 if (node
->n_assoc
< config
.load_kick_min_clients
) {
457 ev
.type
= UEV_LOAD_KICK_MIN_CLIENTS
;
458 ev
.threshold
.cur
= node
->n_assoc
;
459 ev
.threshold
.ref
= config
.load_kick_min_clients
;
463 list_for_each_entry(si
, &ln
->node
.sta_info
, node_list
) {
464 struct sta_info
*tmp
;
466 if (si
->connected
!= STA_CONNECTED
)
469 if (is_more_kickable(kick1
, si
))
472 tmp
= find_better_candidate(si
, NULL
, (1 << UEV_SELECT_REASON_LOAD
));
476 if (is_more_kickable(kick2
, si
)) {
483 ev
.type
= UEV_LOAD_KICK_NO_CLIENT
;
492 ev
.type
= UEV_LOAD_KICK_CLIENT
;
494 ev
.si_other
= candidate
;
495 ev
.count
= kick1
->kick_count
;
497 usteer_ubus_kick_client(kick1
);