remote: close file on usteer_init_local_id fread fail
[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 usteer_node *node_cur, struct usteer_node *node_new)
26 {
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;
31
32 if (!config.load_balancing_threshold)
33 return false;
34
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;
39
40 n_assoc_new += config.load_balancing_threshold;
41
42 return n_assoc_new <= n_assoc_cur;
43 }
44
45 static bool
46 better_signal_strength(int signal_cur, int signal_new)
47 {
48 const bool is_better = signal_new - signal_cur
49 > (int) config.signal_diff_threshold;
50
51 if (!config.signal_diff_threshold)
52 return false;
53
54 return is_better;
55 }
56
57 static bool
58 below_load_threshold(struct usteer_node *node)
59 {
60 return node->n_assoc >= config.load_kick_min_clients &&
61 node->load > config.load_kick_threshold;
62 }
63
64 static bool
65 has_better_load(struct usteer_node *node_cur, struct usteer_node *node_new)
66 {
67 return !below_load_threshold(node_cur) && below_load_threshold(node_new);
68 }
69
70 bool
71 usteer_policy_node_below_max_assoc(struct usteer_node *node)
72 {
73 return !node->max_assoc || node->n_assoc < node->max_assoc;
74 }
75
76 static bool
77 over_min_signal(struct usteer_node *node, int signal)
78 {
79 if (config.min_snr && signal < usteer_snr_to_signal(node, config.min_snr))
80 return false;
81
82 if (config.roam_trigger_snr && signal < usteer_snr_to_signal(node, config.roam_trigger_snr))
83 return false;
84
85 return true;
86 }
87
88 static uint32_t
89 is_better_candidate(struct sta_info *si_cur, struct sta_info *si_new)
90 {
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;
95 uint32_t reasons = 0;
96
97 if (!usteer_policy_node_below_max_assoc(new_node))
98 return 0;
99
100 if (!over_min_signal(new_node, new_signal))
101 return 0;
102
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);
106
107 if (better_signal_strength(current_signal, new_signal))
108 reasons |= (1 << UEV_SELECT_REASON_SIGNAL);
109
110 if (has_better_load(current_node, new_node) &&
111 !has_better_load(current_node, new_node))
112 reasons |= (1 << UEV_SELECT_REASON_LOAD);
113
114 return reasons;
115 }
116
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)
119 {
120 struct sta_info *si, *candidate = NULL;
121 struct sta *sta = si_ref->sta;
122 uint32_t reasons;
123
124 list_for_each_entry(si, &sta->nodes, list) {
125 if (si == si_ref)
126 continue;
127
128 if (current_time - si->seen > config.seen_policy_timeout)
129 continue;
130
131 if (strcmp(si->node->ssid, si_ref->node->ssid) != 0)
132 continue;
133
134 if (max_age && max_age < current_time - si->seen)
135 continue;
136
137 reasons = is_better_candidate(si_ref, si);
138 if (!reasons)
139 continue;
140
141 if (!(reasons & required_criteria))
142 continue;
143
144 if (ev) {
145 ev->si_other = si;
146 ev->select_reasons = reasons;
147 }
148
149 if (!candidate || si->signal > candidate->signal)
150 candidate = si;
151 }
152
153 return candidate;
154 }
155
156 int
157 usteer_snr_to_signal(struct usteer_node *node, int snr)
158 {
159 int noise = -95;
160
161 if (snr < 0)
162 return snr;
163
164 if (node->noise)
165 noise = node->noise;
166
167 return noise + snr;
168 }
169
170 bool
171 usteer_check_request(struct sta_info *si, enum usteer_event_type type)
172 {
173 struct uevent ev = {
174 .si_cur = si,
175 };
176 int min_signal;
177 bool ret = true;
178
179 if (type == EVENT_TYPE_PROBE && !config.probe_steering)
180 goto out;
181
182 if (type == EVENT_TYPE_AUTH)
183 goto out;
184
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.
188 *
189 * Otherwise, the client potentially ends up in a assoc - kick loop.
190 */
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);
195 ret = false;
196 goto out;
197 } else if (!config.assoc_steering) {
198 goto out;
199 }
200 }
201
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;
207 ret = false;
208 goto out;
209 }
210
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;
215 ret = false;
216 goto out;
217 }
218
219 if (!find_better_candidate(si, &ev, UEV_SELECT_REASON_ALL, 0))
220 goto out;
221
222 ev.reason = UEV_REASON_BETTER_CANDIDATE;
223 ev.node_cur = si->node;
224 ret = false;
225
226 out:
227 switch (type) {
228 case EVENT_TYPE_PROBE:
229 ev.type = ret ? UEV_PROBE_REQ_ACCEPT : UEV_PROBE_REQ_DENY;
230 break;
231 case EVENT_TYPE_ASSOC:
232 ev.type = ret ? UEV_ASSOC_REQ_ACCEPT : UEV_ASSOC_REQ_DENY;
233 break;
234 case EVENT_TYPE_AUTH:
235 ev.type = ret ? UEV_AUTH_REQ_ACCEPT : UEV_AUTH_REQ_DENY;
236 break;
237 default:
238 break;
239 }
240
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;
245 }
246 usteer_event(&ev);
247
248 return ret;
249 }
250
251 static bool
252 is_more_kickable(struct sta_info *si_cur, struct sta_info *si_new)
253 {
254 if (!si_cur)
255 return true;
256
257 if (si_new->kick_count > si_cur->kick_count)
258 return false;
259
260 return si_cur->signal > si_new->signal;
261 }
262
263 static void
264 usteer_roam_set_state(struct sta_info *si, enum roam_trigger_state state,
265 struct uevent *ev)
266 {
267 /* NOP in case we remain idle */
268 if (si->roam_state == state && si->roam_state == ROAM_TRIGGER_IDLE) {
269 si->roam_tries = 0;
270 return;
271 }
272
273 si->roam_event = current_time;
274
275 if (si->roam_state == state) {
276 si->roam_tries++;
277 } else {
278 si->roam_tries = 0;
279 }
280
281 si->roam_state = state;
282 usteer_event(ev);
283 }
284
285 static void
286 usteer_roam_sm_start_scan(struct sta_info *si, struct uevent *ev)
287 {
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);
292 return;
293 }
294
295 /* We are currently in scan timeout / cooldown.
296 * Check if we are in ROAM_TRIGGER_IDLE state. Enter this state if not.
297 */
298 if (si->roam_state == ROAM_TRIGGER_IDLE)
299 return;
300
301 /* Enter idle state */
302 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, ev);
303 }
304
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)
307 {
308 uint64_t max_age = 2 * config.roam_scan_interval;
309 struct sta_info *candidate;
310
311 if (max_age > current_time - si->roam_scan_start)
312 max_age = current_time - si->roam_scan_start;
313
314 candidate = find_better_candidate(si, ev, (1 << UEV_SELECT_REASON_SIGNAL), max_age);
315 if (candidate)
316 usteer_roam_set_state(si, next_state, ev);
317
318 return candidate;
319 }
320
321 static bool
322 usteer_roam_trigger_sm(struct usteer_local_node *ln, struct sta_info *si)
323 {
324 struct sta_info *candidate;
325 struct uevent ev = {
326 .si_cur = si,
327 };
328
329 switch (si->roam_state) {
330 case ROAM_TRIGGER_SCAN:
331 if (!si->roam_tries) {
332 si->roam_scan_start = current_time;
333 }
334
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))
337 break;
338
339 /* Only scan every scan-interval */
340 if (current_time - si->roam_event < config.roam_scan_interval)
341 break;
342
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);
348 } else {
349 /* Kick in scan timeout */
350 si->roam_scan_timeout_start = current_time;
351 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev);
352 }
353 break;
354 }
355
356 /* Send beacon-request to client */
357 usteer_ubus_trigger_client_scan(si);
358 usteer_roam_sm_start_scan(si, &ev);
359 break;
360
361 case ROAM_TRIGGER_IDLE:
362 usteer_roam_sm_start_scan(si, &ev);
363 break;
364
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 */
368 if (!candidate) {
369 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev);
370 break;
371 }
372
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);
376 break;
377 }
378
379 return false;
380 }
381
382 bool usteer_policy_can_perform_roam(struct sta_info *si)
383 {
384 /* Only trigger for connected STAs */
385 if (si->connected != STA_CONNECTED)
386 return false;
387
388 /* Skip on pending kick */
389 if (si->kick_time)
390 return false;
391
392 /* Skip on rejected transition */
393 if (si->bss_transition_response.status_code && current_time - si->bss_transition_response.timestamp < config.steer_reject_timeout)
394 return false;
395
396 /* Skip on previous kick attempt */
397 if (current_time - si->roam_kick < config.roam_trigger_interval)
398 return false;
399
400 /* Skip if connection is established shorter than the trigger-interval */
401 if (current_time - si->connected_since < config.roam_trigger_interval)
402 return false;
403
404 return true;
405 }
406
407 static bool
408 usteer_local_node_roam_sm_active(struct sta_info *si, int min_signal)
409 {
410 if (!usteer_policy_can_perform_roam(si))
411 return false;
412
413 /* Signal has to be below scan / roam threshold */
414 if (si->signal >= min_signal)
415 return false;
416
417 return true;
418 }
419
420 static void
421 usteer_local_node_roam_check(struct usteer_local_node *ln, struct uevent *ev)
422 {
423 struct sta_info *si;
424 int min_signal;
425
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;
430 else
431 return;
432
433 usteer_update_time();
434 min_signal = usteer_snr_to_signal(&ln->node, min_signal);
435
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);
439 continue;
440 }
441
442 /*
443 * If the state machine kicked a client, other clients should wait
444 * until the next turn
445 */
446 if (usteer_roam_trigger_sm(ln, si))
447 return;
448 }
449 }
450
451 static void
452 usteer_local_node_snr_kick(struct usteer_local_node *ln)
453 {
454 unsigned int min_count = DIV_ROUND_UP(config.min_snr_kick_delay, config.local_sta_update);
455 struct uevent ev = {
456 .node_local = &ln->node,
457 };
458 struct sta_info *si;
459 int min_signal;
460
461 if (!config.min_snr)
462 return;
463
464 min_signal = usteer_snr_to_signal(&ln->node, config.min_snr);
465 ev.threshold.ref = min_signal;
466
467 list_for_each_entry(si, &ln->node.sta_info, node_list) {
468 if (si->connected != STA_CONNECTED)
469 continue;
470
471 if (si->signal >= min_signal) {
472 si->below_min_snr = 0;
473 continue;
474 } else {
475 si->below_min_snr++;
476 }
477
478 if (si->below_min_snr <= min_count)
479 continue;
480
481 ev.type = UEV_SIGNAL_KICK;
482 ev.threshold.cur = si->signal;
483 ev.count = si->kick_count;
484 usteer_event(&ev);
485
486 usteer_ubus_kick_client(si);
487 return;
488 }
489 }
490
491 static void
492 usteer_local_node_load_kick(struct usteer_local_node *ln)
493 {
494 struct usteer_node *node = &ln->node;
495 struct sta_info *kick1 = NULL, *kick2 = NULL;
496 struct sta_info *candidate = NULL;
497 struct sta_info *si;
498 struct uevent ev = {
499 .node_local = &ln->node,
500 };
501 unsigned int min_count = DIV_ROUND_UP(config.load_kick_delay, config.local_sta_update);
502
503 if (!config.load_kick_enabled || !config.load_kick_threshold ||
504 !config.load_kick_delay)
505 return;
506
507 if (node->load < config.load_kick_threshold) {
508 if (!ln->load_thr_count)
509 return;
510
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;
515 goto out;
516 }
517
518 if (++ln->load_thr_count <= min_count) {
519 if (ln->load_thr_count > 1)
520 return;
521
522 ev.type = UEV_LOAD_KICK_TRIGGER;
523 ev.threshold.cur = node->load;
524 ev.threshold.ref = config.load_kick_threshold;
525 goto out;
526 }
527
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;
533 goto out;
534 }
535
536 list_for_each_entry(si, &ln->node.sta_info, node_list) {
537 struct sta_info *tmp;
538
539 if (si->connected != STA_CONNECTED)
540 continue;
541
542 if (is_more_kickable(kick1, si))
543 kick1 = si;
544
545 tmp = find_better_candidate(si, NULL, (1 << UEV_SELECT_REASON_LOAD), 0);
546 if (!tmp)
547 continue;
548
549 if (is_more_kickable(kick2, si)) {
550 kick2 = si;
551 candidate = tmp;
552 }
553 }
554
555 if (!kick1) {
556 ev.type = UEV_LOAD_KICK_NO_CLIENT;
557 goto out;
558 }
559
560 if (kick2)
561 kick1 = kick2;
562
563 kick1->kick_count++;
564
565 ev.type = UEV_LOAD_KICK_CLIENT;
566 ev.si_cur = kick1;
567 ev.si_other = candidate;
568 ev.count = kick1->kick_count;
569
570 usteer_ubus_kick_client(kick1);
571
572 out:
573 usteer_event(&ev);
574 }
575
576 static void
577 usteer_local_node_perform_kick(struct usteer_local_node *ln)
578 {
579 struct sta_info *si;
580
581 list_for_each_entry(si, &ln->node.sta_info, node_list) {
582 if (!si->kick_time || si->kick_time > current_time)
583 continue;
584
585 usteer_ubus_kick_client(si);
586 }
587 }
588
589 void
590 usteer_local_node_kick(struct usteer_local_node *ln)
591 {
592 struct uevent ev = {
593 .node_local = &ln->node,
594 };
595
596 usteer_local_node_perform_kick(ln);
597
598 usteer_local_node_snr_kick(ln);
599 usteer_local_node_load_kick(ln);
600 usteer_local_node_roam_check(ln, &ev);
601 }