1 // SPDX-License-Identifier: GPL-2.0+
3 * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
15 #include <libubox/uloop.h>
16 #include <libubox/avl-cmp.h>
20 static int qosify_map_entry_cmp(const void *k1
, const void *k2
, void *ptr
);
22 static int qosify_map_fds
[__CL_MAP_MAX
];
23 static AVL_TREE(map_data
, qosify_map_entry_cmp
, false, NULL
);
24 static LIST_HEAD(map_files
);
25 static AVL_TREE(map_aliases
, avl_strcmp
, false, NULL
);
26 static uint32_t next_timeout
;
27 static struct qosify_dscp_val qosify_dscp_default
[2] = {
31 int qosify_map_timeout
;
32 int qosify_active_timeout
;
33 struct qosify_config config
;
35 struct qosify_map_file
{
36 struct list_head list
;
40 struct qosify_map_alias
{
42 struct qosify_dscp_val value
;
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_DNS
] = { "dns", "dns" },
87 static void qosify_map_timer_cb(struct uloop_timeout
*t
)
92 static struct uloop_timeout qosify_map_timer
= {
93 .cb
= qosify_map_timer_cb
,
96 static uint32_t qosify_gettime(void)
100 clock_gettime(CLOCK_MONOTONIC
, &ts
);
106 qosify_map_path(enum qosify_map_id id
)
108 static char path
[128];
111 if (id
>= ARRAY_SIZE(qosify_map_info
))
114 name
= qosify_map_info
[id
].name
;
118 snprintf(path
, sizeof(path
), "%s/%s", CLASSIFY_DATA_PATH
, name
);
123 static int qosify_map_get_fd(enum qosify_map_id id
)
125 const char *path
= qosify_map_path(id
);
131 fd
= bpf_obj_get(path
);
133 fprintf(stderr
, "Failed to open map %s: %s\n", path
, strerror(errno
));
138 static void qosify_map_clear_list(enum qosify_map_id id
)
140 int fd
= qosify_map_fds
[id
];
143 while (bpf_map_get_next_key(fd
, &key
, &key
) != -1)
144 bpf_map_delete_elem(fd
, &key
);
147 static void __qosify_map_set_dscp_default(enum qosify_map_id id
, struct qosify_dscp_val
*val
)
149 struct qosify_map_data data
= {
152 int fd
= qosify_map_fds
[id
];
155 val
->flags
|= QOSIFY_VAL_FLAG_PRIO_CHECK
|
156 QOSIFY_VAL_FLAG_BULK_CHECK
;
158 for (i
= 0; i
< (1 << 16); i
++) {
159 data
.addr
.port
= htons(i
);
160 if (avl_find(&map_data
, &data
))
163 bpf_map_update_elem(fd
, &data
.addr
, val
, BPF_ANY
);
167 void qosify_map_set_dscp_default(enum qosify_map_id id
, struct qosify_dscp_val val
)
171 if (id
== CL_MAP_TCP_PORTS
)
173 else if (id
== CL_MAP_UDP_PORTS
)
178 if (!memcmp(&qosify_dscp_default
[udp
], &val
, sizeof(val
)))
181 qosify_dscp_default
[udp
] = val
;
182 __qosify_map_set_dscp_default(id
, &qosify_dscp_default
[udp
]);
185 int qosify_map_init(void)
189 for (i
= 0; i
< CL_MAP_DNS
; i
++) {
190 qosify_map_fds
[i
] = qosify_map_get_fd(i
);
191 if (qosify_map_fds
[i
] < 0)
195 qosify_map_clear_list(CL_MAP_IPV4_ADDR
);
196 qosify_map_clear_list(CL_MAP_IPV6_ADDR
);
197 qosify_map_reset_config();
202 static char *str_skip(char *str
, bool space
)
204 while (*str
&& isspace(*str
) == space
)
211 qosify_map_codepoint(const char *val
)
215 for (i
= 0; i
< ARRAY_SIZE(codepoints
); i
++)
216 if (!strcmp(codepoints
[i
].name
, val
))
217 return codepoints
[i
].val
;
222 static int qosify_map_entry_cmp(const void *k1
, const void *k2
, void *ptr
)
224 const struct qosify_map_data
*d1
= k1
;
225 const struct qosify_map_data
*d2
= k2
;
227 if (d1
->id
!= d2
->id
)
228 return d2
->id
- d1
->id
;
230 if (d1
->id
== CL_MAP_DNS
)
231 return strcmp(d1
->addr
.dns
.pattern
, d2
->addr
.dns
.pattern
);
233 return memcmp(&d1
->addr
, &d2
->addr
, sizeof(d1
->addr
));
236 static struct qosify_map_entry
*
237 __qosify_map_alloc_entry(struct qosify_map_data
*data
)
239 struct qosify_map_entry
*e
;
243 if (data
->id
< CL_MAP_DNS
) {
244 e
= calloc(1, sizeof(*e
));
245 memcpy(&e
->data
.addr
, &data
->addr
, sizeof(e
->data
.addr
));
250 e
= calloc_a(sizeof(*e
), &pattern
, strlen(data
->addr
.dns
.pattern
) + 1);
251 strcpy(pattern
, data
->addr
.dns
.pattern
);
252 e
->data
.addr
.dns
.pattern
= pattern
;
254 for (c
= pattern
; *c
; c
++)
257 if (pattern
[0] == '/' &&
258 regcomp(&e
->data
.addr
.dns
.regex
, pattern
+ 1,
259 REG_EXTENDED
| REG_NOSUB
)) {
267 static void __qosify_map_set_entry(struct qosify_map_data
*data
)
269 int fd
= qosify_map_fds
[data
->id
];
270 struct qosify_dscp_val prev_dscp
= { 0xff, 0xff };
271 struct qosify_map_entry
*e
;
272 bool file
= data
->file
;
274 bool add
= data
->dscp
.ingress
!= 0xff;
276 e
= avl_find_element(&map_data
, data
, e
, avl
);
281 e
= __qosify_map_alloc_entry(data
);
285 e
->avl
.key
= &e
->data
;
286 e
->data
.id
= data
->id
;
287 avl_insert(&map_data
, &e
->avl
);
289 prev_dscp
= e
->data
.dscp
;
299 e
->data
.file_dscp
= data
->dscp
;
300 if (!e
->data
.user
|| !file
)
301 e
->data
.dscp
= data
->dscp
;
302 } else if (e
->data
.file
&& !file
) {
303 e
->data
.dscp
= e
->data
.file_dscp
;
306 if (memcmp(&e
->data
.dscp
, &prev_dscp
, sizeof(prev_dscp
)) != 0 &&
307 data
->id
< CL_MAP_DNS
) {
308 struct qosify_ip_map_val val
= {
309 .dscp
= e
->data
.dscp
,
313 bpf_map_update_elem(fd
, &data
->addr
, &val
, BPF_ANY
);
317 if (qosify_map_timeout
== ~0 || file
) {
322 e
->timeout
= qosify_gettime() + qosify_map_timeout
;
323 delta
= e
->timeout
- next_timeout
;
324 if (next_timeout
&& delta
>= 0)
328 uloop_timeout_set(&qosify_map_timer
, 1);
332 qosify_map_set_port(struct qosify_map_data
*data
, const char *str
)
334 unsigned long start_port
, end_port
;
338 start_port
= end_port
= strtoul(str
, &err
, 0);
341 end_port
= strtoul(err
+ 1, &err
, 0);
346 if (!start_port
|| end_port
< start_port
||
350 for (i
= start_port
; i
<= end_port
; i
++) {
351 data
->addr
.port
= htons(i
);
352 __qosify_map_set_entry(data
);
359 qosify_map_fill_ip(struct qosify_map_data
*data
, const char *str
)
363 if (data
->id
== CL_MAP_IPV6_ADDR
)
368 if (inet_pton(af
, str
, &data
->addr
) != 1)
374 int qosify_map_set_entry(enum qosify_map_id id
, bool file
, const char *str
,
375 struct qosify_dscp_val dscp
)
377 struct qosify_map_data data
= {
385 data
.addr
.dns
.pattern
= str
;
387 case CL_MAP_TCP_PORTS
:
388 case CL_MAP_UDP_PORTS
:
389 return qosify_map_set_port(&data
, str
);
390 case CL_MAP_IPV4_ADDR
:
391 case CL_MAP_IPV6_ADDR
:
392 if (qosify_map_fill_ip(&data
, str
))
399 __qosify_map_set_entry(&data
);
405 __qosify_map_dscp_value(const char *val
, uint8_t *dscp_val
)
408 bool fallback
= false;
416 dscp
= strtoul(val
, &err
, 0);
418 dscp
= qosify_map_codepoint(val
);
423 *dscp_val
= dscp
| (fallback
<< 6);
428 int qosify_map_dscp_value(const char *val
, struct qosify_dscp_val
*dscp_val
)
430 struct qosify_map_alias
*alias
;
431 bool fallback
= false;
438 alias
= avl_find_element(&map_aliases
, val
, alias
, avl
);
440 *dscp_val
= alias
->value
;
442 if (__qosify_map_dscp_value(val
, &dscp_val
->egress
))
445 dscp_val
->ingress
= dscp_val
->egress
;
449 dscp_val
->ingress
|= (1 << 6);
450 dscp_val
->egress
|= (1 << 6);
457 qosify_map_dscp_codepoint_str(char *dest
, int len
, uint8_t dscp
)
461 if (dscp
& QOSIFY_DSCP_FALLBACK_FLAG
) {
464 dscp
&= ~QOSIFY_DSCP_FALLBACK_FLAG
;
467 for (i
= 0; i
< ARRAY_SIZE(codepoints
); i
++) {
468 if (codepoints
[i
].val
!= dscp
)
471 snprintf(dest
, len
, "%s", codepoints
[i
].name
);
475 snprintf(dest
, len
, "0x%x", dscp
);
479 qosify_map_parse_line(char *str
)
481 const char *key
, *value
;
482 struct qosify_dscp_val dscp
;
484 str
= str_skip(str
, true);
487 str
= str_skip(str
, false);
492 str
= str_skip(str
, true);
495 if (qosify_map_dscp_value(value
, &dscp
))
498 if (!strncmp(key
, "dns:", 4))
499 qosify_map_set_entry(CL_MAP_DNS
, true, key
+ 4, dscp
);
500 if (!strncmp(key
, "tcp:", 4))
501 qosify_map_set_entry(CL_MAP_TCP_PORTS
, true, key
+ 4, dscp
);
502 else if (!strncmp(key
, "udp:", 4))
503 qosify_map_set_entry(CL_MAP_UDP_PORTS
, true, key
+ 4, dscp
);
504 else if (strchr(key
, ':'))
505 qosify_map_set_entry(CL_MAP_IPV6_ADDR
, true, key
, dscp
);
506 else if (strchr(key
, '.'))
507 qosify_map_set_entry(CL_MAP_IPV4_ADDR
, true, key
, dscp
);
511 __qosify_map_load_file_data(FILE *f
)
516 while (fgets(line
, sizeof(line
), f
)) {
517 cur
= strchr(line
, '#');
521 cur
= line
+ strlen(line
);
525 while (cur
> line
&& isspace(cur
[-1]))
529 qosify_map_parse_line(line
);
535 __qosify_map_load_file(const char *file
)
544 glob(file
, 0, NULL
, &gl
);
546 for (i
= 0; i
< gl
.gl_pathc
; i
++) {
547 f
= fopen(file
, "r");
551 __qosify_map_load_file_data(f
);
560 int qosify_map_load_file(const char *file
)
562 struct qosify_map_file
*f
;
567 f
= calloc(1, sizeof(*f
) + strlen(file
) + 1);
568 strcpy(f
->filename
, file
);
569 list_add_tail(&f
->list
, &map_files
);
571 return __qosify_map_load_file(file
);
574 static void qosify_map_reset_file_entries(void)
576 struct qosify_map_entry
*e
;
578 avl_for_each_element(&map_data
, e
, avl
)
579 e
->data
.file
= false;
582 void qosify_map_clear_files(void)
584 struct qosify_map_file
*f
, *tmp
;
586 qosify_map_reset_file_entries();
588 list_for_each_entry_safe(f
, tmp
, &map_files
, list
) {
594 void qosify_map_reset_config(void)
596 struct qosify_dscp_val val
= {};
598 qosify_map_clear_files();
599 qosify_map_set_dscp_default(CL_MAP_TCP_PORTS
, val
);
600 qosify_map_set_dscp_default(CL_MAP_UDP_PORTS
, val
);
601 qosify_map_timeout
= 3600;
602 qosify_active_timeout
= 300;
604 memset(&config
, 0, sizeof(config
));
605 config
.flow
.dscp_prio
.ingress
= 0xff;
606 config
.flow
.dscp_prio
.egress
= 0xff;
607 config
.flow
.dscp_bulk
.ingress
= 0xff;
608 config
.flow
.dscp_bulk
.egress
= 0xff;
609 config
.dscp_icmp
.ingress
= 0xff;
610 config
.dscp_icmp
.egress
= 0xff;
613 void qosify_map_reload(void)
615 struct qosify_map_file
*f
;
617 qosify_map_reset_file_entries();
619 list_for_each_entry(f
, &map_files
, list
)
620 __qosify_map_load_file(f
->filename
);
625 static void qosify_map_free_entry(struct qosify_map_entry
*e
)
627 int fd
= qosify_map_fds
[e
->data
.id
];
629 avl_delete(&map_data
, &e
->avl
);
630 if (e
->data
.id
< CL_MAP_DNS
)
631 bpf_map_delete_elem(fd
, &e
->data
.addr
);
636 qosify_map_entry_refresh_timeout(struct qosify_map_entry
*e
)
638 struct qosify_ip_map_val val
;
639 int fd
= qosify_map_fds
[e
->data
.id
];
641 if (e
->data
.id
!= CL_MAP_IPV4_ADDR
&&
642 e
->data
.id
!= CL_MAP_IPV6_ADDR
)
645 if (bpf_map_lookup_elem(fd
, &e
->data
.addr
, &val
))
651 e
->timeout
= qosify_gettime() + qosify_active_timeout
;
653 bpf_map_update_elem(fd
, &e
->data
.addr
, &val
, BPF_ANY
);
658 void qosify_map_gc(void)
660 struct qosify_map_entry
*e
, *tmp
;
662 uint32_t cur_time
= qosify_gettime();
665 avl_for_each_element_safe(&map_data
, e
, avl
, tmp
) {
668 if (e
->data
.user
&& e
->timeout
!= ~0) {
669 cur_timeout
= e
->timeout
- cur_time
;
670 if (cur_timeout
<= 0 &&
671 qosify_map_entry_refresh_timeout(e
))
672 cur_timeout
= e
->timeout
- cur_time
;
673 if (cur_timeout
<= 0) {
674 e
->data
.user
= false;
675 e
->data
.dscp
= e
->data
.file_dscp
;
676 } else if (!timeout
|| cur_timeout
< timeout
) {
677 timeout
= cur_timeout
;
678 next_timeout
= e
->timeout
;
682 if (e
->data
.file
|| e
->data
.user
)
685 qosify_map_free_entry(e
);
691 uloop_timeout_set(&qosify_map_timer
, timeout
* 1000);
695 int qosify_map_add_dns_host(char *host
, const char *addr
, const char *type
, int ttl
)
697 struct qosify_map_data data
= {
699 .addr
.dns
.pattern
= "",
701 struct qosify_map_entry
*e
;
702 int prev_timeout
= qosify_map_timeout
;
705 e
= avl_find_ge_element(&map_data
, &data
, e
, avl
);
709 memset(&data
, 0, sizeof(data
));
711 if (!strcmp(type
, "A"))
712 data
.id
= CL_MAP_IPV4_ADDR
;
713 else if (!strcmp(type
, "AAAA"))
714 data
.id
= CL_MAP_IPV6_ADDR
;
718 if (qosify_map_fill_ip(&data
, addr
))
721 for (c
= host
; *c
; c
++)
724 avl_for_element_to_last(&map_data
, e
, e
, avl
) {
725 regex_t
*regex
= &e
->data
.addr
.dns
.regex
;
727 if (e
->data
.id
!= CL_MAP_DNS
)
730 if (e
->data
.addr
.dns
.pattern
[0] == '/') {
731 if (regexec(regex
, host
, 0, NULL
, 0) != 0)
734 if (fnmatch(e
->data
.addr
.dns
.pattern
, host
, 0))
739 qosify_map_timeout
= ttl
;
740 data
.dscp
= e
->data
.dscp
;
741 __qosify_map_set_entry(&data
);
742 qosify_map_timeout
= prev_timeout
;
749 blobmsg_add_dscp(struct blob_buf
*b
, const char *name
, uint8_t dscp
)
754 buf
= blobmsg_alloc_string_buffer(b
, name
, buf_len
);
755 qosify_map_dscp_codepoint_str(buf
, buf_len
, dscp
);
756 blobmsg_add_string_buffer(b
);
760 void qosify_map_dump(struct blob_buf
*b
)
762 struct qosify_map_entry
*e
;
763 uint32_t cur_time
= qosify_gettime();
764 int buf_len
= INET6_ADDRSTRLEN
+ 1;
769 a
= blobmsg_open_array(b
, "entries");
770 avl_for_each_element(&map_data
, e
, avl
) {
773 if (!e
->data
.file
&& !e
->data
.user
)
776 c
= blobmsg_open_table(b
, NULL
);
777 if (e
->data
.user
&& e
->timeout
!= ~0) {
778 int32_t cur_timeout
= e
->timeout
- cur_time
;
783 blobmsg_add_u32(b
, "timeout", cur_timeout
);
786 blobmsg_add_u8(b
, "file", e
->data
.file
);
787 blobmsg_add_u8(b
, "user", e
->data
.user
);
789 blobmsg_add_dscp(b
, "dscp_ingress", e
->data
.dscp
.ingress
);
790 blobmsg_add_dscp(b
, "dscp_egress", e
->data
.dscp
.egress
);
792 blobmsg_add_string(b
, "type", qosify_map_info
[e
->data
.id
].type_name
);
794 switch (e
->data
.id
) {
795 case CL_MAP_TCP_PORTS
:
796 case CL_MAP_UDP_PORTS
:
797 blobmsg_printf(b
, "addr", "%d", ntohs(e
->data
.addr
.port
));
799 case CL_MAP_IPV4_ADDR
:
800 case CL_MAP_IPV6_ADDR
:
801 buf
= blobmsg_alloc_string_buffer(b
, "addr", buf_len
);
802 af
= e
->data
.id
== CL_MAP_IPV6_ADDR
? AF_INET6
: AF_INET
;
803 inet_ntop(af
, &e
->data
.addr
, buf
, buf_len
);
804 blobmsg_add_string_buffer(b
);
807 blobmsg_add_string(b
, "addr", e
->data
.addr
.dns
.pattern
);
812 blobmsg_close_table(b
, c
);
814 blobmsg_close_array(b
, a
);
818 qosify_map_create_alias(struct blob_attr
*attr
)
820 struct qosify_map_alias
*alias
;
826 static const struct blobmsg_policy policy
[__MAP_ALIAS_MAX
] = {
827 [MAP_ALIAS_INGRESS
] = { .type
= BLOBMSG_TYPE_STRING
},
828 [MAP_ALIAS_EGRESS
] = { .type
= BLOBMSG_TYPE_STRING
},
830 struct blob_attr
*tb
[__MAP_ALIAS_MAX
];
834 if (blobmsg_check_array(attr
, BLOBMSG_TYPE_STRING
) != 2)
837 blobmsg_parse_array(policy
, __MAP_ALIAS_MAX
, tb
,
838 blobmsg_data(attr
), blobmsg_len(attr
));
840 if (!tb
[MAP_ALIAS_INGRESS
] || !tb
[MAP_ALIAS_EGRESS
])
843 name
= blobmsg_name(attr
);
844 alias
= calloc_a(sizeof(*alias
), &name_buf
, strlen(name
) + 1);
845 alias
->avl
.key
= strcpy(name_buf
, name
);
846 if (__qosify_map_dscp_value(blobmsg_get_string(tb
[MAP_ALIAS_INGRESS
]),
847 &alias
->value
.ingress
) ||
848 __qosify_map_dscp_value(blobmsg_get_string(tb
[MAP_ALIAS_EGRESS
]),
849 &alias
->value
.egress
) ||
850 avl_insert(&map_aliases
, &alias
->avl
)) {
858 void qosify_map_set_aliases(struct blob_attr
*val
)
860 struct qosify_map_alias
*alias
, *tmp
;
861 struct blob_attr
*cur
;
864 avl_remove_all_elements(&map_aliases
, alias
, avl
, tmp
)
867 blobmsg_for_each_attr(cur
, val
, rem
)
868 qosify_map_create_alias(cur
);
871 void qosify_map_update_config(void)
873 int fd
= qosify_map_fds
[CL_MAP_CONFIG
];
876 bpf_map_update_elem(fd
, &key
, &config
, BPF_ANY
);