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