1 // SPDX-License-Identifier: GPL-2.0+
3 * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
13 #include <libubox/uloop.h>
17 static int qosify_map_entry_cmp(const void *k1
, const void *k2
, void *ptr
);
19 static int qosify_map_fds
[__CL_MAP_MAX
];
20 static AVL_TREE(map_data
, qosify_map_entry_cmp
, false, NULL
);
21 static LIST_HEAD(map_files
);
22 static uint32_t next_timeout
;
23 static uint8_t qosify_dscp_default
[2] = { 0xff, 0xff };
24 int qosify_map_timeout
= 3600;
25 struct qosify_config config
;
27 struct qosify_map_file
{
28 struct list_head list
;
34 const char *type_name
;
35 } qosify_map_info
[] = {
36 [CL_MAP_TCP_PORTS
] = { "tcp_ports", "tcp_port" },
37 [CL_MAP_UDP_PORTS
] = { "udp_ports", "udp_port" },
38 [CL_MAP_IPV4_ADDR
] = { "ipv4_map", "ipv4_addr" },
39 [CL_MAP_IPV6_ADDR
] = { "ipv6_map", "ipv6_addr" },
40 [CL_MAP_CONFIG
] = { "config", "config" },
41 [CL_MAP_DNS
] = { "dns", "dns" },
72 static void qosify_map_timer_cb(struct uloop_timeout
*t
)
77 static struct uloop_timeout qosify_map_timer
= {
78 .cb
= qosify_map_timer_cb
,
81 static uint32_t qosify_gettime(void)
85 clock_gettime(CLOCK_MONOTONIC
, &ts
);
91 qosify_map_path(enum qosify_map_id id
)
93 static char path
[128];
96 if (id
>= ARRAY_SIZE(qosify_map_info
))
99 name
= qosify_map_info
[id
].name
;
103 snprintf(path
, sizeof(path
), "%s/%s", CLASSIFY_DATA_PATH
, name
);
108 static int qosify_map_get_fd(enum qosify_map_id id
)
110 const char *path
= qosify_map_path(id
);
116 fd
= bpf_obj_get(path
);
118 fprintf(stderr
, "Failed to open map %s: %s\n", path
, strerror(errno
));
123 static void qosify_map_clear_list(enum qosify_map_id id
)
125 int fd
= qosify_map_fds
[id
];
128 while (bpf_map_get_next_key(fd
, &key
, &key
) != -1)
129 bpf_map_delete_elem(fd
, &key
);
132 static void __qosify_map_set_dscp_default(enum qosify_map_id id
, uint8_t val
)
134 struct qosify_map_data data
= {
137 int fd
= qosify_map_fds
[id
];
140 val
|= QOSIFY_DSCP_DEFAULT_FLAG
;
142 for (i
= 0; i
< (1 << 16); i
++) {
143 data
.addr
.port
= htons(i
);
144 if (avl_find(&map_data
, &data
))
147 bpf_map_update_elem(fd
, &data
.addr
, &val
, BPF_ANY
);
151 void qosify_map_set_dscp_default(enum qosify_map_id id
, uint8_t val
)
155 if (id
== CL_MAP_TCP_PORTS
)
157 else if (id
== CL_MAP_UDP_PORTS
)
162 if (qosify_dscp_default
[udp
] == val
)
165 qosify_dscp_default
[udp
] = val
;
166 __qosify_map_set_dscp_default(id
, val
);
169 int qosify_map_init(void)
173 for (i
= 0; i
< CL_MAP_DNS
; i
++) {
174 qosify_map_fds
[i
] = qosify_map_get_fd(i
);
175 if (qosify_map_fds
[i
] < 0)
179 qosify_map_clear_list(CL_MAP_IPV4_ADDR
);
180 qosify_map_clear_list(CL_MAP_IPV6_ADDR
);
181 qosify_map_reset_config();
186 static char *str_skip(char *str
, bool space
)
188 while (*str
&& isspace(*str
) == space
)
195 qosify_map_codepoint(const char *val
)
199 for (i
= 0; i
< ARRAY_SIZE(codepoints
); i
++)
200 if (!strcmp(codepoints
[i
].name
, val
))
201 return codepoints
[i
].val
;
206 static int qosify_map_entry_cmp(const void *k1
, const void *k2
, void *ptr
)
208 const struct qosify_map_data
*d1
= k1
;
209 const struct qosify_map_data
*d2
= k2
;
211 if (d1
->id
!= d2
->id
)
212 return d2
->id
- d1
->id
;
214 if (d1
->id
== CL_MAP_DNS
)
215 return strcmp(d1
->addr
.dns
.pattern
, d2
->addr
.dns
.pattern
);
217 return memcmp(&d1
->addr
, &d2
->addr
, sizeof(d1
->addr
));
220 static struct qosify_map_entry
*
221 __qosify_map_alloc_entry(struct qosify_map_data
*data
)
223 struct qosify_map_entry
*e
;
226 if (data
->id
< CL_MAP_DNS
) {
227 e
= calloc(1, sizeof(*e
));
228 memcpy(&e
->data
.addr
, &data
->addr
, sizeof(e
->data
.addr
));
233 e
= calloc_a(sizeof(*e
), &pattern
, strlen(data
->addr
.dns
.pattern
) + 1);
234 strcpy(pattern
, data
->addr
.dns
.pattern
);
235 e
->data
.addr
.dns
.pattern
= pattern
;
236 if (regcomp(&e
->data
.addr
.dns
.regex
, pattern
,
237 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) {
245 static void __qosify_map_set_entry(struct qosify_map_data
*data
)
247 int fd
= qosify_map_fds
[data
->id
];
248 struct qosify_map_entry
*e
;
249 bool file
= data
->file
;
251 bool add
= data
->dscp
!= 0xff;
252 uint8_t prev_dscp
= 0xff;
254 e
= avl_find_element(&map_data
, data
, e
, avl
);
259 e
= __qosify_map_alloc_entry(data
);
263 e
->avl
.key
= &e
->data
;
264 e
->data
.id
= data
->id
;
265 avl_insert(&map_data
, &e
->avl
);
267 prev_dscp
= e
->data
.dscp
;
277 e
->data
.file_dscp
= data
->dscp
;
278 if (!e
->data
.user
|| !file
)
279 e
->data
.dscp
= data
->dscp
;
280 } else if (e
->data
.file
&& !file
) {
281 e
->data
.dscp
= e
->data
.file_dscp
;
284 if (e
->data
.dscp
!= prev_dscp
&& data
->id
< CL_MAP_DNS
)
285 bpf_map_update_elem(fd
, &data
->addr
, &e
->data
.dscp
, BPF_ANY
);
288 if (qosify_map_timeout
== ~0 || file
) {
293 e
->timeout
= qosify_gettime() + qosify_map_timeout
;
294 delta
= e
->timeout
- next_timeout
;
295 if (next_timeout
&& delta
>= 0)
299 uloop_timeout_set(&qosify_map_timer
, 1);
303 qosify_map_set_port(struct qosify_map_data
*data
, const char *str
)
305 unsigned long start_port
, end_port
;
309 start_port
= end_port
= strtoul(str
, &err
, 0);
312 end_port
= strtoul(err
+ 1, &err
, 0);
317 if (!start_port
|| end_port
< start_port
||
321 for (i
= start_port
; i
<= end_port
; i
++) {
322 data
->addr
.port
= htons(i
);
323 __qosify_map_set_entry(data
);
330 qosify_map_fill_ip(struct qosify_map_data
*data
, const char *str
)
334 if (data
->id
== CL_MAP_IPV6_ADDR
)
339 if (inet_pton(af
, str
, &data
->addr
) != 1)
345 int qosify_map_set_entry(enum qosify_map_id id
, bool file
, const char *str
, uint8_t dscp
)
347 struct qosify_map_data data
= {
355 data
.addr
.dns
.pattern
= str
;
357 case CL_MAP_TCP_PORTS
:
358 case CL_MAP_UDP_PORTS
:
359 return qosify_map_set_port(&data
, str
);
360 case CL_MAP_IPV4_ADDR
:
361 case CL_MAP_IPV6_ADDR
:
362 if (qosify_map_fill_ip(&data
, str
))
369 __qosify_map_set_entry(&data
);
374 int qosify_map_dscp_value(const char *val
)
378 bool fallback
= false;
385 dscp
= strtoul(val
, &err
, 0);
387 dscp
= qosify_map_codepoint(val
);
392 return dscp
+ (fallback
<< 6);
396 qosify_map_dscp_codepoint_str(char *dest
, int len
, uint8_t dscp
)
400 if (dscp
& QOSIFY_DSCP_FALLBACK_FLAG
) {
403 dscp
&= ~QOSIFY_DSCP_FALLBACK_FLAG
;
406 for (i
= 0; i
< ARRAY_SIZE(codepoints
); i
++) {
407 if (codepoints
[i
].val
!= dscp
)
410 snprintf(dest
, len
, "%s", codepoints
[i
].name
);
414 snprintf(dest
, len
, "0x%x", dscp
);
418 qosify_map_parse_line(char *str
)
420 const char *key
, *value
;
423 str
= str_skip(str
, true);
426 str
= str_skip(str
, false);
431 str
= str_skip(str
, true);
434 dscp
= qosify_map_dscp_value(value
);
438 if (!strncmp(key
, "dns:", 4))
439 qosify_map_set_entry(CL_MAP_DNS
, true, key
+ 4, dscp
);
440 if (!strncmp(key
, "tcp:", 4))
441 qosify_map_set_entry(CL_MAP_TCP_PORTS
, true, key
+ 4, dscp
);
442 else if (!strncmp(key
, "udp:", 4))
443 qosify_map_set_entry(CL_MAP_UDP_PORTS
, true, key
+ 4, dscp
);
444 else if (strchr(key
, ':'))
445 qosify_map_set_entry(CL_MAP_IPV6_ADDR
, true, key
, dscp
);
446 else if (strchr(key
, '.'))
447 qosify_map_set_entry(CL_MAP_IPV4_ADDR
, true, key
, dscp
);
450 static int __qosify_map_load_file(const char *file
)
459 f
= fopen(file
, "r");
461 fprintf(stderr
, "Can't open data file %s\n", file
);
465 while (fgets(line
, sizeof(line
), f
)) {
466 cur
= strchr(line
, '#');
470 cur
= line
+ strlen(line
);
474 while (cur
> line
&& isspace(cur
[-1]))
478 qosify_map_parse_line(line
);
486 int qosify_map_load_file(const char *file
)
488 struct qosify_map_file
*f
;
493 f
= calloc(1, sizeof(*f
) + strlen(file
) + 1);
494 strcpy(f
->filename
, file
);
495 list_add_tail(&f
->list
, &map_files
);
497 return __qosify_map_load_file(file
);
500 static void qosify_map_reset_file_entries(void)
502 struct qosify_map_entry
*e
;
504 avl_for_each_element(&map_data
, e
, avl
)
505 e
->data
.file
= false;
508 void qosify_map_clear_files(void)
510 struct qosify_map_file
*f
, *tmp
;
512 qosify_map_reset_file_entries();
514 list_for_each_entry_safe(f
, tmp
, &map_files
, list
) {
520 void qosify_map_reset_config(void)
522 qosify_map_clear_files();
523 qosify_map_set_dscp_default(CL_MAP_TCP_PORTS
, 0);
524 qosify_map_set_dscp_default(CL_MAP_UDP_PORTS
, 0);
525 qosify_map_timeout
= 3600;
527 memset(&config
, 0, sizeof(config
));
528 config
.dscp_prio
= 0xff;
529 config
.dscp_bulk
= 0xff;
530 config
.dscp_icmp
= 0xff;
533 void qosify_map_reload(void)
535 struct qosify_map_file
*f
;
537 qosify_map_reset_file_entries();
539 list_for_each_entry(f
, &map_files
, list
)
540 __qosify_map_load_file(f
->filename
);
545 static void qosify_map_free_entry(struct qosify_map_entry
*e
)
547 int fd
= qosify_map_fds
[e
->data
.id
];
549 avl_delete(&map_data
, &e
->avl
);
550 if (e
->data
.id
< CL_MAP_DNS
)
551 bpf_map_delete_elem(fd
, &e
->data
.addr
);
555 void qosify_map_gc(void)
557 struct qosify_map_entry
*e
, *tmp
;
559 uint32_t cur_time
= qosify_gettime();
562 avl_for_each_element_safe(&map_data
, e
, avl
, tmp
) {
565 if (e
->data
.user
&& e
->timeout
!= ~0) {
566 cur_timeout
= e
->timeout
- cur_time
;
567 if (cur_timeout
<= 0) {
568 e
->data
.user
= false;
569 e
->data
.dscp
= e
->data
.file_dscp
;
570 } else if (!timeout
|| cur_timeout
< timeout
) {
571 timeout
= cur_timeout
;
572 next_timeout
= e
->timeout
;
576 if (e
->data
.file
|| e
->data
.user
)
579 qosify_map_free_entry(e
);
585 uloop_timeout_set(&qosify_map_timer
, timeout
* 1000);
589 int qosify_map_add_dns_host(const char *host
, const char *addr
, const char *type
, int ttl
)
591 struct qosify_map_data data
= {
593 .addr
.dns
.pattern
= "",
595 struct qosify_map_entry
*e
;
596 int prev_timeout
= qosify_map_timeout
;
598 e
= avl_find_ge_element(&map_data
, &data
, e
, avl
);
602 memset(&data
, 0, sizeof(data
));
604 if (!strcmp(type
, "A"))
605 data
.id
= CL_MAP_IPV4_ADDR
;
606 else if (!strcmp(type
, "AAAA"))
607 data
.id
= CL_MAP_IPV6_ADDR
;
611 if (qosify_map_fill_ip(&data
, addr
))
614 avl_for_element_to_last(&map_data
, e
, e
, avl
) {
615 regex_t
*regex
= &e
->data
.addr
.dns
.regex
;
617 if (e
->data
.id
!= CL_MAP_DNS
)
620 if (regexec(regex
, host
, 0, NULL
, 0) != 0)
624 qosify_map_timeout
= ttl
;
625 data
.dscp
= e
->data
.dscp
;
626 __qosify_map_set_entry(&data
);
627 qosify_map_timeout
= prev_timeout
;
634 void qosify_map_dump(struct blob_buf
*b
)
636 struct qosify_map_entry
*e
;
637 uint32_t cur_time
= qosify_gettime();
638 int buf_len
= INET6_ADDRSTRLEN
+ 1;
643 a
= blobmsg_open_array(b
, "entries");
644 avl_for_each_element(&map_data
, e
, avl
) {
647 if (!e
->data
.file
&& !e
->data
.user
)
650 c
= blobmsg_open_table(b
, NULL
);
651 if (e
->data
.user
&& e
->timeout
!= ~0) {
652 int32_t cur_timeout
= e
->timeout
- cur_time
;
657 blobmsg_add_u32(b
, "timeout", cur_timeout
);
660 blobmsg_add_u8(b
, "file", e
->data
.file
);
661 blobmsg_add_u8(b
, "user", e
->data
.user
);
663 buf
= blobmsg_alloc_string_buffer(b
, "dscp", buf_len
);
664 qosify_map_dscp_codepoint_str(buf
, buf_len
, e
->data
.dscp
);
665 blobmsg_add_string_buffer(b
);
667 blobmsg_add_string(b
, "type", qosify_map_info
[e
->data
.id
].type_name
);
669 switch (e
->data
.id
) {
670 case CL_MAP_TCP_PORTS
:
671 case CL_MAP_UDP_PORTS
:
672 blobmsg_printf(b
, "addr", "%d", ntohs(e
->data
.addr
.port
));
674 case CL_MAP_IPV4_ADDR
:
675 case CL_MAP_IPV6_ADDR
:
676 buf
= blobmsg_alloc_string_buffer(b
, "addr", buf_len
);
677 af
= e
->data
.id
== CL_MAP_IPV6_ADDR
? AF_INET6
: AF_INET
;
678 inet_ntop(af
, &e
->data
.addr
, buf
, buf_len
);
679 blobmsg_add_string_buffer(b
);
682 blobmsg_add_string(b
, "addr", e
->data
.addr
.dns
.pattern
);
688 blobmsg_close_table(b
, c
);
690 blobmsg_close_array(b
, a
);
693 void qosify_map_update_config(void)
695 int fd
= qosify_map_fds
[CL_MAP_CONFIG
];
698 bpf_map_update_elem(fd
, &key
, &config
, BPF_ANY
);