policy: always accept assoc requests by default
[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
23 static bool
24 below_assoc_threshold(struct sta_info *si_cur, struct sta_info *si_new)
25 {
26 int n_assoc_cur = si_cur->node->n_assoc;
27 int n_assoc_new = si_new->node->n_assoc;
28 bool ref_5g = si_cur->node->freq > 4000;
29 bool node_5g = si_new->node->freq > 4000;
30
31 if (ref_5g && !node_5g)
32 n_assoc_new += config.band_steering_threshold;
33 else if (!ref_5g && node_5g)
34 n_assoc_cur += config.band_steering_threshold;
35
36 n_assoc_new += config.load_balancing_threshold;
37
38 if (n_assoc_new > n_assoc_cur) {
39 MSG_T_STA("band_steering_threshold,load_balancing_threshold",
40 si_cur->sta->addr, "exeeded (bs=%u, lb=%u)\n",
41 config.band_steering_threshold,
42 config.load_balancing_threshold);
43 }
44 return n_assoc_new <= n_assoc_cur;
45 }
46
47 static bool
48 better_signal_strength(struct sta_info *si_cur, struct sta_info *si_new)
49 {
50 const bool is_better = si_new->signal - si_cur->signal
51 > (int) config.signal_diff_threshold;
52
53 if (!config.signal_diff_threshold)
54 return false;
55
56 if (is_better) {
57 MSG_T_STA("signal_diff_threshold", si_cur->sta->addr,
58 "exceeded (config=%i) (real=%i)\n",
59 config.signal_diff_threshold,
60 si_new->signal - si_cur->signal);
61 }
62 return is_better;
63 }
64
65 static bool
66 below_load_threshold(struct sta_info *si)
67 {
68 return si->node->n_assoc >= config.load_kick_min_clients &&
69 si->node->load > config.load_kick_threshold;
70 }
71
72 static bool
73 has_better_load(struct sta_info *si_cur, struct sta_info *si_new)
74 {
75 return !below_load_threshold(si_cur) && below_load_threshold(si_new);
76 }
77
78 static bool
79 below_max_assoc(struct sta_info *si)
80 {
81 struct usteer_node *node = si->node;
82
83 return !node->max_assoc || node->n_assoc < node->max_assoc;
84 }
85
86 static bool
87 is_better_candidate(struct sta_info *si_cur, struct sta_info *si_new)
88 {
89 if (!below_max_assoc(si_new))
90 return false;
91
92 return below_assoc_threshold(si_cur, si_new) ||
93 better_signal_strength(si_cur, si_new) ||
94 has_better_load(si_cur, si_new);
95 }
96
97 static struct sta_info *
98 find_better_candidate(struct sta_info *si_ref)
99 {
100 struct sta_info *si;
101 struct sta *sta = si_ref->sta;
102
103 list_for_each_entry(si, &sta->nodes, list) {
104 if (si == si_ref)
105 continue;
106
107 if (current_time - si->seen > config.seen_policy_timeout) {
108 MSG_T_STA("seen_policy_timeout", si->sta->addr,
109 "timeout exceeded (%u)\n", config.seen_policy_timeout);
110 continue;
111 }
112
113 if (strcmp(si->node->ssid, si_ref->node->ssid) != 0)
114 continue;
115
116 if (is_better_candidate(si_ref, si) &&
117 !is_better_candidate(si, si_ref))
118 return si;
119 }
120 return NULL;
121 }
122
123 static int
124 snr_to_signal(struct usteer_node *node, int snr)
125 {
126 int noise = -95;
127
128 if (snr < 0)
129 return snr;
130
131 if (node->noise)
132 noise = node->noise;
133
134 return noise + snr;
135 }
136
137 bool
138 usteer_check_request(struct sta_info *si, enum usteer_event_type type)
139 {
140 struct sta_info *si_new;
141 int min_signal;
142
143 if (type == EVENT_TYPE_AUTH)
144 return true;
145
146 if (type == EVENT_TYPE_ASSOC && !config.assoc_steering)
147 return true;
148
149 if (si->stats[type].blocked_cur >= config.max_retry_band) {
150 MSG_T_STA("max_retry_band", si->sta->addr,
151 "max retry (%u) exceeded\n", config.max_retry_band);
152 return true;
153 }
154
155 min_signal = snr_to_signal(si->node, config.min_connect_snr);
156 if (si->signal < min_signal) {
157 if (type != EVENT_TYPE_PROBE || config.debug_level >= MSG_DEBUG)
158 MSG(VERBOSE, "Ignoring %s request from "MAC_ADDR_FMT" due to low signal (%d < %d)\n",
159 event_types[type], MAC_ADDR_DATA(si->sta->addr),
160 si->signal, min_signal);
161 MSG_T_STA("min_connect_snr", si->sta->addr,
162 "snr to low (config=%i) (real=%i)\n",
163 min_signal, si->signal);
164 return false;
165 }
166
167 if (current_time - si->created < config.initial_connect_delay) {
168 if (type != EVENT_TYPE_PROBE || config.debug_level >= MSG_DEBUG)
169 MSG(VERBOSE, "Ignoring %s request from "MAC_ADDR_FMT" during initial connect delay\n",
170 event_types[type], MAC_ADDR_DATA(si->sta->addr));
171 MSG_T_STA("initial_connect_delay", si->sta->addr,
172 "is below delay (%u)\n", config.initial_connect_delay);
173 return false;
174 }
175
176 si_new = find_better_candidate(si);
177 if (!si_new)
178 return true;
179
180 if (type != EVENT_TYPE_PROBE || config.debug_level >= MSG_DEBUG)
181 MSG(VERBOSE, "Ignoring %s request from "MAC_ADDR_FMT", "
182 "node (local/remote): %s/%s, "
183 "signal=%d/%d, n_assoc=%d/%d\n", event_types[type],
184 MAC_ADDR_DATA(si->sta->addr),
185 usteer_node_name(si->node), usteer_node_name(si_new->node),
186 si->signal, si_new->signal,
187 si->node->n_assoc, si_new->node->n_assoc);
188
189 return false;
190 }
191
192 static bool
193 is_more_kickable(struct sta_info *si_cur, struct sta_info *si_new)
194 {
195 if (!si_cur)
196 return true;
197
198 if (si_new->kick_count > si_cur->kick_count)
199 return false;
200
201 return si_cur->signal > si_new->signal;
202 }
203
204 static void
205 usteer_roam_set_state(struct sta_info *si, enum roam_trigger_state state)
206 {
207 static const char * const state_names[] = {
208 #define _S(n) [ROAM_TRIGGER_##n] = #n,
209 __roam_trigger_states
210 #undef _S
211 };
212
213 si->roam_event = current_time;
214
215 if (si->roam_state == state) {
216 if (si->roam_state == ROAM_TRIGGER_IDLE) {
217 si->roam_tries = 0;
218 return;
219 }
220
221 si->roam_tries++;
222 } else {
223 si->roam_tries = 0;
224 }
225
226 si->roam_state = state;
227
228 MSG(VERBOSE, "Roam trigger SM for client "MAC_ADDR_FMT": state=%s, tries=%d, signal=%d\n",
229 MAC_ADDR_DATA(si->sta->addr), state_names[state], si->roam_tries, si->signal);
230 }
231
232 static bool
233 usteer_roam_trigger_sm(struct sta_info *si)
234 {
235 struct sta_info *si_new;
236 int min_signal;
237
238 min_signal = snr_to_signal(si->node, config.roam_trigger_snr);
239
240 switch (si->roam_state) {
241 case ROAM_TRIGGER_SCAN:
242 if (current_time - si->roam_event < config.roam_scan_interval)
243 break;
244
245 if (find_better_candidate(si) ||
246 si->roam_scan_done > si->roam_event) {
247 usteer_roam_set_state(si, ROAM_TRIGGER_SCAN_DONE);
248 break;
249 }
250
251 if (config.roam_scan_tries &&
252 si->roam_tries >= config.roam_scan_tries) {
253 usteer_roam_set_state(si, ROAM_TRIGGER_WAIT_KICK);
254 break;
255 }
256
257 usteer_ubus_trigger_client_scan(si);
258 usteer_roam_set_state(si, ROAM_TRIGGER_SCAN);
259 break;
260
261 case ROAM_TRIGGER_IDLE:
262 if (find_better_candidate(si)) {
263 usteer_roam_set_state(si, ROAM_TRIGGER_SCAN_DONE);
264 break;
265 }
266
267 usteer_roam_set_state(si, ROAM_TRIGGER_SCAN);
268 break;
269
270 case ROAM_TRIGGER_SCAN_DONE:
271 /* Check for stale scan results, kick back to SCAN state if necessary */
272 if (current_time - si->roam_scan_done > 2 * config.roam_scan_interval) {
273 usteer_roam_set_state(si, ROAM_TRIGGER_SCAN);
274 break;
275 }
276
277 si_new = find_better_candidate(si);
278 if (!si_new)
279 break;
280
281 usteer_roam_set_state(si, ROAM_TRIGGER_WAIT_KICK);
282 break;
283
284 case ROAM_TRIGGER_WAIT_KICK:
285 if (si->signal > min_signal)
286 break;
287
288 usteer_roam_set_state(si, ROAM_TRIGGER_NOTIFY_KICK);
289 usteer_ubus_notify_client_disassoc(si);
290 break;
291 case ROAM_TRIGGER_NOTIFY_KICK:
292 if (current_time - si->roam_event < config.roam_kick_delay * 100)
293 break;
294
295 usteer_roam_set_state(si, ROAM_TRIGGER_KICK);
296 break;
297 case ROAM_TRIGGER_KICK:
298 usteer_ubus_kick_client(si);
299 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE);
300 return true;
301 }
302
303 return false;
304 }
305
306 static void
307 usteer_local_node_roam_check(struct usteer_local_node *ln)
308 {
309 struct sta_info *si;
310 int min_signal;
311
312 if (config.roam_scan_snr)
313 min_signal = config.roam_scan_snr;
314 else if (config.roam_trigger_snr)
315 min_signal = config.roam_trigger_snr;
316 else
317 return;
318
319 usteer_update_time();
320 min_signal = snr_to_signal(&ln->node, min_signal);
321
322 list_for_each_entry(si, &ln->node.sta_info, node_list) {
323 if (!si->connected || si->signal >= min_signal ||
324 current_time - si->roam_kick < config.roam_trigger_interval) {
325 usteer_roam_set_state(si, ROAM_TRIGGER_IDLE);
326 continue;
327 }
328
329 /*
330 * If the state machine kicked a client, other clients should wait
331 * until the next turn
332 */
333 if (usteer_roam_trigger_sm(si))
334 return;
335 }
336 }
337
338 static void
339 usteer_local_node_snr_kick(struct usteer_local_node *ln)
340 {
341 struct sta_info *si;
342 int min_signal;
343
344 if (!config.min_snr)
345 return;
346
347 min_signal = snr_to_signal(&ln->node, config.min_snr);
348
349 list_for_each_entry(si, &ln->node.sta_info, node_list) {
350 if (!si->connected)
351 continue;
352
353 if (si->signal >= min_signal)
354 continue;
355
356 si->kick_count++;
357
358 MSG(VERBOSE, "Kicking client "MAC_ADDR_FMT" due to low SNR, signal=%d\n",
359 MAC_ADDR_DATA(si->sta->addr), si->signal);
360
361 usteer_ubus_kick_client(si);
362 return;
363 }
364 }
365
366 void
367 usteer_local_node_kick(struct usteer_local_node *ln)
368 {
369 struct usteer_node *node = &ln->node;
370 struct sta_info *kick1 = NULL, *kick2 = NULL;
371 struct sta_info *candidate = NULL;
372 struct sta_info *si;
373
374 usteer_local_node_roam_check(ln);
375 usteer_local_node_snr_kick(ln);
376
377 if (!config.load_kick_enabled || !config.load_kick_threshold ||
378 !config.load_kick_delay)
379 return;
380
381 if (node->load < config.load_kick_threshold) {
382 MSG_T("load_kick_threshold",
383 "is below load for this node (config=%i) (real=%i)\n",
384 config.load_kick_threshold, node->load);
385 ln->load_thr_count = 0;
386 return;
387 }
388
389 if (++ln->load_thr_count <=
390 DIV_ROUND_UP(config.load_kick_delay, config.local_sta_update)) {
391 MSG_T("load_kick_delay", "delay kicking (config=%i)\n",
392 config.load_kick_delay);
393 return;
394 }
395
396 MSG(VERBOSE, "AP load threshold exceeded on %s (%d), try to kick a client\n",
397 usteer_node_name(node), node->load);
398
399 ln->load_thr_count = 0;
400 if (node->n_assoc < config.load_kick_min_clients) {
401 MSG_T("load_kick_min_clients",
402 "min limit reached, stop kicking clients on this node "
403 "(n_assoc=%i) (config=%i)\n",
404 node->n_assoc, config.load_kick_min_clients);
405 return;
406 }
407
408 list_for_each_entry(si, &ln->node.sta_info, node_list) {
409 struct sta_info *tmp;
410
411 if (!si->connected)
412 continue;
413
414 if (is_more_kickable(kick1, si))
415 kick1 = si;
416
417 tmp = find_better_candidate(si);
418 if (!tmp)
419 continue;
420
421 if (is_more_kickable(kick2, si)) {
422 kick2 = si;
423 candidate = tmp;
424 }
425 }
426
427 if (!kick1)
428 return;
429
430 if (kick2)
431 kick1 = kick2;
432
433 MSG(VERBOSE, "Kicking client "MAC_ADDR_FMT", signal=%d, better_candidate=%s\n",
434 MAC_ADDR_DATA(kick1->sta->addr), kick1->signal,
435 candidate ? usteer_node_name(candidate->node) : "(none)");
436
437 kick1->kick_count++;
438 usteer_ubus_kick_client(kick1);
439 }