ubus: fix crash caused by missing static keyword
[project/qosify.git] / map.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
4 */
5 #include <arpa/inet.h>
6
7 #include <errno.h>
8 #include <stdio.h>
9 #include <ctype.h>
10 #include <stdlib.h>
11 #include <time.h>
12 #include <fnmatch.h>
13
14 #include <libubox/uloop.h>
15
16 #include "qosify.h"
17
18 static int qosify_map_entry_cmp(const void *k1, const void *k2, void *ptr);
19
20 static int qosify_map_fds[__CL_MAP_MAX];
21 static AVL_TREE(map_data, qosify_map_entry_cmp, false, NULL);
22 static LIST_HEAD(map_files);
23 static uint32_t next_timeout;
24 static uint8_t qosify_dscp_default[2] = { 0xff, 0xff };
25 int qosify_map_timeout;
26 int qosify_active_timeout;
27 struct qosify_config config;
28
29 struct qosify_map_file {
30 struct list_head list;
31 char filename[];
32 };
33
34 static const struct {
35 const char *name;
36 const char *type_name;
37 } qosify_map_info[] = {
38 [CL_MAP_TCP_PORTS] = { "tcp_ports", "tcp_port" },
39 [CL_MAP_UDP_PORTS] = { "udp_ports", "udp_port" },
40 [CL_MAP_IPV4_ADDR] = { "ipv4_map", "ipv4_addr" },
41 [CL_MAP_IPV6_ADDR] = { "ipv6_map", "ipv6_addr" },
42 [CL_MAP_CONFIG] = { "config", "config" },
43 [CL_MAP_DNS] = { "dns", "dns" },
44 };
45
46 static const struct {
47 const char name[5];
48 uint8_t val;
49 } codepoints[] = {
50 { "CS0", 0 },
51 { "CS1", 8 },
52 { "CS2", 16 },
53 { "CS3", 24 },
54 { "CS4", 32 },
55 { "CS5", 40 },
56 { "CS6", 48 },
57 { "CS7", 56 },
58 { "AF11", 10 },
59 { "AF12", 12 },
60 { "AF13", 14 },
61 { "AF21", 18 },
62 { "AF22", 20 },
63 { "AF22", 22 },
64 { "AF31", 26 },
65 { "AF32", 28 },
66 { "AF33", 30 },
67 { "AF41", 34 },
68 { "AF42", 36 },
69 { "AF43", 38 },
70 { "EF", 46 },
71 { "VA", 44 },
72 { "LE", 1 },
73 { "DF", 0 },
74 };
75
76 static void qosify_map_timer_cb(struct uloop_timeout *t)
77 {
78 qosify_map_gc();
79 }
80
81 static struct uloop_timeout qosify_map_timer = {
82 .cb = qosify_map_timer_cb,
83 };
84
85 static uint32_t qosify_gettime(void)
86 {
87 struct timespec ts;
88
89 clock_gettime(CLOCK_MONOTONIC, &ts);
90
91 return ts.tv_sec;
92 }
93
94 static const char *
95 qosify_map_path(enum qosify_map_id id)
96 {
97 static char path[128];
98 const char *name;
99
100 if (id >= ARRAY_SIZE(qosify_map_info))
101 return NULL;
102
103 name = qosify_map_info[id].name;
104 if (!name)
105 return NULL;
106
107 snprintf(path, sizeof(path), "%s/%s", CLASSIFY_DATA_PATH, name);
108
109 return path;
110 }
111
112 static int qosify_map_get_fd(enum qosify_map_id id)
113 {
114 const char *path = qosify_map_path(id);
115 int fd;
116
117 if (!path)
118 return -1;
119
120 fd = bpf_obj_get(path);
121 if (fd < 0)
122 fprintf(stderr, "Failed to open map %s: %s\n", path, strerror(errno));
123
124 return fd;
125 }
126
127 static void qosify_map_clear_list(enum qosify_map_id id)
128 {
129 int fd = qosify_map_fds[id];
130 __u32 key[4] = {};
131
132 while (bpf_map_get_next_key(fd, &key, &key) != -1)
133 bpf_map_delete_elem(fd, &key);
134 }
135
136 static void __qosify_map_set_dscp_default(enum qosify_map_id id, uint8_t val)
137 {
138 struct qosify_map_data data = {
139 .id = id,
140 };
141 int fd = qosify_map_fds[id];
142 int i;
143
144 val |= QOSIFY_DSCP_DEFAULT_FLAG;
145
146 for (i = 0; i < (1 << 16); i++) {
147 data.addr.port = htons(i);
148 if (avl_find(&map_data, &data))
149 continue;
150
151 bpf_map_update_elem(fd, &data.addr, &val, BPF_ANY);
152 }
153 }
154
155 void qosify_map_set_dscp_default(enum qosify_map_id id, uint8_t val)
156 {
157 bool udp;
158
159 if (id == CL_MAP_TCP_PORTS)
160 udp = false;
161 else if (id == CL_MAP_UDP_PORTS)
162 udp = true;
163 else
164 return;
165
166 if (qosify_dscp_default[udp] == val)
167 return;
168
169 qosify_dscp_default[udp] = val;
170 __qosify_map_set_dscp_default(id, val);
171 }
172
173 int qosify_map_init(void)
174 {
175 int i;
176
177 for (i = 0; i < CL_MAP_DNS; i++) {
178 qosify_map_fds[i] = qosify_map_get_fd(i);
179 if (qosify_map_fds[i] < 0)
180 return -1;
181 }
182
183 qosify_map_clear_list(CL_MAP_IPV4_ADDR);
184 qosify_map_clear_list(CL_MAP_IPV6_ADDR);
185 qosify_map_reset_config();
186
187 return 0;
188 }
189
190 static char *str_skip(char *str, bool space)
191 {
192 while (*str && isspace(*str) == space)
193 str++;
194
195 return str;
196 }
197
198 static int
199 qosify_map_codepoint(const char *val)
200 {
201 int i;
202
203 for (i = 0; i < ARRAY_SIZE(codepoints); i++)
204 if (!strcmp(codepoints[i].name, val))
205 return codepoints[i].val;
206
207 return 0xff;
208 }
209
210 static int qosify_map_entry_cmp(const void *k1, const void *k2, void *ptr)
211 {
212 const struct qosify_map_data *d1 = k1;
213 const struct qosify_map_data *d2 = k2;
214
215 if (d1->id != d2->id)
216 return d2->id - d1->id;
217
218 if (d1->id == CL_MAP_DNS)
219 return strcmp(d1->addr.dns.pattern, d2->addr.dns.pattern);
220
221 return memcmp(&d1->addr, &d2->addr, sizeof(d1->addr));
222 }
223
224 static struct qosify_map_entry *
225 __qosify_map_alloc_entry(struct qosify_map_data *data)
226 {
227 struct qosify_map_entry *e;
228 char *pattern;
229 char *c;
230
231 if (data->id < CL_MAP_DNS) {
232 e = calloc(1, sizeof(*e));
233 memcpy(&e->data.addr, &data->addr, sizeof(e->data.addr));
234
235 return e;
236 }
237
238 e = calloc_a(sizeof(*e), &pattern, strlen(data->addr.dns.pattern) + 1);
239 strcpy(pattern, data->addr.dns.pattern);
240 e->data.addr.dns.pattern = pattern;
241
242 for (c = pattern; *c; c++)
243 *c = tolower(*c);
244
245 if (pattern[0] == '/' &&
246 regcomp(&e->data.addr.dns.regex, pattern + 1,
247 REG_EXTENDED | REG_NOSUB)) {
248 free(e);
249 return NULL;
250 }
251
252 return e;
253 }
254
255 static void __qosify_map_set_entry(struct qosify_map_data *data)
256 {
257 int fd = qosify_map_fds[data->id];
258 struct qosify_map_entry *e;
259 bool file = data->file;
260 int32_t delta = 0;
261 bool add = data->dscp != 0xff;
262 uint8_t prev_dscp = 0xff;
263
264 e = avl_find_element(&map_data, data, e, avl);
265 if (!e) {
266 if (!add)
267 return;
268
269 e = __qosify_map_alloc_entry(data);
270 if (!e)
271 return;
272
273 e->avl.key = &e->data;
274 e->data.id = data->id;
275 avl_insert(&map_data, &e->avl);
276 } else {
277 prev_dscp = e->data.dscp;
278 }
279
280 if (file)
281 e->data.file = add;
282 else
283 e->data.user = add;
284
285 if (add) {
286 if (file)
287 e->data.file_dscp = data->dscp;
288 if (!e->data.user || !file)
289 e->data.dscp = data->dscp;
290 } else if (e->data.file && !file) {
291 e->data.dscp = e->data.file_dscp;
292 }
293
294 if (e->data.dscp != prev_dscp && data->id < CL_MAP_DNS) {
295 struct qosify_ip_map_val val = {
296 .dscp = e->data.dscp,
297 .seen = 1,
298 };
299
300 bpf_map_update_elem(fd, &data->addr, &val, BPF_ANY);
301 }
302
303 if (add) {
304 if (qosify_map_timeout == ~0 || file) {
305 e->timeout = ~0;
306 return;
307 }
308
309 e->timeout = qosify_gettime() + qosify_map_timeout;
310 delta = e->timeout - next_timeout;
311 if (next_timeout && delta >= 0)
312 return;
313 }
314
315 uloop_timeout_set(&qosify_map_timer, 1);
316 }
317
318 static int
319 qosify_map_set_port(struct qosify_map_data *data, const char *str)
320 {
321 unsigned long start_port, end_port;
322 char *err;
323 int i;
324
325 start_port = end_port = strtoul(str, &err, 0);
326 if (err && *err) {
327 if (*err == '-')
328 end_port = strtoul(err + 1, &err, 0);
329 if (*err)
330 return -1;
331 }
332
333 if (!start_port || end_port < start_port ||
334 end_port >= 65535)
335 return -1;
336
337 for (i = start_port; i <= end_port; i++) {
338 data->addr.port = htons(i);
339 __qosify_map_set_entry(data);
340 }
341
342 return 0;
343 }
344
345 static int
346 qosify_map_fill_ip(struct qosify_map_data *data, const char *str)
347 {
348 int af;
349
350 if (data->id == CL_MAP_IPV6_ADDR)
351 af = AF_INET6;
352 else
353 af = AF_INET;
354
355 if (inet_pton(af, str, &data->addr) != 1)
356 return -1;
357
358 return 0;
359 }
360
361 int qosify_map_set_entry(enum qosify_map_id id, bool file, const char *str, uint8_t dscp)
362 {
363 struct qosify_map_data data = {
364 .id = id,
365 .file = file,
366 .dscp = dscp,
367 };
368
369 switch (id) {
370 case CL_MAP_DNS:
371 data.addr.dns.pattern = str;
372 break;
373 case CL_MAP_TCP_PORTS:
374 case CL_MAP_UDP_PORTS:
375 return qosify_map_set_port(&data, str);
376 case CL_MAP_IPV4_ADDR:
377 case CL_MAP_IPV6_ADDR:
378 if (qosify_map_fill_ip(&data, str))
379 return -1;
380 break;
381 default:
382 return -1;
383 }
384
385 __qosify_map_set_entry(&data);
386
387 return 0;
388 }
389
390 int qosify_map_dscp_value(const char *val)
391 {
392 unsigned long dscp;
393 char *err;
394 bool fallback = false;
395
396 if (*val == '+') {
397 fallback = true;
398 val++;
399 }
400
401 dscp = strtoul(val, &err, 0);
402 if (err && *err)
403 dscp = qosify_map_codepoint(val);
404
405 if (dscp >= 64)
406 return -1;
407
408 return dscp + (fallback << 6);
409 }
410
411 static void
412 qosify_map_dscp_codepoint_str(char *dest, int len, uint8_t dscp)
413 {
414 int i;
415
416 if (dscp & QOSIFY_DSCP_FALLBACK_FLAG) {
417 *(dest++) = '+';
418 len--;
419 dscp &= ~QOSIFY_DSCP_FALLBACK_FLAG;
420 }
421
422 for (i = 0; i < ARRAY_SIZE(codepoints); i++) {
423 if (codepoints[i].val != dscp)
424 continue;
425
426 snprintf(dest, len, "%s", codepoints[i].name);
427 return;
428 }
429
430 snprintf(dest, len, "0x%x", dscp);
431 }
432
433 static void
434 qosify_map_parse_line(char *str)
435 {
436 const char *key, *value;
437 int dscp;
438
439 str = str_skip(str, true);
440 key = str;
441
442 str = str_skip(str, false);
443 if (!*str)
444 return;
445
446 *(str++) = 0;
447 str = str_skip(str, true);
448 value = str;
449
450 dscp = qosify_map_dscp_value(value);
451 if (dscp < 0)
452 return;
453
454 if (!strncmp(key, "dns:", 4))
455 qosify_map_set_entry(CL_MAP_DNS, true, key + 4, dscp);
456 if (!strncmp(key, "tcp:", 4))
457 qosify_map_set_entry(CL_MAP_TCP_PORTS, true, key + 4, dscp);
458 else if (!strncmp(key, "udp:", 4))
459 qosify_map_set_entry(CL_MAP_UDP_PORTS, true, key + 4, dscp);
460 else if (strchr(key, ':'))
461 qosify_map_set_entry(CL_MAP_IPV6_ADDR, true, key, dscp);
462 else if (strchr(key, '.'))
463 qosify_map_set_entry(CL_MAP_IPV4_ADDR, true, key, dscp);
464 }
465
466 static int __qosify_map_load_file(const char *file)
467 {
468 char line[1024];
469 char *cur;
470 FILE *f;
471
472 if (!file)
473 return 0;
474
475 f = fopen(file, "r");
476 if (!f) {
477 fprintf(stderr, "Can't open data file %s\n", file);
478 return -1;
479 }
480
481 while (fgets(line, sizeof(line), f)) {
482 cur = strchr(line, '#');
483 if (cur)
484 *cur = 0;
485
486 cur = line + strlen(line);
487 if (cur == line)
488 continue;
489
490 while (cur > line && isspace(cur[-1]))
491 cur--;
492
493 *cur = 0;
494 qosify_map_parse_line(line);
495 }
496
497 fclose(f);
498
499 return 0;
500 }
501
502 int qosify_map_load_file(const char *file)
503 {
504 struct qosify_map_file *f;
505
506 if (!file)
507 return 0;
508
509 f = calloc(1, sizeof(*f) + strlen(file) + 1);
510 strcpy(f->filename, file);
511 list_add_tail(&f->list, &map_files);
512
513 return __qosify_map_load_file(file);
514 }
515
516 static void qosify_map_reset_file_entries(void)
517 {
518 struct qosify_map_entry *e;
519
520 avl_for_each_element(&map_data, e, avl)
521 e->data.file = false;
522 }
523
524 void qosify_map_clear_files(void)
525 {
526 struct qosify_map_file *f, *tmp;
527
528 qosify_map_reset_file_entries();
529
530 list_for_each_entry_safe(f, tmp, &map_files, list) {
531 list_del(&f->list);
532 free(f);
533 }
534 }
535
536 void qosify_map_reset_config(void)
537 {
538 qosify_map_clear_files();
539 qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, 0);
540 qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, 0);
541 qosify_map_timeout = 3600;
542 qosify_active_timeout = 300;
543
544 memset(&config, 0, sizeof(config));
545 config.dscp_prio = 0xff;
546 config.dscp_bulk = 0xff;
547 config.dscp_icmp = 0xff;
548 }
549
550 void qosify_map_reload(void)
551 {
552 struct qosify_map_file *f;
553
554 qosify_map_reset_file_entries();
555
556 list_for_each_entry(f, &map_files, list)
557 __qosify_map_load_file(f->filename);
558
559 qosify_map_gc();
560 }
561
562 static void qosify_map_free_entry(struct qosify_map_entry *e)
563 {
564 int fd = qosify_map_fds[e->data.id];
565
566 avl_delete(&map_data, &e->avl);
567 if (e->data.id < CL_MAP_DNS)
568 bpf_map_delete_elem(fd, &e->data.addr);
569 free(e);
570 }
571
572 static bool
573 qosify_map_entry_refresh_timeout(struct qosify_map_entry *e)
574 {
575 struct qosify_ip_map_val val;
576 int fd = qosify_map_fds[e->data.id];
577
578 if (e->data.id != CL_MAP_IPV4_ADDR &&
579 e->data.id != CL_MAP_IPV6_ADDR)
580 return false;
581
582 if (bpf_map_lookup_elem(fd, &e->data.addr, &val))
583 return false;
584
585 if (!val.seen)
586 return false;
587
588 e->timeout = qosify_gettime() + qosify_active_timeout;
589 val.seen = 0;
590 bpf_map_update_elem(fd, &e->data.addr, &val, BPF_ANY);
591
592 return true;
593 }
594
595 void qosify_map_gc(void)
596 {
597 struct qosify_map_entry *e, *tmp;
598 int32_t timeout = 0;
599 uint32_t cur_time = qosify_gettime();
600
601 next_timeout = 0;
602 avl_for_each_element_safe(&map_data, e, avl, tmp) {
603 int32_t cur_timeout;
604
605 if (e->data.user && e->timeout != ~0) {
606 cur_timeout = e->timeout - cur_time;
607 if (cur_timeout <= 0 &&
608 qosify_map_entry_refresh_timeout(e))
609 cur_timeout = e->timeout - cur_time;
610 if (cur_timeout <= 0) {
611 e->data.user = false;
612 e->data.dscp = e->data.file_dscp;
613 } else if (!timeout || cur_timeout < timeout) {
614 timeout = cur_timeout;
615 next_timeout = e->timeout;
616 }
617 }
618
619 if (e->data.file || e->data.user)
620 continue;
621
622 qosify_map_free_entry(e);
623 }
624
625 if (!timeout)
626 return;
627
628 uloop_timeout_set(&qosify_map_timer, timeout * 1000);
629 }
630
631
632 int qosify_map_add_dns_host(char *host, const char *addr, const char *type, int ttl)
633 {
634 struct qosify_map_data data = {
635 .id = CL_MAP_DNS,
636 .addr.dns.pattern = "",
637 };
638 struct qosify_map_entry *e;
639 int prev_timeout = qosify_map_timeout;
640 char *c;
641
642 e = avl_find_ge_element(&map_data, &data, e, avl);
643 if (!e)
644 return 0;
645
646 memset(&data, 0, sizeof(data));
647 data.user = true;
648 if (!strcmp(type, "A"))
649 data.id = CL_MAP_IPV4_ADDR;
650 else if (!strcmp(type, "AAAA"))
651 data.id = CL_MAP_IPV6_ADDR;
652 else
653 return 0;
654
655 if (qosify_map_fill_ip(&data, addr))
656 return -1;
657
658 for (c = host; *c; c++)
659 *c = tolower(*c);
660
661 avl_for_element_to_last(&map_data, e, e, avl) {
662 regex_t *regex = &e->data.addr.dns.regex;
663
664 if (e->data.id != CL_MAP_DNS)
665 return 0;
666
667 if (e->data.addr.dns.pattern[0] == '/') {
668 if (regexec(regex, host, 0, NULL, 0) != 0)
669 continue;
670 } else {
671 if (fnmatch(e->data.addr.dns.pattern, host, 0))
672 continue;
673 }
674
675 if (ttl)
676 qosify_map_timeout = ttl;
677 data.dscp = e->data.dscp;
678 __qosify_map_set_entry(&data);
679 qosify_map_timeout = prev_timeout;
680 }
681
682 return 0;
683 }
684
685
686 void qosify_map_dump(struct blob_buf *b)
687 {
688 struct qosify_map_entry *e;
689 uint32_t cur_time = qosify_gettime();
690 int buf_len = INET6_ADDRSTRLEN + 1;
691 char *buf;
692 void *a;
693 int af;
694
695 a = blobmsg_open_array(b, "entries");
696 avl_for_each_element(&map_data, e, avl) {
697 void *c;
698
699 if (!e->data.file && !e->data.user)
700 continue;
701
702 c = blobmsg_open_table(b, NULL);
703 if (e->data.user && e->timeout != ~0) {
704 int32_t cur_timeout = e->timeout - cur_time;
705
706 if (cur_timeout < 0)
707 cur_timeout = 0;
708
709 blobmsg_add_u32(b, "timeout", cur_timeout);
710 }
711
712 blobmsg_add_u8(b, "file", e->data.file);
713 blobmsg_add_u8(b, "user", e->data.user);
714
715 buf = blobmsg_alloc_string_buffer(b, "dscp", buf_len);
716 qosify_map_dscp_codepoint_str(buf, buf_len, e->data.dscp);
717 blobmsg_add_string_buffer(b);
718
719 blobmsg_add_string(b, "type", qosify_map_info[e->data.id].type_name);
720
721 switch (e->data.id) {
722 case CL_MAP_TCP_PORTS:
723 case CL_MAP_UDP_PORTS:
724 blobmsg_printf(b, "addr", "%d", ntohs(e->data.addr.port));
725 break;
726 case CL_MAP_IPV4_ADDR:
727 case CL_MAP_IPV6_ADDR:
728 buf = blobmsg_alloc_string_buffer(b, "addr", buf_len);
729 af = e->data.id == CL_MAP_IPV6_ADDR ? AF_INET6 : AF_INET;
730 inet_ntop(af, &e->data.addr, buf, buf_len);
731 blobmsg_add_string_buffer(b);
732 break;
733 case CL_MAP_DNS:
734 blobmsg_add_string(b, "addr", e->data.addr.dns.pattern);
735 break;
736 default:
737 *buf = 0;
738 break;
739 }
740 blobmsg_close_table(b, c);
741 }
742 blobmsg_close_array(b, a);
743 }
744
745 void qosify_map_update_config(void)
746 {
747 int fd = qosify_map_fds[CL_MAP_CONFIG];
748 uint32_t key = 0;
749
750 bpf_map_update_elem(fd, &key, &config, BPF_ANY);
751 }