1 // SPDX-License-Identifier: GPL-2.0+
3 * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
15 #include <libubox/uloop.h>
19 static int qosify_map_entry_cmp(const void *k1
, const void *k2
, void *ptr
);
21 static int qosify_map_fds
[__CL_MAP_MAX
];
22 static AVL_TREE(map_data
, qosify_map_entry_cmp
, false, NULL
);
23 static LIST_HEAD(map_files
);
24 static uint32_t next_timeout
;
25 static struct qosify_dscp_val qosify_dscp_default
[2] = {
29 int qosify_map_timeout
;
30 int qosify_active_timeout
;
31 struct qosify_config config
;
33 struct qosify_map_file
{
34 struct list_head list
;
40 const char *type_name
;
41 } qosify_map_info
[] = {
42 [CL_MAP_TCP_PORTS
] = { "tcp_ports", "tcp_port" },
43 [CL_MAP_UDP_PORTS
] = { "udp_ports", "udp_port" },
44 [CL_MAP_IPV4_ADDR
] = { "ipv4_map", "ipv4_addr" },
45 [CL_MAP_IPV6_ADDR
] = { "ipv6_map", "ipv6_addr" },
46 [CL_MAP_CONFIG
] = { "config", "config" },
47 [CL_MAP_DNS
] = { "dns", "dns" },
80 static void qosify_map_timer_cb(struct uloop_timeout
*t
)
85 static struct uloop_timeout qosify_map_timer
= {
86 .cb
= qosify_map_timer_cb
,
89 static uint32_t qosify_gettime(void)
93 clock_gettime(CLOCK_MONOTONIC
, &ts
);
99 qosify_map_path(enum qosify_map_id id
)
101 static char path
[128];
104 if (id
>= ARRAY_SIZE(qosify_map_info
))
107 name
= qosify_map_info
[id
].name
;
111 snprintf(path
, sizeof(path
), "%s/%s", CLASSIFY_DATA_PATH
, name
);
116 static int qosify_map_get_fd(enum qosify_map_id id
)
118 const char *path
= qosify_map_path(id
);
124 fd
= bpf_obj_get(path
);
126 fprintf(stderr
, "Failed to open map %s: %s\n", path
, strerror(errno
));
131 static void qosify_map_clear_list(enum qosify_map_id id
)
133 int fd
= qosify_map_fds
[id
];
136 while (bpf_map_get_next_key(fd
, &key
, &key
) != -1)
137 bpf_map_delete_elem(fd
, &key
);
140 static void __qosify_map_set_dscp_default(enum qosify_map_id id
, struct qosify_dscp_val
*val
)
142 struct qosify_map_data data
= {
145 int fd
= qosify_map_fds
[id
];
148 val
->ingress
|= QOSIFY_DSCP_DEFAULT_FLAG
;
149 val
->egress
|= QOSIFY_DSCP_DEFAULT_FLAG
;
151 for (i
= 0; i
< (1 << 16); i
++) {
152 data
.addr
.port
= htons(i
);
153 if (avl_find(&map_data
, &data
))
156 bpf_map_update_elem(fd
, &data
.addr
, val
, BPF_ANY
);
160 void qosify_map_set_dscp_default(enum qosify_map_id id
, struct qosify_dscp_val val
)
164 if (id
== CL_MAP_TCP_PORTS
)
166 else if (id
== CL_MAP_UDP_PORTS
)
171 if (!memcmp(&qosify_dscp_default
[udp
], &val
, sizeof(val
)))
174 qosify_dscp_default
[udp
] = val
;
175 __qosify_map_set_dscp_default(id
, &qosify_dscp_default
[udp
]);
178 int qosify_map_init(void)
182 for (i
= 0; i
< CL_MAP_DNS
; i
++) {
183 qosify_map_fds
[i
] = qosify_map_get_fd(i
);
184 if (qosify_map_fds
[i
] < 0)
188 qosify_map_clear_list(CL_MAP_IPV4_ADDR
);
189 qosify_map_clear_list(CL_MAP_IPV6_ADDR
);
190 qosify_map_reset_config();
195 static char *str_skip(char *str
, bool space
)
197 while (*str
&& isspace(*str
) == space
)
204 qosify_map_codepoint(const char *val
)
208 for (i
= 0; i
< ARRAY_SIZE(codepoints
); i
++)
209 if (!strcmp(codepoints
[i
].name
, val
))
210 return codepoints
[i
].val
;
215 static int qosify_map_entry_cmp(const void *k1
, const void *k2
, void *ptr
)
217 const struct qosify_map_data
*d1
= k1
;
218 const struct qosify_map_data
*d2
= k2
;
220 if (d1
->id
!= d2
->id
)
221 return d2
->id
- d1
->id
;
223 if (d1
->id
== CL_MAP_DNS
)
224 return strcmp(d1
->addr
.dns
.pattern
, d2
->addr
.dns
.pattern
);
226 return memcmp(&d1
->addr
, &d2
->addr
, sizeof(d1
->addr
));
229 static struct qosify_map_entry
*
230 __qosify_map_alloc_entry(struct qosify_map_data
*data
)
232 struct qosify_map_entry
*e
;
236 if (data
->id
< CL_MAP_DNS
) {
237 e
= calloc(1, sizeof(*e
));
238 memcpy(&e
->data
.addr
, &data
->addr
, sizeof(e
->data
.addr
));
243 e
= calloc_a(sizeof(*e
), &pattern
, strlen(data
->addr
.dns
.pattern
) + 1);
244 strcpy(pattern
, data
->addr
.dns
.pattern
);
245 e
->data
.addr
.dns
.pattern
= pattern
;
247 for (c
= pattern
; *c
; c
++)
250 if (pattern
[0] == '/' &&
251 regcomp(&e
->data
.addr
.dns
.regex
, pattern
+ 1,
252 REG_EXTENDED
| REG_NOSUB
)) {
260 static void __qosify_map_set_entry(struct qosify_map_data
*data
)
262 int fd
= qosify_map_fds
[data
->id
];
263 struct qosify_dscp_val prev_dscp
= { 0xff, 0xff };
264 struct qosify_map_entry
*e
;
265 bool file
= data
->file
;
267 bool add
= data
->dscp
.ingress
!= 0xff;
269 e
= avl_find_element(&map_data
, data
, e
, avl
);
274 e
= __qosify_map_alloc_entry(data
);
278 e
->avl
.key
= &e
->data
;
279 e
->data
.id
= data
->id
;
280 avl_insert(&map_data
, &e
->avl
);
282 prev_dscp
= e
->data
.dscp
;
292 e
->data
.file_dscp
= data
->dscp
;
293 if (!e
->data
.user
|| !file
)
294 e
->data
.dscp
= data
->dscp
;
295 } else if (e
->data
.file
&& !file
) {
296 e
->data
.dscp
= e
->data
.file_dscp
;
299 if (memcmp(&e
->data
.dscp
, &prev_dscp
, sizeof(prev_dscp
)) != 0 &&
300 data
->id
< CL_MAP_DNS
) {
301 struct qosify_ip_map_val val
= {
302 .dscp
= e
->data
.dscp
,
306 bpf_map_update_elem(fd
, &data
->addr
, &val
, BPF_ANY
);
310 if (qosify_map_timeout
== ~0 || file
) {
315 e
->timeout
= qosify_gettime() + qosify_map_timeout
;
316 delta
= e
->timeout
- next_timeout
;
317 if (next_timeout
&& delta
>= 0)
321 uloop_timeout_set(&qosify_map_timer
, 1);
325 qosify_map_set_port(struct qosify_map_data
*data
, const char *str
)
327 unsigned long start_port
, end_port
;
331 start_port
= end_port
= strtoul(str
, &err
, 0);
334 end_port
= strtoul(err
+ 1, &err
, 0);
339 if (!start_port
|| end_port
< start_port
||
343 for (i
= start_port
; i
<= end_port
; i
++) {
344 data
->addr
.port
= htons(i
);
345 __qosify_map_set_entry(data
);
352 qosify_map_fill_ip(struct qosify_map_data
*data
, const char *str
)
356 if (data
->id
== CL_MAP_IPV6_ADDR
)
361 if (inet_pton(af
, str
, &data
->addr
) != 1)
367 int qosify_map_set_entry(enum qosify_map_id id
, bool file
, const char *str
,
368 struct qosify_dscp_val dscp
)
370 struct qosify_map_data data
= {
378 data
.addr
.dns
.pattern
= str
;
380 case CL_MAP_TCP_PORTS
:
381 case CL_MAP_UDP_PORTS
:
382 return qosify_map_set_port(&data
, str
);
383 case CL_MAP_IPV4_ADDR
:
384 case CL_MAP_IPV6_ADDR
:
385 if (qosify_map_fill_ip(&data
, str
))
392 __qosify_map_set_entry(&data
);
397 int qosify_map_dscp_value(const char *val
, struct qosify_dscp_val
*dscp_val
)
401 bool fallback
= false;
408 dscp
= strtoul(val
, &err
, 0);
410 dscp
= qosify_map_codepoint(val
);
415 dscp_val
->ingress
= dscp_val
->egress
= dscp
+ (fallback
<< 6);
421 qosify_map_dscp_codepoint_str(char *dest
, int len
, uint8_t dscp
)
425 if (dscp
& QOSIFY_DSCP_FALLBACK_FLAG
) {
428 dscp
&= ~QOSIFY_DSCP_FALLBACK_FLAG
;
431 for (i
= 0; i
< ARRAY_SIZE(codepoints
); i
++) {
432 if (codepoints
[i
].val
!= dscp
)
435 snprintf(dest
, len
, "%s", codepoints
[i
].name
);
439 snprintf(dest
, len
, "0x%x", dscp
);
443 qosify_map_parse_line(char *str
)
445 const char *key
, *value
;
446 struct qosify_dscp_val dscp
;
448 str
= str_skip(str
, true);
451 str
= str_skip(str
, false);
456 str
= str_skip(str
, true);
459 if (qosify_map_dscp_value(value
, &dscp
))
462 if (!strncmp(key
, "dns:", 4))
463 qosify_map_set_entry(CL_MAP_DNS
, true, key
+ 4, dscp
);
464 if (!strncmp(key
, "tcp:", 4))
465 qosify_map_set_entry(CL_MAP_TCP_PORTS
, true, key
+ 4, dscp
);
466 else if (!strncmp(key
, "udp:", 4))
467 qosify_map_set_entry(CL_MAP_UDP_PORTS
, true, key
+ 4, dscp
);
468 else if (strchr(key
, ':'))
469 qosify_map_set_entry(CL_MAP_IPV6_ADDR
, true, key
, dscp
);
470 else if (strchr(key
, '.'))
471 qosify_map_set_entry(CL_MAP_IPV4_ADDR
, true, key
, dscp
);
475 __qosify_map_load_file_data(FILE *f
)
480 while (fgets(line
, sizeof(line
), f
)) {
481 cur
= strchr(line
, '#');
485 cur
= line
+ strlen(line
);
489 while (cur
> line
&& isspace(cur
[-1]))
493 qosify_map_parse_line(line
);
499 __qosify_map_load_file(const char *file
)
508 glob(file
, 0, NULL
, &gl
);
510 for (i
= 0; i
< gl
.gl_pathc
; i
++) {
511 f
= fopen(file
, "r");
515 __qosify_map_load_file_data(f
);
524 int qosify_map_load_file(const char *file
)
526 struct qosify_map_file
*f
;
531 f
= calloc(1, sizeof(*f
) + strlen(file
) + 1);
532 strcpy(f
->filename
, file
);
533 list_add_tail(&f
->list
, &map_files
);
535 return __qosify_map_load_file(file
);
538 static void qosify_map_reset_file_entries(void)
540 struct qosify_map_entry
*e
;
542 avl_for_each_element(&map_data
, e
, avl
)
543 e
->data
.file
= false;
546 void qosify_map_clear_files(void)
548 struct qosify_map_file
*f
, *tmp
;
550 qosify_map_reset_file_entries();
552 list_for_each_entry_safe(f
, tmp
, &map_files
, list
) {
558 void qosify_map_reset_config(void)
560 struct qosify_dscp_val val
= {};
562 qosify_map_clear_files();
563 qosify_map_set_dscp_default(CL_MAP_TCP_PORTS
, val
);
564 qosify_map_set_dscp_default(CL_MAP_UDP_PORTS
, val
);
565 qosify_map_timeout
= 3600;
566 qosify_active_timeout
= 300;
568 memset(&config
, 0, sizeof(config
));
569 config
.dscp_prio
.ingress
= 0xff;
570 config
.dscp_prio
.egress
= 0xff;
571 config
.dscp_bulk
.ingress
= 0xff;
572 config
.dscp_bulk
.egress
= 0xff;
573 config
.dscp_icmp
.ingress
= 0xff;
574 config
.dscp_icmp
.egress
= 0xff;
577 void qosify_map_reload(void)
579 struct qosify_map_file
*f
;
581 qosify_map_reset_file_entries();
583 list_for_each_entry(f
, &map_files
, list
)
584 __qosify_map_load_file(f
->filename
);
589 static void qosify_map_free_entry(struct qosify_map_entry
*e
)
591 int fd
= qosify_map_fds
[e
->data
.id
];
593 avl_delete(&map_data
, &e
->avl
);
594 if (e
->data
.id
< CL_MAP_DNS
)
595 bpf_map_delete_elem(fd
, &e
->data
.addr
);
600 qosify_map_entry_refresh_timeout(struct qosify_map_entry
*e
)
602 struct qosify_ip_map_val val
;
603 int fd
= qosify_map_fds
[e
->data
.id
];
605 if (e
->data
.id
!= CL_MAP_IPV4_ADDR
&&
606 e
->data
.id
!= CL_MAP_IPV6_ADDR
)
609 if (bpf_map_lookup_elem(fd
, &e
->data
.addr
, &val
))
615 e
->timeout
= qosify_gettime() + qosify_active_timeout
;
617 bpf_map_update_elem(fd
, &e
->data
.addr
, &val
, BPF_ANY
);
622 void qosify_map_gc(void)
624 struct qosify_map_entry
*e
, *tmp
;
626 uint32_t cur_time
= qosify_gettime();
629 avl_for_each_element_safe(&map_data
, e
, avl
, tmp
) {
632 if (e
->data
.user
&& e
->timeout
!= ~0) {
633 cur_timeout
= e
->timeout
- cur_time
;
634 if (cur_timeout
<= 0 &&
635 qosify_map_entry_refresh_timeout(e
))
636 cur_timeout
= e
->timeout
- cur_time
;
637 if (cur_timeout
<= 0) {
638 e
->data
.user
= false;
639 e
->data
.dscp
= e
->data
.file_dscp
;
640 } else if (!timeout
|| cur_timeout
< timeout
) {
641 timeout
= cur_timeout
;
642 next_timeout
= e
->timeout
;
646 if (e
->data
.file
|| e
->data
.user
)
649 qosify_map_free_entry(e
);
655 uloop_timeout_set(&qosify_map_timer
, timeout
* 1000);
659 int qosify_map_add_dns_host(char *host
, const char *addr
, const char *type
, int ttl
)
661 struct qosify_map_data data
= {
663 .addr
.dns
.pattern
= "",
665 struct qosify_map_entry
*e
;
666 int prev_timeout
= qosify_map_timeout
;
669 e
= avl_find_ge_element(&map_data
, &data
, e
, avl
);
673 memset(&data
, 0, sizeof(data
));
675 if (!strcmp(type
, "A"))
676 data
.id
= CL_MAP_IPV4_ADDR
;
677 else if (!strcmp(type
, "AAAA"))
678 data
.id
= CL_MAP_IPV6_ADDR
;
682 if (qosify_map_fill_ip(&data
, addr
))
685 for (c
= host
; *c
; c
++)
688 avl_for_element_to_last(&map_data
, e
, e
, avl
) {
689 regex_t
*regex
= &e
->data
.addr
.dns
.regex
;
691 if (e
->data
.id
!= CL_MAP_DNS
)
694 if (e
->data
.addr
.dns
.pattern
[0] == '/') {
695 if (regexec(regex
, host
, 0, NULL
, 0) != 0)
698 if (fnmatch(e
->data
.addr
.dns
.pattern
, host
, 0))
703 qosify_map_timeout
= ttl
;
704 data
.dscp
= e
->data
.dscp
;
705 __qosify_map_set_entry(&data
);
706 qosify_map_timeout
= prev_timeout
;
713 blobmsg_add_dscp(struct blob_buf
*b
, const char *name
, uint8_t dscp
)
718 buf
= blobmsg_alloc_string_buffer(b
, name
, buf_len
);
719 qosify_map_dscp_codepoint_str(buf
, buf_len
, dscp
);
720 blobmsg_add_string_buffer(b
);
724 void qosify_map_dump(struct blob_buf
*b
)
726 struct qosify_map_entry
*e
;
727 uint32_t cur_time
= qosify_gettime();
728 int buf_len
= INET6_ADDRSTRLEN
+ 1;
733 a
= blobmsg_open_array(b
, "entries");
734 avl_for_each_element(&map_data
, e
, avl
) {
737 if (!e
->data
.file
&& !e
->data
.user
)
740 c
= blobmsg_open_table(b
, NULL
);
741 if (e
->data
.user
&& e
->timeout
!= ~0) {
742 int32_t cur_timeout
= e
->timeout
- cur_time
;
747 blobmsg_add_u32(b
, "timeout", cur_timeout
);
750 blobmsg_add_u8(b
, "file", e
->data
.file
);
751 blobmsg_add_u8(b
, "user", e
->data
.user
);
753 blobmsg_add_dscp(b
, "dscp_ingress", e
->data
.dscp
.ingress
);
754 blobmsg_add_dscp(b
, "dscp_egress", e
->data
.dscp
.egress
);
756 blobmsg_add_string(b
, "type", qosify_map_info
[e
->data
.id
].type_name
);
758 switch (e
->data
.id
) {
759 case CL_MAP_TCP_PORTS
:
760 case CL_MAP_UDP_PORTS
:
761 blobmsg_printf(b
, "addr", "%d", ntohs(e
->data
.addr
.port
));
763 case CL_MAP_IPV4_ADDR
:
764 case CL_MAP_IPV6_ADDR
:
765 buf
= blobmsg_alloc_string_buffer(b
, "addr", buf_len
);
766 af
= e
->data
.id
== CL_MAP_IPV6_ADDR
? AF_INET6
: AF_INET
;
767 inet_ntop(af
, &e
->data
.addr
, buf
, buf_len
);
768 blobmsg_add_string_buffer(b
);
771 blobmsg_add_string(b
, "addr", e
->data
.addr
.dns
.pattern
);
776 blobmsg_close_table(b
, c
);
778 blobmsg_close_array(b
, a
);
781 void qosify_map_update_config(void)
783 int fd
= qosify_map_fds
[CL_MAP_CONFIG
];
786 bpf_map_update_elem(fd
, &key
, &config
, BPF_ANY
);