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 } 602
This page was automatically generated by LXR 0.3.1. • OpenWrt