qosify: add support for keeping stats
[project/qosify.git] / ubus.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
4 */
5 #include <libubus.h>
6
7 #include "qosify.h"
8
9 static struct blob_buf b;
10
11 static int
12 qosify_ubus_add_array(struct blob_attr *attr, uint8_t val, enum qosify_map_id id)
13 {
14 struct blob_attr *cur;
15 int rem;
16
17 if (blobmsg_check_array(attr, BLOBMSG_TYPE_STRING) < 0)
18 return UBUS_STATUS_INVALID_ARGUMENT;
19
20 blobmsg_for_each_attr(cur, attr, rem)
21 qosify_map_set_entry(id, false, blobmsg_get_string(cur), val);
22
23 return 0;
24 }
25
26 static int
27 qosify_ubus_set_files(struct blob_attr *attr)
28 {
29 struct blob_attr *cur;
30 int rem;
31
32 if (blobmsg_check_array(attr, BLOBMSG_TYPE_STRING) < 0)
33 return UBUS_STATUS_INVALID_ARGUMENT;
34
35 qosify_map_clear_files();
36
37 blobmsg_for_each_attr(cur, attr, rem)
38 qosify_map_load_file(blobmsg_get_string(cur));
39
40 qosify_map_gc();
41
42 return 0;
43 }
44
45
46 enum {
47 CL_ADD_DSCP,
48 CL_ADD_TIMEOUT,
49 CL_ADD_IPV4,
50 CL_ADD_IPV6,
51 CL_ADD_TCP_PORT,
52 CL_ADD_UDP_PORT,
53 CL_ADD_DNS,
54 __CL_ADD_MAX
55 };
56
57 static const struct blobmsg_policy qosify_add_policy[__CL_ADD_MAX] = {
58 [CL_ADD_DSCP] = { "dscp", BLOBMSG_TYPE_STRING },
59 [CL_ADD_TIMEOUT] = { "timeout", BLOBMSG_TYPE_INT32 },
60 [CL_ADD_IPV4] = { "ipv4", BLOBMSG_TYPE_ARRAY },
61 [CL_ADD_IPV6] = { "ipv6", BLOBMSG_TYPE_ARRAY },
62 [CL_ADD_TCP_PORT] = { "tcp_port", BLOBMSG_TYPE_ARRAY },
63 [CL_ADD_UDP_PORT] = { "udp_port", BLOBMSG_TYPE_ARRAY },
64 [CL_ADD_DNS] = { "dns", BLOBMSG_TYPE_ARRAY },
65 };
66
67
68 static int
69 qosify_ubus_reload(struct ubus_context *ctx, struct ubus_object *obj,
70 struct ubus_request_data *req, const char *method,
71 struct blob_attr *msg)
72 {
73 qosify_map_reload();
74 return 0;
75 }
76
77
78 static int
79 qosify_ubus_add(struct ubus_context *ctx, struct ubus_object *obj,
80 struct ubus_request_data *req, const char *method,
81 struct blob_attr *msg)
82 {
83 int prev_timemout = qosify_map_timeout;
84 struct blob_attr *tb[__CL_ADD_MAX];
85 struct blob_attr *cur;
86 uint8_t dscp = 0xff;
87 int ret;
88
89 blobmsg_parse(qosify_add_policy, __CL_ADD_MAX, tb,
90 blobmsg_data(msg), blobmsg_len(msg));
91
92 if (!strcmp(method, "add")) {
93 if ((cur = tb[CL_ADD_DSCP]) == NULL ||
94 qosify_map_dscp_value(blobmsg_get_string(cur), &dscp))
95 return UBUS_STATUS_INVALID_ARGUMENT;
96
97 if ((cur = tb[CL_ADD_TIMEOUT]) != NULL)
98 qosify_map_timeout = blobmsg_get_u32(cur);
99 }
100
101 if ((cur = tb[CL_ADD_IPV4]) != NULL &&
102 (ret = qosify_ubus_add_array(cur, dscp, CL_MAP_IPV4_ADDR) != 0))
103 return ret;
104
105 if ((cur = tb[CL_ADD_IPV6]) != NULL &&
106 (ret = qosify_ubus_add_array(cur, dscp, CL_MAP_IPV6_ADDR) != 0))
107 return ret;
108
109 if ((cur = tb[CL_ADD_TCP_PORT]) != NULL &&
110 (ret = qosify_ubus_add_array(cur, dscp, CL_MAP_TCP_PORTS) != 0))
111 return ret;
112
113 if ((cur = tb[CL_ADD_UDP_PORT]) != NULL &&
114 (ret = qosify_ubus_add_array(cur, dscp, CL_MAP_UDP_PORTS) != 0))
115 return ret;
116
117 if ((cur = tb[CL_ADD_DNS]) != NULL &&
118 (ret = qosify_ubus_add_array(cur, dscp, CL_MAP_DNS) != 0))
119 return ret;
120
121 qosify_map_timeout = prev_timemout;
122
123 return 0;
124 }
125
126 enum {
127 CL_CONFIG_RESET,
128 CL_CONFIG_FILES,
129 CL_CONFIG_TIMEOUT,
130 CL_CONFIG_DSCP_UDP,
131 CL_CONFIG_DSCP_TCP,
132 CL_CONFIG_DSCP_ICMP,
133 CL_CONFIG_INTERFACES,
134 CL_CONFIG_DEVICES,
135 CL_CONFIG_CLASSES,
136 __CL_CONFIG_MAX
137 };
138
139 static const struct blobmsg_policy qosify_config_policy[__CL_CONFIG_MAX] = {
140 [CL_CONFIG_RESET] = { "reset", BLOBMSG_TYPE_BOOL },
141 [CL_CONFIG_FILES] = { "files", BLOBMSG_TYPE_ARRAY },
142 [CL_CONFIG_TIMEOUT] = { "timeout", BLOBMSG_TYPE_INT32 },
143 [CL_CONFIG_DSCP_UDP] = { "dscp_default_udp", BLOBMSG_TYPE_STRING },
144 [CL_CONFIG_DSCP_TCP] = { "dscp_default_tcp", BLOBMSG_TYPE_STRING },
145 [CL_CONFIG_DSCP_ICMP] = { "dscp_icmp", BLOBMSG_TYPE_STRING },
146 [CL_CONFIG_INTERFACES] = { "interfaces", BLOBMSG_TYPE_TABLE },
147 [CL_CONFIG_DEVICES] = { "devices", BLOBMSG_TYPE_TABLE },
148 [CL_CONFIG_CLASSES] = { "classes", BLOBMSG_TYPE_TABLE },
149 };
150
151 static int
152 qosify_ubus_config(struct ubus_context *ctx, struct ubus_object *obj,
153 struct ubus_request_data *req, const char *method,
154 struct blob_attr *msg)
155 {
156 struct blob_attr *tb[__CL_CONFIG_MAX];
157 struct blob_attr *cur;
158 uint8_t dscp;
159 bool reset = false;
160 int ret;
161
162 blobmsg_parse(qosify_config_policy, __CL_CONFIG_MAX, tb,
163 blobmsg_data(msg), blobmsg_len(msg));
164
165 if ((cur = tb[CL_CONFIG_RESET]) != NULL)
166 reset = blobmsg_get_bool(cur);
167
168 if (reset)
169 qosify_map_reset_config();
170
171 if ((cur = tb[CL_CONFIG_CLASSES]) != NULL || reset)
172 qosify_map_set_classes(cur);
173
174 if ((cur = tb[CL_CONFIG_TIMEOUT]) != NULL)
175 qosify_map_timeout = blobmsg_get_u32(cur);
176
177 if ((cur = tb[CL_CONFIG_FILES]) != NULL &&
178 (ret = qosify_ubus_set_files(cur) != 0))
179 return ret;
180
181 if (map_parse_flow_config(&flow_config, msg, reset) ||
182 map_fill_dscp_value(&config.dscp_icmp, tb[CL_CONFIG_DSCP_ICMP], reset))
183 return UBUS_STATUS_INVALID_ARGUMENT;
184
185 map_fill_dscp_value(&dscp, tb[CL_CONFIG_DSCP_UDP], true);
186 if (dscp != 0xff)
187 qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, dscp);
188
189 map_fill_dscp_value(&dscp, tb[CL_CONFIG_DSCP_TCP], true);
190 if (dscp != 0xff)
191 qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, dscp);
192
193 qosify_map_update_config();
194
195 qosify_iface_config_update(tb[CL_CONFIG_INTERFACES], tb[CL_CONFIG_DEVICES]);
196
197 qosify_iface_check();
198
199 return 0;
200 }
201
202
203 static int
204 qosify_ubus_dump(struct ubus_context *ctx, struct ubus_object *obj,
205 struct ubus_request_data *req, const char *method,
206 struct blob_attr *msg)
207 {
208 blob_buf_init(&b, 0);
209 qosify_map_dump(&b);
210 ubus_send_reply(ctx, req, b.head);
211 blob_buf_free(&b);
212
213 return 0;
214 }
215
216 static int
217 qosify_ubus_status(struct ubus_context *ctx, struct ubus_object *obj,
218 struct ubus_request_data *req, const char *method,
219 struct blob_attr *msg)
220 {
221 blob_buf_init(&b, 0);
222 qosify_iface_status(&b);
223 ubus_send_reply(ctx, req, b.head);
224 blob_buf_free(&b);
225
226 return 0;
227 }
228
229 static int
230 qosify_ubus_get_stats(struct ubus_context *ctx, struct ubus_object *obj,
231 struct ubus_request_data *req, const char *method,
232 struct blob_attr *msg)
233 {
234 static const struct blobmsg_policy policy =
235 { "reset", BLOBMSG_TYPE_BOOL };
236 struct blob_attr *tb;
237 bool reset = false;
238
239 blobmsg_parse(&policy, 1, &tb, blobmsg_data(msg), blobmsg_len(msg));
240
241 reset = tb && blobmsg_get_u8(tb);
242
243 blob_buf_init(&b, 0);
244 qosify_map_stats(&b, reset);
245 ubus_send_reply(ctx, req, b.head);
246 blob_buf_free(&b);
247
248 return 0;
249 }
250
251 static int
252 qosify_ubus_check_devices(struct ubus_context *ctx, struct ubus_object *obj,
253 struct ubus_request_data *req, const char *method,
254 struct blob_attr *msg)
255 {
256 qosify_iface_check();
257
258 return 0;
259 }
260
261 enum {
262 CL_DNS_HOST_NAME,
263 CL_DNS_HOST_TYPE,
264 CL_DNS_HOST_ADDR,
265 CL_DNS_HOST_TTL,
266 __CL_DNS_HOST_MAX
267 };
268
269 static const struct blobmsg_policy qosify_dns_policy[__CL_DNS_HOST_MAX] = {
270 [CL_DNS_HOST_NAME] = { "name", BLOBMSG_TYPE_STRING },
271 [CL_DNS_HOST_TYPE] = { "type", BLOBMSG_TYPE_STRING },
272 [CL_DNS_HOST_ADDR] = { "address", BLOBMSG_TYPE_STRING },
273 [CL_DNS_HOST_TTL] = { "ttl", BLOBMSG_TYPE_INT32 },
274 };
275
276 static int
277 __qosify_ubus_add_dns_host(struct blob_attr *msg)
278 {
279 struct blob_attr *tb[__CL_DNS_HOST_MAX];
280 struct blob_attr *cur;
281 uint32_t ttl = 0;
282
283 blobmsg_parse(qosify_dns_policy, __CL_DNS_HOST_MAX, tb,
284 blobmsg_data(msg), blobmsg_len(msg));
285
286 if (!tb[CL_DNS_HOST_NAME] || !tb[CL_DNS_HOST_TYPE] ||
287 !tb[CL_DNS_HOST_ADDR])
288 return UBUS_STATUS_INVALID_ARGUMENT;
289
290 if ((cur = tb[CL_DNS_HOST_TTL]) != NULL)
291 ttl = blobmsg_get_u32(cur);
292
293 if (qosify_map_add_dns_host(blobmsg_get_string(tb[CL_DNS_HOST_NAME]),
294 blobmsg_get_string(tb[CL_DNS_HOST_ADDR]),
295 blobmsg_get_string(tb[CL_DNS_HOST_TYPE]),
296 ttl))
297 return UBUS_STATUS_INVALID_ARGUMENT;
298
299 return 0;
300 }
301
302 static int
303 qosify_ubus_add_dns_host(struct ubus_context *ctx, struct ubus_object *obj,
304 struct ubus_request_data *req, const char *method,
305 struct blob_attr *msg)
306 {
307 return __qosify_ubus_add_dns_host(msg);
308 }
309
310 static const struct ubus_method qosify_methods[] = {
311 UBUS_METHOD_NOARG("reload", qosify_ubus_reload),
312 UBUS_METHOD("add", qosify_ubus_add, qosify_add_policy),
313 UBUS_METHOD_MASK("remove", qosify_ubus_add, qosify_add_policy,
314 ((1 << __CL_ADD_MAX) - 1) & ~(1 << CL_ADD_DSCP)),
315 UBUS_METHOD("config", qosify_ubus_config, qosify_config_policy),
316 UBUS_METHOD_NOARG("dump", qosify_ubus_dump),
317 UBUS_METHOD_NOARG("status", qosify_ubus_status),
318 UBUS_METHOD_NOARG("get_stats", qosify_ubus_get_stats),
319 UBUS_METHOD("add_dns_host", qosify_ubus_add_dns_host, qosify_dns_policy),
320 UBUS_METHOD_NOARG("check_devices", qosify_ubus_check_devices),
321 };
322
323 static struct ubus_object_type qosify_object_type =
324 UBUS_OBJECT_TYPE("qosify", qosify_methods);
325
326 static struct ubus_object qosify_object = {
327 .name = "qosify",
328 .type = &qosify_object_type,
329 .methods = qosify_methods,
330 .n_methods = ARRAY_SIZE(qosify_methods),
331 };
332
333 static void
334 qosify_subscribe_dnsmasq(struct ubus_context *ctx)
335 {
336 static struct ubus_subscriber sub = {
337 .cb = qosify_ubus_add_dns_host,
338 };
339 uint32_t id;
340
341 if (!sub.obj.id &&
342 ubus_register_subscriber(ctx, &sub))
343 return;
344
345 if (ubus_lookup_id(ctx, "dnsmasq.dns", &id))
346 return;
347
348 ubus_subscribe(ctx, &sub, id);
349 }
350
351 static void
352 qosify_ubus_event_cb(struct ubus_context *ctx, struct ubus_event_handler *ev,
353 const char *type, struct blob_attr *msg)
354 {
355 static const struct blobmsg_policy policy =
356 { "path", BLOBMSG_TYPE_STRING };
357 struct blob_attr *attr;
358 const char *path;
359
360 blobmsg_parse(&policy, 1, &attr, blobmsg_data(msg), blobmsg_len(msg));
361
362 if (!attr)
363 return;
364
365 path = blobmsg_get_string(attr);
366 if (!strcmp(path, "dnsmasq.dns"))
367 qosify_subscribe_dnsmasq(ctx);
368 else if (!strcmp(path, "bridger"))
369 qosify_ubus_update_bridger(false);
370 }
371
372
373 static void
374 ubus_connect_handler(struct ubus_context *ctx)
375 {
376 static struct ubus_event_handler ev = {
377 .cb = qosify_ubus_event_cb
378 };
379
380 ubus_add_object(ctx, &qosify_object);
381 ubus_register_event_handler(ctx, &ev, "ubus.object.add");
382 qosify_subscribe_dnsmasq(ctx);
383 }
384
385 static struct ubus_auto_conn conn;
386
387 void qosify_ubus_update_bridger(bool shutdown)
388 {
389 struct ubus_request req;
390 uint32_t id;
391 void *c;
392
393 if (ubus_lookup_id(&conn.ctx, "bridger", &id))
394 return;
395
396 blob_buf_init(&b, 0);
397 blobmsg_add_string(&b, "name", "qosify");
398 c = blobmsg_open_array(&b, "devices");
399 if (!shutdown)
400 qosify_iface_get_devices(&b);
401 blobmsg_close_array(&b, c);
402
403 ubus_invoke_async(&conn.ctx, id, "set_blacklist", b.head, &req);
404 }
405
406 int qosify_ubus_init(void)
407 {
408 conn.cb = ubus_connect_handler;
409 ubus_auto_connect(&conn);
410
411 return 0;
412 }
413
414 void qosify_ubus_stop(void)
415 {
416 qosify_ubus_update_bridger(true);
417 ubus_auto_shutdown(&conn);
418 }
419
420 struct iface_req {
421 char *name;
422 int len;
423 };
424
425 static void
426 netifd_if_cb(struct ubus_request *req, int type, struct blob_attr *msg)
427 {
428 struct iface_req *ifr = req->priv;
429 enum {
430 IFS_ATTR_UP,
431 IFS_ATTR_DEV,
432 __IFS_ATTR_MAX
433 };
434 static const struct blobmsg_policy policy[__IFS_ATTR_MAX] = {
435 [IFS_ATTR_UP] = { "up", BLOBMSG_TYPE_BOOL },
436 [IFS_ATTR_DEV] = { "l3_device", BLOBMSG_TYPE_STRING },
437 };
438 struct blob_attr *tb[__IFS_ATTR_MAX];
439
440 blobmsg_parse(policy, __IFS_ATTR_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
441
442 if (!tb[IFS_ATTR_UP] || !tb[IFS_ATTR_DEV])
443 return;
444
445 if (!blobmsg_get_bool(tb[IFS_ATTR_UP]))
446 return;
447
448 snprintf(ifr->name, ifr->len, "%s", blobmsg_get_string(tb[IFS_ATTR_DEV]));
449 }
450
451 int qosify_ubus_check_interface(const char *name, char *ifname, int ifname_len)
452 {
453 struct iface_req req = { ifname, ifname_len };
454 char *obj_name = "network.interface.";
455 uint32_t id;
456
457 #define PREFIX "network.interface."
458 obj_name = alloca(sizeof(PREFIX) + strlen(name) + 1);
459 sprintf(obj_name, PREFIX "%s", name);
460 #undef PREFIX
461
462 ifname[0] = 0;
463
464 if (ubus_lookup_id(&conn.ctx, obj_name, &id))
465 return -1;
466
467 blob_buf_init(&b, 0);
468 ubus_invoke(&conn.ctx, id, "status", b.head, netifd_if_cb, &req, 1000);
469
470 if (!ifname[0])
471 return -1;
472
473 return 0;
474 }