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 si->roam_event = current_time; 268 269 if (si->roam_state == state) { 270 if (si->roam_state == ROAM_TRIGGER_IDLE) { 271 si->roam_tries = 0; 272 return; 273 } 274 275 si->roam_tries++; 276 } else { 277 si->roam_tries = 0; 278 } 279 280 si->roam_state = state; 281 usteer_event(ev); 282 } 283 284 static void 285 usteer_roam_sm_start_scan(struct sta_info *si, struct uevent *ev) 286 { 287 /* Start scanning in case we are not timeout-constrained or timeout has expired */ 288 if (!config.roam_scan_timeout || 289 current_time > si->roam_scan_timeout_start + config.roam_scan_timeout) { 290 usteer_roam_set_state(si, ROAM_TRIGGER_SCAN, ev); 291 return; 292 } 293 294 /* We are currently in scan timeout / cooldown. 295 * Check if we are in ROAM_TRIGGER_IDLE state. Enter this state if not. 296 */ 297 if (si->roam_state == ROAM_TRIGGER_IDLE) 298 return; 299 300 /* Enter idle state */ 301 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, ev); 302 } 303 304 static struct sta_info * 305 usteer_roam_sm_found_better_node(struct sta_info *si, struct uevent *ev, enum roam_trigger_state next_state) 306 { 307 uint64_t max_age = 2 * config.roam_scan_interval; 308 struct sta_info *candidate; 309 310 if (max_age > current_time - si->roam_scan_start) 311 max_age = current_time - si->roam_scan_start; 312 313 candidate = find_better_candidate(si, ev, (1 << UEV_SELECT_REASON_SIGNAL), max_age); 314 if (candidate) 315 usteer_roam_set_state(si, next_state, ev); 316 317 return candidate; 318 } 319 320 static bool 321 usteer_roam_trigger_sm(struct usteer_local_node *ln, struct sta_info *si) 322 { 323 struct sta_info *candidate; 324 struct uevent ev = { 325 .si_cur = si, 326 }; 327 328 switch (si->roam_state) { 329 case ROAM_TRIGGER_SCAN: 330 if (!si->roam_tries) { 331 si->roam_scan_start = current_time; 332 } 333 334 /* Check if we've found a better node regardless of the scan-interval */ 335 if (usteer_roam_sm_found_better_node(si, &ev, ROAM_TRIGGER_SCAN_DONE)) 336 break; 337 338 /* Only scan every scan-interval */ 339 if (current_time - si->roam_event < config.roam_scan_interval) 340 break; 341 342 /* Check if no node was found within roam_scan_tries tries */ 343 if (config.roam_scan_tries && si->roam_tries >= config.roam_scan_tries) { 344 if (!config.roam_scan_timeout) { 345 /* Prepare to kick client */ 346 usteer_roam_set_state(si, ROAM_TRIGGER_SCAN_DONE, &ev); 347 } else { 348 /* Kick in scan timeout */ 349 si->roam_scan_timeout_start = current_time; 350 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev); 351 } 352 break; 353 } 354 355 /* Send beacon-request to client */ 356 usteer_ubus_trigger_client_scan(si); 357 usteer_roam_sm_start_scan(si, &ev); 358 break; 359 360 case ROAM_TRIGGER_IDLE: 361 usteer_roam_sm_start_scan(si, &ev); 362 break; 363 364 case ROAM_TRIGGER_SCAN_DONE: 365 candidate = usteer_roam_sm_found_better_node(si, &ev, ROAM_TRIGGER_SCAN_DONE); 366 /* Kick back in case no better node is found */ 367 if (!candidate) { 368 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev); 369 break; 370 } 371 372 usteer_ubus_bss_transition_request(si, 1, false, false, 100, candidate->node); 373 si->kick_time = current_time + config.roam_kick_delay; 374 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, &ev); 375 break; 376 } 377 378 return false; 379 } 380 381 static void 382 usteer_local_node_roam_check(struct usteer_local_node *ln, struct uevent *ev) 383 { 384 struct sta_info *si; 385 int min_signal; 386 387 if (config.roam_scan_snr) 388 min_signal = config.roam_scan_snr; 389 else if (config.roam_trigger_snr) 390 min_signal = config.roam_trigger_snr; 391 else 392 return; 393 394 usteer_update_time(); 395 min_signal = usteer_snr_to_signal(&ln->node, min_signal); 396 397 list_for_each_entry(si, &ln->node.sta_info, node_list) { 398 if (si->connected != STA_CONNECTED || si->signal >= min_signal || 399 si->kick_time || 400 (si->bss_transition_response.status_code && current_time - si->bss_transition_response.timestamp < config.steer_reject_timeout) || 401 current_time - si->roam_kick < config.roam_trigger_interval) { 402 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE, ev); 403 continue; 404 } 405 406 /* 407 * If the state machine kicked a client, other clients should wait 408 * until the next turn 409 */ 410 if (usteer_roam_trigger_sm(ln, si)) 411 return; 412 } 413 } 414 415 static void 416 usteer_local_node_snr_kick(struct usteer_local_node *ln) 417 { 418 unsigned int min_count = DIV_ROUND_UP(config.min_snr_kick_delay, config.local_sta_update); 419 struct uevent ev = { 420 .node_local = &ln->node, 421 }; 422 struct sta_info *si; 423 int min_signal; 424 425 if (!config.min_snr) 426 return; 427 428 min_signal = usteer_snr_to_signal(&ln->node, config.min_snr); 429 ev.threshold.ref = min_signal; 430 431 list_for_each_entry(si, &ln->node.sta_info, node_list) { 432 if (si->connected != STA_CONNECTED) 433 continue; 434 435 if (si->signal >= min_signal) { 436 si->below_min_snr = 0; 437 continue; 438 } else { 439 si->below_min_snr++; 440 } 441 442 if (si->below_min_snr <= min_count) 443 continue; 444 445 si->kick_count++; 446 447 ev.type = UEV_SIGNAL_KICK; 448 ev.threshold.cur = si->signal; 449 ev.count = si->kick_count; 450 usteer_event(&ev); 451 452 usteer_ubus_kick_client(si); 453 return; 454 } 455 } 456 457 static void 458 usteer_local_node_load_kick(struct usteer_local_node *ln) 459 { 460 struct usteer_node *node = &ln->node; 461 struct sta_info *kick1 = NULL, *kick2 = NULL; 462 struct sta_info *candidate = NULL; 463 struct sta_info *si; 464 struct uevent ev = { 465 .node_local = &ln->node, 466 }; 467 unsigned int min_count = DIV_ROUND_UP(config.load_kick_delay, config.local_sta_update); 468 469 if (!config.load_kick_enabled || !config.load_kick_threshold || 470 !config.load_kick_delay) 471 return; 472 473 if (node->load < config.load_kick_threshold) { 474 if (!ln->load_thr_count) 475 return; 476 477 ln->load_thr_count = 0; 478 ev.type = UEV_LOAD_KICK_RESET; 479 ev.threshold.cur = node->load; 480 ev.threshold.ref = config.load_kick_threshold; 481 goto out; 482 } 483 484 if (++ln->load_thr_count <= min_count) { 485 if (ln->load_thr_count > 1) 486 return; 487 488 ev.type = UEV_LOAD_KICK_TRIGGER; 489 ev.threshold.cur = node->load; 490 ev.threshold.ref = config.load_kick_threshold; 491 goto out; 492 } 493 494 ln->load_thr_count = 0; 495 if (node->n_assoc < config.load_kick_min_clients) { 496 ev.type = UEV_LOAD_KICK_MIN_CLIENTS; 497 ev.threshold.cur = node->n_assoc; 498 ev.threshold.ref = config.load_kick_min_clients; 499 goto out; 500 } 501 502 list_for_each_entry(si, &ln->node.sta_info, node_list) { 503 struct sta_info *tmp; 504 505 if (si->connected != STA_CONNECTED) 506 continue; 507 508 if (is_more_kickable(kick1, si)) 509 kick1 = si; 510 511 tmp = find_better_candidate(si, NULL, (1 << UEV_SELECT_REASON_LOAD), 0); 512 if (!tmp) 513 continue; 514 515 if (is_more_kickable(kick2, si)) { 516 kick2 = si; 517 candidate = tmp; 518 } 519 } 520 521 if (!kick1) { 522 ev.type = UEV_LOAD_KICK_NO_CLIENT; 523 goto out; 524 } 525 526 if (kick2) 527 kick1 = kick2; 528 529 kick1->kick_count++; 530 531 ev.type = UEV_LOAD_KICK_CLIENT; 532 ev.si_cur = kick1; 533 ev.si_other = candidate; 534 ev.count = kick1->kick_count; 535 536 usteer_ubus_kick_client(kick1); 537 538 out: 539 usteer_event(&ev); 540 } 541 542 static void 543 usteer_local_node_perform_kick(struct usteer_local_node *ln) 544 { 545 struct sta_info *si; 546 547 list_for_each_entry(si, &ln->node.sta_info, node_list) { 548 if (!si->kick_time || si->kick_time > current_time) 549 continue; 550 551 usteer_ubus_kick_client(si); 552 } 553 } 554 555 void 556 usteer_local_node_kick(struct usteer_local_node *ln) 557 { 558 struct uevent ev = { 559 .node_local = &ln->node, 560 }; 561 562 usteer_local_node_perform_kick(ln); 563 564 usteer_local_node_snr_kick(ln); 565 usteer_local_node_load_kick(ln); 566 usteer_local_node_roam_check(ln, &ev); 567 } 568
This page was automatically generated by LXR 0.3.1. • OpenWrt