interface: switch to using clsact for filters
[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) != -1)
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 (!memcmp(&qosify_dscp_default[udp], &val, sizeof(val)))
200 return;
201
202 qosify_dscp_default[udp] = val;
203 __qosify_map_set_dscp_default(id, val);
204 }
205
206 int qosify_map_init(void)
207 {
208 int i;
209
210 for (i = 0; i < CL_MAP_DNS; i++) {
211 qosify_map_fds[i] = qosify_map_get_fd(i);
212 if (qosify_map_fds[i] < 0)
213 return -1;
214 }
215
216 qosify_map_clear_list(CL_MAP_IPV4_ADDR);
217 qosify_map_clear_list(CL_MAP_IPV6_ADDR);
218 qosify_map_reset_config();
219
220 return 0;
221 }
222
223 static char *str_skip(char *str, bool space)
224 {
225 while (*str && isspace(*str) == space)
226 str++;
227
228 return str;
229 }
230
231 static int
232 qosify_map_codepoint(const char *val)
233 {
234 int i;
235
236 for (i = 0; i < ARRAY_SIZE(codepoints); i++)
237 if (!strcmp(codepoints[i].name, val))
238 return codepoints[i].val;
239
240 return 0xff;
241 }
242
243 static int qosify_map_entry_cmp(const void *k1, const void *k2, void *ptr)
244 {
245 const struct qosify_map_data *d1 = k1;
246 const struct qosify_map_data *d2 = k2;
247
248 if (d1->id != d2->id)
249 return d2->id - d1->id;
250
251 if (d1->id == CL_MAP_DNS)
252 return strcmp(d1->addr.dns.pattern, d2->addr.dns.pattern);
253
254 return memcmp(&d1->addr, &d2->addr, sizeof(d1->addr));
255 }
256
257 static struct qosify_map_entry *
258 __qosify_map_alloc_entry(struct qosify_map_data *data)
259 {
260 struct qosify_map_entry *e;
261 char *pattern;
262 char *c;
263
264 if (data->id < CL_MAP_DNS) {
265 e = calloc(1, sizeof(*e));
266 memcpy(&e->data.addr, &data->addr, sizeof(e->data.addr));
267
268 return e;
269 }
270
271 e = calloc_a(sizeof(*e), &pattern, strlen(data->addr.dns.pattern) + 1);
272 strcpy(pattern, data->addr.dns.pattern);
273 e->data.addr.dns.pattern = pattern;
274
275 for (c = pattern; *c; c++)
276 *c = tolower(*c);
277
278 if (pattern[0] == '/' &&
279 regcomp(&e->data.addr.dns.regex, pattern + 1,
280 REG_EXTENDED | REG_NOSUB)) {
281 free(e);
282 return NULL;
283 }
284
285 return e;
286 }
287
288 void __qosify_map_set_entry(struct qosify_map_data *data)
289 {
290 int fd = qosify_map_fds[data->id];
291 struct qosify_map_entry *e;
292 bool file = data->file;
293 uint8_t prev_dscp = 0xff;
294 int32_t delta = 0;
295 bool add = data->dscp != 0xff;
296
297 e = avl_find_element(&map_data, data, e, avl);
298 if (!e) {
299 if (!add)
300 return;
301
302 e = __qosify_map_alloc_entry(data);
303 if (!e)
304 return;
305
306 e->avl.key = &e->data;
307 e->data.id = data->id;
308 avl_insert(&map_data, &e->avl);
309 } else {
310 prev_dscp = e->data.dscp;
311 }
312
313 if (file)
314 e->data.file = add;
315 else
316 e->data.user = add;
317
318 if (add) {
319 if (file)
320 e->data.file_dscp = data->dscp;
321 if (!e->data.user || !file)
322 e->data.dscp = data->dscp;
323 } else if (e->data.file && !file) {
324 e->data.dscp = e->data.file_dscp;
325 }
326
327 if (e->data.dscp != prev_dscp && data->id < CL_MAP_DNS) {
328 struct qosify_ip_map_val val = {
329 .dscp = e->data.dscp,
330 .seen = 1,
331 };
332
333 bpf_map_update_elem(fd, &data->addr, &val, BPF_ANY);
334 }
335
336 if (data->id == CL_MAP_DNS)
337 e->data.addr.dns.seq = ++map_dns_seq;
338
339 if (add) {
340 if (qosify_map_timeout == ~0 || file) {
341 e->timeout = ~0;
342 return;
343 }
344
345 e->timeout = qosify_gettime() + qosify_map_timeout;
346 delta = e->timeout - next_timeout;
347 if (next_timeout && delta >= 0)
348 return;
349 }
350
351 uloop_timeout_set(&qosify_map_timer, 1);
352 }
353
354 static int
355 qosify_map_set_port(struct qosify_map_data *data, const char *str)
356 {
357 unsigned long start_port, end_port;
358 char *err;
359 int i;
360
361 start_port = end_port = strtoul(str, &err, 0);
362 if (err && *err) {
363 if (*err == '-')
364 end_port = strtoul(err + 1, &err, 0);
365 if (*err)
366 return -1;
367 }
368
369 if (!start_port || end_port < start_port ||
370 end_port >= 65535)
371 return -1;
372
373 for (i = start_port; i <= end_port; i++) {
374 data->addr.port = htons(i);
375 __qosify_map_set_entry(data);
376 }
377
378 return 0;
379 }
380
381 static int
382 qosify_map_fill_ip(struct qosify_map_data *data, const char *str)
383 {
384 int af;
385
386 if (data->id == CL_MAP_IPV6_ADDR)
387 af = AF_INET6;
388 else
389 af = AF_INET;
390
391 if (inet_pton(af, str, &data->addr) != 1)
392 return -1;
393
394 return 0;
395 }
396
397 int qosify_map_set_entry(enum qosify_map_id id, bool file, const char *str,
398 uint8_t dscp)
399 {
400 struct qosify_map_data data = {
401 .id = id,
402 .file = file,
403 .dscp = dscp,
404 };
405
406 switch (id) {
407 case CL_MAP_DNS:
408 data.addr.dns.pattern = str;
409 if (str[-2] == 'c')
410 data.addr.dns.only_cname = 1;
411 break;
412 case CL_MAP_TCP_PORTS:
413 case CL_MAP_UDP_PORTS:
414 return qosify_map_set_port(&data, str);
415 case CL_MAP_IPV4_ADDR:
416 case CL_MAP_IPV6_ADDR:
417 if (qosify_map_fill_ip(&data, str))
418 return -1;
419 break;
420 default:
421 return -1;
422 }
423
424 __qosify_map_set_entry(&data);
425
426 return 0;
427 }
428
429 static int
430 __qosify_map_dscp_value(const char *val, uint8_t *dscp_val)
431 {
432 unsigned long dscp;
433 bool fallback = false;
434 char *err;
435
436 if (*val == '+') {
437 fallback = true;
438 val++;
439 }
440
441 dscp = strtoul(val, &err, 0);
442 if (err && *err)
443 dscp = qosify_map_codepoint(val);
444
445 if (dscp >= 64)
446 return -1;
447
448 *dscp_val = dscp | (fallback << 6);
449
450 return 0;
451 }
452
453 static int
454 qosify_map_check_class(const char *val, uint8_t *dscp_val)
455 {
456 int i;
457
458 for (i = 0; i < ARRAY_SIZE(map_class); i++) {
459 if (map_class[i] && !strcmp(val, map_class[i]->name)) {
460 *dscp_val = i | QOSIFY_DSCP_CLASS_FLAG;
461 return 0;
462 }
463 }
464
465 return -1;
466 }
467
468 int qosify_map_dscp_value(const char *val, uint8_t *dscp_val)
469 {
470 uint8_t fallback = 0;
471
472 if (*val == '+') {
473 fallback = QOSIFY_DSCP_FALLBACK_FLAG;
474 val++;
475 }
476
477 if (qosify_map_check_class(val, dscp_val) &&
478 __qosify_map_dscp_value(val, dscp_val))
479 return -1;
480
481 *dscp_val |= fallback;
482
483 return 0;
484 }
485
486 static void
487 qosify_map_dscp_codepoint_str(char *dest, int len, uint8_t dscp)
488 {
489 int i;
490
491 if (dscp & QOSIFY_DSCP_FALLBACK_FLAG) {
492 *(dest++) = '+';
493 len--;
494 dscp &= ~QOSIFY_DSCP_FALLBACK_FLAG;
495 }
496
497 for (i = 0; i < ARRAY_SIZE(codepoints); i++) {
498 if (codepoints[i].val != dscp)
499 continue;
500
501 snprintf(dest, len, "%s", codepoints[i].name);
502 return;
503 }
504
505 snprintf(dest, len, "0x%x", dscp);
506 }
507
508 static void
509 qosify_map_parse_line(char *str)
510 {
511 const char *key, *value;
512 uint8_t dscp;
513
514 str = str_skip(str, true);
515 key = str;
516
517 str = str_skip(str, false);
518 if (!*str)
519 return;
520
521 *(str++) = 0;
522 str = str_skip(str, true);
523 value = str;
524
525 if (qosify_map_dscp_value(value, &dscp))
526 return;
527
528 if (!strncmp(key, "dns:", 4))
529 qosify_map_set_entry(CL_MAP_DNS, true, key + 4, dscp);
530 if (!strncmp(key, "dns_q:", 6) || !strncmp(key, "dns_c:", 6))
531 qosify_map_set_entry(CL_MAP_DNS, true, key + 6, dscp);
532 if (!strncmp(key, "tcp:", 4))
533 qosify_map_set_entry(CL_MAP_TCP_PORTS, true, key + 4, dscp);
534 else if (!strncmp(key, "udp:", 4))
535 qosify_map_set_entry(CL_MAP_UDP_PORTS, true, key + 4, dscp);
536 else if (strchr(key, ':'))
537 qosify_map_set_entry(CL_MAP_IPV6_ADDR, true, key, dscp);
538 else if (strchr(key, '.'))
539 qosify_map_set_entry(CL_MAP_IPV4_ADDR, true, key, dscp);
540 }
541
542 static void
543 __qosify_map_load_file_data(FILE *f)
544 {
545 char line[1024];
546 char *cur;
547
548 while (fgets(line, sizeof(line), f)) {
549 cur = strchr(line, '#');
550 if (cur)
551 *cur = 0;
552
553 cur = line + strlen(line);
554 if (cur == line)
555 continue;
556
557 while (cur > line && isspace(cur[-1]))
558 cur--;
559
560 *cur = 0;
561 qosify_map_parse_line(line);
562 }
563
564 }
565
566 static int
567 __qosify_map_load_file(const char *file)
568 {
569 glob_t gl;
570 FILE *f;
571 int i;
572
573 if (!file)
574 return 0;
575
576 glob(file, 0, NULL, &gl);
577
578 for (i = 0; i < gl.gl_pathc; i++) {
579 f = fopen(file, "r");
580 if (!f)
581 continue;
582
583 __qosify_map_load_file_data(f);
584 fclose(f);
585 }
586
587 globfree(&gl);
588
589 return 0;
590 }
591
592 int qosify_map_load_file(const char *file)
593 {
594 struct qosify_map_file *f;
595
596 if (!file)
597 return 0;
598
599 f = calloc(1, sizeof(*f) + strlen(file) + 1);
600 strcpy(f->filename, file);
601 list_add_tail(&f->list, &map_files);
602
603 return __qosify_map_load_file(file);
604 }
605
606 static void qosify_map_reset_file_entries(void)
607 {
608 struct qosify_map_entry *e;
609
610 map_dns_seq = 0;
611 avl_for_each_element(&map_data, e, avl)
612 e->data.file = false;
613 }
614
615 void qosify_map_clear_files(void)
616 {
617 struct qosify_map_file *f, *tmp;
618
619 qosify_map_reset_file_entries();
620
621 list_for_each_entry_safe(f, tmp, &map_files, list) {
622 list_del(&f->list);
623 free(f);
624 }
625 }
626
627 void qosify_map_reset_config(void)
628 {
629 qosify_map_clear_files();
630 qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, 0);
631 qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, 0);
632 qosify_map_timeout = 3600;
633 qosify_active_timeout = 300;
634
635 memset(&config, 0, sizeof(config));
636 flow_config.dscp_prio = 0xff;
637 flow_config.dscp_bulk = 0xff;
638 config.dscp_icmp = 0xff;
639 }
640
641 void qosify_map_reload(void)
642 {
643 struct qosify_map_file *f;
644
645 qosify_map_reset_file_entries();
646
647 list_for_each_entry(f, &map_files, list)
648 __qosify_map_load_file(f->filename);
649
650 qosify_map_gc();
651 }
652
653 static void qosify_map_free_entry(struct qosify_map_entry *e)
654 {
655 int fd = qosify_map_fds[e->data.id];
656
657 avl_delete(&map_data, &e->avl);
658 if (e->data.id < CL_MAP_DNS)
659 bpf_map_delete_elem(fd, &e->data.addr);
660 free(e);
661 }
662
663 static bool
664 qosify_map_entry_refresh_timeout(struct qosify_map_entry *e)
665 {
666 struct qosify_ip_map_val val;
667 int fd = qosify_map_fds[e->data.id];
668
669 if (e->data.id != CL_MAP_IPV4_ADDR &&
670 e->data.id != CL_MAP_IPV6_ADDR)
671 return false;
672
673 if (bpf_map_lookup_elem(fd, &e->data.addr, &val))
674 return false;
675
676 if (!val.seen)
677 return false;
678
679 e->timeout = qosify_gettime() + qosify_active_timeout;
680 val.seen = 0;
681 bpf_map_update_elem(fd, &e->data.addr, &val, BPF_ANY);
682
683 return true;
684 }
685
686 void qosify_map_gc(void)
687 {
688 struct qosify_map_entry *e, *tmp;
689 int32_t timeout = 0;
690 uint32_t cur_time = qosify_gettime();
691
692 next_timeout = 0;
693 avl_for_each_element_safe(&map_data, e, avl, tmp) {
694 int32_t cur_timeout;
695
696 if (e->data.user && e->timeout != ~0) {
697 cur_timeout = e->timeout - cur_time;
698 if (cur_timeout <= 0 &&
699 qosify_map_entry_refresh_timeout(e))
700 cur_timeout = e->timeout - cur_time;
701 if (cur_timeout <= 0) {
702 e->data.user = false;
703 e->data.dscp = e->data.file_dscp;
704 } else if (!timeout || cur_timeout < timeout) {
705 timeout = cur_timeout;
706 next_timeout = e->timeout;
707 }
708 }
709
710 if (e->data.file || e->data.user)
711 continue;
712
713 qosify_map_free_entry(e);
714 }
715
716 if (!timeout)
717 return;
718
719 uloop_timeout_set(&qosify_map_timer, timeout * 1000);
720 }
721
722 int qosify_map_lookup_dns_entry(char *host, bool cname, uint8_t *dscp, uint32_t *seq)
723 {
724 struct qosify_map_data data = {
725 .id = CL_MAP_DNS,
726 .addr.dns.pattern = "",
727 };
728 struct qosify_map_entry *e;
729 bool ret = -1;
730 char *c;
731
732 e = avl_find_ge_element(&map_data, &data, e, avl);
733 if (!e)
734 return -1;
735
736 for (c = host; *c; c++)
737 *c = tolower(*c);
738
739 avl_for_element_to_last(&map_data, e, e, avl) {
740 regex_t *regex = &e->data.addr.dns.regex;
741
742 if (e->data.id != CL_MAP_DNS)
743 break;
744
745 if (!cname && e->data.addr.dns.only_cname)
746 continue;
747
748 if (e->data.addr.dns.pattern[0] == '/') {
749 if (regexec(regex, host, 0, NULL, 0) != 0)
750 continue;
751 } else {
752 if (fnmatch(e->data.addr.dns.pattern, host, 0))
753 continue;
754 }
755
756 if (*dscp == 0xff || e->data.addr.dns.seq < *seq) {
757 *dscp = e->data.dscp;
758 *seq = e->data.addr.dns.seq;
759 }
760 ret = 0;
761 }
762
763 return ret;
764 }
765
766
767 int qosify_map_add_dns_host(char *host, const char *addr, const char *type, int ttl)
768 {
769 struct qosify_map_data data = {};
770 int prev_timeout = qosify_map_timeout;
771 uint32_t lookup_seq = 0;
772
773 if (qosify_map_lookup_dns_entry(host, false, &data.dscp, &lookup_seq))
774 return 0;
775
776 data.user = true;
777 if (!strcmp(type, "A"))
778 data.id = CL_MAP_IPV4_ADDR;
779 else if (!strcmp(type, "AAAA"))
780 data.id = CL_MAP_IPV6_ADDR;
781 else
782 return 0;
783
784 if (qosify_map_fill_ip(&data, addr))
785 return -1;
786
787 if (ttl)
788 qosify_map_timeout = ttl;
789 __qosify_map_set_entry(&data);
790 qosify_map_timeout = prev_timeout;
791
792 return 0;
793 }
794
795 static void
796 blobmsg_add_dscp(struct blob_buf *b, const char *name, uint8_t dscp)
797 {
798 int buf_len = 8;
799 char *buf;
800
801 if (dscp & QOSIFY_DSCP_CLASS_FLAG) {
802 const char *val;
803 int idx;
804
805 idx = dscp & QOSIFY_DSCP_VALUE_MASK;
806 if (map_class[idx])
807 val = map_class[idx]->name;
808 else
809 val = "<invalid>";
810
811 blobmsg_printf(b, name, "%s%s",
812 (dscp & QOSIFY_DSCP_FALLBACK_FLAG) ? "+" : "", val);
813 return;
814 }
815
816 buf = blobmsg_alloc_string_buffer(b, name, buf_len);
817 qosify_map_dscp_codepoint_str(buf, buf_len, dscp);
818 blobmsg_add_string_buffer(b);
819 }
820
821
822 void qosify_map_dump(struct blob_buf *b)
823 {
824 struct qosify_map_entry *e;
825 uint32_t cur_time = qosify_gettime();
826 int buf_len = INET6_ADDRSTRLEN + 1;
827 char *buf;
828 void *a;
829 int af;
830
831 a = blobmsg_open_array(b, "entries");
832 avl_for_each_element(&map_data, e, avl) {
833 void *c;
834
835 if (!e->data.file && !e->data.user)
836 continue;
837
838 c = blobmsg_open_table(b, NULL);
839 if (e->data.user && e->timeout != ~0) {
840 int32_t cur_timeout = e->timeout - cur_time;
841
842 if (cur_timeout < 0)
843 cur_timeout = 0;
844
845 blobmsg_add_u32(b, "timeout", cur_timeout);
846 }
847
848 blobmsg_add_u8(b, "file", e->data.file);
849 blobmsg_add_u8(b, "user", e->data.user);
850
851 blobmsg_add_dscp(b, "dscp", e->data.dscp);
852
853 blobmsg_add_string(b, "type", qosify_map_info[e->data.id].type_name);
854
855 switch (e->data.id) {
856 case CL_MAP_TCP_PORTS:
857 case CL_MAP_UDP_PORTS:
858 blobmsg_printf(b, "addr", "%d", ntohs(e->data.addr.port));
859 break;
860 case CL_MAP_IPV4_ADDR:
861 case CL_MAP_IPV6_ADDR:
862 buf = blobmsg_alloc_string_buffer(b, "addr", buf_len);
863 af = e->data.id == CL_MAP_IPV6_ADDR ? AF_INET6 : AF_INET;
864 inet_ntop(af, &e->data.addr, buf, buf_len);
865 blobmsg_add_string_buffer(b);
866 break;
867 case CL_MAP_DNS:
868 blobmsg_add_string(b, "addr", e->data.addr.dns.pattern);
869 break;
870 default:
871 break;
872 }
873 blobmsg_close_table(b, c);
874 }
875 blobmsg_close_array(b, a);
876 }
877
878 static int32_t
879 qosify_map_get_class_id(const char *name)
880 {
881 int i;
882
883 for (i = 0; i < ARRAY_SIZE(map_class); i++)
884 if (map_class[i] && !strcmp(map_class[i]->name, name))
885 return i;
886
887 for (i = 0; i < ARRAY_SIZE(map_class); i++)
888 if (!map_class[i])
889 return i;
890
891 for (i = 0; i < ARRAY_SIZE(map_class); i++) {
892 if (!(map_class[i]->data.flags & QOSIFY_CLASS_FLAG_PRESENT)) {
893 free(map_class[i]);
894 map_class[i] = NULL;
895 return i;
896 }
897 }
898
899 return -1;
900 }
901
902 int map_fill_dscp_value(uint8_t *dest, struct blob_attr *attr, bool reset)
903 {
904 if (reset)
905 *dest = 0xff;
906
907 if (!attr)
908 return 0;
909
910 if (qosify_map_dscp_value(blobmsg_get_string(attr), dest))
911 return -1;
912
913 return 0;
914 }
915
916 int map_parse_flow_config(struct qosify_flow_config *cfg, struct blob_attr *attr,
917 bool reset)
918 {
919 enum {
920 CL_CONFIG_DSCP_PRIO,
921 CL_CONFIG_DSCP_BULK,
922 CL_CONFIG_BULK_TIMEOUT,
923 CL_CONFIG_BULK_PPS,
924 CL_CONFIG_PRIO_PKT_LEN,
925 __CL_CONFIG_MAX
926 };
927 static const struct blobmsg_policy policy[__CL_CONFIG_MAX] = {
928 [CL_CONFIG_DSCP_PRIO] = { "dscp_prio", BLOBMSG_TYPE_STRING },
929 [CL_CONFIG_DSCP_BULK] = { "dscp_bulk", BLOBMSG_TYPE_STRING },
930 [CL_CONFIG_BULK_TIMEOUT] = { "bulk_trigger_timeout", BLOBMSG_TYPE_INT32 },
931 [CL_CONFIG_BULK_PPS] = { "bulk_trigger_pps", BLOBMSG_TYPE_INT32 },
932 [CL_CONFIG_PRIO_PKT_LEN] = { "prio_max_avg_pkt_len", BLOBMSG_TYPE_INT32 },
933 };
934 struct blob_attr *tb[__CL_CONFIG_MAX];
935 struct blob_attr *cur;
936
937 if (reset)
938 memset(cfg, 0, sizeof(*cfg));
939
940 blobmsg_parse(policy, __CL_CONFIG_MAX, tb, blobmsg_data(attr), blobmsg_len(attr));
941
942 if (map_fill_dscp_value(&cfg->dscp_prio, tb[CL_CONFIG_DSCP_PRIO], reset) ||
943 map_fill_dscp_value(&cfg->dscp_bulk, tb[CL_CONFIG_DSCP_BULK], reset))
944 return -1;
945
946 if ((cur = tb[CL_CONFIG_BULK_TIMEOUT]) != NULL)
947 cfg->bulk_trigger_timeout = blobmsg_get_u32(cur);
948
949 if ((cur = tb[CL_CONFIG_BULK_PPS]) != NULL)
950 cfg->bulk_trigger_pps = blobmsg_get_u32(cur);
951
952 if ((cur = tb[CL_CONFIG_PRIO_PKT_LEN]) != NULL)
953 cfg->prio_max_avg_pkt_len = blobmsg_get_u32(cur);
954
955 return 0;
956 }
957
958 static int
959 qosify_map_create_class(struct blob_attr *attr)
960 {
961 struct qosify_map_class *class;
962 enum {
963 MAP_CLASS_INGRESS,
964 MAP_CLASS_EGRESS,
965 __MAP_CLASS_MAX
966 };
967 static const struct blobmsg_policy policy[__MAP_CLASS_MAX] = {
968 [MAP_CLASS_INGRESS] = { "ingress", BLOBMSG_TYPE_STRING },
969 [MAP_CLASS_EGRESS] = { "egress", BLOBMSG_TYPE_STRING },
970 };
971 struct blob_attr *tb[__MAP_CLASS_MAX];
972 const char *name;
973 char *name_buf;
974 int32_t slot;
975
976 blobmsg_parse(policy, __MAP_CLASS_MAX, tb,
977 blobmsg_data(attr), blobmsg_len(attr));
978
979 if (!tb[MAP_CLASS_INGRESS] || !tb[MAP_CLASS_EGRESS])
980 return -1;
981
982 name = blobmsg_name(attr);
983 slot = qosify_map_get_class_id(name);
984 if (slot < 0)
985 return -1;
986
987 class = map_class[slot];
988 if (!class) {
989 class = calloc_a(sizeof(*class), &name_buf, strlen(name) + 1);
990 class->name = strcpy(name_buf, name);
991 map_class[slot] = class;
992 }
993
994 class->data.flags |= QOSIFY_CLASS_FLAG_PRESENT;
995 if (__qosify_map_dscp_value(blobmsg_get_string(tb[MAP_CLASS_INGRESS]),
996 &class->data.val.ingress) ||
997 __qosify_map_dscp_value(blobmsg_get_string(tb[MAP_CLASS_EGRESS]),
998 &class->data.val.egress)) {
999 map_class[slot] = NULL;
1000 free(class);
1001 return -1;
1002 }
1003
1004 return 0;
1005 }
1006
1007 void qosify_map_set_classes(struct blob_attr *val)
1008 {
1009 int fd = qosify_map_fds[CL_MAP_CLASS];
1010 struct qosify_class empty_data = {};
1011 struct blob_attr *cur;
1012 int32_t i;
1013 int rem;
1014
1015 for (i = 0; i < ARRAY_SIZE(map_class); i++)
1016 if (map_class[i])
1017 map_class[i]->data.flags &= ~QOSIFY_CLASS_FLAG_PRESENT;
1018
1019 blobmsg_for_each_attr(cur, val, rem)
1020 qosify_map_create_class(cur);
1021
1022 for (i = 0; i < ARRAY_SIZE(map_class); i++) {
1023 if (map_class[i] &&
1024 (map_class[i]->data.flags & QOSIFY_CLASS_FLAG_PRESENT))
1025 continue;
1026
1027 free(map_class[i]);
1028 map_class[i] = NULL;
1029 }
1030
1031 blobmsg_for_each_attr(cur, val, rem) {
1032 i = qosify_map_get_class_id(blobmsg_name(cur));
1033 if (i < 0 || !map_class[i])
1034 continue;
1035
1036 map_parse_flow_config(&map_class[i]->data.config, cur, true);
1037 }
1038
1039 for (i = 0; i < ARRAY_SIZE(map_class); i++) {
1040 struct qosify_class *data;
1041
1042 data = map_class[i] ? &map_class[i]->data : &empty_data;
1043 bpf_map_update_elem(fd, &i, data, BPF_ANY);
1044 }
1045 }
1046
1047 void qosify_map_update_config(void)
1048 {
1049 int fd = qosify_map_fds[CL_MAP_CONFIG];
1050 uint32_t key = 0;
1051
1052 bpf_map_update_elem(fd, &key, &config, BPF_ANY);
1053 }