policy: avoid creating kick loop for client
[project/usteer.git] / policy.c
1 /*
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.
5 *
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.
10 *
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.
14 *
15 * Copyright (C) 2020 embedd.ch
16 * Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
17 * Copyright (C) 2020 John Crispin <john@phrozen.org>
18 */
19
20 #include "usteer.h"
21 #include "node.h"
22 #include "event.h"
23
24 static bool
25 below_assoc_threshold(struct sta_info *si_cur, struct sta_info *si_new)
26 {
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;
31
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;
36
37 n_assoc_new += config.load_balancing_threshold;
38
39 return n_assoc_new <= n_assoc_cur;
40 }
41
42 static bool
43 better_signal_strength(struct sta_info *si_cur, struct sta_info *si_new)
44 {
45 const bool is_better = si_new->signal - si_cur->signal
46 > (int) config.signal_diff_threshold;
47
48 if (!config.signal_diff_threshold)
49 return false;
50
51 return is_better;
52 }
53
54 static bool
55 below_load_threshold(struct sta_info *si)
56 {
57 return si->node->n_assoc >= config.load_kick_min_clients &&
58 si->node->load > config.load_kick_threshold;
59 }
60
61 static bool
62 has_better_load(struct sta_info *si_cur, struct sta_info *si_new)
63 {
64 return !below_load_threshold(si_cur) && below_load_threshold(si_new);
65 }
66
67 static bool
68 below_max_assoc(struct sta_info *si)
69 {
70 struct usteer_node *node = si->node;
71
72 return !node->max_assoc || node->n_assoc < node->max_assoc;
73 }
74
75 static uint32_t
76 is_better_candidate(struct sta_info *si_cur, struct sta_info *si_new)
77 {
78 uint32_t reasons = 0;
79
80 if (!below_max_assoc(si_new))
81 return 0;
82
83 if (below_assoc_threshold(si_cur, si_new) &&
84 !below_assoc_threshold(si_new, si_cur))
85 reasons |= (1 << UEV_SELECT_REASON_NUM_ASSOC);
86
87 if (better_signal_strength(si_cur, si_new))
88 reasons |= (1 << UEV_SELECT_REASON_SIGNAL);
89
90 if (has_better_load(si_cur, si_new) &&
91 !has_better_load(si_cur, si_new))
92 reasons |= (1 << UEV_SELECT_REASON_LOAD);
93
94 return reasons;
95 }
96
97 static struct sta_info *
98 find_better_candidate(struct sta_info *si_ref, struct uevent *ev)
99 {
100 struct sta_info *si;
101 struct sta *sta = si_ref->sta;
102 uint32_t reasons;
103
104 list_for_each_entry(si, &sta->nodes, list) {
105 if (si == si_ref)
106 continue;
107
108 if (current_time - si->seen > config.seen_policy_timeout)
109 continue;
110
111 if (strcmp(si->node->ssid, si_ref->node->ssid) != 0)
112 continue;
113
114 reasons = is_better_candidate(si_ref, si);
115 if (!reasons)
116 continue;
117
118 if (is_better_candidate(si, si_ref))
119 continue;
120
121 if (ev) {
122 ev->si_other = si;
123 ev->select_reasons = reasons;
124 }
125
126 return si;
127 }
128
129 return NULL;
130 }
131
132 static int
133 snr_to_signal(struct usteer_node *node, int snr)
134 {
135 int noise = -95;
136
137 if (snr < 0)
138 return snr;
139
140 if (node->noise)
141 noise = node->noise;
142
143 return noise + snr;
144 }
145
146 bool
147 usteer_check_request(struct sta_info *si, enum usteer_event_type type)
148 {
149 struct uevent ev = {
150 .si_cur = si,
151 };
152 int min_signal;
153 bool ret = true;
154
155 if (type == EVENT_TYPE_AUTH)
156 goto out;
157
158 if (type == EVENT_TYPE_ASSOC) {
159 /* Check if assoc request has lower signal than min_signal.
160 * If this is the case, block assoc even when assoc steering is enabled.
161 *
162 * Otherwise, the client potentially ends up in a assoc - kick loop.
163 */
164 if (config.min_snr && si->signal < snr_to_signal(si->node, config.min_snr)) {
165 ev.reason = UEV_REASON_LOW_SIGNAL;
166 ev.threshold.cur = si->signal;
167 ev.threshold.ref = min_signal;
168 ret = false;
169 goto out;
170 } else if (!config.assoc_steering) {
171 goto out;
172 }
173 }
174
175 min_signal = snr_to_signal(si->node, config.min_connect_snr);
176 if (si->signal < min_signal) {
177 ev.reason = UEV_REASON_LOW_SIGNAL;
178 ev.threshold.cur = si->signal;
179 ev.threshold.ref = min_signal;
180 ret = false;
181 goto out;
182 }
183
184 if (current_time - si->created < config.initial_connect_delay) {
185 ev.reason = UEV_REASON_CONNECT_DELAY;
186 ev.threshold.cur = current_time - si->created;
187 ev.threshold.ref = config.initial_connect_delay;
188 ret = false;
189 goto out;
190 }
191
192 if (!find_better_candidate(si, &ev))
193 goto out;
194
195 ev.reason = UEV_REASON_BETTER_CANDIDATE;
196 ev.node_cur = si->node;
197 ret = false;
198
199 out:
200 switch (type) {
201 case EVENT_TYPE_PROBE:
202 ev.type = UEV_PROBE_REQ_ACCEPT;
203 break;
204 case EVENT_TYPE_ASSOC:
205 ev.type = UEV_ASSOC_REQ_ACCEPT;
206 break;
207 case EVENT_TYPE_AUTH:
208 ev.type = UEV_AUTH_REQ_ACCEPT;
209 break;
210 default:
211 break;
212 }
213
214 if (!ret)
215 ev.type++;
216
217 if (!ret && si->stats[type].blocked_cur >= config.max_retry_band) {
218 ev.reason = UEV_REASON_RETRY_EXCEEDED;
219 ev.threshold.cur = si->stats[type].blocked_cur;
220 ev.threshold.ref = config.max_retry_band;
221 }
222 usteer_event(&ev);
223
224 return ret;
225 }
226
227 static bool
228 is_more_kickable(struct sta_info *si_cur, struct sta_info *si_new)
229 {
230 if (!si_cur)
231 return true;
232
233 if (si_new->kick_count > si_cur->kick_count)
234 return false;
235
236 return si_cur->signal > si_new->signal;
237 }
238
239 static void
240 usteer_roam_set_state(struct sta_info *si, enum roam_trigger_state state,
241 struct uevent *ev)
242 {
243 si->roam_event = current_time;
244
245 if (si->roam_state == state) {
246 if (si->roam_state == ROAM_TRIGGER_IDLE) {
247 si->roam_tries = 0;
248 return;
249 }
250
251 si->roam_tries++;
252 } else {
253 si->roam_tries = 0;
254 }
255
256 si->roam_state = state;
257 usteer_event(ev);
258 }
259
260 static bool
261 usteer_roam_trigger_sm(struct sta_info *si)
262 {
263 struct uevent ev = {
264 .si_cur = si,
265 };
266 int min_signal;
267
268 min_signal = snr_to_signal(si->node, config.roam_trigger_snr);
269
270 switch (si->roam_state) {
271 case ROAM_TRIGGER_SCAN:
272 if (current_time - si->roam_event < config.roam_scan_interval)
273 break;
274
275 if (find_better_candidate(si, &ev) ||
276 si->roam_scan_done > si->roam_event) {
277 usteer_roam_set_state(si, ROAM_TRIGGER_SCAN_DONE, &ev);
278 break;
279 }
280
281 if (config.roam_scan_tries &&
282 si->roam_tries >= config.roam_scan_tries) {
283 usteer_roam_set_state(si, ROAM_TRIGGER_WAIT_KICK, &ev);
284 break;
285 }
286
287 usteer_ubus_trigger_client_scan(si);
288 usteer_roam_set_state(si, ROAM_TRIGGER_SCAN, &ev);
289 break;
290
291 case ROAM_TRIGGER_IDLE:
292 if (find_better_candidate(si, &ev)) {
293 usteer_roam_set_state(si, ROAM_TRIGGER_SCAN_DONE, &ev);
294 break;
295 }
296
297 usteer_roam_set_state(si, ROAM_TRIGGER_SCAN, &ev);
298 break;
299
300 case ROAM_TRIGGER_SCAN_DONE:
301 /* Check for stale scan results, kick back to SCAN state if necessary */
302 if (current_time - si->roam_scan_done > 2 * config.roam_scan_interval) {
303 usteer_roam_set_state(si, ROAM_TRIGGER_SCAN, &ev);
304 break;
305 }
306
307 if (find_better_candidate(si, &ev))
308 usteer_roam_set_state(si, ROAM_TRIGGER_WAIT_KICK, &ev);
309
310 break;
311
312 case ROAM_TRIGGER_WAIT_KICK:
313 if (si->signal > min_signal)
314 break;
315
316 usteer_roam_set_state(si, ROAM_TRIGGER_NOTIFY_KICK, &ev);
317 usteer_ubus_notify_client_disassoc(si);
318 break;
319 case ROAM_TRIGGER_NOTIFY_KICK:
320 if (current_time - si->roam_event < config.roam_kick_delay * 100)
321 break;
322
323 usteer_roam_set_state(si, ROAM_TRIGGER_KICK, &ev);
324 break;
325 case ROAM_TRIGGER_KICK:
326 usteer_ubus_kick_client(si);
327 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev);
328 return true;
329 }
330
331 return false;
332 }
333
334 static void
335 usteer_local_node_roam_check(struct usteer_local_node *ln, struct uevent *ev)
336 {
337 struct sta_info *si;
338 int min_signal;
339
340 if (config.roam_scan_snr)
341 min_signal = config.roam_scan_snr;
342 else if (config.roam_trigger_snr)
343 min_signal = config.roam_trigger_snr;
344 else
345 return;
346
347 usteer_update_time();
348 min_signal = snr_to_signal(&ln->node, min_signal);
349
350 list_for_each_entry(si, &ln->node.sta_info, node_list) {
351 if (si->connected != STA_CONNECTED || si->signal >= min_signal ||
352 current_time - si->roam_kick < config.roam_trigger_interval) {
353 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, ev);
354 continue;
355 }
356
357 /*
358 * If the state machine kicked a client, other clients should wait
359 * until the next turn
360 */
361 if (usteer_roam_trigger_sm(si))
362 return;
363 }
364 }
365
366 static void
367 usteer_local_node_snr_kick(struct usteer_local_node *ln)
368 {
369 struct uevent ev = {
370 .node_local = &ln->node,
371 };
372 struct sta_info *si;
373 int min_signal;
374
375 if (!config.min_snr)
376 return;
377
378 min_signal = snr_to_signal(&ln->node, config.min_snr);
379 ev.threshold.ref = min_signal;
380
381 list_for_each_entry(si, &ln->node.sta_info, node_list) {
382 if (si->connected != STA_CONNECTED)
383 continue;
384
385 if (si->signal >= min_signal)
386 continue;
387
388 si->kick_count++;
389
390 ev.type = UEV_SIGNAL_KICK;
391 ev.threshold.cur = si->signal;
392 ev.count = si->kick_count;
393 usteer_event(&ev);
394
395 usteer_ubus_kick_client(si);
396 return;
397 }
398 }
399
400 void
401 usteer_local_node_kick(struct usteer_local_node *ln)
402 {
403 struct usteer_node *node = &ln->node;
404 struct sta_info *kick1 = NULL, *kick2 = NULL;
405 struct sta_info *candidate = NULL;
406 struct sta_info *si;
407 struct uevent ev = {
408 .node_local = &ln->node,
409 };
410 unsigned int min_count = DIV_ROUND_UP(config.load_kick_delay, config.local_sta_update);
411
412 usteer_local_node_roam_check(ln, &ev);
413 usteer_local_node_snr_kick(ln);
414
415 if (!config.load_kick_enabled || !config.load_kick_threshold ||
416 !config.load_kick_delay)
417 return;
418
419 if (node->load < config.load_kick_threshold) {
420 if (!ln->load_thr_count)
421 return;
422
423 ln->load_thr_count = 0;
424 ev.type = UEV_LOAD_KICK_RESET;
425 ev.threshold.cur = node->load;
426 ev.threshold.ref = config.load_kick_threshold;
427 goto out;
428 }
429
430 if (++ln->load_thr_count <= min_count) {
431 if (ln->load_thr_count > 1)
432 return;
433
434 ev.type = UEV_LOAD_KICK_TRIGGER;
435 ev.threshold.cur = node->load;
436 ev.threshold.ref = config.load_kick_threshold;
437 goto out;
438 }
439
440 ln->load_thr_count = 0;
441 if (node->n_assoc < config.load_kick_min_clients) {
442 ev.type = UEV_LOAD_KICK_MIN_CLIENTS;
443 ev.threshold.cur = node->n_assoc;
444 ev.threshold.ref = config.load_kick_min_clients;
445 goto out;
446 }
447
448 list_for_each_entry(si, &ln->node.sta_info, node_list) {
449 struct sta_info *tmp;
450
451 if (si->connected != STA_CONNECTED)
452 continue;
453
454 if (is_more_kickable(kick1, si))
455 kick1 = si;
456
457 tmp = find_better_candidate(si, NULL);
458 if (!tmp)
459 continue;
460
461 if (is_more_kickable(kick2, si)) {
462 kick2 = si;
463 candidate = tmp;
464 }
465 }
466
467 if (!kick1) {
468 ev.type = UEV_LOAD_KICK_NO_CLIENT;
469 goto out;
470 }
471
472 if (kick2)
473 kick1 = kick2;
474
475 kick1->kick_count++;
476
477 ev.type = UEV_LOAD_KICK_CLIENT;
478 ev.si_cur = kick1;
479 ev.si_other = candidate;
480 ev.count = kick1->kick_count;
481
482 usteer_ubus_kick_client(kick1);
483
484 out:
485 usteer_event(&ev);
486 }