• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/usteer/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(new_node, current_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         uint32_t disassoc_timer;
326         uint32_t validity_period;
327         struct uevent ev = {
328                 .si_cur = si,
329         };
330 
331         switch (si->roam_state) {
332         case ROAM_TRIGGER_SCAN:
333                 if (!si->roam_tries) {
334                         si->roam_scan_start = current_time;
335                 }
336 
337                 /* Check if we've found a better node regardless of the scan-interval */
338                 if (usteer_roam_sm_found_better_node(si, &ev, ROAM_TRIGGER_SCAN_DONE))
339                         break;
340 
341                 /* Only scan every scan-interval */
342                 if (current_time - si->roam_event < config.roam_scan_interval)
343                         break;
344 
345                 /* Check if no node was found within roam_scan_tries tries */
346                 if (config.roam_scan_tries && si->roam_tries >= config.roam_scan_tries) {
347                         if (!config.roam_scan_timeout) {
348                                 usteer_roam_set_state(si, ROAM_TRIGGER_SCAN_DONE, &ev);
349                         } else {
350                                 /* Set timeout until roam_scans are paused */
351                                 si->roam_scan_timeout_start = current_time;
352                                 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev);
353                         }
354                         break;
355                 }
356 
357                 /* Send beacon-request to client */
358                 usteer_ubus_trigger_client_scan(si);
359                 usteer_roam_sm_start_scan(si, &ev);
360                 break;
361 
362         case ROAM_TRIGGER_IDLE:
363                 usteer_roam_sm_start_scan(si, &ev);
364                 break;
365 
366         case ROAM_TRIGGER_SCAN_DONE:
367                 /* Roaming time over, switch back to ROAM_TRIGGER_IDLE */
368                 if (si->roam_transition_start && current_time - si->roam_transition_start > config.roam_kick_delay) {
369                         si->roam_transition_start = 0;
370                         usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev);
371                         break;
372                 }
373 
374                 candidate = usteer_roam_sm_found_better_node(si, &ev, ROAM_TRIGGER_SCAN_DONE);
375                 /* Kick back in case no better node is found */
376                 if (!candidate) {
377                         usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev);
378                         break;
379                 }
380 
381                 if (!candidate->node->rrm_nr)
382                         MSG(VERBOSE, "Candidates node rrm nr not returned from hostapd. Neighbor list empty!");
383 
384                 if (!si->roam_transition_start)
385                         si->roam_transition_start = current_time;
386                 si->roam_transition_request_validity_end = current_time + 10000;
387                 validity_period = 10000 / usteer_local_node_get_beacon_interval(ln); /* ~ 10 seconds */
388                 if (si->sta->aggressiveness >= 2) {
389                         if (!si->kick_time)
390                                 si->kick_time = current_time + config.roam_kick_delay;
391                         if (si->sta->aggressiveness >= 3)
392                                 disassoc_timer = (si->kick_time - current_time) / usteer_local_node_get_beacon_interval(ln);
393                         else
394                                 disassoc_timer = 0;
395                         usteer_ubus_bss_transition_request(si, 1, true, disassoc_timer, true, validity_period, candidate->node);
396                         /* Countdown end */
397                         if (disassoc_timer < validity_period) {
398                                 si->roam_transition_start = 0;
399                                 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev);
400                         }
401                 } else {
402                         usteer_ubus_bss_transition_request(si, 1, false, 0, true, validity_period, candidate->node);
403                         usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev);
404                 }
405                 break;
406         }
407 
408         return false;
409 }
410 
411 bool usteer_policy_can_perform_roam(struct sta_info *si)
412 {
413         /* Only trigger for connected STAs */
414         if (si->connected != STA_CONNECTED)
415                 return false;
416 
417         /* Only trigger for STA with active roaming */
418         if (!si->sta->aggressiveness)
419                 return false;
420 
421         /* Skip on pending kick */
422         if (si->kick_time && si->kick_time <= current_time)
423                 return false;
424 
425         /* Skip if in validity period */
426         if (current_time < si->roam_transition_request_validity_end)
427                 return false;
428 
429         /* Skip on rejected transition */
430         if (si->bss_transition_response.status_code && current_time - si->bss_transition_response.timestamp < config.steer_reject_timeout)
431                 return false;
432 
433         /* Skip if transition accepted */
434         if (!si->bss_transition_response.status_code && current_time - si->bss_transition_response.timestamp < config.roam_kick_delay)
435                 return false;
436 
437         /* Skip on previous kick attempt */
438         if (current_time - si->roam_kick < config.roam_trigger_interval)
439                 return false;
440 
441         /* Skip if connection is established shorter than the trigger-interval */
442         if (current_time - si->connected_since < config.roam_trigger_interval)
443                 return false;
444 
445         return true;
446 }
447 
448 static bool
449 usteer_local_node_roam_sm_active(struct sta_info *si, int min_signal)
450 {
451         if (!usteer_policy_can_perform_roam(si))
452                 return false;
453 
454         /* Signal has to be below scan / roam threshold */
455         if (si->signal >= min_signal)
456                 return false;
457 
458         return true;
459 }
460 
461 static void
462 usteer_local_node_roam_check(struct usteer_local_node *ln, struct uevent *ev)
463 {
464         struct sta_info *si;
465         int min_signal;
466 
467         if (config.roam_scan_snr)
468                 min_signal = config.roam_scan_snr;
469         else if (config.roam_trigger_snr)
470                 min_signal = config.roam_trigger_snr;
471         else
472                 return;
473 
474         usteer_update_time();
475         min_signal = usteer_snr_to_signal(&ln->node, min_signal);
476 
477         list_for_each_entry(si, &ln->node.sta_info, node_list) {
478                 if (!usteer_local_node_roam_sm_active(si, min_signal)) {
479                         usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, ev);
480                         continue;
481                 }
482 
483                 /*
484                  * If the state machine kicked a client, other clients should wait
485                  * until the next turn
486                  */
487                 if (usteer_roam_trigger_sm(ln, si))
488                         return;
489         }
490 }
491 
492 static void
493 usteer_local_node_snr_kick(struct usteer_local_node *ln)
494 {
495         unsigned int min_count = DIV_ROUND_UP(config.min_snr_kick_delay, config.local_sta_update);
496         struct uevent ev = {
497                 .node_local = &ln->node,
498         };
499         struct sta_info *si;
500         int min_signal;
501 
502         if (!config.min_snr)
503                 return;
504 
505         min_signal = usteer_snr_to_signal(&ln->node, config.min_snr);
506         ev.threshold.ref = min_signal;
507 
508         list_for_each_entry(si, &ln->node.sta_info, node_list) {
509                 if (si->connected != STA_CONNECTED)
510                         continue;
511 
512                 if (si->signal >= min_signal) {
513                         si->below_min_snr = 0;
514                         continue;
515                 } else {
516                         si->below_min_snr++;
517                 }
518 
519                 if (si->below_min_snr <= min_count)
520                         continue;
521 
522                 ev.type = UEV_SIGNAL_KICK;
523                 ev.threshold.cur = si->signal;
524                 ev.count = si->kick_count;
525                 usteer_event(&ev);
526 
527                 usteer_ubus_kick_client(si, KICK_REASON_UNSPECIFIED);
528                 return;
529         }
530 }
531 
532 static void
533 usteer_local_node_load_kick(struct usteer_local_node *ln)
534 {
535         struct usteer_node *node = &ln->node;
536         struct sta_info *kick1 = NULL, *kick2 = NULL;
537         struct sta_info *candidate = NULL;
538         struct sta_info *si;
539         struct uevent ev = {
540                 .node_local = &ln->node,
541         };
542         unsigned int min_count = DIV_ROUND_UP(config.load_kick_delay, config.local_sta_update);
543 
544         if (!config.load_kick_enabled || !config.load_kick_threshold ||
545             !config.load_kick_delay)
546                 return;
547 
548         if (node->load < config.load_kick_threshold) {
549                 if (!ln->load_thr_count)
550                         return;
551 
552                 ln->load_thr_count = 0;
553                 ev.type = UEV_LOAD_KICK_RESET;
554                 ev.threshold.cur = node->load;
555                 ev.threshold.ref = config.load_kick_threshold;
556                 goto out;
557         }
558 
559         if (++ln->load_thr_count <= min_count) {
560                 if (ln->load_thr_count > 1)
561                         return;
562 
563                 ev.type = UEV_LOAD_KICK_TRIGGER;
564                 ev.threshold.cur = node->load;
565                 ev.threshold.ref = config.load_kick_threshold;
566                 goto out;
567         }
568 
569         ln->load_thr_count = 0;
570         if (node->n_assoc < config.load_kick_min_clients) {
571                 ev.type = UEV_LOAD_KICK_MIN_CLIENTS;
572                 ev.threshold.cur = node->n_assoc;
573                 ev.threshold.ref = config.load_kick_min_clients;
574                 goto out;
575         }
576 
577         list_for_each_entry(si, &ln->node.sta_info, node_list) {
578                 struct sta_info *tmp;
579 
580                 if (si->connected != STA_CONNECTED)
581                         continue;
582 
583                 if (is_more_kickable(kick1, si))
584                         kick1 = si;
585 
586                 tmp = find_better_candidate(si, NULL, (1 << UEV_SELECT_REASON_LOAD), 0);
587                 if (!tmp)
588                         continue;
589 
590                 if (is_more_kickable(kick2, si)) {
591                         kick2 = si;
592                         candidate = tmp;
593                 }
594         }
595 
596         if (!kick1) {
597                 ev.type = UEV_LOAD_KICK_NO_CLIENT;
598                 goto out;
599         }
600 
601         if (kick2)
602                 kick1 = kick2;
603 
604         kick1->kick_count++;
605 
606         ev.type = UEV_LOAD_KICK_CLIENT;
607         ev.si_cur = kick1;
608         ev.si_other = candidate;
609         ev.count = kick1->kick_count;
610 
611         usteer_ubus_kick_client(kick1, config.load_kick_reason_code);
612 
613 out:
614         usteer_event(&ev);
615 }
616 
617 static void
618 usteer_local_node_perform_kick(struct usteer_local_node *ln)
619 {
620         struct sta_info *si;
621 
622         list_for_each_entry(si, &ln->node.sta_info, node_list) {
623                 if (!si->kick_time || si->kick_time > current_time)
624                         continue;
625 
626                 usteer_ubus_kick_client(si, KICK_REASON_BSS_TRANSITION);
627         }
628 }
629 
630 void
631 usteer_local_node_kick(struct usteer_local_node *ln)
632 {
633         struct uevent ev = {
634                 .node_local = &ln->node,
635         };
636 
637         usteer_local_node_perform_kick(ln);
638 
639         usteer_local_node_snr_kick(ln);
640         usteer_local_node_load_kick(ln);
641         usteer_local_node_roam_check(ln, &ev);
642 }
643 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt