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>
24 below_assoc_threshold(struct sta_info
*si_cur
, struct sta_info
*si_new
)
26 int n_assoc_cur
= si_cur
->node
->n_assoc
;
27 int n_assoc_new
= si_new
->node
->n_assoc
;
28 bool ref_5g
= si_cur
->node
->freq
> 4000;
29 bool node_5g
= si_new
->node
->freq
> 4000;
31 if (ref_5g
&& !node_5g
)
32 n_assoc_new
+= config
.band_steering_threshold
;
33 else if (!ref_5g
&& node_5g
)
34 n_assoc_cur
+= config
.band_steering_threshold
;
36 n_assoc_new
+= config
.load_balancing_threshold
;
38 if (n_assoc_new
> n_assoc_cur
) {
39 MSG_T_STA("band_steering_threshold,load_balancing_threshold",
40 si_cur
->sta
->addr
, "exeeded (bs=%u, lb=%u)\n",
41 config
.band_steering_threshold
,
42 config
.load_balancing_threshold
);
44 return n_assoc_new
<= n_assoc_cur
;
48 better_signal_strength(struct sta_info
*si_cur
, struct sta_info
*si_new
)
50 const bool is_better
= si_new
->signal
- si_cur
->signal
51 > (int) config
.signal_diff_threshold
;
53 if (!config
.signal_diff_threshold
)
57 MSG_T_STA("signal_diff_threshold", si_cur
->sta
->addr
,
58 "exceeded (config=%i) (real=%i)\n",
59 config
.signal_diff_threshold
,
60 si_new
->signal
- si_cur
->signal
);
66 below_load_threshold(struct sta_info
*si
)
68 return si
->node
->n_assoc
>= config
.load_kick_min_clients
&&
69 si
->node
->load
> config
.load_kick_threshold
;
73 has_better_load(struct sta_info
*si_cur
, struct sta_info
*si_new
)
75 return !below_load_threshold(si_cur
) && below_load_threshold(si_new
);
79 below_max_assoc(struct sta_info
*si
)
81 struct usteer_node
*node
= si
->node
;
83 return !node
->max_assoc
|| node
->n_assoc
< node
->max_assoc
;
87 is_better_candidate(struct sta_info
*si_cur
, struct sta_info
*si_new
)
89 if (!below_max_assoc(si_new
))
92 return below_assoc_threshold(si_cur
, si_new
) ||
93 better_signal_strength(si_cur
, si_new
) ||
94 has_better_load(si_cur
, si_new
);
97 static struct sta_info
*
98 find_better_candidate(struct sta_info
*si_ref
)
101 struct sta
*sta
= si_ref
->sta
;
103 list_for_each_entry(si
, &sta
->nodes
, list
) {
107 if (current_time
- si
->seen
> config
.seen_policy_timeout
) {
108 MSG_T_STA("seen_policy_timeout", si
->sta
->addr
,
109 "timeout exceeded (%u)\n", config
.seen_policy_timeout
);
113 if (strcmp(si
->node
->ssid
, si_ref
->node
->ssid
) != 0)
116 if (is_better_candidate(si_ref
, si
) &&
117 !is_better_candidate(si
, si_ref
))
124 snr_to_signal(struct usteer_node
*node
, int snr
)
138 usteer_check_request(struct sta_info
*si
, enum usteer_event_type type
)
140 struct sta_info
*si_new
;
143 if (type
== EVENT_TYPE_AUTH
)
146 if (type
== EVENT_TYPE_ASSOC
&& !config
.assoc_steering
)
149 if (si
->stats
[type
].blocked_cur
>= config
.max_retry_band
) {
150 MSG_T_STA("max_retry_band", si
->sta
->addr
,
151 "max retry (%u) exceeded\n", config
.max_retry_band
);
155 min_signal
= snr_to_signal(si
->node
, config
.min_connect_snr
);
156 if (si
->signal
< min_signal
) {
157 if (type
!= EVENT_TYPE_PROBE
|| config
.debug_level
>= MSG_DEBUG
)
158 MSG(VERBOSE
, "Ignoring %s request from "MAC_ADDR_FMT
" due to low signal (%d < %d)\n",
159 event_types
[type
], MAC_ADDR_DATA(si
->sta
->addr
),
160 si
->signal
, min_signal
);
161 MSG_T_STA("min_connect_snr", si
->sta
->addr
,
162 "snr to low (config=%i) (real=%i)\n",
163 min_signal
, si
->signal
);
167 if (current_time
- si
->created
< config
.initial_connect_delay
) {
168 if (type
!= EVENT_TYPE_PROBE
|| config
.debug_level
>= MSG_DEBUG
)
169 MSG(VERBOSE
, "Ignoring %s request from "MAC_ADDR_FMT
" during initial connect delay\n",
170 event_types
[type
], MAC_ADDR_DATA(si
->sta
->addr
));
171 MSG_T_STA("initial_connect_delay", si
->sta
->addr
,
172 "is below delay (%u)\n", config
.initial_connect_delay
);
176 si_new
= find_better_candidate(si
);
180 if (type
!= EVENT_TYPE_PROBE
|| config
.debug_level
>= MSG_DEBUG
)
181 MSG(VERBOSE
, "Ignoring %s request from "MAC_ADDR_FMT
", "
182 "node (local/remote): %s/%s, "
183 "signal=%d/%d, n_assoc=%d/%d\n", event_types
[type
],
184 MAC_ADDR_DATA(si
->sta
->addr
),
185 usteer_node_name(si
->node
), usteer_node_name(si_new
->node
),
186 si
->signal
, si_new
->signal
,
187 si
->node
->n_assoc
, si_new
->node
->n_assoc
);
193 is_more_kickable(struct sta_info
*si_cur
, struct sta_info
*si_new
)
198 if (si_new
->kick_count
> si_cur
->kick_count
)
201 return si_cur
->signal
> si_new
->signal
;
205 usteer_roam_set_state(struct sta_info
*si
, enum roam_trigger_state state
)
207 static const char * const state_names
[] = {
208 #define _S(n) [ROAM_TRIGGER_##n] = #n,
209 __roam_trigger_states
213 si
->roam_event
= current_time
;
215 if (si
->roam_state
== state
) {
216 if (si
->roam_state
== ROAM_TRIGGER_IDLE
) {
226 si
->roam_state
= state
;
228 MSG(VERBOSE
, "Roam trigger SM for client "MAC_ADDR_FMT
": state=%s, tries=%d, signal=%d\n",
229 MAC_ADDR_DATA(si
->sta
->addr
), state_names
[state
], si
->roam_tries
, si
->signal
);
233 usteer_roam_trigger_sm(struct sta_info
*si
)
235 struct sta_info
*si_new
;
238 min_signal
= snr_to_signal(si
->node
, config
.roam_trigger_snr
);
240 switch (si
->roam_state
) {
241 case ROAM_TRIGGER_SCAN
:
242 if (current_time
- si
->roam_event
< config
.roam_scan_interval
)
245 if (find_better_candidate(si
) ||
246 si
->roam_scan_done
> si
->roam_event
) {
247 usteer_roam_set_state(si
, ROAM_TRIGGER_SCAN_DONE
);
251 if (config
.roam_scan_tries
&&
252 si
->roam_tries
>= config
.roam_scan_tries
) {
253 usteer_roam_set_state(si
, ROAM_TRIGGER_WAIT_KICK
);
257 usteer_ubus_trigger_client_scan(si
);
258 usteer_roam_set_state(si
, ROAM_TRIGGER_SCAN
);
261 case ROAM_TRIGGER_IDLE
:
262 if (find_better_candidate(si
)) {
263 usteer_roam_set_state(si
, ROAM_TRIGGER_SCAN_DONE
);
267 usteer_roam_set_state(si
, ROAM_TRIGGER_SCAN
);
270 case ROAM_TRIGGER_SCAN_DONE
:
271 /* Check for stale scan results, kick back to SCAN state if necessary */
272 if (current_time
- si
->roam_scan_done
> 2 * config
.roam_scan_interval
) {
273 usteer_roam_set_state(si
, ROAM_TRIGGER_SCAN
);
277 si_new
= find_better_candidate(si
);
281 usteer_roam_set_state(si
, ROAM_TRIGGER_WAIT_KICK
);
284 case ROAM_TRIGGER_WAIT_KICK
:
285 if (si
->signal
> min_signal
)
288 usteer_roam_set_state(si
, ROAM_TRIGGER_NOTIFY_KICK
);
289 usteer_ubus_notify_client_disassoc(si
);
291 case ROAM_TRIGGER_NOTIFY_KICK
:
292 if (current_time
- si
->roam_event
< config
.roam_kick_delay
* 100)
295 usteer_roam_set_state(si
, ROAM_TRIGGER_KICK
);
297 case ROAM_TRIGGER_KICK
:
298 usteer_ubus_kick_client(si
);
299 usteer_roam_set_state(si
, ROAM_TRIGGER_IDLE
);
307 usteer_local_node_roam_check(struct usteer_local_node
*ln
)
312 if (config
.roam_scan_snr
)
313 min_signal
= config
.roam_scan_snr
;
314 else if (config
.roam_trigger_snr
)
315 min_signal
= config
.roam_trigger_snr
;
319 usteer_update_time();
320 min_signal
= snr_to_signal(&ln
->node
, min_signal
);
322 list_for_each_entry(si
, &ln
->node
.sta_info
, node_list
) {
323 if (!si
->connected
|| si
->signal
>= min_signal
||
324 current_time
- si
->roam_kick
< config
.roam_trigger_interval
) {
325 usteer_roam_set_state(si
, ROAM_TRIGGER_IDLE
);
330 * If the state machine kicked a client, other clients should wait
331 * until the next turn
333 if (usteer_roam_trigger_sm(si
))
339 usteer_local_node_snr_kick(struct usteer_local_node
*ln
)
347 min_signal
= snr_to_signal(&ln
->node
, config
.min_snr
);
349 list_for_each_entry(si
, &ln
->node
.sta_info
, node_list
) {
353 if (si
->signal
>= min_signal
)
358 MSG(VERBOSE
, "Kicking client "MAC_ADDR_FMT
" due to low SNR, signal=%d\n",
359 MAC_ADDR_DATA(si
->sta
->addr
), si
->signal
);
361 usteer_ubus_kick_client(si
);
367 usteer_local_node_kick(struct usteer_local_node
*ln
)
369 struct usteer_node
*node
= &ln
->node
;
370 struct sta_info
*kick1
= NULL
, *kick2
= NULL
;
371 struct sta_info
*candidate
= NULL
;
374 usteer_local_node_roam_check(ln
);
375 usteer_local_node_snr_kick(ln
);
377 if (!config
.load_kick_enabled
|| !config
.load_kick_threshold
||
378 !config
.load_kick_delay
)
381 if (node
->load
< config
.load_kick_threshold
) {
382 MSG_T("load_kick_threshold",
383 "is below load for this node (config=%i) (real=%i)\n",
384 config
.load_kick_threshold
, node
->load
);
385 ln
->load_thr_count
= 0;
389 if (++ln
->load_thr_count
<=
390 DIV_ROUND_UP(config
.load_kick_delay
, config
.local_sta_update
)) {
391 MSG_T("load_kick_delay", "delay kicking (config=%i)\n",
392 config
.load_kick_delay
);
396 MSG(VERBOSE
, "AP load threshold exceeded on %s (%d), try to kick a client\n",
397 usteer_node_name(node
), node
->load
);
399 ln
->load_thr_count
= 0;
400 if (node
->n_assoc
< config
.load_kick_min_clients
) {
401 MSG_T("load_kick_min_clients",
402 "min limit reached, stop kicking clients on this node "
403 "(n_assoc=%i) (config=%i)\n",
404 node
->n_assoc
, config
.load_kick_min_clients
);
408 list_for_each_entry(si
, &ln
->node
.sta_info
, node_list
) {
409 struct sta_info
*tmp
;
414 if (is_more_kickable(kick1
, si
))
417 tmp
= find_better_candidate(si
);
421 if (is_more_kickable(kick2
, si
)) {
433 MSG(VERBOSE
, "Kicking client "MAC_ADDR_FMT
", signal=%d, better_candidate=%s\n",
434 MAC_ADDR_DATA(kick1
->sta
->addr
), kick1
->signal
,
435 candidate
? usteer_node_name(candidate
->node
) : "(none)");
438 usteer_ubus_kick_client(kick1
);