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