Files
wlan-ap/feeds/ucentral/qosify/src/map.c
John Crispin 4a3d4f5609 qosify: add new QoS package
Signed-off-by: John Crispin <john@phrozen.org>
2021-10-28 10:15:58 +02:00

576 lines
11 KiB
C

#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#include <libubox/uloop.h>
#include "qosify.h"
static int qosify_map_entry_cmp(const void *k1, const void *k2, void *ptr);
static int qosify_map_fds[__CL_MAP_MAX];
static AVL_TREE(map_data, qosify_map_entry_cmp, false, NULL);
static LIST_HEAD(map_files);
static uint32_t next_timeout;
static uint8_t qosify_dscp_default[2] = { 0xff, 0xff };
int qosify_map_timeout = 3600;
struct qosify_config config;
struct qosify_map_file {
struct list_head list;
char filename[];
};
static const struct {
const char *name;
const char *type_name;
} qosify_map_info[] = {
[CL_MAP_TCP_PORTS] = { "tcp_ports", "tcp_port" },
[CL_MAP_UDP_PORTS] = { "udp_ports", "udp_port" },
[CL_MAP_IPV4_ADDR] = { "ipv4_map", "ipv4_addr" },
[CL_MAP_IPV6_ADDR] = { "ipv6_map", "ipv6_addr" },
[CL_MAP_CONFIG] = { "config", "config" },
};
static void qosify_map_timer_cb(struct uloop_timeout *t)
{
qosify_map_gc();
}
static struct uloop_timeout qosify_map_timer = {
.cb = qosify_map_timer_cb,
};
static uint32_t qosify_gettime(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec;
}
static const char *
qosify_map_path(enum qosify_map_id id)
{
static char path[128];
const char *name;
if (id >= ARRAY_SIZE(qosify_map_info))
return NULL;
name = qosify_map_info[id].name;
if (!name)
return NULL;
snprintf(path, sizeof(path), "%s/%s", CLASSIFY_DATA_PATH, name);
return path;
}
static int qosify_map_get_fd(enum qosify_map_id id)
{
const char *path = qosify_map_path(id);
int fd;
if (!path)
return -1;
fd = bpf_obj_get(path);
if (fd < 0)
fprintf(stderr, "Failed to open map %s: %s\n", path, strerror(errno));
return fd;
}
static void qosify_map_clear_list(enum qosify_map_id id)
{
int fd = qosify_map_fds[id];
__u32 key[4] = {};
while (bpf_map_get_next_key(fd, &key, &key) != -1)
bpf_map_delete_elem(fd, &key);
}
static void __qosify_map_set_dscp_default(enum qosify_map_id id, uint8_t val)
{
struct qosify_map_data data = {
.id = id,
};
int fd = qosify_map_fds[id];
int i;
for (i = 0; i < (1 << 16); i++) {
data.addr.port = htons(i);
if (avl_find(&map_data, &data))
continue;
bpf_map_update_elem(fd, &data.addr, &val, BPF_ANY);
}
}
void qosify_map_set_dscp_default(enum qosify_map_id id, uint8_t val)
{
bool udp;
if (id == CL_MAP_TCP_PORTS)
udp = false;
else if (id == CL_MAP_UDP_PORTS)
udp = true;
else
return;
if (qosify_dscp_default[udp] == val)
return;
qosify_dscp_default[udp] = val;
__qosify_map_set_dscp_default(id, val);
}
int qosify_map_init(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(qosify_map_fds); i++) {
qosify_map_fds[i] = qosify_map_get_fd(i);
if (qosify_map_fds[i] < 0)
return -1;
}
qosify_map_clear_list(CL_MAP_IPV4_ADDR);
qosify_map_clear_list(CL_MAP_IPV6_ADDR);
qosify_map_reset_config();
return 0;
}
static char *str_skip(char *str, bool space)
{
while (*str && isspace(*str) == space)
str++;
return str;
}
static int
qosify_map_codepoint(const char *val)
{
static const struct {
const char name[5];
uint8_t val;
} cp[] = {
{ "CS0", 0 },
{ "CS1", 8 },
{ "CS2", 16 },
{ "CS3", 24 },
{ "CS4", 32 },
{ "CS5", 40 },
{ "CS6", 48 },
{ "CS7", 56 },
{ "AF11", 10 },
{ "AF12", 12 },
{ "AF13", 14 },
{ "AF21", 18 },
{ "AF22", 20 },
{ "AF22", 22 },
{ "AF31", 26 },
{ "AF32", 28 },
{ "AF33", 30 },
{ "AF41", 34 },
{ "AF42", 36 },
{ "AF43", 38 },
{ "EF", 46 },
{ "VA", 44 },
};
int i;
for (i = 0; i < ARRAY_SIZE(cp); i++)
if (!strcmp(cp[i].name, val))
return cp[i].val;
return 0xff;
}
static int qosify_map_entry_cmp(const void *k1, const void *k2, void *ptr)
{
const struct qosify_map_data *d1 = k1;
const struct qosify_map_data *d2 = k2;
if (d1->id != d2->id)
return d2->id - d1->id;
return memcmp(&d1->addr, &d2->addr, sizeof(d1->addr));
}
static void __qosify_map_set_entry(struct qosify_map_data *data)
{
int fd = qosify_map_fds[data->id];
struct qosify_map_entry *e;
bool file = data->file;
int32_t delta = 0;
bool add = data->dscp != 0xff;
uint8_t prev_dscp = 0xff;
e = avl_find_element(&map_data, data, e, avl);
if (!e) {
if (!add)
return;
e = calloc(1, sizeof(*e));
e->avl.key = &e->data;
e->data.id = data->id;
memcpy(&e->data.addr, &data->addr, sizeof(e->data.addr));
avl_insert(&map_data, &e->avl);
} else {
prev_dscp = e->data.dscp;
}
if (file)
e->data.file = add;
else
e->data.user = add;
if (add) {
if (file)
e->data.file_dscp = data->dscp;
if (!e->data.user || !file)
e->data.dscp = data->dscp;
} else if (e->data.file && !file) {
e->data.dscp = e->data.file_dscp;
}
if (e->data.dscp != prev_dscp)
bpf_map_update_elem(fd, &data->addr, &e->data.dscp, BPF_ANY);
if (add) {
if (qosify_map_timeout == ~0 || file) {
e->timeout = ~0;
return;
}
e->timeout = qosify_gettime() + qosify_map_timeout;
delta = e->timeout - next_timeout;
if (next_timeout && delta >= 0)
return;
}
uloop_timeout_set(&qosify_map_timer, 1);
}
static int
qosify_map_set_port(struct qosify_map_data *data, const char *str)
{
unsigned long start_port, end_port;
char *err;
int i;
start_port = end_port = strtoul(str, &err, 0);
if (err && *err) {
if (*err == '-')
end_port = strtoul(err + 1, &err, 0);
if (*err)
return -1;
}
if (!start_port || end_port < start_port ||
end_port >= 65535)
return -1;
for (i = start_port; i <= end_port; i++) {
data->addr.port = htons(i);
__qosify_map_set_entry(data);
}
return 0;
}
static int
qosify_map_fill_ip(struct qosify_map_data *data, const char *str)
{
int af;
if (data->id == CL_MAP_IPV6_ADDR)
af = AF_INET6;
else
af = AF_INET;
if (inet_pton(af, str, &data->addr) != 1)
return -1;
return 0;
}
int qosify_map_set_entry(enum qosify_map_id id, bool file, const char *str, uint8_t dscp)
{
struct qosify_map_data data = {
.id = id,
.file = file,
.dscp = dscp,
};
switch (id) {
case CL_MAP_TCP_PORTS:
case CL_MAP_UDP_PORTS:
return qosify_map_set_port(&data, str);
case CL_MAP_IPV4_ADDR:
case CL_MAP_IPV6_ADDR:
if (qosify_map_fill_ip(&data, str))
return -1;
break;
default:
return -1;
}
__qosify_map_set_entry(&data);
return 0;
}
int qosify_map_dscp_value(const char *val)
{
unsigned long dscp;
char *err;
bool fallback = false;
if (*val == '+') {
fallback = true;
val++;
}
dscp = strtoul(val, &err, 0);
if (err && *err)
dscp = qosify_map_codepoint(val);
if (dscp >= 64)
return -1;
return dscp + (fallback << 6);
}
static void
qosify_map_parse_line(char *str)
{
const char *key, *value;
int dscp;
str = str_skip(str, true);
key = str;
str = str_skip(str, false);
if (!*str)
return;
*(str++) = 0;
str = str_skip(str, true);
value = str;
dscp = qosify_map_dscp_value(value);
if (dscp < 0)
return;
if (!strncmp(key, "tcp:", 4))
qosify_map_set_entry(CL_MAP_TCP_PORTS, true, key + 4, dscp);
else if (!strncmp(key, "udp:", 4))
qosify_map_set_entry(CL_MAP_UDP_PORTS, true, key + 4, dscp);
else if (strchr(key, ':'))
qosify_map_set_entry(CL_MAP_IPV6_ADDR, true, key, dscp);
else if (strchr(key, '.'))
qosify_map_set_entry(CL_MAP_IPV4_ADDR, true, key, dscp);
}
static int __qosify_map_load_file(const char *file)
{
char line[1024];
char *cur;
FILE *f;
if (!file)
return 0;
f = fopen(file, "r");
if (!f) {
fprintf(stderr, "Can't open data file %s\n", file);
return -1;
}
while (fgets(line, sizeof(line), f)) {
cur = strchr(line, '#');
if (cur)
*cur = 0;
cur = line + strlen(line);
if (cur == line)
continue;
while (cur > line && isspace(cur[-1]))
cur--;
*cur = 0;
qosify_map_parse_line(line);
}
fclose(f);
return 0;
}
int qosify_map_load_file(const char *file)
{
struct qosify_map_file *f;
if (!file)
return 0;
f = calloc(1, sizeof(*f) + strlen(file) + 1);
strcpy(f->filename, file);
list_add_tail(&f->list, &map_files);
return __qosify_map_load_file(file);
}
static void qosify_map_reset_file_entries(void)
{
struct qosify_map_entry *e;
avl_for_each_element(&map_data, e, avl)
e->data.file = false;
}
void qosify_map_clear_files(void)
{
struct qosify_map_file *f, *tmp;
qosify_map_reset_file_entries();
list_for_each_entry_safe(f, tmp, &map_files, list) {
list_del(&f->list);
free(f);
}
}
void qosify_map_reset_config(void)
{
qosify_map_clear_files();
qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, 0);
qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, 0);
qosify_map_timeout = 3600;
memset(&config, 0, sizeof(config));
config.dscp_prio = 0xff;
config.dscp_bulk = 0xff;
config.dscp_icmp = 0xff;
}
void qosify_map_reload(void)
{
struct qosify_map_file *f;
qosify_map_reset_file_entries();
list_for_each_entry(f, &map_files, list)
__qosify_map_load_file(f->filename);
qosify_map_gc();
}
void qosify_map_gc(void)
{
struct qosify_map_entry *e, *tmp;
int32_t timeout = 0;
uint32_t cur_time = qosify_gettime();
int fd;
next_timeout = 0;
avl_for_each_element_safe(&map_data, e, avl, tmp) {
int32_t cur_timeout;
if (e->data.user && e->timeout != ~0) {
cur_timeout = e->timeout - cur_time;
if (cur_timeout <= 0) {
e->data.user = false;
e->data.dscp = e->data.file_dscp;
} else if (!timeout || cur_timeout < timeout) {
timeout = cur_timeout;
next_timeout = e->timeout;
}
}
if (e->data.file || e->data.user)
continue;
avl_delete(&map_data, &e->avl);
fd = qosify_map_fds[e->data.id];
bpf_map_delete_elem(fd, &e->data.addr);
free(e);
}
if (!timeout)
return;
uloop_timeout_set(&qosify_map_timer, timeout * 1000);
}
void qosify_map_dump(struct blob_buf *b)
{
struct qosify_map_entry *e;
uint32_t cur_time = qosify_gettime();
int buf_len = INET6_ADDRSTRLEN + 1;
char *buf;
void *a;
int af;
a = blobmsg_open_array(b, "entries");
avl_for_each_element(&map_data, e, avl) {
void *c;
if (!e->data.file && !e->data.user)
continue;
c = blobmsg_open_table(b, NULL);
if (e->data.user && e->timeout != ~0) {
int32_t cur_timeout = e->timeout - cur_time;
if (cur_timeout < 0)
cur_timeout = 0;
blobmsg_add_u32(b, "timeout", cur_timeout);
}
blobmsg_add_u8(b, "file", e->data.file);
blobmsg_add_u8(b, "user", e->data.user);
blobmsg_add_string(b, "type", qosify_map_info[e->data.id].type_name);
buf = blobmsg_alloc_string_buffer(b, "value", buf_len);
switch (e->data.id) {
case CL_MAP_TCP_PORTS:
case CL_MAP_UDP_PORTS:
snprintf(buf, buf_len, "%d", ntohs(e->data.addr.port));
break;
case CL_MAP_IPV4_ADDR:
case CL_MAP_IPV6_ADDR:
af = e->data.id == CL_MAP_IPV6_ADDR ? AF_INET6 : AF_INET;
inet_ntop(af, &e->data.addr, buf, buf_len);
break;
default:
*buf = 0;
break;
}
blobmsg_add_string_buffer(b);
blobmsg_close_table(b, c);
}
blobmsg_close_array(b, a);
}
void qosify_map_update_config(void)
{
int fd = qosify_map_fds[CL_MAP_CONFIG];
uint32_t key = 0;
bpf_map_update_elem(fd, &key, &config, BPF_ANY);
}