usteer: Initial import
[project/usteer.git] / ubus.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 <sys/types.h>
21 #include <sys/socket.h>
22 #include <net/ethernet.h>
23 #ifdef linux
24 #include <netinet/ether.h>
25 #endif
26
27 #include "usteer.h"
28 #include "node.h"
29
30 static struct blob_buf b;
31
32 static int
33 usteer_ubus_get_clients(struct ubus_context *ctx, struct ubus_object *obj,
34 struct ubus_request_data *req, const char *method,
35 struct blob_attr *msg)
36 {
37 struct sta_info *si;
38 struct sta *sta;
39 char str[20];
40 void *_s, *_cur_n;
41
42 blob_buf_init(&b, 0);
43 avl_for_each_element(&stations, sta, avl) {
44 sprintf(str, MAC_ADDR_FMT, MAC_ADDR_DATA(sta->addr));
45 _s = blobmsg_open_table(&b, str);
46 list_for_each_entry(si, &sta->nodes, list) {
47 _cur_n = blobmsg_open_table(&b, usteer_node_name(si->node));
48 blobmsg_add_u8(&b, "connected", si->connected);
49 blobmsg_add_u32(&b, "signal", si->signal);
50 blobmsg_close_table(&b, _cur_n);
51 }
52 blobmsg_close_table(&b, _s);
53 }
54 ubus_send_reply(ctx, req, b.head);
55 return 0;
56 }
57
58 static struct blobmsg_policy client_arg[] = {
59 { .name = "address", .type = BLOBMSG_TYPE_STRING, },
60 };
61
62 static void
63 usteer_ubus_add_stats(struct sta_info_stats *stats, const char *name)
64 {
65 void *s;
66
67 s = blobmsg_open_table(&b, name);
68 blobmsg_add_u32(&b, "requests", stats->requests);
69 blobmsg_add_u32(&b, "blocked_cur", stats->blocked_cur);
70 blobmsg_add_u32(&b, "blocked_total", stats->blocked_total);
71 blobmsg_close_table(&b, s);
72 }
73
74 static int
75 usteer_ubus_get_client_info(struct ubus_context *ctx, struct ubus_object *obj,
76 struct ubus_request_data *req, const char *method,
77 struct blob_attr *msg)
78 {
79 struct sta_info *si;
80 struct sta *sta;
81 struct blob_attr *mac_str;
82 uint8_t *mac;
83 void *_n, *_cur_n, *_s;
84 int i;
85
86 blobmsg_parse(client_arg, 1, &mac_str, blob_data(msg), blob_len(msg));
87 if (!mac_str)
88 return UBUS_STATUS_INVALID_ARGUMENT;
89
90 mac = (uint8_t *) ether_aton(blobmsg_data(mac_str));
91 if (!mac)
92 return UBUS_STATUS_INVALID_ARGUMENT;
93
94 sta = usteer_sta_get(mac, false);
95 if (!sta)
96 return UBUS_STATUS_NOT_FOUND;
97
98 blob_buf_init(&b, 0);
99 blobmsg_add_u8(&b, "2ghz", sta->seen_2ghz);
100 blobmsg_add_u8(&b, "5ghz", sta->seen_5ghz);
101 _n = blobmsg_open_table(&b, "nodes");
102 list_for_each_entry(si, &sta->nodes, list) {
103 _cur_n = blobmsg_open_table(&b, usteer_node_name(si->node));
104 blobmsg_add_u8(&b, "connected", si->connected);
105 blobmsg_add_u32(&b, "signal", si->signal);
106 _s = blobmsg_open_table(&b, "stats");
107 for (i = 0; i < __EVENT_TYPE_MAX; i++)
108 usteer_ubus_add_stats(&si->stats[EVENT_TYPE_PROBE], event_types[i]);
109 blobmsg_close_table(&b, _s);
110 blobmsg_close_table(&b, _cur_n);
111 }
112 blobmsg_close_table(&b, _n);
113
114 ubus_send_reply(ctx, req, b.head);
115
116 return 0;
117 }
118
119 enum cfg_type {
120 CFG_BOOL,
121 CFG_I32,
122 CFG_U32,
123 CFG_ARRAY_CB,
124 CFG_STRING_CB,
125 };
126
127 struct cfg_item {
128 enum cfg_type type;
129 union {
130 bool *BOOL;
131 uint32_t *U32;
132 int32_t *I32;
133 struct {
134 void (*set)(struct blob_attr *data);
135 void (*get)(struct blob_buf *buf);
136 } CB;
137 } ptr;
138 };
139
140 #define __config_items \
141 _cfg(BOOL, syslog), \
142 _cfg(U32, debug_level), \
143 _cfg(U32, sta_block_timeout), \
144 _cfg(U32, local_sta_timeout), \
145 _cfg(U32, local_sta_update), \
146 _cfg(U32, max_retry_band), \
147 _cfg(U32, seen_policy_timeout), \
148 _cfg(U32, load_balancing_threshold), \
149 _cfg(U32, band_steering_threshold), \
150 _cfg(U32, remote_update_interval), \
151 _cfg(I32, min_connect_snr), \
152 _cfg(I32, min_snr), \
153 _cfg(I32, roam_scan_snr), \
154 _cfg(U32, roam_scan_tries), \
155 _cfg(U32, roam_scan_interval), \
156 _cfg(I32, roam_trigger_snr), \
157 _cfg(U32, roam_trigger_interval), \
158 _cfg(U32, roam_kick_delay), \
159 _cfg(U32, signal_diff_threshold), \
160 _cfg(U32, initial_connect_delay), \
161 _cfg(BOOL, load_kick_enabled), \
162 _cfg(U32, load_kick_threshold), \
163 _cfg(U32, load_kick_delay), \
164 _cfg(U32, load_kick_min_clients), \
165 _cfg(U32, load_kick_reason_code), \
166 _cfg(ARRAY_CB, interfaces), \
167 _cfg(STRING_CB, node_up_script)
168
169 enum cfg_items {
170 #define _cfg(_type, _name) CFG_##_name
171 __config_items,
172 #undef _cfg
173 __CFG_MAX,
174 };
175
176 static const struct blobmsg_policy config_policy[__CFG_MAX] = {
177 #define _cfg_policy(_type, _name) [CFG_##_name] = { .name = #_name, .type = BLOBMSG_TYPE_ ## _type }
178 #define _cfg_policy_BOOL(_name) _cfg_policy(BOOL, _name)
179 #define _cfg_policy_U32(_name) _cfg_policy(INT32, _name)
180 #define _cfg_policy_I32(_name) _cfg_policy(INT32, _name)
181 #define _cfg_policy_ARRAY_CB(_name) _cfg_policy(ARRAY, _name)
182 #define _cfg_policy_STRING_CB(_name) _cfg_policy(STRING, _name)
183 #define _cfg(_type, _name) _cfg_policy_##_type(_name)
184 __config_items,
185 #undef _cfg
186 };
187
188 static const struct cfg_item config_data[__CFG_MAX] = {
189 #define _cfg_data_BOOL(_name) .ptr.BOOL = &config._name
190 #define _cfg_data_U32(_name) .ptr.U32 = &config._name
191 #define _cfg_data_I32(_name) .ptr.I32 = &config._name
192 #define _cfg_data_ARRAY_CB(_name) .ptr.CB = { .set = config_set_##_name, .get = config_get_##_name }
193 #define _cfg_data_STRING_CB(_name) .ptr.CB = { .set = config_set_##_name, .get = config_get_##_name }
194 #define _cfg(_type, _name) [CFG_##_name] = { .type = CFG_##_type, _cfg_data_##_type(_name) }
195 __config_items,
196 #undef _cfg
197 };
198
199 static int
200 usteer_ubus_get_config(struct ubus_context *ctx, struct ubus_object *obj,
201 struct ubus_request_data *req, const char *method,
202 struct blob_attr *msg)
203 {
204 int i;
205
206 blob_buf_init(&b, 0);
207 for (i = 0; i < __CFG_MAX; i++) {
208 switch(config_data[i].type) {
209 case CFG_BOOL:
210 blobmsg_add_u8(&b, config_policy[i].name,
211 *config_data[i].ptr.BOOL);
212 break;
213 case CFG_I32:
214 case CFG_U32:
215 blobmsg_add_u32(&b, config_policy[i].name,
216 *config_data[i].ptr.U32);
217 break;
218 case CFG_ARRAY_CB:
219 case CFG_STRING_CB:
220 config_data[i].ptr.CB.get(&b);
221 break;
222 }
223 }
224 ubus_send_reply(ctx, req, b.head);
225 return 0;
226 }
227
228 static int
229 usteer_ubus_set_config(struct ubus_context *ctx, struct ubus_object *obj,
230 struct ubus_request_data *req, const char *method,
231 struct blob_attr *msg)
232 {
233 struct blob_attr *tb[__CFG_MAX];
234 int i;
235
236 if (!strcmp(method, "set_config"))
237 usteer_init_defaults();
238
239 blobmsg_parse(config_policy, __CFG_MAX, tb, blob_data(msg), blob_len(msg));
240 for (i = 0; i < __CFG_MAX; i++) {
241 if (!tb[i])
242 continue;
243
244 switch(config_data[i].type) {
245 case CFG_BOOL:
246 *config_data[i].ptr.BOOL = blobmsg_get_u8(tb[i]);
247 break;
248 case CFG_I32:
249 case CFG_U32:
250 *config_data[i].ptr.U32 = blobmsg_get_u32(tb[i]);
251 break;
252 case CFG_ARRAY_CB:
253 case CFG_STRING_CB:
254 config_data[i].ptr.CB.set(tb[i]);
255 break;
256 }
257 }
258
259 return 0;
260 }
261
262 static void
263 usteer_dump_node_info(struct usteer_node *node)
264 {
265 void *c;
266
267 c = blobmsg_open_table(&b, usteer_node_name(node));
268 blobmsg_add_u32(&b, "freq", node->freq);
269 blobmsg_add_u32(&b, "n_assoc", node->n_assoc);
270 blobmsg_add_u32(&b, "noise", node->noise);
271 blobmsg_add_u32(&b, "load", node->load);
272 blobmsg_add_u32(&b, "max_assoc", node->max_assoc);
273 if (node->rrm_nr)
274 blobmsg_add_field(&b, BLOBMSG_TYPE_ARRAY, "rrm_nr",
275 blobmsg_data(node->rrm_nr),
276 blobmsg_data_len(node->rrm_nr));
277 blobmsg_close_table(&b, c);
278 }
279
280 static int
281 usteer_ubus_local_info(struct ubus_context *ctx, struct ubus_object *obj,
282 struct ubus_request_data *req, const char *method,
283 struct blob_attr *msg)
284 {
285 struct usteer_node *node;
286
287 blob_buf_init(&b, 0);
288
289 avl_for_each_element(&local_nodes, node, avl)
290 usteer_dump_node_info(node);
291
292 ubus_send_reply(ctx, req, b.head);
293
294 return 0;
295 }
296
297 static int
298 usteer_ubus_remote_info(struct ubus_context *ctx, struct ubus_object *obj,
299 struct ubus_request_data *req, const char *method,
300 struct blob_attr *msg)
301 {
302 struct usteer_remote_node *rn;
303
304 blob_buf_init(&b, 0);
305
306 avl_for_each_element(&remote_nodes, rn, avl)
307 usteer_dump_node_info(&rn->node);
308
309 ubus_send_reply(ctx, req, b.head);
310
311 return 0;
312 }
313
314 static const struct ubus_method usteer_methods[] = {
315 UBUS_METHOD_NOARG("local_info", usteer_ubus_local_info),
316 UBUS_METHOD_NOARG("remote_info", usteer_ubus_remote_info),
317 UBUS_METHOD_NOARG("get_clients", usteer_ubus_get_clients),
318 UBUS_METHOD("get_client_info", usteer_ubus_get_client_info, client_arg),
319 UBUS_METHOD_NOARG("get_config", usteer_ubus_get_config),
320 UBUS_METHOD("set_config", usteer_ubus_set_config, config_policy),
321 UBUS_METHOD("update_config", usteer_ubus_set_config, config_policy),
322 };
323
324 static struct ubus_object_type usteer_obj_type =
325 UBUS_OBJECT_TYPE("usteer", usteer_methods);
326
327 static struct ubus_object usteer_obj = {
328 .name = "usteer",
329 .type = &usteer_obj_type,
330 .methods = usteer_methods,
331 .n_methods = ARRAY_SIZE(usteer_methods),
332 };
333
334 static void
335 usteer_add_nr_entry(struct usteer_node *ln, struct usteer_node *node)
336 {
337 struct blobmsg_policy policy[3] = {
338 { .type = BLOBMSG_TYPE_STRING },
339 { .type = BLOBMSG_TYPE_STRING },
340 { .type = BLOBMSG_TYPE_STRING },
341 };
342 struct blob_attr *tb[3];
343
344 if (!node->rrm_nr)
345 return;
346
347 if (strcmp(ln->ssid, node->ssid) != 0)
348 return;
349
350 blobmsg_parse_array(policy, ARRAY_SIZE(tb), tb,
351 blobmsg_data(node->rrm_nr),
352 blobmsg_data_len(node->rrm_nr));
353 if (!tb[2])
354 return;
355
356 blobmsg_add_field(&b, BLOBMSG_TYPE_STRING, "",
357 blobmsg_data(tb[2]),
358 blobmsg_data_len(tb[2]));
359 }
360
361 int usteer_ubus_notify_client_disassoc(struct sta_info *si)
362 {
363 struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node);
364 struct usteer_remote_node *rn;
365 struct usteer_node *node;
366 void *c;
367
368 blob_buf_init(&b, 0);
369 blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr));
370 blobmsg_add_u32(&b, "duration", config.roam_kick_delay);
371 c = blobmsg_open_array(&b, "neighbors");
372 avl_for_each_element(&local_nodes, node, avl)
373 usteer_add_nr_entry(si->node, node);
374 avl_for_each_element(&remote_nodes, rn, avl)
375 usteer_add_nr_entry(si->node, &rn->node);
376 blobmsg_close_array(&b, c);
377 return ubus_invoke(ubus_ctx, ln->obj_id, "wnm_disassoc_imminent", b.head, NULL, 0, 100);
378 }
379
380 int usteer_ubus_trigger_client_scan(struct sta_info *si)
381 {
382 struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node);
383
384 si->scan_band = !si->scan_band;
385
386 MSG_T_STA("load_kick_reason_code", si->sta->addr,
387 "tell hostapd to issue a client beacon request (5ghz: %d)\n",
388 si->scan_band);
389
390 blob_buf_init(&b, 0);
391 blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr));
392 blobmsg_add_u32(&b, "mode", 1);
393 blobmsg_add_u32(&b, "duration", 65535);
394 blobmsg_add_u32(&b, "channel", 255);
395 blobmsg_add_u32(&b, "op_class", si->scan_band ? 1 : 12);
396 return ubus_invoke(ubus_ctx, ln->obj_id, "rrm_beacon_req", b.head, NULL, 0, 100);
397 }
398
399 void usteer_ubus_kick_client(struct sta_info *si)
400 {
401 struct usteer_local_node *ln = container_of(si->node, struct usteer_local_node, node);
402
403 MSG_T_STA("load_kick_reason_code", si->sta->addr,
404 "tell hostapd to kick client with reason code %u\n",
405 config.load_kick_reason_code);
406
407 blob_buf_init(&b, 0);
408 blobmsg_printf(&b, "addr", MAC_ADDR_FMT, MAC_ADDR_DATA(si->sta->addr));
409 blobmsg_add_u32(&b, "reason", config.load_kick_reason_code);
410 blobmsg_add_u8(&b, "deauth", 1);
411 ubus_invoke(ubus_ctx, ln->obj_id, "del_client", b.head, NULL, 0, 100);
412 si->connected = 0;
413 si->roam_kick = current_time;
414 }
415
416 void usteer_ubus_init(struct ubus_context *ctx)
417 {
418 ubus_add_object(ctx, &usteer_obj);
419 }