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