qosify: add support for keeping stats
[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 #include <libubox/avl-cmp.h>
17
18 #include "qosify.h"
19
20 struct qosify_map_class;
21
22 static int qosify_map_entry_cmp(const void *k1, const void *k2, void *ptr);
23
24 static int qosify_map_fds[__CL_MAP_MAX];
25 static AVL_TREE(map_data, qosify_map_entry_cmp, false, NULL);
26 static LIST_HEAD(map_files);
27 static struct qosify_map_class *map_class[QOSIFY_MAX_CLASS_ENTRIES];
28 static uint32_t next_timeout;
29 static uint8_t qosify_dscp_default[2] = { 0xff, 0xff };
30 int qosify_map_timeout;
31 int qosify_active_timeout;
32 struct qosify_config config;
33 struct qosify_flow_config flow_config;
34 static uint32_t map_dns_seq;
35
36 struct qosify_map_file {
37 struct list_head list;
38 char filename[];
39 };
40
41 struct qosify_map_class {
42 const char *name;
43 struct qosify_class data;
44 };
45
46 static const struct {
47 const char *name;
48 const char *type_name;
49 } qosify_map_info[] = {
50 [CL_MAP_TCP_PORTS] = { "tcp_ports", "tcp_port" },
51 [CL_MAP_UDP_PORTS] = { "udp_ports", "udp_port" },
52 [CL_MAP_IPV4_ADDR] = { "ipv4_map", "ipv4_addr" },
53 [CL_MAP_IPV6_ADDR] = { "ipv6_map", "ipv6_addr" },
54 [CL_MAP_CONFIG] = { "config", "config" },
55 [CL_MAP_CLASS] = { "class_map", "class" },
56 [CL_MAP_DNS] = { "dns", "dns" },
57 };
58
59 static const struct {
60 const char name[5];
61 uint8_t val;
62 } codepoints[] = {
63 { "CS0", 0 },
64 { "CS1", 8 },
65 { "CS2", 16 },
66 { "CS3", 24 },
67 { "CS4", 32 },
68 { "CS5", 40 },
69 { "CS6", 48 },
70 { "CS7", 56 },
71 { "AF11", 10 },
72 { "AF12", 12 },
73 { "AF13", 14 },
74 { "AF21", 18 },
75 { "AF22", 20 },
76 { "AF23", 22 },
77 { "AF31", 26 },
78 { "AF32", 28 },
79 { "AF33", 30 },
80 { "AF41", 34 },
81 { "AF42", 36 },
82 { "AF43", 38 },
83 { "EF", 46 },
84 { "VA", 44 },
85 { "LE", 1 },
86 { "DF", 0 },
87 };
88
89 static void qosify_map_timer_cb(struct uloop_timeout *t)
90 {
91 qosify_map_gc();
92 }
93
94 static struct uloop_timeout qosify_map_timer = {
95 .cb = qosify_map_timer_cb,
96 };
97
98 static uint32_t qosify_gettime(void)
99 {
100 struct timespec ts;
101
102 clock_gettime(CLOCK_MONOTONIC, &ts);
103
104 return ts.tv_sec;
105 }
106
107 static const char *
108 qosify_map_path(enum qosify_map_id id)
109 {
110 static char path[128];
111 const char *name;
112
113 if (id >= ARRAY_SIZE(qosify_map_info))
114 return NULL;
115
116 name = qosify_map_info[id].name;
117 if (!name)
118 return NULL;
119
120 snprintf(path, sizeof(path), "%s/%s", CLASSIFY_DATA_PATH, name);
121
122 return path;
123 }
124
125 static int qosify_map_get_fd(enum qosify_map_id id)
126 {
127 const char *path = qosify_map_path(id);
128 int fd;
129
130 if (!path)
131 return -1;
132
133 fd = bpf_obj_get(path);
134 if (fd < 0)
135 fprintf(stderr, "Failed to open map %s: %s\n", path, strerror(errno));
136
137 return fd;
138 }
139
140 static void qosify_map_clear_list(enum qosify_map_id id)
141 {
142 int fd = qosify_map_fds[id];
143 __u32 key[4] = {};
144
145 while (bpf_map_get_next_key(fd, &key, &key) == 0)
146 bpf_map_delete_elem(fd, &key);
147 }
148
149 static void __qosify_map_set_dscp_default(enum qosify_map_id id, uint8_t val)
150 {
151 struct qosify_map_data data = {
152 .id = id,
153 };
154 struct qosify_class class = {
155 .val.ingress = val,
156 .val.egress = val,
157 };
158 uint32_t key;
159 int fd;
160 int i;
161
162 if (!(val & QOSIFY_DSCP_CLASS_FLAG)) {
163 if (id == CL_MAP_TCP_PORTS)
164 key = QOSIFY_MAX_CLASS_ENTRIES;
165 else if (id == CL_MAP_UDP_PORTS)
166 key = QOSIFY_MAX_CLASS_ENTRIES + 1;
167 else
168 return;
169
170 fd = qosify_map_fds[CL_MAP_CLASS];
171
172 memcpy(&class.config, &flow_config, sizeof(class.config));
173 bpf_map_update_elem(fd, &key, &class, BPF_ANY);
174
175 val = key | QOSIFY_DSCP_CLASS_FLAG;
176 }
177
178 fd = qosify_map_fds[id];
179 for (i = 0; i < (1 << 16); i++) {
180 data.addr.port = htons(i);
181 if (avl_find(&map_data, &data))
182 continue;
183
184 bpf_map_update_elem(fd, &data.addr, &val, BPF_ANY);
185 }
186 }
187
188 void qosify_map_set_dscp_default(enum qosify_map_id id, uint8_t val)
189 {
190 bool udp;
191
192 if (id == CL_MAP_TCP_PORTS)
193 udp = false;
194 else if (id == CL_MAP_UDP_PORTS)
195 udp = true;
196 else
197 return;
198
199 if (val != 0xff) {
200 if (qosify_dscp_default[udp] == val)
201 return;
202
203 qosify_dscp_default[udp] = val;
204 }
205
206 __qosify_map_set_dscp_default(id, qosify_dscp_default[udp]);
207 }
208
209 int qosify_map_init(void)
210 {
211 int i;
212
213 for (i = 0; i < CL_MAP_DNS; i++) {
214 qosify_map_fds[i] = qosify_map_get_fd(i);
215 if (qosify_map_fds[i] < 0)
216 return -1;
217 }
218
219 qosify_map_clear_list(CL_MAP_IPV4_ADDR);
220 qosify_map_clear_list(CL_MAP_IPV6_ADDR);
221 qosify_map_reset_config();
222
223 return 0;
224 }
225
226 static char *str_skip(char *str, bool space)
227 {
228 while (*str && isspace(*str) == space)
229 str++;
230
231 return str;
232 }
233
234 static int
235 qosify_map_codepoint(const char *val)
236 {
237 int i;
238
239 for (i = 0; i < ARRAY_SIZE(codepoints); i++)
240 if (!strcmp(codepoints[i].name, val))
241 return codepoints[i].val;
242
243 return 0xff;
244 }
245
246 static int qosify_map_entry_cmp(const void *k1, const void *k2, void *ptr)
247 {
248 const struct qosify_map_data *d1 = k1;
249 const struct qosify_map_data *d2 = k2;
250
251 if (d1->id != d2->id)
252 return d2->id - d1->id;
253
254 if (d1->id == CL_MAP_DNS)
255 return strcmp(d1->addr.dns.pattern, d2->addr.dns.pattern);
256
257 return memcmp(&d1->addr, &d2->addr, sizeof(d1->addr));
258 }
259
260 static struct qosify_map_entry *
261 __qosify_map_alloc_entry(struct qosify_map_data *data)
262 {
263 struct qosify_map_entry *e;
264 char *pattern;
265 char *c;
266
267 if (data->id < CL_MAP_DNS) {
268 e = calloc(1, sizeof(*e));
269 memcpy(&e->data.addr, &data->addr, sizeof(e->data.addr));
270
271 return e;
272 }
273
274 e = calloc_a(sizeof(*e), &pattern, strlen(data->addr.dns.pattern) + 1);
275 strcpy(pattern, data->addr.dns.pattern);
276 e->data.addr.dns.pattern = pattern;
277
278 for (c = pattern; *c; c++)
279 *c = tolower(*c);
280
281 if (pattern[0] == '/' &&
282 regcomp(&e->data.addr.dns.regex, pattern + 1,
283 REG_EXTENDED | REG_NOSUB)) {
284 free(e);
285 return NULL;
286 }
287
288 return e;
289 }
290
291 void __qosify_map_set_entry(struct qosify_map_data *data)
292 {
293 int fd = qosify_map_fds[data->id];
294 struct qosify_map_entry *e;
295 bool file = data->file;
296 uint8_t prev_dscp = 0xff;
297 int32_t delta = 0;
298 bool add = data->dscp != 0xff;
299
300 e = avl_find_element(&map_data, data, e, avl);
301 if (!e) {
302 if (!add)
303 return;
304
305 e = __qosify_map_alloc_entry(data);
306 if (!e)
307 return;
308
309 e->avl.key = &e->data;
310 e->data.id = data->id;
311 avl_insert(&map_data, &e->avl);
312 } else {
313 prev_dscp = e->data.dscp;
314 }
315
316 if (file)
317 e->data.file = add;
318 else
319 e->data.user = add;
320
321 if (add) {
322 if (file)
323 e->data.file_dscp = data->dscp;
324 if (!e->data.user || !file)
325 e->data.dscp = data->dscp;
326 } else if (e->data.file && !file) {
327 e->data.dscp = e->data.file_dscp;
328 }
329
330 if (e->data.dscp != prev_dscp && data->id < CL_MAP_DNS) {
331 struct qosify_ip_map_val val = {
332 .dscp = e->data.dscp,
333 .seen = 1,
334 };
335
336 bpf_map_update_elem(fd, &data->addr, &val, BPF_ANY);
337 }
338
339 if (data->id == CL_MAP_DNS)
340 e->data.addr.dns.seq = ++map_dns_seq;
341
342 if (add) {
343 if (qosify_map_timeout == ~0 || file) {
344 e->timeout = ~0;
345 return;
346 }
347
348 e->timeout = qosify_gettime() + qosify_map_timeout;
349 delta = e->timeout - next_timeout;
350 if (next_timeout && delta >= 0)
351 return;
352 }
353
354 uloop_timeout_set(&qosify_map_timer, 1);
355 }
356
357 static int
358 qosify_map_set_port(struct qosify_map_data *data, const char *str)
359 {
360 unsigned long start_port, end_port;
361 char *err;
362 int i;
363
364 start_port = end_port = strtoul(str, &err, 0);
365 if (err && *err) {
366 if (*err == '-')
367 end_port = strtoul(err + 1, &err, 0);
368 if (*err)
369 return -1;
370 }
371
372 if (!start_port || end_port < start_port ||
373 end_port >= 65535)
374 return -1;
375
376 for (i = start_port; i <= end_port; i++) {
377 data->addr.port = htons(i);
378 __qosify_map_set_entry(data);
379 }
380
381 return 0;
382 }
383
384 static int
385 qosify_map_fill_ip(struct qosify_map_data *data, const char *str)
386 {
387 int af;
388
389 if (data->id == CL_MAP_IPV6_ADDR)
390 af = AF_INET6;
391 else
392 af = AF_INET;
393
394 if (inet_pton(af, str, &data->addr) != 1)
395 return -1;
396
397 return 0;
398 }
399
400 int qosify_map_set_entry(enum qosify_map_id id, bool file, const char *str,
401 uint8_t dscp)
402 {
403 struct qosify_map_data data = {
404 .id = id,
405 .file = file,
406 .dscp = dscp,
407 };
408
409 switch (id) {
410 case CL_MAP_DNS:
411 data.addr.dns.pattern = str;
412 if (str[-2] == 'c')
413 data.addr.dns.only_cname = 1;
414 break;
415 case CL_MAP_TCP_PORTS:
416 case CL_MAP_UDP_PORTS:
417 return qosify_map_set_port(&data, str);
418 case CL_MAP_IPV4_ADDR:
419 case CL_MAP_IPV6_ADDR:
420 if (qosify_map_fill_ip(&data, str))
421 return -1;
422 break;
423 default:
424 return -1;
425 }
426
427 __qosify_map_set_entry(&data);
428
429 return 0;
430 }
431
432 static int
433 __qosify_map_dscp_value(const char *val, uint8_t *dscp_val)
434 {
435 unsigned long dscp;
436 bool fallback = false;
437 char *err;
438
439 if (*val == '+') {
440 fallback = true;
441 val++;
442 }
443
444 dscp = strtoul(val, &err, 0);
445 if (err && *err)
446 dscp = qosify_map_codepoint(val);
447
448 if (dscp >= 64)
449 return -1;
450
451 *dscp_val = dscp | (fallback << 6);
452
453 return 0;
454 }
455
456 static int
457 qosify_map_check_class(const char *val, uint8_t *dscp_val)
458 {
459 int i;
460
461 for (i = 0; i < ARRAY_SIZE(map_class); i++) {
462 if (map_class[i] && !strcmp(val, map_class[i]->name)) {
463 *dscp_val = i | QOSIFY_DSCP_CLASS_FLAG;
464 return 0;
465 }
466 }
467
468 return -1;
469 }
470
471 int qosify_map_dscp_value(const char *val, uint8_t *dscp_val)
472 {
473 uint8_t fallback = 0;
474
475 if (*val == '+') {
476 fallback = QOSIFY_DSCP_FALLBACK_FLAG;
477 val++;
478 }
479
480 if (qosify_map_check_class(val, dscp_val) &&
481 __qosify_map_dscp_value(val, dscp_val))
482 return -1;
483
484 *dscp_val |= fallback;
485
486 return 0;
487 }
488
489 static void
490 qosify_map_dscp_codepoint_str(char *dest, int len, uint8_t dscp)
491 {
492 int i;
493
494 if (dscp & QOSIFY_DSCP_FALLBACK_FLAG) {
495 *(dest++) = '+';
496 len--;
497 dscp &= ~QOSIFY_DSCP_FALLBACK_FLAG;
498 }
499
500 for (i = 0; i < ARRAY_SIZE(codepoints); i++) {
501 if (codepoints[i].val != dscp)
502 continue;
503
504 snprintf(dest, len, "%s", codepoints[i].name);
505 return;
506 }
507
508 snprintf(dest, len, "0x%x", dscp);
509 }
510
511 static void
512 qosify_map_parse_line(char *str)
513 {
514 const char *key, *value;
515 uint8_t dscp;
516
517 str = str_skip(str, true);
518 key = str;
519
520 str = str_skip(str, false);
521 if (!*str)
522 return;
523
524 *(str++) = 0;
525 str = str_skip(str, true);
526 value = str;
527
528 if (qosify_map_dscp_value(value, &dscp))
529 return;
530
531 if (!strncmp(key, "dns:", 4))
532 qosify_map_set_entry(CL_MAP_DNS, true, key + 4, dscp);
533 if (!strncmp(key, "dns_q:", 6) || !strncmp(key, "dns_c:", 6))
534 qosify_map_set_entry(CL_MAP_DNS, true, key + 6, dscp);
535 if (!strncmp(key, "tcp:", 4))
536 qosify_map_set_entry(CL_MAP_TCP_PORTS, true, key + 4, dscp);
537 else if (!strncmp(key, "udp:", 4))
538 qosify_map_set_entry(CL_MAP_UDP_PORTS, true, key + 4, dscp);
539 else if (strchr(key, ':'))
540 qosify_map_set_entry(CL_MAP_IPV6_ADDR, true, key, dscp);
541 else if (strchr(key, '.'))
542 qosify_map_set_entry(CL_MAP_IPV4_ADDR, true, key, dscp);
543 }
544
545 static void
546 __qosify_map_load_file_data(FILE *f)
547 {
548 char line[1024];
549 char *cur;
550
551 while (fgets(line, sizeof(line), f)) {
552 cur = strchr(line, '#');
553 if (cur)
554 *cur = 0;
555
556 cur = line + strlen(line);
557 if (cur == line)
558 continue;
559
560 while (cur > line && isspace(cur[-1]))
561 cur--;
562
563 *cur = 0;
564 qosify_map_parse_line(line);
565 }
566
567 }
568
569 static int
570 __qosify_map_load_file(const char *file)
571 {
572 glob_t gl;
573 FILE *f;
574 int i;
575
576 if (!file)
577 return 0;
578
579 glob(file, 0, NULL, &gl);
580
581 for (i = 0; i < gl.gl_pathc; i++) {
582 f = fopen(file, "r");
583 if (!f)
584 continue;
585
586 __qosify_map_load_file_data(f);
587 fclose(f);
588 }
589
590 globfree(&gl);
591
592 return 0;
593 }
594
595 int qosify_map_load_file(const char *file)
596 {
597 struct qosify_map_file *f;
598
599 if (!file)
600 return 0;
601
602 f = calloc(1, sizeof(*f) + strlen(file) + 1);
603 strcpy(f->filename, file);
604 list_add_tail(&f->list, &map_files);
605
606 return __qosify_map_load_file(file);
607 }
608
609 static void qosify_map_reset_file_entries(void)
610 {
611 struct qosify_map_entry *e;
612
613 map_dns_seq = 0;
614 avl_for_each_element(&map_data, e, avl)
615 e->data.file = false;
616 }
617
618 void qosify_map_clear_files(void)
619 {
620 struct qosify_map_file *f, *tmp;
621
622 qosify_map_reset_file_entries();
623
624 list_for_each_entry_safe(f, tmp, &map_files, list) {
625 list_del(&f->list);
626 free(f);
627 }
628 }
629
630 void qosify_map_reset_config(void)
631 {
632 qosify_map_clear_files();
633 qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, 0);
634 qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, 0);
635 qosify_map_timeout = 3600;
636 qosify_active_timeout = 300;
637
638 memset(&config, 0, sizeof(config));
639 flow_config.dscp_prio = 0xff;
640 flow_config.dscp_bulk = 0xff;
641 config.dscp_icmp = 0xff;
642 }
643
644 void qosify_map_reload(void)
645 {
646 struct qosify_map_file *f;
647
648 qosify_map_reset_file_entries();
649
650 list_for_each_entry(f, &map_files, list)
651 __qosify_map_load_file(f->filename);
652
653 qosify_map_gc();
654
655 qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, 0xff);
656 qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, 0xff);
657 }
658
659 static void qosify_map_free_entry(struct qosify_map_entry *e)
660 {
661 int fd = qosify_map_fds[e->data.id];
662
663 avl_delete(&map_data, &e->avl);
664 if (e->data.id < CL_MAP_DNS)
665 bpf_map_delete_elem(fd, &e->data.addr);
666 free(e);
667 }
668
669 static bool
670 qosify_map_entry_refresh_timeout(struct qosify_map_entry *e)
671 {
672 struct qosify_ip_map_val val;
673 int fd = qosify_map_fds[e->data.id];
674
675 if (e->data.id != CL_MAP_IPV4_ADDR &&
676 e->data.id != CL_MAP_IPV6_ADDR)
677 return false;
678
679 if (bpf_map_lookup_elem(fd, &e->data.addr, &val))
680 return false;
681
682 if (!val.seen)
683 return false;
684
685 e->timeout = qosify_gettime() + qosify_active_timeout;
686 val.seen = 0;
687 bpf_map_update_elem(fd, &e->data.addr, &val, BPF_ANY);
688
689 return true;
690 }
691
692 void qosify_map_gc(void)
693 {
694 struct qosify_map_entry *e, *tmp;
695 int32_t timeout = 0;
696 uint32_t cur_time = qosify_gettime();
697
698 next_timeout = 0;
699 avl_for_each_element_safe(&map_data, e, avl, tmp) {
700 int32_t cur_timeout;
701
702 if (e->data.user && e->timeout != ~0) {
703 cur_timeout = e->timeout - cur_time;
704 if (cur_timeout <= 0 &&
705 qosify_map_entry_refresh_timeout(e))
706 cur_timeout = e->timeout - cur_time;
707 if (cur_timeout <= 0) {
708 e->data.user = false;
709 e->data.dscp = e->data.file_dscp;
710 } else if (!timeout || cur_timeout < timeout) {
711 timeout = cur_timeout;
712 next_timeout = e->timeout;
713 }
714 }
715
716 if (e->data.file || e->data.user)
717 continue;
718
719 qosify_map_free_entry(e);
720 }
721
722 if (!timeout)
723 return;
724
725 uloop_timeout_set(&qosify_map_timer, timeout * 1000);
726 }
727
728 int qosify_map_lookup_dns_entry(char *host, bool cname, uint8_t *dscp, uint32_t *seq)
729 {
730 struct qosify_map_data data = {
731 .id = CL_MAP_DNS,
732 .addr.dns.pattern = "",
733 };
734 struct qosify_map_entry *e;
735 bool ret = -1;
736 char *c;
737
738 e = avl_find_ge_element(&map_data, &data, e, avl);
739 if (!e)
740 return -1;
741
742 for (c = host; *c; c++)
743 *c = tolower(*c);
744
745 avl_for_element_to_last(&map_data, e, e, avl) {
746 regex_t *regex = &e->data.addr.dns.regex;
747
748 if (e->data.id != CL_MAP_DNS)
749 break;
750
751 if (!cname && e->data.addr.dns.only_cname)
752 continue;
753
754 if (e->data.addr.dns.pattern[0] == '/') {
755 if (regexec(regex, host, 0, NULL, 0) != 0)
756 continue;
757 } else {
758 if (fnmatch(e->data.addr.dns.pattern, host, 0))
759 continue;
760 }
761
762 if (*dscp == 0xff || e->data.addr.dns.seq < *seq) {
763 *dscp = e->data.dscp;
764 *seq = e->data.addr.dns.seq;
765 }
766 ret = 0;
767 }
768
769 return ret;
770 }
771
772
773 int qosify_map_add_dns_host(char *host, const char *addr, const char *type, int ttl)
774 {
775 struct qosify_map_data data = {
776 .dscp = 0xff
777 };
778 int prev_timeout = qosify_map_timeout;
779 uint32_t lookup_seq = 0;
780
781 if (qosify_map_lookup_dns_entry(host, false, &data.dscp, &lookup_seq))
782 return 0;
783
784 data.user = true;
785 if (!strcmp(type, "A"))
786 data.id = CL_MAP_IPV4_ADDR;
787 else if (!strcmp(type, "AAAA"))
788 data.id = CL_MAP_IPV6_ADDR;
789 else
790 return 0;
791
792 if (qosify_map_fill_ip(&data, addr))
793 return -1;
794
795 if (ttl)
796 qosify_map_timeout = ttl;
797 __qosify_map_set_entry(&data);
798 qosify_map_timeout = prev_timeout;
799
800 return 0;
801 }
802
803 static void
804 blobmsg_add_dscp(struct blob_buf *b, const char *name, uint8_t dscp)
805 {
806 int buf_len = 8;
807 char *buf;
808
809 if (dscp & QOSIFY_DSCP_CLASS_FLAG) {
810 const char *val;
811 int idx;
812
813 idx = dscp & QOSIFY_DSCP_VALUE_MASK;
814 if (map_class[idx])
815 val = map_class[idx]->name;
816 else
817 val = "<invalid>";
818
819 blobmsg_printf(b, name, "%s%s",
820 (dscp & QOSIFY_DSCP_FALLBACK_FLAG) ? "+" : "", val);
821 return;
822 }
823
824 buf = blobmsg_alloc_string_buffer(b, name, buf_len);
825 qosify_map_dscp_codepoint_str(buf, buf_len, dscp);
826 blobmsg_add_string_buffer(b);
827 }
828
829
830 void qosify_map_dump(struct blob_buf *b)
831 {
832 struct qosify_map_entry *e;
833 uint32_t cur_time = qosify_gettime();
834 int buf_len = INET6_ADDRSTRLEN + 1;
835 char *buf;
836 void *a;
837 int af;
838
839 a = blobmsg_open_array(b, "entries");
840 avl_for_each_element(&map_data, e, avl) {
841 void *c;
842
843 if (!e->data.file && !e->data.user)
844 continue;
845
846 c = blobmsg_open_table(b, NULL);
847 if (e->data.user && e->timeout != ~0) {
848 int32_t cur_timeout = e->timeout - cur_time;
849
850 if (cur_timeout < 0)
851 cur_timeout = 0;
852
853 blobmsg_add_u32(b, "timeout", cur_timeout);
854 }
855
856 blobmsg_add_u8(b, "file", e->data.file);
857 blobmsg_add_u8(b, "user", e->data.user);
858
859 blobmsg_add_dscp(b, "dscp", e->data.dscp);
860
861 blobmsg_add_string(b, "type", qosify_map_info[e->data.id].type_name);
862
863 switch (e->data.id) {
864 case CL_MAP_TCP_PORTS:
865 case CL_MAP_UDP_PORTS:
866 blobmsg_printf(b, "addr", "%d", ntohs(e->data.addr.port));
867 break;
868 case CL_MAP_IPV4_ADDR:
869 case CL_MAP_IPV6_ADDR:
870 buf = blobmsg_alloc_string_buffer(b, "addr", buf_len);
871 af = e->data.id == CL_MAP_IPV6_ADDR ? AF_INET6 : AF_INET;
872 inet_ntop(af, &e->data.addr, buf, buf_len);
873 blobmsg_add_string_buffer(b);
874 break;
875 case CL_MAP_DNS:
876 blobmsg_add_string(b, "addr", e->data.addr.dns.pattern);
877 break;
878 default:
879 break;
880 }
881 blobmsg_close_table(b, c);
882 }
883 blobmsg_close_array(b, a);
884 }
885
886 void qosify_map_stats(struct blob_buf *b, bool reset)
887 {
888 struct qosify_class data;
889 uint32_t i;
890
891 for (i = 0; i < ARRAY_SIZE(map_class); i++) {
892 void *c;
893
894 if (!map_class[i])
895 continue;
896
897 if (bpf_map_lookup_elem(qosify_map_fds[CL_MAP_CLASS], &i, &data) < 0)
898 continue;
899
900 c = blobmsg_open_table(b, map_class[i]->name);
901 blobmsg_add_u64(b, "packets", data.packets);
902 blobmsg_close_table(b, c);
903
904 if (!reset)
905 continue;
906
907 data.packets = 0;
908 bpf_map_update_elem(qosify_map_fds[CL_MAP_CLASS], &i, &data, BPF_ANY);
909 }
910 }
911
912 static int32_t
913 qosify_map_get_class_id(const char *name)
914 {
915 int i;
916
917 for (i = 0; i < ARRAY_SIZE(map_class); i++)
918 if (map_class[i] && !strcmp(map_class[i]->name, name))
919 return i;
920
921 for (i = 0; i < ARRAY_SIZE(map_class); i++)
922 if (!map_class[i])
923 return i;
924
925 for (i = 0; i < ARRAY_SIZE(map_class); i++) {
926 if (!(map_class[i]->data.flags & QOSIFY_CLASS_FLAG_PRESENT)) {
927 free(map_class[i]);
928 map_class[i] = NULL;
929 return i;
930 }
931 }
932
933 return -1;
934 }
935
936 int map_fill_dscp_value(uint8_t *dest, struct blob_attr *attr, bool reset)
937 {
938 if (reset)
939 *dest = 0xff;
940
941 if (!attr)
942 return 0;
943
944 if (qosify_map_dscp_value(blobmsg_get_string(attr), dest))
945 return -1;
946
947 return 0;
948 }
949
950 int map_parse_flow_config(struct qosify_flow_config *cfg, struct blob_attr *attr,
951 bool reset)
952 {
953 enum {
954 CL_CONFIG_DSCP_PRIO,
955 CL_CONFIG_DSCP_BULK,
956 CL_CONFIG_BULK_TIMEOUT,
957 CL_CONFIG_BULK_PPS,
958 CL_CONFIG_PRIO_PKT_LEN,
959 __CL_CONFIG_MAX
960 };
961 static const struct blobmsg_policy policy[__CL_CONFIG_MAX] = {
962 [CL_CONFIG_DSCP_PRIO] = { "dscp_prio", BLOBMSG_TYPE_STRING },
963 [CL_CONFIG_DSCP_BULK] = { "dscp_bulk", BLOBMSG_TYPE_STRING },
964 [CL_CONFIG_BULK_TIMEOUT] = { "bulk_trigger_timeout", BLOBMSG_TYPE_INT32 },
965 [CL_CONFIG_BULK_PPS] = { "bulk_trigger_pps", BLOBMSG_TYPE_INT32 },
966 [CL_CONFIG_PRIO_PKT_LEN] = { "prio_max_avg_pkt_len", BLOBMSG_TYPE_INT32 },
967 };
968 struct blob_attr *tb[__CL_CONFIG_MAX];
969 struct blob_attr *cur;
970
971 if (reset)
972 memset(cfg, 0, sizeof(*cfg));
973
974 blobmsg_parse(policy, __CL_CONFIG_MAX, tb, blobmsg_data(attr), blobmsg_len(attr));
975
976 if (map_fill_dscp_value(&cfg->dscp_prio, tb[CL_CONFIG_DSCP_PRIO], reset) ||
977 map_fill_dscp_value(&cfg->dscp_bulk, tb[CL_CONFIG_DSCP_BULK], reset))
978 return -1;
979
980 if ((cur = tb[CL_CONFIG_BULK_TIMEOUT]) != NULL)
981 cfg->bulk_trigger_timeout = blobmsg_get_u32(cur);
982
983 if ((cur = tb[CL_CONFIG_BULK_PPS]) != NULL)
984 cfg->bulk_trigger_pps = blobmsg_get_u32(cur);
985
986 if ((cur = tb[CL_CONFIG_PRIO_PKT_LEN]) != NULL)
987 cfg->prio_max_avg_pkt_len = blobmsg_get_u32(cur);
988
989 return 0;
990 }
991
992 static int
993 qosify_map_create_class(struct blob_attr *attr)
994 {
995 struct qosify_map_class *class;
996 enum {
997 MAP_CLASS_INGRESS,
998 MAP_CLASS_EGRESS,
999 __MAP_CLASS_MAX
1000 };
1001 static const struct blobmsg_policy policy[__MAP_CLASS_MAX] = {
1002 [MAP_CLASS_INGRESS] = { "ingress", BLOBMSG_TYPE_STRING },
1003 [MAP_CLASS_EGRESS] = { "egress", BLOBMSG_TYPE_STRING },
1004 };
1005 struct blob_attr *tb[__MAP_CLASS_MAX];
1006 const char *name;
1007 char *name_buf;
1008 int32_t slot;
1009
1010 blobmsg_parse(policy, __MAP_CLASS_MAX, tb,
1011 blobmsg_data(attr), blobmsg_len(attr));
1012
1013 if (!tb[MAP_CLASS_INGRESS] || !tb[MAP_CLASS_EGRESS])
1014 return -1;
1015
1016 name = blobmsg_name(attr);
1017 slot = qosify_map_get_class_id(name);
1018 if (slot < 0)
1019 return -1;
1020
1021 class = map_class[slot];
1022 if (!class) {
1023 class = calloc_a(sizeof(*class), &name_buf, strlen(name) + 1);
1024 class->name = strcpy(name_buf, name);
1025 map_class[slot] = class;
1026 }
1027
1028 class->data.flags |= QOSIFY_CLASS_FLAG_PRESENT;
1029 if (__qosify_map_dscp_value(blobmsg_get_string(tb[MAP_CLASS_INGRESS]),
1030 &class->data.val.ingress) ||
1031 __qosify_map_dscp_value(blobmsg_get_string(tb[MAP_CLASS_EGRESS]),
1032 &class->data.val.egress)) {
1033 map_class[slot] = NULL;
1034 free(class);
1035 return -1;
1036 }
1037
1038 return 0;
1039 }
1040
1041 void qosify_map_set_classes(struct blob_attr *val)
1042 {
1043 int fd = qosify_map_fds[CL_MAP_CLASS];
1044 struct qosify_class empty_data = {};
1045 struct blob_attr *cur;
1046 int32_t i;
1047 int rem;
1048
1049 for (i = 0; i < ARRAY_SIZE(map_class); i++)
1050 if (map_class[i])
1051 map_class[i]->data.flags &= ~QOSIFY_CLASS_FLAG_PRESENT;
1052
1053 blobmsg_for_each_attr(cur, val, rem)
1054 qosify_map_create_class(cur);
1055
1056 for (i = 0; i < ARRAY_SIZE(map_class); i++) {
1057 if (map_class[i] &&
1058 (map_class[i]->data.flags & QOSIFY_CLASS_FLAG_PRESENT))
1059 continue;
1060
1061 free(map_class[i]);
1062 map_class[i] = NULL;
1063 }
1064
1065 blobmsg_for_each_attr(cur, val, rem) {
1066 i = qosify_map_get_class_id(blobmsg_name(cur));
1067 if (i < 0 || !map_class[i])
1068 continue;
1069
1070 map_parse_flow_config(&map_class[i]->data.config, cur, true);
1071 }
1072
1073 for (i = 0; i < ARRAY_SIZE(map_class); i++) {
1074 struct qosify_class *data;
1075
1076 data = map_class[i] ? &map_class[i]->data : &empty_data;
1077 bpf_map_update_elem(fd, &i, data, BPF_ANY);
1078 }
1079 }
1080
1081 void qosify_map_update_config(void)
1082 {
1083 int fd = qosify_map_fds[CL_MAP_CONFIG];
1084 uint32_t key = 0;
1085
1086 bpf_map_update_elem(fd, &key, &config, BPF_ANY);
1087 }