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