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